dockerutil: block SSRF in remote.Image via private-IP dialer#1108
Open
evilgensec wants to merge 1 commit into
Open
dockerutil: block SSRF in remote.Image via private-IP dialer#1108evilgensec wants to merge 1 commit into
evilgensec wants to merge 1 commit into
Conversation
The pin of github.com/google/go-containerregistry is from 2022-02 and predates the upstream Bearer-realm SSRF fix (validateRealmURL, google/go-containerregistry#2243). Pre-fix go-containerregistry follows the realm URL from a WWW-Authenticate Bearer challenge verbatim and sends Basic auth from the keychain to it, so a tenant who points an App/Build/SourcePackage at an attacker-controlled registry can drive the kf controller to fetch the cluster's GCE/EC2 metadata endpoint and leak the workload identity token to the attacker on the next request. A clean dep bump pulls invasive OpenTelemetry v0.20 to v1.x, k8s.io, knative.dev, and tekton updates and currently fails `go mod tidy` on removed-package paths. This change adds an SSRF-protected http.Transport in pkg/dockerutil and wires it into every remote.Image call site: - cmd/build-helpers/extract.go - cmd/setup-buildpack-build/main.go - pkg/sourceimage/download.go - pkg/reconciler/task/reconciler.go - pkg/reconciler/appstartcommand/reconciler.go The protection runs as a net.Dialer Control hook after DNS resolution, so it is DNS-rebinding safe. Blocked ranges: loopback, link-local (unicast and multicast), interface-local multicast, all multicast, unspecified, RFC1918, ULA, and RFC6598 CGNAT (100.64.0.0/10). Public addresses pass through. A follow-up PR can still bump go-containerregistry once the rest of the dep tree is ready; this transport remains useful as defense in depth because it also covers the Basic-auth-to-attacker leak when only the initial registry hostname is attacker-controlled (the upstream fix only validates the realm URL, not the registry itself).
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.
Summary
The pin of
github.com/google/go-containerregistryin this repo is from 2022-02 and predates the upstream Bearer-realm SSRF fix (validateRealmURL, google/go-containerregistry#2243, merged 2026-03-31, first tagged inv0.21.4). Pre-fix go-containerregistry follows therealmURL from aWWW-Authenticate: Bearerchallenge verbatim and sends Basic auth from the keychain to it.In kf this is reachable from tenant input. A subject with write access to an
App,Build, orSourcePackagein any kf namespace sets the image reference to an attacker-controlled registry; the reconciler / build-helper invokesremote.Image(ref, dockerutil.GetAuthKeyChain()); the attacker returnsWWW-Authenticate: Bearer realm=\"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token?scopes=...\"; pre-fix go-containerregistry fetches that URL (the metadata server returns a JSONaccess_token), then sendsAuthorization: Bearer <controller-SA-token>to the attacker on the next call. Result: tenant gets the kf controller's GCP / AWS workload-identity token.Reachable call sites in this repo:
cmd/build-helpers/extract.go:171cmd/setup-buildpack-build/main.go:142pkg/sourceimage/download.go:46pkg/reconciler/task/reconciler.go:305pkg/reconciler/appstartcommand/reconciler.go:171Plus the indirect copy via
pkg/reconciler/app/reconciler.go:397(app.Status.Image = *app.Spec.Build.Image).pkg/apis/kf/v1alpha1/build_validation.godoes not allowlist registry hostnames;name.WeakValidationis syntactic only.Why not a clean dep bump
Bumping
github.com/google/go-containerregistrytov0.21.5(the first tagged release containingvalidateRealmURL) pulls invasive concurrent upgrades to OpenTelemetry (v0.20.0tov1.x),k8s.io/apiserver,knative.dev, and tekton. A cleango mod tidyagainstv0.21.5against this repo's 2022-era graph currently fails on removed-package paths in OpenTelemetry's old SDK structure (e.g.go.opentelemetry.io/otel/sdk/metric/aggregator/lastvalue,go.opentelemetry.io/otel/metric/registry). The dep bump deserves its own coordinated PR.This change adds defense in depth that does not require the dep bump and remains useful even after the bump, because it also covers the Basic-auth-to-attacker leak when the registry hostname itself is attacker-controlled (the upstream fix only validates the realm URL, not the initial registry connection).
What this PR does
pkg/dockerutil/ssrfprotect.go:SafeRemoteTransport() remote.Optionreturns aremote.WithTransport(...)that wraps anhttp.TransportwhoseDialer.Controlhook rejects connections to non-public addresses.100.64.0.0/10). Public addresses pass through.pkg/dockerutil/ssrfprotect_test.gocovering blocked/allowed ranges, the loopback-handler integration check, and malformed/hostname input.dockerutil.SafeRemoteTransport()into everyremote.Imagecall in production code:cmd/build-helpers/extract.gocmd/setup-buildpack-build/main.gopkg/sourceimage/download.go(addsdockerutilimport)pkg/reconciler/task/reconciler.gopkg/reconciler/appstartcommand/reconciler.goTest plan
go build ./...cleango test ./pkg/dockerutil/ -run \"SSRF|IsBlocked|Safe|Reject\" -vpasses (5/5)go-containerregistrypastv0.21.4in a separate PR once the OpenTelemetry / k8s / knative / tekton graph is updated.pkg/apis/kf/v1alpha1/build_validation.gotied to the Space's configured registry. That closes the broader "tenant points kf at any attacker registry" class (e.g. credential exfil via Basic auth indockerutil.GetAuthKeyChain) beyond SSRF to private IPs.Related