Harden operator-set target headers (closes #233) (#242)
All checks were successful
check / check (push) Successful in 2m58s

This commit was merged in pull request #242.
This commit is contained in:
2026-08-20 10:54:43 +02:00
parent 03cd1859d7
commit 687405993e
9 changed files with 746 additions and 62 deletions

View File

@@ -26,12 +26,12 @@ func newSSRFTestEngine() *delivery.Engine {
return delivery.NewTestEngine(log, client, 1)
}
// TestClientForConfig_TimeoutKeepsSSRFGuard asserts that a
// client returned by clientForConfig for a config with a
// TestClientForRequest_TimeoutKeepsSSRFGuard asserts that a
// client returned by clientForRequest for a config with a
// per-target timeout still refuses connections to
// private/reserved addresses (the timeout must not drop the
// SSRF-safe transport).
func TestClientForConfig_TimeoutKeepsSSRFGuard(t *testing.T) {
func TestClientForRequest_TimeoutKeepsSSRFGuard(t *testing.T) {
t.Parallel()
engine := newSSRFTestEngine()
@@ -51,7 +51,7 @@ func TestClientForConfig_TimeoutKeepsSSRFGuard(t *testing.T) {
Timeout: 5,
}
client := engine.ExportClientForConfig(cfg)
client := engine.ExportClientForRequest(cfg, nil)
require.NotSame(t, engine.ExportClient(), client,
"a per-target timeout must yield a "+
@@ -92,10 +92,11 @@ func TestClientForConfig_TimeoutKeepsSSRFGuard(t *testing.T) {
}
}
// TestClientForConfig_NoTimeoutUnchanged asserts that with
// no per-target timeout the shared SSRF-safe client is
// returned unchanged.
func TestClientForConfig_NoTimeoutUnchanged(t *testing.T) {
// TestClientForRequest_NoTimeoutUnchanged asserts that a
// request with neither a per-target timeout nor an origin-scoped
// header gets the shared SSRF-safe client unchanged: there is then
// nothing for a redirect policy to strip.
func TestClientForRequest_NoTimeoutUnchanged(t *testing.T) {
t.Parallel()
engine := newSSRFTestEngine()
@@ -104,10 +105,46 @@ func TestClientForConfig_NoTimeoutUnchanged(t *testing.T) {
URL: "https://example.com/hook",
}
client := engine.ExportClientForConfig(cfg)
client := engine.ExportClientForRequest(cfg, nil)
assert.Same(t, engine.ExportClient(), client,
"without a per-target timeout the shared client "+
"must be returned unchanged",
)
}
// TestClientForRequest_HeadersKeepSSRFGuard asserts that the
// redirect policy an origin-scoped header installs is added to a
// client that still carries the SSRF-safe transport. The guard is
// a dial hook, so keeping it is what makes each redirect hop pass
// the private-IP check too.
func TestClientForRequest_HeadersKeepSSRFGuard(t *testing.T) {
t.Parallel()
engine := newSSRFTestEngine()
cfg := &delivery.HTTPTargetConfig{
URL: "https://example.com/with-headers",
Headers: map[string]string{
"X-Api-Key": "configured",
},
}
client := engine.ExportClientForRequest(
cfg, []string{"X-Api-Key"},
)
require.NotNil(t, client.CheckRedirect,
"an origin-scoped header must install a redirect policy",
)
assert.Same(t,
engine.ExportClient().Transport, client.Transport,
"the SSRF-safe transport must be reused, not dropped",
)
assert.Equal(t,
engine.ExportClient().Timeout, client.Timeout,
"the shared client's timeout must be inherited",
)
}