-
Notifications
You must be signed in to change notification settings - Fork 225
Block private and loopback dials in webhook HTTP client #5190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
JAORMX
wants to merge
1
commit into
main
Choose a base branch
from
pr/webhook-ssrf-fix
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| // SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package webhook | ||
|
|
||
| import ( | ||
| "syscall" | ||
| "testing" | ||
| ) | ||
|
|
||
| // The functions in this file are test-only helpers that intentionally live in a | ||
| // non-_test.go file so that sub-package tests (e.g. pkg/webhook/validating, | ||
| // pkg/webhook/mutating) can call into them via TestMain. There is no clean | ||
| // alternative for cross-package test-time injection of the package-level | ||
| // dialerControl hook, so these helpers are exported. The testing.TB argument | ||
| // (or the explicit "ForTestMain" suffix) is the signal that the call is | ||
| // test-scoped; production code MUST NOT call any of them. | ||
|
|
||
| // SetDialerControlForTesting overrides the package-level dialerControl hook | ||
| // for the duration of tb. It is the sanctioned way for tests to bypass the | ||
| // production SSRF dial-time guard so they can talk to httptest servers, | ||
| // which always bind 127.0.0.1. The previous value is restored via t.Cleanup. | ||
| // | ||
| // Production code MUST NOT call this function. The testing.TB argument is the | ||
| // signal that the call is test-scoped. | ||
| func SetDialerControlForTesting(tb testing.TB, control func(network, address string, c syscall.RawConn) error) { | ||
| tb.Helper() | ||
| prev := dialerControl.Load() | ||
| fn := dialerControlFunc(control) | ||
| dialerControl.Store(&fn) | ||
| tb.Cleanup(func() { dialerControl.Store(prev) }) | ||
| } | ||
|
|
||
| // SetDialerControlForTestMain installs control as the dialer guard for the | ||
| // rest of the test binary's lifetime. Use this in TestMain in sub-packages | ||
| // whose entire test suite legitimately dials httptest servers bound to | ||
| // 127.0.0.1. There is no restore — the binary exits anyway. | ||
| // | ||
| // Production code MUST NOT call this function. | ||
| func SetDialerControlForTestMain(control func(network, address string, c syscall.RawConn) error) { | ||
| fn := dialerControlFunc(control) | ||
| dialerControl.Store(&fn) | ||
| } | ||
|
|
||
| // AllowAnyDialerControl is a permissive Control function for tests that | ||
| // need to dial httptest servers on 127.0.0.1. It performs no validation | ||
| // and always returns nil. | ||
| // | ||
| // Production code MUST NOT use this function. | ||
| func AllowAnyDialerControl(_, _ string, _ syscall.RawConn) error { return nil } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package mutating | ||
|
|
||
| import ( | ||
| "os" | ||
| "testing" | ||
|
|
||
| "github.com/stacklok/toolhive/pkg/webhook" | ||
| ) | ||
|
|
||
| // TestMain installs a permissive dialer control hook for the entire test | ||
| // binary so that webhook clients can dial httptest servers bound to 127.0.0.1. | ||
| // The production hook (networking.ProtectedDialerControl) would otherwise reject | ||
| // loopback addresses as part of the SSRF guard. | ||
| func TestMain(m *testing.M) { | ||
| webhook.SetDialerControlForTestMain(webhook.AllowAnyDialerControl) | ||
| os.Exit(m.Run()) | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package validating | ||
|
|
||
| import ( | ||
| "os" | ||
| "testing" | ||
|
|
||
| "github.com/stacklok/toolhive/pkg/webhook" | ||
| ) | ||
|
|
||
| // TestMain installs a permissive dialer control hook for the entire test | ||
| // binary so that webhook clients can dial httptest servers bound to 127.0.0.1. | ||
| // The production hook (networking.ProtectedDialerControl) would otherwise reject | ||
| // loopback addresses as part of the SSRF guard. | ||
| func TestMain(m *testing.M) { | ||
| webhook.SetDialerControlForTestMain(webhook.AllowAnyDialerControl) | ||
| os.Exit(m.Run()) | ||
| } |
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking / follow-up thought, not for this PR.
This hand-builds a transport with its own dialer guard, but
pkg/networking.HttpClientBuilderalready does almost exactly this: it installsProtectedDialerControland gives tests a clean opt-out viaWithPrivateIPs(true)— no package-level global, noatomic.Pointer, and no exported test helpers. The bespokedialerControlglobal (plusdialer_testing.go, which is the only*_testing.gofile in the repo) is really what forces us to export the guard-disabling helpers.As far as I can tell, only two gaps stop webhook from just using the builder today:
ClientCertPath/ClientKeyPath)tls.Config.InsecureSkipVerifyknob distinct from the HTTP-allow knobSo a possible follow-up (bigger than this PR): add
WithClientCert(certPath, keyPath)andWithInsecureSkipVerify(bool)toHttpClientBuilder, then have webhook call the builder and delete the global +dialer_testing.goentirely. Happy to file an issue for it rather than expand scope here.