Skip to content

transport: allow bearer realm at same host:port as registry#2302

Merged
Subserial merged 1 commit into
google:mainfrom
iahsanGill:fix/bearer-realm-same-host
May 18, 2026
Merged

transport: allow bearer realm at same host:port as registry#2302
Subserial merged 1 commit into
google:mainfrom
iahsanGill:fix/bearer-realm-same-host

Conversation

@iahsanGill

@iahsanGill iahsanGill commented May 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #2258.

The realm-URL validation introduced in #2243 rejects realms whose host resolves to a private, loopback, or link-local IP. This is the right default for the cross-host SSRF case (a malicious registry pointing the token endpoint at 169.254.169.254 or at a sister internal service), but it breaks legitimate setups where a user explicitly targets a private/loopback registry that returns a same-host token endpoint — e.g. test suites running an authenticated registry on 127.0.0.1, or on-prem deployments that colocate registry and token server.

This PR adds the same-host:port exception agreed in the issue thread (cc @evilgensec, @Subserial): if the realm URL host:port equals the registry host:port the user is already talking to, the private/link-local check is skipped. The realm is by definition not crossing a trust boundary the user did not already trust by passing the reference.

The match is strict — host and port — so that the "different port on the same loopback host" case (e.g. a realm pointed at a local database or metadata-service port that happens to live alongside the registry) remains blocked.

This mirrors the same-host exception pattern already used by the blob upload Location-header check in pkg/v1/remote/write.go.

Changes

Where Change
validateRealmURL Add registryHost parameter; return nil early when u.Host == registryHost. Scheme check still applies.
realmRedirectCheck Threads registryHost through to validateRealmURL so redirects honor the same exception.
fromChallenge callsite Passes reg.RegistryStr().
refreshOauth / refreshBasic callsites Pass bt.registry.RegistryStr().
TestValidateRealmURLUnspecified Updated for new signature (empty registryHost preserves prior behavior).
TestValidateRealmURLSameHost (new) Covers: same host:port allowed (loopback, private, IPv6 loopback); same host different port still blocked; cross-host loopback/metadata still blocked; scheme check still applies.

pkg/v1/remote/... tests all pass locally; go vet ./pkg/v1/remote/transport/... clean.

Out of scope

Test plan

  • TestValidateRealmURLSameHost — table-driven, 7 cases
  • TestValidateRealmURLUnspecified — still passes with new signature
  • TestTokenServerRedirectSSRF — still rejects cross-host redirects after the closure signature change
  • Full go test ./pkg/v1/remote/... green

@Subserial Subserial left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but can you reduce the long comments? Just mentioning "always allow realm URLs matching the registry URL" is fine.

@iahsanGill

Copy link
Copy Markdown
Contributor Author

Sure, I will push the changs with short commetns...

@codecov-commenter

codecov-commenter commented May 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 56.92%. Comparing base (c55facd) to head (861caba).

Files with missing lines Patch % Lines
pkg/v1/remote/transport/bearer.go 87.50% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2302      +/-   ##
==========================================
+ Coverage   56.89%   56.92%   +0.02%     
==========================================
  Files         165      165              
  Lines       11331    11333       +2     
==========================================
+ Hits         6447     6451       +4     
+ Misses       4120     4119       -1     
+ Partials      764      763       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Fixes google#2258.

The realm-URL validation introduced in google#2243 rejects realms whose host
resolves to a private, loopback, or link-local IP. This is the right
default for the cross-host SSRF case (a malicious registry pointing
the token endpoint at 169.254.169.254 or a sister internal service),
but it breaks legitimate setups where a user explicitly targets a
private/loopback registry that returns a same-host token endpoint —
e.g. test suites that run an authenticated registry on 127.0.0.1, or
on-prem deployments that colocate the registry and its token server.

Add a same-host:port exception: if the realm URL host:port equals the
registry host:port the user is already talking to, skip the private/
link-local check. The realm is not crossing a trust boundary the user
did not already trust by passing the reference.

The match is strict (host AND port) so that "different port on the
same loopback host" attacks — e.g. a realm pointed at a local
database or metadata-service port — remain blocked.

Mirrors the same-host exception pattern already used by the blob
upload Location-header check in pkg/v1/remote/write.go.

Signed-off-by: iahsanGill <mgill.bese22seecs@seecs.edu.pk>
@iahsanGill iahsanGill force-pushed the fix/bearer-realm-same-host branch from bc8aa5f to 861caba Compare May 18, 2026 18:38
@iahsanGill iahsanGill requested a review from Subserial May 18, 2026 18:39
@Subserial Subserial merged commit bf87c3b into google:main May 18, 2026
17 checks passed
jimmidyson added a commit to mesosphere/mindthegap that referenced this pull request May 19, 2026
Bump google/go-containerregistry from v0.21.5 to v0.21.6 to include
google/go-containerregistry#2302 (transport: allow bearer realm at
same host:port as registry).

The realm-URL validation introduced in go-containerregistry#2243
(shipped in v0.21.5) rejects realms whose host resolves to a private,
loopback, or link-local IP. This is the right default for the
cross-host SSRF case (a malicious registry pointing the token
endpoint at 169.254.169.254 or a sister internal service), but it
breaks `mindthegap push bundle` (and downstream `nkp push bundle`)
against on-prem registries that colocate the registry and bearer-
token endpoint on the same private IP. e.g. for an internal Harbor
at https://10.162.182.23:5000/library the push aborts with:

    invalid realm in www-authenticate: realm host
    "10.162.182.23" is a private or link-local address

v0.21.6 keeps the cross-host SSRF block but adds a same-host:port
exception: when the realm URL host AND port match the registry, the
private-IP check is skipped. The realm cannot escape a trust boundary
the user already crossed by passing the registry reference.

The transitive bumps (docker/cli, moby/moby, docker/go-connections,
klauspost/compress, golang.org/x/{crypto,mod,net,sys,term,text,
tools}) are pulled in by go-containerregistry v0.21.6 go.mod via MVS
resolution. The containerd/stargz-snapshotter/estargz and
vbatts/tar-split indirect dependencies are dropped because v0.21.6
removed estargz support (go-containerregistry#2288).

Test expectations in images/manifest_test.go are updated for the
unrelated OCI-spec compliance change in
google/go-containerregistry#2269: mutate.AppendManifests now sets the
index entry ArtifactType to the image Config.MediaType when the image
manifest does not itself set artifactType. For the Docker schema2
fixture in TestManifestListForImage_RemoteImage this becomes
"application/vnd.docker.container.image.v1+json"
(types.DockerConfigJSON).
jimmidyson added a commit to mesosphere/mindthegap that referenced this pull request May 19, 2026
…223) (#1046)

## Summary

- Bump `github.com/google/go-containerregistry` v0.21.5 → v0.21.6 to
pick up
[google/go-containerregistry#2302](google/go-containerregistry#2302)
— transport: allow bearer realm at same host:port as registry.
- Fixes [NCN-114223](https://jira.nutanix.com/browse/NCN-114223):
`mindthegap push bundle` (and downstream `nkp push bundle`) regression
against on-prem registries that colocate the registry and bearer-token
endpoint on the same private IP. e.g. for an internal Harbor at
`https://10.162.182.23:5000/library` the push aborted with `invalid
realm in www-authenticate: realm host "10.162.182.23" is a private or
link-local address`.
- Add regression test
`TestPushDockerArchive_BearerAuthSameHostLoopbackRealm` in
`cmd/mindthegap/push/imagearchive/push_test.go` that reproduces the
exact NCN-114223 error against v0.21.5 and passes against v0.21.6.

## Background

The realm-URL validation introduced in
[go-containerregistry#2243](google/go-containerregistry#2243)
(shipped in v0.21.5) rejects realms whose host resolves to a private,
loopback, or link-local IP. This is the right default for the cross-host
SSRF case (a malicious registry pointing the token endpoint at
`169.254.169.254` or a sister internal service), but it broke legitimate
on-prem deployments that serve their own token endpoint at the same
host:port as the registry.
[#2258](google/go-containerregistry#2258)
tracked the discussion;
[#2302](google/go-containerregistry#2302)
implements the agreed fix: keep the cross-host SSRF block, but skip the
private-IP check when the realm URL host AND port match the registry
host:port.

## Transitive dependency changes

Pulled in by `go-containerregistry` v0.21.6's `go.mod` via MVS
resolution:

- `docker/cli` v29.4.0 → v29.4.3
- `moby/moby/api` v1.54.1 → v1.54.2
- `moby/moby/client` v0.4.0 → v0.4.1
- `docker/go-connections` v0.6.0 → v0.7.0
- `klauspost/compress` v1.18.5 → v1.18.6
- `golang.org/x/{crypto,mod,net,sys,term,text,tools}` — minor bumps

Dropped (v0.21.6 removed estargz support in
[go-containerregistry#2288](google/go-containerregistry#2288)):

- `containerd/stargz-snapshotter/estargz` (indirect)
- `vbatts/tar-split` (indirect)

## Test fixture update

`images/manifest_test.go` is updated for the unrelated OCI-spec
compliance change in
[go-containerregistry#2269](google/go-containerregistry#2269):
`mutate.AppendManifests` now sets the index entry `ArtifactType` to the
image's `Config.MediaType` when the image manifest does not itself set
`artifactType`. For the Docker schema2 fixture in
`TestManifestListForImage_RemoteImage` this becomes
`"application/vnd.docker.container.image.v1+json"`
(`types.DockerConfigJSON`).

## Test plan

- [x] `go test -count=1 ./...` passes (155 tests).
- [x] New `TestPushDockerArchive_BearerAuthSameHostLoopbackRealm` fails
on v0.21.5 with the exact NCN-114223 error (`invalid realm in
www-authenticate: realm host "127.0.0.1" is a private or link-local
address`) and passes on v0.21.6 — bisected to confirm it is not a
tautology.
- [x] `go build ./...` passes.
- [x] `golangci-lint run ./...` clean.
- [ ] Manual verification against an on-prem Harbor with realm host ==
registry host on an RFC1918 IP (e.g. the reproducer environment in
NCN-114223).

## Out of scope

The two pre-existing govulncheck stdlib findings (`GO-2026-4982`,
`GO-2026-4980`, `GO-2026-4971`, `GO-2026-4918` — all `go1.25.x` fixed in
`go1.25.10`) are unrelated to this change.
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.

question: is bearer realm check over-zealous?

3 participants