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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -4
- data/README.md +166 -155
- data/lib/rack/proxy/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ec993cb1f75831f1f7270d06634ba8c94b40246af6a622e239e29ddd2ff74e20
|
|
4
|
+
data.tar.gz: e5105cdc0ce7f69fb47b36e483dde1bad7799ff90f052566e526cc65d99fe7b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
14
|
-
|
|
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.
|
|
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
|
-
|
|
3
|
+
[](https://rubygems.org/gems/rack-proxy)
|
|
4
|
+
[](https://github.com/ncr/rack-proxy/actions/workflows/ci.yml)
|
|
5
|
+
[](https://rubygems.org/gems/rack-proxy)
|
|
6
|
+
[](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
|
-
- [
|
|
25
|
+
- [Quick start](#quick-start)
|
|
26
|
+
- [How it works](#how-it-works)
|
|
9
27
|
- [Options](#options)
|
|
10
28
|
- [Security considerations](#security-considerations)
|
|
11
|
-
- [
|
|
29
|
+
- [Recipes](#recipes)
|
|
12
30
|
- [Upgrading](#upgrading)
|
|
13
|
-
- [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
+
call(env) → rewrite_env(env) → perform_request(env) → rewrite_response([status, headers, body])
|
|
29
80
|
```
|
|
30
81
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
94
|
+
## Options
|
|
77
95
|
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
320
|
+
config.middleware.use RackPhpProxy, backend: "http://php.net"
|
|
276
321
|
```
|
|
277
322
|
|
|
278
|
-
|
|
323
|
+
or in Sinatra / any Rack app:
|
|
279
324
|
|
|
280
325
|
```ruby
|
|
281
326
|
class MyAwesomeSinatra < Sinatra::Base
|
|
282
|
-
|
|
327
|
+
use RackPhpProxy, backend: "http://php.net"
|
|
283
328
|
end
|
|
284
329
|
```
|
|
285
330
|
|
|
286
|
-
|
|
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
|
-
###
|
|
333
|
+
### Local TLS-terminating proxy
|
|
291
334
|
|
|
292
|
-
|
|
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
|
|
341
|
+
class ForwardProto < Rack::Proxy
|
|
303
342
|
def rewrite_env(env)
|
|
304
|
-
env[
|
|
305
|
-
env[
|
|
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
|
|
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
|
-
|
|
352
|
+
Generate a key/certificate pair for your dev hostname and serve the proxy over TLS, e.g. with puma:
|
|
332
353
|
|
|
333
|
-
|
|
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
|
-
|
|
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
|
-
|
|
360
|
+
### Client TLS certificates (mutual TLS)
|
|
353
361
|
|
|
354
|
-
|
|
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
|
-
|
|
363
|
-
|
|
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 `
|
|
422
|
+
For internal services with a private CA, prefer setting `ca_file`/`cert_store` over disabling verification altogether.
|
|
425
423
|
|
|
426
|
-
|
|
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).
|
data/lib/rack/proxy/version.rb
CHANGED