Skip to content

feat: support distribution v3.x via x5c JWT header#20

Open
wgkingk wants to merge 3 commits into
mainfrom
feat/support-distribution-v3
Open

feat: support distribution v3.x via x5c JWT header#20
wgkingk wants to merge 3 commits into
mainfrom
feat/support-distribution-v3

Conversation

@wgkingk

@wgkingk wgkingk commented May 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • distribution/distribution v3.0.0 replaced libtrust with go-jose and keys its trustedKeys map by RFC 7638 JWK thumbprint, so tokens signed with the libtrust-style kid header that registry-auth used are rejected by v3.
  • This PR switches token signing to the JWS x5c protected header (signing cert in base64 DER). Both v2.8.1 (parseAndVerifyCertChain) and v3.0.0 (verifyCertChain) verify x5c against the rootcertbundle, so the same registry-auth-issued token works on both versions — and the archived github.com/docker/libtrust dependency goes away.
  • Distribution v3.0.0's routes (base/manifest/tags/blob/blob-upload/catalog) and config schema (version: 0.1) match v2 exactly, so no path / scope / registry-config changes were needed.

Changes

  • pkg/server/auth.go — emit x5c instead of libtrust kid; store *x509.Certificate on AuthProcessor; remove libtrust and unused crypto imports.
  • go.mod / go.sum — drop github.com/docker/libtrust.
  • pkg/server/server_test.go — new file. The headline TestSign_EmitsX5cHeaderAndVerifies locks down the x5c header contents and chain-verification path; additional tests cover Authenticate (anonymous / plaintext / bcrypt / wrong-password), Authorize (literal / regex / anonymous fallback), DecodeScope, DecodeScopeFromUrl (base / catalog / manifests / blobs / tags / delete / unknown), and IsScopeActionMatch. Coverage on pkg/server rises from 0% to ~27%.
  • scripts/simple-tests.sh — parametrized via REGISTRY_VERSIONS (default "v2.8.1 v3.0.0"); each version runs the full skopeo push/pull round-trip in its own pass with storage wiped between runs.
  • README.md / README_zh.md — note v2/v3 dual compatibility and v3's /etc/distribution/config.yml default config path.
  • CLAUDE.md — new project guidance; the JWT signing section explains the x5c rationale.

Test plan

  • make fmt vet test — all 12 unit tests pass.
  • Local end-to-end against both registries in a sandbox (no docker/skopeo). For each of v2.8.1 and v3.0.0: started the official registry binary against the same token.crt rootcertbundle, hit registry-auth's /v2/ proxy with Basic admin and got HTTP 200 (token signed with x5c was accepted by the registry's verify path), without auth got 401, and curl -H "Authorization: Bearer <token>" directly against the registry also returned 200.
  • Reviewer: run bash scripts/simple-tests.sh in an environment where /usr/bin is writable to exercise the full skopeo push/pull round-trip on both versions.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Updated Docker Registry configuration docs with v2.x and v3.x compatibility notes.
    • Added architecture guidance for authentication flows and configuration schema.
  • Bug Fixes

    • Improved token certificate chain embedding for enhanced token verification.
  • Tests

    • Added comprehensive authentication and authorization test suite.
    • Enhanced test scripts to validate multiple registry versions simultaneously.
  • Chores

    • Removed unused dependency.

Review Change Stack

distribution/distribution v3.0.0 replaced libtrust with go-jose and keys
its trustedKeys map by RFC 7638 JWK thumbprint, so tokens signed with the
old libtrust-style kid header (which v2.8.1 used) are no longer matched.

Switch to emitting the signing certificate in the JWS x5c protected
header. Both v2.8.1 (parseAndVerifyCertChain) and v3.0.0 (verifyCertChain)
verify x5c against the rootcertbundle, so the same token works on both —
no libtrust dependency needed.

- pkg/server/auth.go: drop libtrust kid derivation; embed the signing
  cert as base64 DER in the x5c header.
- go.mod/go.sum: remove github.com/docker/libtrust (archived).
- scripts/simple-tests.sh: parametrize REGISTRY_VERSIONS so CI exercises
  both v2.8.1 and v3.0.0 (defaults to both).
- pkg/server/server_test.go: cover the x5c regression and the
  previously-untested Authenticate / Authorize / DecodeScope* paths
  (raises pkg/server coverage from 0% to ~27%).
- README/README_zh: note v2/v3 dual compatibility and v3's config path.
- CLAUDE.md: project guidance with the x5c rationale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@wgkingk, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 43 minutes and 37 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 55daf2be-39b3-4ebf-b5e5-f63aa19a6a00

📥 Commits

Reviewing files that changed from the base of the PR and between 7bd9e7c and 16d704d.

📒 Files selected for processing (3)
  • pkg/server/auth.go
  • pkg/server/server_test.go
  • scripts/simple-tests.sh

Walkthrough

This PR removes the libtrust dependency by refactoring certificate handling in the authentication processor to embed x509 certificates directly in JWS x5c headers instead of deriving and using kid (key ID) values. Comprehensive test coverage validates the auth flow. The test script is refactored to support testing against multiple distribution registry versions. Documentation updates cover architecture guidance and version compatibility.

Changes

Libtrust Removal and x5c Header Migration

Layer / File(s) Summary
Dependency removal and import cleanup
go.mod, pkg/server/auth.go
libtrust is removed from go.mod. Imports in auth.go are cleaned to remove libtrust and unnecessary crypto package references.
Certificate storage and initialization
pkg/server/auth.go
AuthProcessor struct field changes from kid string to cert *x509.Certificate. NewAuthProcessor calls new loadCert method to parse and store the certificate file instead of deriving a kid.
JOSE signer x5c header generation
pkg/server/auth.go
generateSigner embeds certificate raw bytes as base64 in the JOSE x5c header, replacing the previous kid header derived from public key extraction.
Comprehensive auth flow test suite
pkg/server/server_test.go
Complete test coverage including test infrastructure (TestMain, certificate/key helpers), x5c header regression test, authentication flows (empty header, plaintext, bcrypt, errors), authorization rules with YAML loading and regex matching, scope decoding from query/URL parameters, and matching logic validation.

Multi-Version Registry Test Harness

Layer / File(s) Summary
Multi-version registry lifecycle and test execution
scripts/simple-tests.sh
REGISTRY_VERSIONS variable enables testing multiple distribution versions. Registry setup is decomposed into install_registry, start_registry, and stop_registry functions. do_tests accepts a version parameter and derives version-scoped repo/tag names. main loops over versions and orchestrates full lifecycle per version.

Documentation and Compatibility Notes

Layer / File(s) Summary
Architecture guidance and version compatibility documentation
CLAUDE.md, README.md, README_zh.md
CLAUDE.md documents service structure, JWT signing behavior, auth lookup and scope rules, configuration sources, YAML schema, and known gotchas. README files note distribution v2.x/v3.x compatibility with identical token config keys and v3 default config path change from /etc/docker/registry/config.yml to /etc/distribution/config.yml.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A rabbit hops through auth refactors bright,
Swapping kid headers for x5c's light,
With libtrust gone and tests laid thick,
Multiple registries tested quick!
🐰 Cryptography done with care,
Certificate chains floating everywhere. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: support distribution v3.x via x5c JWT header' accurately and concisely describes the main change: adding support for distribution v3.x by switching token signing to use the x5c JWT header instead of libtrust kid.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/support-distribution-v3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

scripts/simple-tests.sh ran `skopeo copy ... docker-archive:/tmp/registry-src.tar`
inside do_tests, so the second iteration (v3.0.0) failed with "docker-archive
doesn't support modifying existing images" because the v2.8.1 pass had already
written the file. Move the source-image fetch into its own prepare_source_image
called once from main(), and guard with -f so reruns are idempotent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
pkg/server/options/basic.go (1)

127-131: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

AuthConfigLabelSelector is assigned from the wrong field.

Line 130 currently assigns o.AuthConfigNamespace; this drops the configured selector and breaks secret selection behavior.

Proposed fix
 func (o *BasicOptions) ApplyToServer(server *server.Server) error {
 	server.BasicConfig.AuthConfigFile = o.AuthConfigFile
 	server.BasicConfig.AuthConfigNamespace = o.AuthConfigNamespace
-	server.BasicConfig.AuthConfigLabelSelector = o.AuthConfigNamespace
+	server.BasicConfig.AuthConfigLabelSelector = o.AuthConfigLabelSelector
 	server.BasicConfig.AuthTokenDuration = o.AuthTokenDuration
 	server.BasicConfig.AuthThirdpartyServer = o.AuthThirdpartyServer

As per coding guidelines: "Fix copy-paste bug in BasicOptions.ApplyToServer (pkg/server/options/basic.go:130): assign o.AuthConfigLabelSelector (not o.AuthConfigNamespace) into server.BasicConfig.AuthConfigLabelSelector."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/server/options/basic.go` around lines 127 - 131, In
BasicOptions.ApplyToServer, fix the copy-paste bug where
server.BasicConfig.AuthConfigLabelSelector is set from o.AuthConfigNamespace;
change the assignment to use o.AuthConfigLabelSelector instead so the label
selector configured on BasicOptions is propagated to
server.BasicConfig.AuthConfigLabelSelector (locate the assignment in the
ApplyToServer method and replace the wrong RHS).
pkg/server/auth.go (1)

226-249: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate the cert matches the RSA private key before emitting x5c

generateSigner only checks the private key is RSA, but it emits x5c from a.cert.Raw without ensuring a.cert.PublicKey matches rsaPrivKey.PublicKey (and without guarding a.cert being nil). If --auth-public-cert-file and --auth-private-key-file drift, startup succeeds and every issued token will fail verification downstream.

Suggested guard
 func (a *AuthProcessor) generateSigner(privateKeyFile string) error {
 	data, err := ioutil.ReadFile(privateKeyFile)
 	if err != nil {
 		return err
 	}
 	privKey, err := keyutil.ParsePrivateKeyPEM(data)
 	if err != nil {
 		return err
 	}
 
 	rsaPrivKey, ok := privKey.(*rsa.PrivateKey)
 	if !ok {
 		return fmt.Errorf("the private key is not in RSA format")
 	}
+	if a.cert == nil {
+		return fmt.Errorf("public certificate not loaded")
+	}
+	certPubKey, ok := a.cert.PublicKey.(*rsa.PublicKey)
+	if !ok {
+		return fmt.Errorf("the certificate public key is not in RSA format")
+	}
+	if certPubKey.N.Cmp(rsaPrivKey.PublicKey.N) != 0 || certPubKey.E != rsaPrivKey.PublicKey.E {
+		return fmt.Errorf("the certificate does not match the private key")
+	}
 
 	op := (&jose.SignerOptions{}).WithHeader(
 		jose.HeaderKey("x5c"),
 		[]string{base64.StdEncoding.EncodeToString(a.cert.Raw)},
 	)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/server/auth.go` around lines 226 - 249, generateSigner currently assumes
a.cert is set and that its public key matches the parsed RSA private key before
adding the x5c header; add checks in generateSigner to ensure a.cert is not nil
and that a.cert.PublicKey corresponds to rsaPrivKey.PublicKey (compare
modulus/exponent for *rsa.PublicKey) and return a clear error if they differ (so
tokens aren't signed with a mismatched cert), before constructing the
jose.SignerOptions and creating a.signer.
🧹 Nitpick comments (3)
pkg/server/options/basic.go (1)

43-43: ⚡ Quick win

Fix stale constructor comment text.

This comment references NewConsoleOptions/ConsoleOptions, but the function is NewBasicOptions and returns *BasicOptions.

Proposed fix
-// // NewConsoleOptions creates a ConsoleOptions object with default parameters.
+// NewBasicOptions creates a BasicOptions object with default parameters.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/server/options/basic.go` at line 43, Update the stale constructor comment
to reference the correct symbols: replace mentions of
NewConsoleOptions/ConsoleOptions with NewBasicOptions/*BasicOptions* so the doc
comment accurately describes that NewBasicOptions creates a BasicOptions object
with default parameters; ensure the comment text matches the function name
NewBasicOptions and the returned type BasicOptions.
pkg/server/server_test.go (1)

213-268: ⚡ Quick win

Cover the named-user→_anonymous fallback in one handler-level test.

The current suite proves _anonymous rules work when requested directly, but it does not exercise the production path where a named user gets no matching scope and pkg/server/handlers.go falls through to _anonymous. That leaves the documented auth contract unpinned.

As per coding guidelines, Lookup order for users/authorizations is: Secret first, then Static, then third-party. Implement fallthrough to _anonymous user when a user has no matching authorization.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/server/server_test.go` around lines 213 - 268, Add a new assertion in
TestLoadFromFile_YAMLAndAuthorize to cover the handler-level fallthrough to the
_anonymous rules: call p.Authorize("alice", AccessScope{{Type:
RepositoryAccessType, Name: "public/x"}}) and assert it returns a single rule
with PullAction (same expectations as the existing anonymous test), referencing
the TestLoadFromFile_YAMLAndAuthorize test and the Authorize
method/AnonymousUser/_anonymous rule to ensure a named user with no matching
rule falls back to anonymous.
scripts/simple-tests.sh (1)

120-127: ⚡ Quick win

Guarantee registry cleanup on failures.

With set -e, any failure inside install_registry, start_registry, or do_tests exits main before stop_registry runs. That leaves the background registry bound to :5000, which makes local reruns flaky and hides the real failing version.

♻️ Suggested cleanup guard
 function main() {
+    trap 'stop_registry' EXIT
     prepare_certs
     prepare_skopeo
     prepare_auth
     prepare_registry_auth
 
     for version in ${REGISTRY_VERSIONS}; do
         echo "=== testing against distribution ${version} ==="
         install_registry "${version}"
         start_registry
         do_tests "${version}"
         stop_registry
     done
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/simple-tests.sh` around lines 120 - 127, The loop can leave the
registry running if a command fails because set -e exits before stop_registry;
fix by adding a cleanup trap and a flag around each iteration: set
REGISTRY_STARTED=0, call install_registry, call start_registry and set
REGISTRY_STARTED=1, install a trap on EXIT that checks REGISTRY_STARTED and
calls stop_registry if set, run do_tests, then explicitly call stop_registry,
clear REGISTRY_STARTED and remove the trap; reference the existing functions
install_registry, start_registry, do_tests, stop_registry to implement this
guard so the registry is always cleaned up on failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/server/server_test.go`:
- Line 271: Replace all uses of httptest.NewRequest in the test to call
httptest.NewRequestWithContext(context.Background(), ...) instead and add an
import for the context package; specifically update the calls creating req (the
instances at lines noted: the occurrences that construct the request for
"/auth/token" etc.) to use NewRequestWithContext(context.Background(), "GET",
"http://x/auth/token?service=svc&scope=repository:foo/bar:pull,push", nil)
pattern, and add "context" to the import block so the tests compile and use a
background context.

---

Outside diff comments:
In `@pkg/server/auth.go`:
- Around line 226-249: generateSigner currently assumes a.cert is set and that
its public key matches the parsed RSA private key before adding the x5c header;
add checks in generateSigner to ensure a.cert is not nil and that
a.cert.PublicKey corresponds to rsaPrivKey.PublicKey (compare modulus/exponent
for *rsa.PublicKey) and return a clear error if they differ (so tokens aren't
signed with a mismatched cert), before constructing the jose.SignerOptions and
creating a.signer.

In `@pkg/server/options/basic.go`:
- Around line 127-131: In BasicOptions.ApplyToServer, fix the copy-paste bug
where server.BasicConfig.AuthConfigLabelSelector is set from
o.AuthConfigNamespace; change the assignment to use o.AuthConfigLabelSelector
instead so the label selector configured on BasicOptions is propagated to
server.BasicConfig.AuthConfigLabelSelector (locate the assignment in the
ApplyToServer method and replace the wrong RHS).

---

Nitpick comments:
In `@pkg/server/options/basic.go`:
- Line 43: Update the stale constructor comment to reference the correct
symbols: replace mentions of NewConsoleOptions/ConsoleOptions with
NewBasicOptions/*BasicOptions* so the doc comment accurately describes that
NewBasicOptions creates a BasicOptions object with default parameters; ensure
the comment text matches the function name NewBasicOptions and the returned type
BasicOptions.

In `@pkg/server/server_test.go`:
- Around line 213-268: Add a new assertion in TestLoadFromFile_YAMLAndAuthorize
to cover the handler-level fallthrough to the _anonymous rules: call
p.Authorize("alice", AccessScope{{Type: RepositoryAccessType, Name:
"public/x"}}) and assert it returns a single rule with PullAction (same
expectations as the existing anonymous test), referencing the
TestLoadFromFile_YAMLAndAuthorize test and the Authorize
method/AnonymousUser/_anonymous rule to ensure a named user with no matching
rule falls back to anonymous.

In `@scripts/simple-tests.sh`:
- Around line 120-127: The loop can leave the registry running if a command
fails because set -e exits before stop_registry; fix by adding a cleanup trap
and a flag around each iteration: set REGISTRY_STARTED=0, call install_registry,
call start_registry and set REGISTRY_STARTED=1, install a trap on EXIT that
checks REGISTRY_STARTED and calls stop_registry if set, run do_tests, then
explicitly call stop_registry, clear REGISTRY_STARTED and remove the trap;
reference the existing functions install_registry, start_registry, do_tests,
stop_registry to implement this guard so the registry is always cleaned up on
failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9a084e42-e013-401d-968b-be24ca1c0d0c

📥 Commits

Reviewing files that changed from the base of the PR and between 51ef8b2 and 7bd9e7c.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (8)
  • CLAUDE.md
  • README.md
  • README_zh.md
  • go.mod
  • pkg/server/auth.go
  • pkg/server/options/basic.go
  • pkg/server/server_test.go
  • scripts/simple-tests.sh
💤 Files with no reviewable changes (1)
  • go.mod

Comment thread pkg/server/server_test.go
}

func TestDecodeScope_FromQuery(t *testing.T) {
req := httptest.NewRequest("GET", "http://x/auth/token?service=svc&scope=repository:foo/bar:pull,push", nil)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP --type=go '\bhttptest\.NewRequest\s*\(' pkg/server/server_test.go

Repository: alauda/registry-auth

Length of output: 594


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Check whether golangci-lint enables the noctx rule
rg -n "noctx" -S . || true

# 2) See whether the file already uses NewRequestWithContext anywhere
rg -n --type=go "httptest\.NewRequestWithContext" pkg/server/server_test.go || true

# 3) Confirm context imports in the file
rg -n --type=go "import \\(|context\\." pkg/server/server_test.go || true

Repository: alauda/registry-auth

Length of output: 75


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate golangci config
ls -la .golangci.* 2>/dev/null || true
fd -t f -g ".golangci.y*ml" -g "golangci.y*ml" -g "*lint*" . || true

# Print any explicit noctx configuration
rg -n "noctx" -S . || true

# Show the import block in the target test file (small slice)
sed -n '1,80p' pkg/server/server_test.go

Repository: alauda/registry-auth

Length of output: 2727


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Find golangci-lint config files (including hidden ones)
echo "== config files =="
ls -la .golangci* 2>/dev/null || true
fd -t f --hidden -g '*golangci*.y*ml' --color never . || true
fd -t f --hidden -g '*golangci*.toml' --color never . || true

# 2) Search for noctx rule enablement in config
echo "== noctx mentions =="
rg -n "noctx" --hidden -S . || true

# 3) Check current usage in the target test file
echo "== NewRequestWithContext usage =="
rg -n --type=go "httptest\.NewRequestWithContext" pkg/server/server_test.go || true

echo "== context import/usage =="
rg -n --type=go "(^|\s)\"context\"|context\." pkg/server/server_test.go || true

Repository: alauda/registry-auth

Length of output: 165


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== root listing (look for config dirs/files) =="
ls -la

echo "== golangci-lint mentions =="
rg -n --hidden -S "golangci-lint|golangci" . || true

echo "== noctx mentions =="
rg -n --hidden -S "noctx" . || true

echo "== any NewRequestWithContext usage in repo =="
rg -n --hidden --type=go "httptest\.NewRequestWithContext" . || true

Repository: alauda/registry-auth

Length of output: 1508


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== search for golangci-lint usage =="
rg -n --hidden -S "golangci-lint" . || true

echo "== search for noctx enabled flags =="
rg -n --hidden -S "(--enable[= ]noctx|--disable[= ]noctx|noctx)" . || true

echo "== inspect call sites in pkg/server/server_test.go =="
nl -ba pkg/server/server_test.go | sed -n '250,410p'

Repository: alauda/registry-auth

Length of output: 254


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Makefile (lint section) =="
rg -n "golangci|lint|noctx" Makefile .github -S || true
echo
sed -n '1,220p' Makefile

echo
echo "== workflows (if any) =="
fd -t f --hidden .github -e yml -e yaml -e json 2>/dev/null || true
rg -n "golangci|noctx" .github -S --hidden || true

echo
echo "== pkg/server/server_test.go around call sites =="
sed -n '240,410p' pkg/server/server_test.go

Repository: alauda/registry-auth

Length of output: 6557


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== search .github for golangci-lint/noctx =="
rg -n --hidden -S "golangci-lint|noctx" .github scripts . || true

echo
echo "== search for 'context' usage in pkg/server/server_test.go =="
rg -n --type=go "(^|\s)\"context\"|context\." pkg/server/server_test.go || true

echo
echo "== show exact import block for pkg/server/server_test.go =="
sed -n '1,40p' pkg/server/server_test.go

echo
echo "== show exact call sites with a bit more surrounding context =="
# show around each line reported earlier
for n in 271 288 299 327 363 371 380; do
  echo "---- around $n ----"
  sed -n "$((n-3)),$((n+3))p" pkg/server/server_test.go
done

Repository: alauda/registry-auth

Length of output: 3178


🏁 Script executed:

rg -nP --type=go '\bhttptest\.NewRequest\s*\(' pkg/server/server_test.go

Repository: alauda/registry-auth

Length of output: 594


Replace httptest.NewRequest with httptest.NewRequestWithContext (add context import).

pkg/server/server_test.go uses httptest.NewRequest(...) at lines 271, 288, 299, 327, 363, 371, and 380. Convert each to httptest.NewRequestWithContext(context.Background(), ...) and add context to the imports (it’s not currently imported).

🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 271-271: net/http/httptest.NewRequest must not be called. use net/http/httptest.NewRequestWithContext

(noctx)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/server/server_test.go` at line 271, Replace all uses of
httptest.NewRequest in the test to call
httptest.NewRequestWithContext(context.Background(), ...) instead and add an
import for the context package; specifically update the calls creating req (the
instances at lines noted: the occurrences that construct the request for
"/auth/token" etc.) to use NewRequestWithContext(context.Background(), "GET",
"http://x/auth/token?service=svc&scope=repository:foo/bar:pull,push", nil)
pattern, and add "context" to the import block so the tests compile and use a
background context.

ClaimAccess fields had no `json:` tags, so the JWT's `access` array was
marshalled with the Go field names — `Type`/`Name`/`Actions`. Distribution
v2.8.1 happened to accept that (Go's encoding/json does a case-insensitive
fallback), but distribution v3 uses go-jose v3's JSON fork which dropped
that fallback. v3 parsed the access entries as empty and returned
"insufficient scope" on blob HEAD/GET, even though registry-auth had
signed a token covering pull,push.

Tag the fields with the lowercase keys mandated by the Docker token spec
(type/name/actions) and extend TestSign_EmitsX5cHeaderAndVerifies to
assert the raw payload uses those keys, so this can't regress.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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