rack-proxy 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -4
  3. data/README.md +166 -155
  4. data/lib/rack/proxy/version.rb +1 -1
  5. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e04f14fa6327e4f329211d6ca95a1865b2e2db929c954c613cd3017f2d9a587
4
- data.tar.gz: ca3a3bbc4451fbcc21495317889bdb2afc2b5c328e8a0b6561a82617f3871c0c
3
+ metadata.gz: ec993cb1f75831f1f7270d06634ba8c94b40246af6a622e239e29ddd2ff74e20
4
+ data.tar.gz: e5105cdc0ce7f69fb47b36e483dde1bad7799ff90f052566e526cc65d99fe7b5
5
5
  SHA512:
6
- metadata.gz: 37d457fb14f1cd3f48729b36db119b5d72585e85cad6f745d37c8a6857bddd6636e2ed8ad18f6d1975b61b184eb0cfb4b262d80b3304dfeebaa765c6bb273871
7
- data.tar.gz: 1e2f04812c516b6ecaf1266b941bca1797fd72546217e506443b8e8a619617d77130684250ea343c19e90a686185a8f6cfd659970b9393d4378e7307bf26af2b
6
+ metadata.gz: 701b98893deaf804c09f965a6ad12d3a7c219be357bfd1d3457beeab4598af699863b44df149a0d4c5c433dab75cdb3ee080a9503e9916142adb7a60ba3b8bf8
7
+ data.tar.gz: e75ba8fe0ed1beaa2aa480f3f19805e3a8be98c8ed05af84e9b00e6812b420a67d8d102031afcff78c23026193a43354b20126477e40d9f41ab50514ec0b8acc
data/CHANGELOG.md CHANGED
@@ -8,11 +8,22 @@ follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
9
  Nothing yet.
10
10
 
11
+ ## [1.0.1] - 2026-07-23
12
+
13
+ Docs and housekeeping — no library behavior changes.
14
+
15
+ ### Changed
16
+
17
+ - README modernized: badges, quick start, a "How it works" pipeline overview,
18
+ grouped options, and tightened recipes.
19
+ - Removed the internal modernization roadmap document, completed by 1.0.0.
20
+ - Development dependencies refreshed (rake 13.4.2, test-unit 3.7.8,
21
+ rack-test 2.2.0) and CI bumped to `actions/checkout@v7`. (#136–#139)
22
+
11
23
  ## [1.0.0] - 2026-07-18
12
24
 
13
- The 2026 modernization + security-hardening release (see
14
- `MODERNIZATION_PLAN.md`). From 1.0.0 on this project follows SemVer strictly:
15
- breaking changes only in majors.
25
+ The 2026 modernization + security-hardening release. From 1.0.0 on this
26
+ project follows SemVer strictly: breaking changes only in majors.
16
27
 
17
28
  ### Breaking changes
18
29
 
@@ -164,7 +175,8 @@ or a compatible fix. See the README's "Upgrading" section for migration steps.
164
175
 
165
176
  Older releases (≤ 0.7.8) predate this changelog; see the git history and tags.
166
177
 
167
- [Unreleased]: https://github.com/ncr/rack-proxy/compare/v1.0.0...HEAD
178
+ [Unreleased]: https://github.com/ncr/rack-proxy/compare/v1.0.1...HEAD
179
+ [1.0.1]: https://github.com/ncr/rack-proxy/compare/v1.0.0...v1.0.1
168
180
  [1.0.0]: https://github.com/ncr/rack-proxy/compare/v0.8.3...v1.0.0
169
181
  [0.8.3]: https://github.com/ncr/rack-proxy/compare/v0.8.2...v0.8.3
170
182
  [0.8.2]: https://github.com/ncr/rack-proxy/compare/v0.8.1...v0.8.2
data/README.md CHANGED
@@ -1,90 +1,138 @@
1
1
  # Rack::Proxy
2
2
 
3
- A request/response rewriting HTTP proxy. A Rack app. Subclass `Rack::Proxy` and provide your `rewrite_env` and `rewrite_response` methods.
3
+ [![Gem Version](https://img.shields.io/gem/v/rack-proxy)](https://rubygems.org/gems/rack-proxy)
4
+ [![CI](https://github.com/ncr/rack-proxy/actions/workflows/ci.yml/badge.svg)](https://github.com/ncr/rack-proxy/actions/workflows/ci.yml)
5
+ [![Downloads](https://img.shields.io/gem/dt/rack-proxy)](https://rubygems.org/gems/rack-proxy)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
+
8
+ A request/response rewriting HTTP proxy for Rack. Run it standalone as a tiny reverse proxy, or mount it as middleware and subclass it to rewrite requests and responses in flight.
9
+
10
+ - **Streams by default** — response bodies are relayed chunk by chunk straight off the backend socket, so large responses never buffer in memory.
11
+ - **Safe by default** — TLS verification on (`VERIFY_PEER`), Host-derived backends refused unless explicitly opted in, hop-by-hop headers stripped in both directions, backend failures mapped to `502` instead of raising.
12
+ - **Small** — two files on top of plain `Net::HTTP`; the only runtime dependency is Rack.
13
+
14
+ Typical uses:
15
+
16
+ - an authenticating/authorizing gateway in front of a trusting internal backend
17
+ - serving another app from the same origin to avoid CORS complications
18
+ - subdomain- or path-based routing to multiple internal services
19
+ - redirecting awkward legacy paths (e.g. `.php` pages) to another app
20
+ - inserting or stripping headers that are required — or problematic — for certain clients
4
21
 
5
22
  ## Contents
6
23
 
7
24
  - [Installation](#installation)
8
- - [Use Cases](#use-cases)
25
+ - [Quick start](#quick-start)
26
+ - [How it works](#how-it-works)
9
27
  - [Options](#options)
10
28
  - [Security considerations](#security-considerations)
11
- - [Examples](#examples)
29
+ - [Recipes](#recipes)
12
30
  - [Upgrading](#upgrading)
13
- - [A note on header keys](#a-note-on-header-keys-96)
31
+ - [Header keys and underscores](#header-keys-and-underscores)
14
32
  - [Compatibility notes](#compatibility-notes)
33
+ - [Development](#development)
15
34
 
16
- Installation
17
- ----
35
+ ## Installation
18
36
 
19
- Add the following to your `Gemfile`:
37
+ Requires Ruby >= 3.0 and Rack 2.x or 3.x. Add to your `Gemfile`:
20
38
 
39
+ ```ruby
40
+ gem "rack-proxy", "~> 1.0"
21
41
  ```
22
- gem 'rack-proxy', '~> 1.0'
42
+
43
+ ## Quick start
44
+
45
+ A standalone reverse proxy is one line of `config.ru`:
46
+
47
+ ```ruby
48
+ require "rack-proxy"
49
+
50
+ run Rack::Proxy.new(backend: "http://localhost:8080")
23
51
  ```
24
52
 
25
- Or install:
53
+ As middleware, subclass it and decide per request: call `super` to proxy, or hand the request to the rest of your app:
54
+
55
+ ```ruby
56
+ class ApiProxy < Rack::Proxy
57
+ def perform_request(env)
58
+ if env["PATH_INFO"].start_with?("/api/")
59
+ env["HTTP_HOST"] = "api.internal.example" # most backends route on Host
60
+ super
61
+ else
62
+ @app.call(env)
63
+ end
64
+ end
65
+ end
66
+
67
+ # Rails (config/initializers/proxy.rb):
68
+ Rails.application.config.middleware.use ApiProxy, backend: "https://api.internal.example"
69
+
70
+ # Any Rack app (config.ru or Sinatra):
71
+ use ApiProxy, backend: "https://api.internal.example"
72
+ ```
73
+
74
+ ## How it works
75
+
76
+ Every request runs through a three-step pipeline:
26
77
 
27
78
  ```
28
- gem install rack-proxy
79
+ call(env) rewrite_env(env) → perform_request(env) → rewrite_response([status, headers, body])
29
80
  ```
30
81
 
31
- Use Cases
32
- ----
33
-
34
- 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.
35
-
36
- * Allowing one app to act as central trust authority
37
- * handle accepting self-sign certificates for internal apps
38
- * authentication / authorization prior to proxying requests to a blindly trusting backend
39
- * avoiding CORs complications by proxying from same domain to another backend
40
- * subdomain based pass-through to multiple apps
41
- * Complex redirect rules
42
- * redirect pages with different extensions (ex: `.php`) to another app
43
- * useful for handling awkward redirection rules for moved pages
44
- * fan Parallel Requests: turning a single API request to [multiple concurrent backend requests](https://github.com/typhoeus/typhoeus#making-parallel-requests) & merging results.
45
- * inserting or stripping headers required or problematic for certain clients
46
-
47
- Options
48
- ----
49
-
50
- Options can be set when initializing the middleware or overriding a method.
51
-
52
- * `:streaming` - stream the backend response as it arrives (default `true`). Set to `false` to buffer the whole response before returning it (also recommended under `webmock`/`vcr` — see [Compatibility notes](#compatibility-notes)).
53
- * `:backend` - URI (or URI-parseable string) of the backend host/port/scheme to proxy to. If not set, the destination is derived from the incoming request's `Host` — which is **refused by default** since 1.0; see `:allow_dynamic_backend` and [Security considerations](#security-considerations).
54
- * `:allow_dynamic_backend` - opt in (`true`) to deriving the destination from the client-supplied `Host` header when no `:backend` is configured. Off by default (such requests get `502`), because a bare dynamic proxy is an open proxy. Combine with a `backend_allowed?` allowlist.
55
- * `:read_timeout` - per-read timeout in seconds (default `60`).
56
- * `:open_timeout` - connection-open timeout in seconds.
57
- * `:write_timeout` - per-write timeout in seconds.
58
- * `:ssl_verify_none` - skip TLS certificate verification. Verification is on by default (`VERIFY_PEER`) — see [Upgrading](#upgrading).
59
- * `:verify_mode` - explicit `OpenSSL::SSL::VERIFY_*` constant; wins over `ssl_verify_none`.
60
- * `:ca_file` - path to a PEM CA bundle used to verify the backend certificate (prefer this over disabling verification for private CAs).
61
- * `:cert_store` - an `OpenSSL::X509::Store` used to verify the backend certificate.
62
- * `:cert` / `:key` - client certificate and key for mutual TLS to the backend.
63
- * `:min_version` / `:max_version` - TLS protocol range (e.g. `:TLS1_2`), mapped to `Net::HTTP#min_version=` / `#max_version=`.
64
- * `:ssl_version` - **deprecated**; pins an exact protocol (forbids TLS 1.3). Use `:min_version` / `:max_version`.
65
- * `:max_response_length` - cap (in bytes) on the backend response size; a larger response is refused with `502` (streaming aborts once the cap is passed).
66
- * `:username` / `:password` - HTTP Basic credentials sent to the backend.
67
- * `:strip_credentials` - when `true`, drop the client's `Cookie` and `Authorization` headers instead of forwarding them — see [Security considerations](#security-considerations). The strip applies **after** `rewrite_env`, so a credential injected there is stripped too; attach a proxy-owned credential with `:username`/`:password` instead.
68
- * `:replace_x_forwarded_for` - when `true`, discard the client-supplied `X-Forwarded-For` chain and forward only this hop's `REMOTE_ADDR` (default appends to the chain) — see [Security considerations](#security-considerations).
69
- * `:logger` - any object responding to `#<<` (e.g. `$stdout`, a `StringIO`, or a Ruby `Logger`). Wired to `Net::HTTP#set_debug_output` so the HTTP wire-level conversation is written to the sink. Useful for debugging.
82
+ Override the steps you need:
83
+
84
+ - **`rewrite_env(env)`** — modify the request before it is forwarded (`HTTP_HOST`, path, headers, …). Return the env.
85
+ - **`rewrite_response(triplet)`** post-process the backend's `[status, headers, body]`. Return the triplet. If you change the body, delete or recalculate `Content-Length` (`headers["content-length"] = nil`) or clients may receive truncated responses.
86
+ - **`perform_request(env)`** — take over routing: `super` proxies the request, `@app.call(env)` passes it through to the wrapped app (middleware mode).
87
+ - **`backend_allowed?(backend)`** per-request allowlist hook, consulted for every request; return `false` to refuse with `502`. See [Security considerations](#security-considerations).
70
88
 
71
89
  Two request-scoped overrides can also be set in `env` (e.g. from `rewrite_env`):
72
90
 
73
- * `env['rack.backend']` - a URI overriding `:backend` for this request.
74
- * `env['http.read_timeout']` - override `:read_timeout` for this request.
91
+ - `env["rack.backend"]` a URI (or URI-parseable string) overriding `:backend` for this request.
92
+ - `env["http.read_timeout"]` override `:read_timeout` for this request.
75
93
 
76
- To pass in options, when you configure your middleware you can pass them in as an optional hash.
94
+ ## Options
77
95
 
78
- ```ruby
79
- Rails.application.config.middleware.use ExampleServiceProxy, backend: 'http://guides.rubyonrails.org', streaming: false
80
- ```
96
+ Pass options when instantiating (`Rack::Proxy.new(backend: ...)`) or mounting middleware (`use ApiProxy, backend: ...`).
97
+
98
+ ### Routing and mode
99
+
100
+ - `:backend` — URI (or URI-parseable string) of the backend host/port/scheme to proxy to. If not set, the destination is derived from the incoming request's `Host` — which is **refused by default** since 1.0; see `:allow_dynamic_backend`.
101
+ - `:allow_dynamic_backend` — opt in (`true`) to deriving the destination from the client-supplied `Host` header when no `:backend` is configured. Off by default (such requests get `502`), because a bare dynamic proxy is an open proxy. Combine with a `backend_allowed?` allowlist.
102
+ - `:streaming` — stream the backend response as it arrives (default `true`). Set to `false` to buffer the whole response before returning it (also recommended under `webmock`/`vcr` — see [Compatibility notes](#compatibility-notes)).
103
+
104
+ ### TLS
105
+
106
+ - `:ssl_verify_none` — skip TLS certificate verification. Verification is on by default (`VERIFY_PEER`) — see [Upgrading](#upgrading).
107
+ - `:verify_mode` — explicit `OpenSSL::SSL::VERIFY_*` constant; wins over `ssl_verify_none`.
108
+ - `:ca_file` — path to a PEM CA bundle used to verify the backend certificate (prefer this over disabling verification for private CAs).
109
+ - `:cert_store` — an `OpenSSL::X509::Store` used to verify the backend certificate.
110
+ - `:cert` / `:key` — client certificate and key for mutual TLS to the backend.
111
+ - `:min_version` / `:max_version` — TLS protocol range (e.g. `:TLS1_2`), mapped to `Net::HTTP#min_version=` / `#max_version=`.
112
+ - `:ssl_version` — **deprecated**; pins an exact protocol (forbids TLS 1.3). Use `:min_version` / `:max_version`.
113
+
114
+ ### Timeouts and limits
115
+
116
+ - `:read_timeout` — per-read timeout in seconds (default `60`).
117
+ - `:open_timeout` — connection-open timeout in seconds.
118
+ - `:write_timeout` — per-write timeout in seconds.
119
+ - `:max_response_length` — cap (in bytes) on the backend response size; a larger response is refused with `502` (streaming aborts once the cap is passed).
120
+
121
+ ### Request shaping
122
+
123
+ - `:username` / `:password` — HTTP Basic credentials sent to the backend.
124
+ - `:strip_credentials` — when `true`, drop the client's `Cookie` and `Authorization` headers instead of forwarding them — see [Security considerations](#security-considerations). The strip applies **after** `rewrite_env`, so a credential injected there is stripped too; attach a proxy-owned credential with `:username`/`:password` instead.
125
+ - `:replace_x_forwarded_for` — when `true`, discard the client-supplied `X-Forwarded-For` chain and forward only this hop's `REMOTE_ADDR` (default appends to the chain) — see [Security considerations](#security-considerations).
126
+
127
+ ### Debugging
128
+
129
+ - `:logger` — any object responding to `#<<` (e.g. `$stdout`, a `StringIO`, or a Ruby `Logger`). Wired to `Net::HTTP#set_debug_output` so the HTTP wire-level conversation is written to the sink.
81
130
 
82
- Security considerations
83
- ----
131
+ ## Security considerations
84
132
 
85
133
  rack-proxy forwards attacker-influenced requests to a backend and relays the backend's response. Configure and subclass it with that in mind.
86
134
 
87
- * **SSRF / open proxy — safe by default since 1.0.** If you do **not** set `:backend`, the destination host/port/scheme would be derived from the incoming request's `Host` / `X-Forwarded-Host` header — meaning a client could steer the proxy at *any* host, including cloud metadata endpoints (`169.254.169.254`), loopback, and private ranges. Such requests are now refused with `502` unless you pass `allow_dynamic_backend: true`. When you do opt in, pin an allowlist on top by overriding `backend_allowed?(backend)` (consulted for every request, static backends included):
135
+ - **SSRF / open proxy — safe by default since 1.0.** If you do **not** set `:backend`, the destination host/port/scheme would be derived from the incoming request's `Host` / `X-Forwarded-Host` header — meaning a client could steer the proxy at *any* host, including cloud metadata endpoints (`169.254.169.254`), loopback, and private ranges. Such requests are now refused with `502` unless you pass `allow_dynamic_backend: true`. When you do opt in, pin an allowlist on top by overriding `backend_allowed?(backend)` (consulted for every request, static backends included):
88
136
 
89
137
  ```ruby
90
138
  class MyProxy < Rack::Proxy
@@ -100,29 +148,23 @@ rack-proxy forwards attacker-influenced requests to a backend and relays the bac
100
148
 
101
149
  A refused backend is answered with `502` (with a hint in the `:logger` output).
102
150
 
103
- * **Credential forwarding.** All incoming `HTTP_*` headers are forwarded, including `Authorization` and `Cookie`. Don't proxy to a different trust domain with credentials attached — pass `strip_credentials: true` to drop both (or do finer-grained filtering in `rewrite_env`). Over an `http://` backend these travel in cleartext.
151
+ - **Credential forwarding.** All incoming `HTTP_*` headers are forwarded, including `Authorization` and `Cookie`. Don't proxy to a different trust domain with credentials attached — pass `strip_credentials: true` to drop both (or do finer-grained filtering in `rewrite_env`). Over an `http://` backend these travel in cleartext.
104
152
 
105
- * **X-Forwarded-For.** rack-proxy appends `REMOTE_ADDR` to any inbound `X-Forwarded-For`. If your clients are not behind a trusted proxy, the inbound value is attacker-controlled; pass `replace_x_forwarded_for: true` to forward only the directly-connected peer's address when the backend trusts that header.
153
+ - **X-Forwarded-For.** rack-proxy appends `REMOTE_ADDR` to any inbound `X-Forwarded-For`. If your clients are not behind a trusted proxy, the inbound value is attacker-controlled; pass `replace_x_forwarded_for: true` to forward only the directly-connected peer's address when the backend trusts that header.
106
154
 
107
- * **TLS verification** defaults to `VERIFY_PEER`. For private-CA backends use `:ca_file` / `:cert_store` rather than `ssl_verify_none: true`.
155
+ - **TLS verification** defaults to `VERIFY_PEER`. For private-CA backends use `:ca_file` / `:cert_store` rather than `ssl_verify_none: true`.
108
156
 
109
- * **Resource limits.** Use `:max_response_length` plus `:open_timeout` / `:write_timeout` / `:read_timeout` to bound memory and stalls against a hostile or slow backend.
157
+ - **Resource limits.** Use `:max_response_length` plus `:open_timeout` / `:write_timeout` / `:read_timeout` to bound memory and stalls against a hostile or slow backend.
110
158
 
111
- * **Hop-by-hop headers** (Connection, TE, Transfer-Encoding, Proxy-Authorization, …) are stripped from both the forwarded request and the response.
159
+ - **Hop-by-hop headers** (Connection, TE, Transfer-Encoding, Proxy-Authorization, …) are stripped from both the forwarded request and the response.
112
160
 
113
161
  To report a vulnerability, see [SECURITY.md](SECURITY.md).
114
162
 
115
- Examples
116
- ----
163
+ ## Recipes
117
164
 
118
- The snippets below (also in [`examples/`](examples/) in the repository) are meant to be **copied into your app** — e.g. into `app/middleware/` or `lib/` — and adapted. They are not shipped in the gem and cannot be `require`d from it. To mount one in Rails, copy the class into your app and add it to the middleware stack in an initializer:
165
+ The snippets below (also in [`examples/`](examples/) in the repository) are meant to be **copied into your app** — e.g. into `app/middleware/` or `lib/` — and adapted. They are not shipped in the gem and cannot be `require`d from it.
119
166
 
120
- ```ruby
121
- # config/initializers/proxy.rb
122
- Rails.application.config.middleware.use ForwardHost, backend: "http://example.com"
123
- ```
124
-
125
- ### Forward request to Host and Insert Header
167
+ ### Rewrite the Host and add a response header
126
168
 
127
169
  From [`examples/forward_host.rb`](examples/forward_host.rb):
128
170
 
@@ -149,7 +191,12 @@ class ForwardHost < Rack::Proxy
149
191
  end
150
192
  ```
151
193
 
152
- ### Disable SSL session verification when proxying a server with e.g. self-signed SSL certs
194
+ ```ruby
195
+ # config/initializers/proxy.rb
196
+ Rails.application.config.middleware.use ForwardHost, backend: "http://example.com"
197
+ ```
198
+
199
+ ### Proxy to a backend with a self-signed certificate
153
200
 
154
201
  From [`examples/trusting_proxy.rb`](examples/trusting_proxy.rb):
155
202
 
@@ -179,7 +226,9 @@ Rails.application.config.middleware.use TrustingProxy,
179
226
  ssl_verify_none: true
180
227
  ```
181
228
 
182
- ### Rails middleware example
229
+ For a backend signed by a private CA, prefer `ca_file: "/path/to/ca.pem"` over disabling verification.
230
+
231
+ ### Mount an external service under a path (Rails)
183
232
 
184
233
  From [`examples/example_service_proxy.rb`](examples/example_service_proxy.rb):
185
234
 
@@ -224,12 +273,10 @@ class ExampleServiceProxy < Rack::Proxy
224
273
  end
225
274
  ```
226
275
 
227
- ### Using as middleware to forward only some extensions to another Application
276
+ ### Proxy only matching requests (e.g. `.php`) as middleware
228
277
 
229
278
  From [`examples/rack_php_proxy.rb`](examples/rack_php_proxy.rb):
230
279
 
231
- Example: Proxying only requests that end with ".php" could be done like this:
232
-
233
280
  ```ruby
234
281
  ###
235
282
  # Open http://localhost:3000/test.php to trigger proxy
@@ -267,113 +314,65 @@ class RackPhpProxy < Rack::Proxy
267
314
  end
268
315
  ```
269
316
 
270
- To use the middleware, please consider the following:
271
-
272
- 1) For Rails we could add a configuration in `config/application.rb`
317
+ Mount it in Rails (`config/application.rb`):
273
318
 
274
319
  ```ruby
275
- config.middleware.use RackPhpProxy, backend: "http://php.net", ssl_verify_none: true
320
+ config.middleware.use RackPhpProxy, backend: "http://php.net"
276
321
  ```
277
322
 
278
- 2) For Sinatra or any Rack-based application:
323
+ or in Sinatra / any Rack app:
279
324
 
280
325
  ```ruby
281
326
  class MyAwesomeSinatra < Sinatra::Base
282
- use RackPhpProxy, backend: "http://php.net", ssl_verify_none: true
327
+ use RackPhpProxy, backend: "http://php.net"
283
328
  end
284
329
  ```
285
330
 
286
- This will allow to run the other requests through the application and only proxy the requests that match the condition from the middleware.
287
-
288
- See tests for more examples.
331
+ Requests matching the condition are proxied; everything else runs through your application as usual. See the tests for more examples.
289
332
 
290
- ### SSL proxy for SpringBoot applications debugging
333
+ ### Local TLS-terminating proxy
291
334
 
292
- 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.
335
+ Useful when an external integration (OAuth callbacks, webhooks) insists on talking HTTPS to your dev machine: terminate TLS locally and forward the decrypted traffic to your app running on plain HTTP.
293
336
 
294
- Create `config.ru` file:
295
337
  ```ruby
296
- #
297
338
  # config.ru
298
- #
299
- require 'rack'
300
- require 'rack-proxy'
339
+ require "rack-proxy"
301
340
 
302
- class ForwardHost < Rack::Proxy
341
+ class ForwardProto < Rack::Proxy
303
342
  def rewrite_env(env)
304
- env['HTTP_X_FORWARDED_HOST'] = env['SERVER_NAME']
305
- env['HTTP_X_FORWARDED_PROTO'] = env['rack.url_scheme']
343
+ env["HTTP_X_FORWARDED_HOST"] = env["SERVER_NAME"]
344
+ env["HTTP_X_FORWARDED_PROTO"] = env["rack.url_scheme"]
306
345
  env
307
346
  end
308
347
  end
309
348
 
310
- run ForwardHost.new(backend: 'http://localhost:8080')
311
- ```
312
-
313
- Create `Gemfile` file:
314
- ```ruby
315
- source "https://rubygems.org"
316
-
317
- gem 'thin'
318
- gem 'rake'
319
- gem 'rack-proxy'
320
- ```
321
-
322
- Create `config.yml` file with configuration of web server `thin`:
323
- ```yml
324
- ---
325
- ssl: true
326
- ssl-key-file: keys/domain.key
327
- ssl-cert-file: keys/domain.crt
328
- ssl-disable-verify: false
349
+ run ForwardProto.new(backend: "http://localhost:8080")
329
350
  ```
330
351
 
331
- Create 'keys' directory and generate SSL key and certificates files `domain.key` and `domain.crt`
352
+ Generate a key/certificate pair for your dev hostname and serve the proxy over TLS, e.g. with puma:
332
353
 
333
- Run `bundle exec thin start` for running it with `thin`'s default port.
334
-
335
- Or use `sudo -E thin start -C config.yml -p 443` for running with default for `https://` port.
336
-
337
- Don't forget to enable processing of `X-Forwarded-...` headers on your application side. Just add following strings to your `resources/application.yml` file.
338
- ```yml
339
- ---
340
- server:
341
- tomcat:
342
- remote-ip-header: x-forwarded-for
343
- protocol-header: x-forwarded-proto
344
- use-forward-headers: true
354
+ ```sh
355
+ puma -b 'ssl://0.0.0.0:9292?key=keys/domain.key&cert=keys/domain.crt' config.ru
345
356
  ```
346
357
 
347
- Add some domain name like `debug.your_app.com` into your local `/etc/hosts` file like
348
- ```
349
- 127.0.0.1 debug.your_app.com
350
- ```
358
+ Point the dev hostname at yourself (`127.0.0.1 debug.your_app.com` in `/etc/hosts`), and make sure your app honors `X-Forwarded-Host` / `X-Forwarded-Proto` from this trusted hop (e.g. `server.forward-headers-strategy: framework` in Spring Boot, or Rails' default `config.action_dispatch` handling).
351
359
 
352
- 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.
360
+ ### Client TLS certificates (mutual TLS)
353
361
 
354
- ### Using SSL/TLS certificates with HTTP connection
355
- 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.
362
+ When a third-party API authenticates clients with TLS certificates, terminate the client's request and re-sign the outgoing connection:
356
363
 
357
- Just specify Rack::Proxy SSL options and your request will use TLS HTTP connection:
358
364
  ```ruby
359
365
  # config.ru
360
- . . .
366
+ cert = OpenSSL::X509::Certificate.new(File.read("./certs/client.crt"))
367
+ key = OpenSSL::PKey.read(File.read("./certs/key.pem"))
361
368
 
362
- cert_raw = File.read('./certs/rootCA.crt')
363
- key_raw = File.read('./certs/key.pem')
364
-
365
- cert = OpenSSL::X509::Certificate.new(cert_raw)
366
- key = OpenSSL::PKey.read(key_raw)
367
-
368
- use TLSProxy, backend: "https://client-tls-auth-api.com", cert: cert, key: key, verify_mode: OpenSSL::SSL::VERIFY_PEER, min_version: :TLS1_2
369
+ use TLSProxy, backend: "https://client-tls-auth-api.com",
370
+ cert: cert, key: key, min_version: :TLS1_2
369
371
  ```
370
372
 
371
- And rewrite host for example:
372
373
  ```ruby
373
374
  # tls_proxy.rb
374
375
  class TLSProxy < Rack::Proxy
375
- attr_accessor :original_request, :query_params
376
-
377
376
  def rewrite_env(env)
378
377
  env["HTTP_HOST"] = "client-tls-auth-api.com:443"
379
378
  env
@@ -381,8 +380,7 @@ class TLSProxy < Rack::Proxy
381
380
  end
382
381
  ```
383
382
 
384
- Upgrading
385
- ----
383
+ ## Upgrading
386
384
 
387
385
  ### 0.8.x → 1.0.0
388
386
 
@@ -421,16 +419,29 @@ Rack::Proxy.new(ssl_verify_none: true) # or
421
419
  Rack::Proxy.new(verify_mode: OpenSSL::SSL::VERIFY_NONE)
422
420
  ```
423
421
 
424
- For internal services with a private CA, prefer setting `cert`/`verify_mode` over disabling verification altogether.
422
+ For internal services with a private CA, prefer setting `ca_file`/`cert_store` over disabling verification altogether.
425
423
 
426
- A note on header keys (#96)
427
- ----
424
+ ## Header keys and underscores
428
425
 
429
- Per the standard Rack/CGI convention, header names received by your proxy are exposed in the env with underscores (`HTTP_X_CUSTOM_HEADER`), and rack-proxy rewrites them with dashes (`X-Custom-Header`) when forwarding. This conversion is lossy: by the time a request reaches rack-proxy, the upstream web server (nginx, Apache, Caddy, Puma) has already collapsed both `X-Custom-Header` and `X_Custom_Header` into the same env key, and rack-proxy cannot recover the original spelling.
426
+ Per the standard Rack/CGI convention, header names received by your proxy are exposed in the env with underscores (`HTTP_X_CUSTOM_HEADER`), and rack-proxy rewrites them with dashes (`X-Custom-Header`) when forwarding. This conversion is lossy: by the time a request reaches rack-proxy, the upstream web server (nginx, Apache, Caddy, Puma) has already collapsed both `X-Custom-Header` and `X_Custom_Header` into the same env key, and rack-proxy cannot recover the original spelling (see [#96](https://github.com/ncr/rack-proxy/issues/96)).
430
427
 
431
428
  If you need underscore-style headers preserved end-to-end, configure your fronting web server (e.g. `underscores_in_headers on;` in nginx, or `HTTPProtocolOptions` in Apache) — rack-proxy is not the right layer to fix this.
432
429
 
433
- Compatibility notes
434
- ----
430
+ ## Compatibility notes
435
431
 
436
432
  The streaming response path (the default) streams straight off the backend socket via `Net::HTTP`. Historically it relied on private `net/http` internals and did not work at all under `webmock`, `vcr`, or `fakeweb`; it now uses only the public `Net::HTTP#request` API, but those libraries still replace the real network layer, so behavior under them is not guaranteed. In tests that stub HTTP, prefer `streaming: false`.
433
+
434
+ ## Development
435
+
436
+ ```sh
437
+ bundle install
438
+ bundle exec rake test # full suite, fully offline, ~2-3s
439
+ LIVE=1 bundle exec rake test # additionally runs real-internet smoke tests
440
+ bundle exec standardrb # style check (CI-blocking)
441
+ ```
442
+
443
+ Bug reports and pull requests are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for the workflow and [CLAUDE.md](CLAUDE.md) for the invariants every change must preserve. Release history lives in [CHANGELOG.md](CHANGELOG.md).
444
+
445
+ ## License
446
+
447
+ Released under the [MIT License](LICENSE).
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rack
4
4
  class Proxy
5
- VERSION = "1.0.0"
5
+ VERSION = "1.0.1"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-proxy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jacek Becela