Add support for private-network upstreams by exempting hosts from the SSRF dial gate - #51
Open
Tilian wants to merge 2 commits into
Open
Add support for private-network upstreams by exempting hosts from the SSRF dial gate#51Tilian wants to merge 2 commits into
Tilian wants to merge 2 commits into
Conversation
Contributor
Author
|
Context: We want to support a multitude of Maven upstreams, so we have a Reposilite instance sitting in between our proxy instance(s) and the actual upstreams. This now needs a public ingress to work. |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces an explicit per-host allowlist to relax the SSRF dial gate for specific upstreams, enabling proxy deployments where upstream registries are only reachable via private network IPs (e.g., Kubernetes ClusterIP-backed services).
Changes:
- Added
WithAllowPrivateHosts(...)option to whitelist specific upstream hosts for relaxed SSRF dial gating. - Introduced
Fetcher.gateOptions(host)to selectsafehttpgate behavior per dial target. - Added unit test coverage for the new allowlist behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| fetch/fetcher.go | Adds host allowlist storage and per-host selection of safehttp.CheckIP options during dialing. |
| fetch/fetcher_test.go | Adds a unit test validating allowlist normalization and strict-by-default behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+113
to
+134
| // WithAllowPrivateHosts exempts the named hosts from the dial gate's loopback and private-address checks. | ||
| func WithAllowPrivateHosts(hosts ...string) Option { | ||
| return func(f *Fetcher) { | ||
| if f.allowPrivate == nil { | ||
| f.allowPrivate = make(map[string]bool, len(hosts)) | ||
| } | ||
| for _, h := range hosts { | ||
| if h = strings.TrimSpace(h); h != "" { | ||
| f.allowPrivate[strings.ToLower(h)] = true | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // gateOptions returns the safehttp options for a dial to host. | ||
| // Zero-value strict gate unless the host was whitelisted via WithAllowPrivateHosts. | ||
| func (f *Fetcher) gateOptions(host string) safehttp.Options { | ||
| if f.allowPrivate[strings.ToLower(host)] { | ||
| return safehttp.Options{AllowLoopback: true, AllowPrivate: true} | ||
| } | ||
| return safehttp.Options{} | ||
| } |
Comment on lines
+129
to
+132
| func (f *Fetcher) gateOptions(host string) safehttp.Options { | ||
| if f.allowPrivate[strings.ToLower(host)] { | ||
| return safehttp.Options{AllowLoopback: true, AllowPrivate: true} | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Currently the
safehttpdial gate refuses loopback and link-local addresses on every upstream dial.When the proxy runs inside a private network next to its upstream (the Kubernetes use-case of pointing an upstream at a local registry service like
maven-mirror.internal.svc.cluster.local, which resolves to a cluster IP) the gate makes that upstream unreachable by design.Today the only workaround is going through a public ingress, which costs an extra network round-trip and forces an otherwise-internal registry to be externally exposed.