Skip to content

dockerutil: block SSRF in remote.Image via private-IP dialer#1108

Open
evilgensec wants to merge 1 commit into
google:mainfrom
evilgensec:fix/ssrf-private-ip-roundtripper
Open

dockerutil: block SSRF in remote.Image via private-IP dialer#1108
evilgensec wants to merge 1 commit into
google:mainfrom
evilgensec:fix/ssrf-private-ip-roundtripper

Conversation

@evilgensec
Copy link
Copy Markdown

Summary

The pin of github.com/google/go-containerregistry in 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 in v0.21.4). Pre-fix go-containerregistry follows the realm URL from a WWW-Authenticate: Bearer challenge 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, or SourcePackage in any kf namespace sets the image reference to an attacker-controlled registry; the reconciler / build-helper invokes remote.Image(ref, dockerutil.GetAuthKeyChain()); the attacker returns WWW-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 JSON access_token), then sends Authorization: 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:171
  • cmd/setup-buildpack-build/main.go:142
  • pkg/sourceimage/download.go:46
  • pkg/reconciler/task/reconciler.go:305
  • pkg/reconciler/appstartcommand/reconciler.go:171

Plus the indirect copy via pkg/reconciler/app/reconciler.go:397 (app.Status.Image = *app.Spec.Build.Image). pkg/apis/kf/v1alpha1/build_validation.go does not allowlist registry hostnames; name.WeakValidation is syntactic only.

Why not a clean dep bump

Bumping github.com/google/go-containerregistry to v0.21.5 (the first tagged release containing validateRealmURL) pulls invasive concurrent upgrades to OpenTelemetry (v0.20.0 to v1.x), k8s.io/apiserver, knative.dev, and tekton. A clean go mod tidy against v0.21.5 against 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

  • Adds pkg/dockerutil/ssrfprotect.go:
    • SafeRemoteTransport() remote.Option returns a remote.WithTransport(...) that wraps an http.Transport whose Dialer.Control hook rejects connections to non-public addresses.
    • Blocked ranges: loopback, link-local (unicast and multicast), interface-local multicast, all multicast, unspecified, RFC1918, ULA, RFC6598 CGNAT (100.64.0.0/10). Public addresses pass through.
    • The check runs after DNS resolution at connect time, so it is DNS-rebinding safe.
  • Adds pkg/dockerutil/ssrfprotect_test.go covering blocked/allowed ranges, the loopback-handler integration check, and malformed/hostname input.
  • Wires dockerutil.SafeRemoteTransport() into every remote.Image call in production code:
    • cmd/build-helpers/extract.go
    • cmd/setup-buildpack-build/main.go
    • pkg/sourceimage/download.go (adds dockerutil import)
    • pkg/reconciler/task/reconciler.go
    • pkg/reconciler/appstartcommand/reconciler.go

Test plan

  • go build ./... clean
  • go test ./pkg/dockerutil/ -run \"SSRF|IsBlocked|Safe|Reject\" -v passes (5/5)
  • Optional follow-up by maintainers: bump go-containerregistry past v0.21.4 in a separate PR once the OpenTelemetry / k8s / knative / tekton graph is updated.
  • Optional follow-up: registry-hostname allowlist in pkg/apis/kf/v1alpha1/build_validation.go tied 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 in dockerutil.GetAuthKeyChain) beyond SSRF to private IPs.

Related

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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant