File: README.md

package info (click to toggle)
ruby-rack-proxy 0.7.7-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 184 kB
  • sloc: ruby: 436; makefile: 3
file content (340 lines) | stat: -rw-r--r-- 10,227 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
A request/response rewriting HTTP proxy. A Rack app. Subclass `Rack::Proxy` and provide your `rewrite_env` and `rewrite_response` methods.

Installation
----

Add the following to your `Gemfile`:

```
gem 'rack-proxy', '~> 0.7.7'
```

Or install:

```
gem install rack-proxy
```

Use Cases
----

Below are some examples of real world use cases for Rack-Proxy. If you have done something interesting, add it to the list below and send a PR.

* Allowing one app to act as central trust authority
  * handle accepting self-sign certificates for internal apps
  * authentication / authorization prior to proxying requests to a blindly trusting backend
  * avoiding CORs complications by proxying from same domain to another backend
* subdomain based pass-through to multiple apps
* Complex redirect rules
   * redirect pages with different extensions (ex: `.php`) to another app
   * useful for handling awkward redirection rules for moved pages
* fan Parallel Requests: turning a single API request to [multiple concurrent backend requests](https://github.com/typhoeus/typhoeus#making-parallel-requests) & merging results.
* inserting or stripping headers required or problematic for certain clients

Options
----

Options can be set when initializing the middleware or overriding a method.


* `:streaming` - defaults to `true`, but does not work on all Ruby versions, recommend to set to `false`
* `:ssl_verify_none` - tell `Net::HTTP` to not validate certs
* `:ssl_version` - tell `Net::HTTP` to set a specific `ssl_version`
* `:backend` - the URI parseable format of host and port of the target proxy backend. If not set it will assume the backend target is the same as the source.
* `:read_timeout` - set proxy timeout it defaults to 60 seconds

To pass in options, when you configure your middleware you can pass them in as an optional hash.

```ruby
Rails.application.config.middleware.use ExampleServiceProxy, backend: 'http://guides.rubyonrails.org', streaming: false
```

Examples
----

See and run the examples below from `lib/rack_proxy_examples/`. To mount any example into an existing Rails app:

1. create `config/initializers/proxy.rb`
2. modify the file to require the example file
```ruby
require 'rack_proxy_examples/forward_host'
```

### Forward request to Host and Insert Header

Test with `require 'rack_proxy_examples/forward_host'`

```ruby
class ForwardHost < Rack::Proxy

  def rewrite_env(env)
    env["HTTP_HOST"] = "example.com"
    env
  end

  def rewrite_response(triplet)
    status, headers, body = triplet

    # example of inserting an additional header
    headers["X-Foo"] = "Bar"

    # if you rewrite env, it appears that content-length isn't calculated correctly
    # resulting in only partial responses being sent to users
    # you can remove it or recalculate it here
    headers["content-length"] = nil

    triplet
  end

end
```

### Disable SSL session verification when proxying a server with e.g. self-signed SSL certs

Test with `require 'rack_proxy_examples/trusting_proxy'`

```ruby
class TrustingProxy < Rack::Proxy

  def rewrite_env(env)
    env["HTTP_HOST"] = "self-signed.badssl.com"

    # We are going to trust the self-signed SSL
    env["rack.ssl_verify_none"] = true
    env
  end

  def rewrite_response(triplet)
    status, headers, body = triplet

    # if you rewrite env, it appears that content-length isn't calculated correctly
    # resulting in only partial responses being sent to users
    # you can remove it or recalculate it here
    headers["content-length"] = nil

    triplet
  end

end
```

The same can be achieved for *all* requests going through the `Rack::Proxy` instance by using

```ruby
Rack::Proxy.new(ssl_verify_none: true)
```

### Rails middleware example

Test with `require 'rack_proxy_examples/example_service_proxy'`

```ruby
###
# This is an example of how to use Rack-Proxy in a Rails application.
#
# Setup:
# 1. rails new test_app
# 2. cd test_app
# 3. install Rack-Proxy in `Gemfile`
#    a. `gem 'rack-proxy', '~> 0.7.7'`
# 4. install gem: `bundle install`
# 5. create `config/initializers/proxy.rb` adding this line `require 'rack_proxy_examples/example_service_proxy'`
# 6. run: `SERVICE_URL=http://guides.rubyonrails.org rails server`
# 7. open in browser: `http://localhost:3000/example_service`
#
###
ENV['SERVICE_URL'] ||= 'http://guides.rubyonrails.org'

class ExampleServiceProxy < Rack::Proxy
  def perform_request(env)
    request = Rack::Request.new(env)

    # use rack proxy for anything hitting our host app at /example_service
    if request.path =~ %r{^/example_service}
        backend = URI(ENV['SERVICE_URL'])
        # most backends required host set properly, but rack-proxy doesn't set this for you automatically
        # even when a backend host is passed in via the options
        env["HTTP_HOST"] = backend.host

        # This is the only path that needs to be set currently on Rails 5 & greater
        env['PATH_INFO'] = ENV['SERVICE_PATH'] || '/configuring.html'

        # don't send your sites cookies to target service, unless it is a trusted internal service that can parse all your cookies
        env['HTTP_COOKIE'] = ''
        super(env)
    else
      @app.call(env)
    end
  end
end
```

### Using as middleware to forward only some extensions to another Application

Test with `require 'rack_proxy_examples/rack_php_proxy'`

Example: Proxying only requests that end with ".php" could be done like this:

```ruby
###
# Open http://localhost:3000/test.php to trigger proxy
###
class RackPhpProxy < Rack::Proxy

  def perform_request(env)
    request = Rack::Request.new(env)
    if request.path =~ %r{\.php}
      env["HTTP_HOST"] = ENV["HTTP_HOST"] ? URI(ENV["HTTP_HOST"]).host : "localhost"
      ENV["PHP_PATH"] ||= '/manual/en/tutorial.firstpage.php'

      # Rails 3 & 4
      env["REQUEST_PATH"] = ENV["PHP_PATH"] || "/php/#{request.fullpath}"
      # Rails 5 and above
      env['PATH_INFO'] = ENV["PHP_PATH"] || "/php/#{request.fullpath}"

      env['content-length'] = nil

      super(env)
    else
      @app.call(env)
    end
  end

  def rewrite_response(triplet)
    status, headers, body = triplet

    # if you proxy depending on the backend, it appears that content-length isn't calculated correctly
    # resulting in only partial responses being sent to users
    # you can remove it or recalculate it here
    headers["content-length"] = nil

    triplet
  end
end
```

To use the middleware, please consider the following:

1) For Rails we could add a configuration in `config/application.rb`

```ruby
  config.middleware.use RackPhpProxy, {ssl_verify_none: true}
```

2) For Sinatra or any Rack-based application:

```ruby
class MyAwesomeSinatra < Sinatra::Base
   use  RackPhpProxy, {ssl_verify_none: true}
end
```

This will allow to run the other requests through the application and only proxy the requests that match the condition from the middleware.

See tests for more examples.

### SSL proxy for SpringBoot applications debugging

Whenever you need to debug communication with external services with HTTPS protocol (like OAuth based) you have to be able to access to your local web app through HTTPS protocol too. Typical way is to use nginx or Apache httpd as a reverse proxy but it might be inconvinuent for development environment. Simple proxy server is a better way in this case. The only what we need is to unpack incoming SSL queries and proxy them to a backend. We can prepare minimal set of files to create autonomous proxy server.

Create `config.ru` file:
```ruby
#
# config.ru
#
require 'rack'
require 'rack-proxy'

class ForwardHost < Rack::Proxy
  def rewrite_env(env)
    env['HTTP_X_FORWARDED_HOST'] = env['SERVER_NAME']
    env['HTTP_X_FORWARDED_PROTO'] = env['rack.url_scheme']
    env
  end
end

run ForwardHost.new(backend: 'http://localhost:8080')
```

Create `Gemfile` file:
```ruby
source "https://rubygems.org"

gem 'thin'
gem 'rake'
gem 'rack-proxy'
```

Create `config.yml` file with configuration of web server `thin`:
```yml
---
ssl: true
ssl-key-file: keys/domain.key
ssl-cert-file: keys/domain.crt
ssl-disable-verify: false
```

Create 'keys' directory and generate SSL key and certificates files `domain.key` and `domain.crt`

Run `bundle exec thin start` for running it with `thin`'s default port.

Or use `sudo -E thin start -C config.yml -p 443` for running with default for `https://` port.

Don't forget to enable processing of `X-Forwarded-...` headers on your application side. Just add following strings to your `resources/application.yml` file.
```yml
---
server:
  tomcat:
    remote-ip-header: x-forwarded-for
    protocol-header:  x-forwarded-proto
  use-forward-headers:  true
```

Add some domain name like `debug.your_app.com` into your local `/etc/hosts` file like
```
127.0.0.1	debug.your_app.com
```

Next start the proxy and your app. And now you can access to your Spring application through SSL connection via `https://debug.your_app.com` URI in a browser.

### Using SSL/TLS certificates with HTTP connection
This may be helpful, when third-party API has authentication by client TLS certificates and you need to proxy your requests and sign them with certificate.

Just specify Rack::Proxy SSL options and your request will use TLS HTTP connection:
```ruby
# config.ru
. . .

cert_raw = File.read('./certs/rootCA.crt')
key_raw = File.read('./certs/key.pem')

cert = OpenSSL::X509::Certificate.new(cert_raw)
key = OpenSSL::PKey.read(key_raw)

use TLSProxy, cert: cert, key: key, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_PEER, ssl_version: 'TLSv1_2'
```

And rewrite host for example:
```ruby
# tls_proxy.rb
class TLSProxy < Rack::Proxy
  attr_accessor :original_request, :query_params

  def rewrite_env(env)
    env["HTTP_HOST"] = "client-tls-auth-api.com:443"
    env
  end
end
```

WARNING
----

Doesn't work with `fakeweb`/`webmock`. Both libraries monkey-patch net/http code.

Todos
----

* Make the docs up to date with the current use case for this code: everything except streaming which involved a rather ugly monkey patch and only worked in 1.8, but does not work now.
* Improve and validate requirements for Host and Path rewrite rules
* Ability to inject logger and set log level