Skip to content

Send bearer auth to ECR Public registry requests - #345

Open
NenfuAT wants to merge 2 commits into
dependabot:mainfrom
NenfuAT:fix-ecr-public-auth
Open

NenfuAT wants to merge 2 commits into
dependabot:mainfrom
NenfuAT:fix-ecr-public-auth

Conversation

@NenfuAT

@NenfuAT NenfuAT commented Oct 28, 2026

Copy link
Copy Markdown

What are you trying to accomplish?

Fixes #157. docker_registry credentials for public.ecr.aws (Amazon ECR Public) currently fail: ECR Public answers Basic auth on registry requests with a 400 (Your Authorization Token is invalid) instead of a 401 challenge, so TokenTransport never starts the token exchange flow, and requests to public.ecr.aws never succeed.

This change:

  • Sends bearer auth directly on public.ecr.aws /v2 requests, built from the GetAuthorizationToken token (base64("AWS:<password>")), instead of going through the Basic/token-challenge flow. Other paths such as /token keep the existing flow. This is exactly the scheme documented under "Using HTTP API authentication" (curl -H "Authorization: Bearer $TOKEN" https://public.ecr.aws/v2/...), and was also confirmed against the live registry (see below).
  • Treats a docker_registry credential for public.ecr.aws with a username other than AWS as an AWS access key pair, as already done for private ECR, and exchanges it for a token via ecr-public:GetAuthorizationToken. This call is only available in us-east-1, so the region is hardcoded for it (the AWS docs likewise instruct --region us-east-1 for both get-login-password and get-authorization-token against ECR Public).

Anything you want to highlight for special attention from reviewers?

An alternative would be to have TokenTransport treat ECR Public's 400 as a challenge, but the 400 carries no WWW-Authenticate header to drive the token exchange (confirmed against the live registry), so there's nothing for that flow to key off of. The bearer scheme is also exactly what AWS documents for direct HTTP API access, so sending it directly is simpler and doesn't change behavior for any other registry.

  • internal/handlers/docker_registry.go
    • authenticateWithCredential: added a bearer-auth path for public.ecr.aws registry (/v2) requests. Private ECR and all other registries keep going through the existing Basic/token-challenge BasicTransport path unchanged.
    • getECRCredentials (defensive fix): now checks for a private ECR host before returning a cached token, so a credential whose cache happens to hold an ECR Public token (ecrUsername/ecrPassword) is never sent as private ECR Basic auth. A single credential only ever matches one of the two host patterns today, so this doesn't change behavior; it just keeps the two token caches from being cross-used if that assumption ever changes.
    • Added getECRPublicCredentials to exchange an AWS access key pair for an ECR Public authorization token, caching it the same way getECRCredentials does for private ECR. The cached token isn't proactively refreshed before its ~12h expiry; the proxy process is short-lived (one dependabot-core job), so this mirrors the existing private ECR behavior and isn't expected to hit the expiry in practice.
    • Added github.com/aws/aws-sdk-go-v2/service/ecrpublic (go.mod / go.sum / vendor/).

Most of the diff is the vendored ecrpublic SDK client that comes from running go mod vendor after adding that dependency (same pattern as #195, which vendored the AWS SDK v2 migration). The actual logic change is confined to internal/handlers/docker_registry.go (+91/-10 lines) and its test file (+176 lines). Reviewers can skip vendor/ and focus on those two files.

No documentation update is needed: docker_registry credential configuration isn't documented in this repo, and the new behavior (treating a non-AWS username for public.ecr.aws as an access key) follows the same convention already used for private ECR hosts.

How will you know you've accomplished your goal?

  • Ran the same checks CI's lint job runs: go mod tidy -diff (no diff), gofmt -l (clean), go build ./..., go vet ./.... go test ./... passes, including new TestDockerRegistryHandlerECRPublic / TestDockerRegistryHandlerECRPublicAccessKey cases.
  • Existing private-ECR and non-ECR docker_registry tests still pass unchanged, so this doesn't affect their behavior.
  • Verified against the live public.ecr.aws registry: reproduced the reported 400 on main, and confirmed a 200 on this branch for both a pre-generated token (aws ecr-public get-login-password) and an AWS access key credential.
Live proxy logs (before/after)

main (before):

[002] GET https://public.ecr.aws:443/v2/docker/library/hello-world/manifests/latest
[002] * authenticating docker registry request (host: public.ecr.aws)
[002] 400 https://public.ecr.aws:443/v2/docker/library/hello-world/manifests/latest
[002] Remote response: {"errors":[{"code":"DENIED","message":"Your Authorization Token is invalid."}]}

This branch (after):

[002] GET https://public.ecr.aws:443/v2/docker/library/hello-world/manifests/latest
[002] * authenticating docker ecr public request (host: public.ecr.aws)
[002] 200 https://public.ecr.aws:443/v2/docker/library/hello-world/manifests/latest

(Credentials, tokens and account identifiers redacted / not reproduced above; both runs used the same pre-generated-token (username: AWS) docker_registry credential for public.ecr.aws. The AWS access key credential case was verified separately and also produced a 200.)

Checklist

  • I have run the complete test suite to ensure all tests and linters pass.
  • I have thoroughly tested my code changes to ensure they work as expected, including adding additional tests for new functionality.
  • I have written clear and descriptive commit messages.
  • I have provided a detailed description of the changes in the pull request, including the problem it addresses, how it fixes the problem, and any relevant details about the implementation.
  • I have ensured that the code is well-documented and easy to understand.

ECR Public answers Basic auth on registry API requests with a 400
("Your Authorization Token is invalid") instead of a 401 challenge, so
TokenTransport never starts the token flow and requests to public.ecr.aws
fail, or fall back to anonymous pulls and hit rate limits.

Per the ECR Public HTTP API docs, registry requests take the
GetAuthorizationToken token, base64("AWS:<password>"), as a bearer token.
For public.ecr.aws /v2 requests, build that token from the configured
username and password (as returned by `aws ecr-public get-login-password`)
and set it directly. Other paths such as /token keep the existing Basic
token flow.

Fixes dependabot#157
docker_registry credentials for public.ecr.aws with a username other
than AWS are now treated as an AWS access key pair, as for private ECR.
The proxy exchanges them for a token with ecr-public:GetAuthorizationToken
in us-east-1, so the token no longer has to be rotated by hand every
12 hours. If the exchange fails, the request is sent anonymously rather
than with the secret key.

getECRCredentials now checks for a private ECR host before returning a
cached token, so an ECR Public token is never sent as private ECR Basic
auth.

Adds github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.47.0, matching
the vendored aws-sdk-go-v2 core version.
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.

docker_registry credentials for public.ecr.aws fail with 400 (Your Authorization Token is invalid)

1 participant