Skip to content

Add generalized OIDCRegistry for collision-free credential storage#78

Merged
kbukum1 merged 8 commits intomainfrom
kamil/oidc-registry-generalized
Apr 4, 2026
Merged

Add generalized OIDCRegistry for collision-free credential storage#78
kbukum1 merged 8 commits intomainfrom
kamil/oidc-registry-generalized

Conversation

@kbukum1
Copy link
Copy Markdown
Contributor

@kbukum1 kbukum1 commented Apr 2, 2026

What

Adds OIDCRegistry type to the oidc package — a generalized credential registry that replaces the per-handler map[string]*OIDCCredential pattern.

Why

Fixes #87. The current OIDC map is keyed by hostname only. When two registries share a host with different paths (e.g., two Azure DevOps feeds on pkgs.dev.azure.com), the second credential silently overwrites the first.

How

OIDCRegistry stores credentials in a map[hostname][]entry structure — each entry holds the parsed path, port, and credential. Lookup is O(1) by host, then longest path-prefix match within the bucket.

Handlers use two methods:

  • Register(cred, urlFields, registryType) — at construction time
  • TryAuth(req, ctx) — at request time
  • RegisterURL(url, cred, registryType) — for nuget HTTP-discovered URLs

This PR is Phase 1 (additive only). No handler changes — each handler is migrated in its own follow-up PR.

Follow-up PRs

PR Handler Type Behavior change
#79 Cargo Pure refactor None — already used full URL
#80 Python Bug fix OIDC key: hostname → full URL (fixes collision)
#81 Composer Bug fix OIDC key: hostname → full URL (fixes collision)
#82 Maven Bug fix OIDC key: hostname → full URL (fixes collision)
#83 Terraform Bug fix OIDC key: hostname → full URL (fixes collision)
#84 Docker Pure refactor None — already used raw registry value
#85 Helm Pure refactor None — already used raw registry value
#86 Rubygems Pure refactor None — already used url-with-host-fallback
#88 Goproxy Pure refactor None — already used url-with-host-fallback
#89 Pub Pure refactor None — already used full URL
#90 Hex Pure refactor None — already used full URL
#91 npm Bug fix OIDC key: hostname → full registry URL (fixes collision)
#92 NuGet Bug fix Uses RegisterURL for discovered URLs; consistent log format

Cleanup PR (removing old TryAuthOIDCRequestWithPrefix) will follow after all handlers are merged.

Testing

  • 21 unit tests covering registration, matching, collision avoidance, longest-prefix matching, case-insensitive host, port handling, deduplication, and nuget URL discovery support
  • All existing tests pass unchanged

Acknowledgment

The host+path keying approach used by OIDCRegistry was inspired by @microblag's earlier work in #72, which identified and fixed the same-host OIDC collision issue for npm specifically.

Introduce OIDCRegistry in the oidc package as the foundation for fixing
OIDC credential collisions when multiple same-ecosystem registries share
a host (dependabot-updates#13014).

Key design decisions:
- Store credentials in a map[hostname][]entry (not map[hostname]*cred)
  so two registries on the same host with different paths coexist
- Parse URLs at registration time, storing host/path/port separately
- Match requests via O(1) host lookup + path prefix check
- Provide Register(), RegisterURL(), and TryAuth() as the handler API

This is Phase 1 (additive only) — no handler changes yet. Existing code
and tests are unaffected.
@kbukum1 kbukum1 requested a review from a team as a code owner April 2, 2026 23:45
Copilot AI review requested due to automatic review settings April 2, 2026 23:45
@kbukum1 kbukum1 marked this pull request as draft April 2, 2026 23:46
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new generalized OIDCRegistry in internal/oidc to store and match OIDC credentials without host-only key collisions (e.g., same host with different registry paths), intended as a Phase 1 additive building block before migrating handlers.

Changes:

  • Introduces OIDCRegistry with host-bucketed storage and path-prefix matching for OIDC credentials.
  • Adds Register, RegisterURL, and TryAuth APIs to support both configured and discovered registry URLs.
  • Adds unit tests covering registration behavior, collision avoidance by path, and port handling.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
internal/oidc/oidc_registry.go Implements the new OIDC credential registry and request-time auth application.
internal/oidc/oidc_registry_test.go Adds unit tests validating registration and matching behaviors for the registry.

- Lowercase request host in TryAuth to match stored entries
- Select longest matching path prefix instead of first match
- Document the (credential, "", false) return case in Register
- Add tests: PathSpecificBeatsHostOnly, LongestPathPrefixWins,
  CaseInsensitiveHost
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

addEntry can fail silently when parseRegistryURL cannot extract a
hostname. Return bool from addEntry so Register returns ok=false
and RegisterURL skips the success log when nothing was stored.
kbukum1 added 3 commits April 2, 2026 19:11
…erURL

Cover remaining handler-driven scenarios:
- Cloudsmith OIDC sets X-Api-Key instead of Authorization
- index-url field priority (Python pattern)
- URL without protocol handled by ParseURLLax
- Multiple RegisterURL calls on same host (NuGet pattern)
addEntry now checks for existing entries with the same path and port
before appending. This keeps storage bounded regardless of how many
times the same URL is registered.
os.Unsetenv doesn't participate in test cleanup and could pollute
subsequent tests. Use t.Setenv with empty string instead.
@kbukum1 kbukum1 marked this pull request as ready for review April 3, 2026 00:25
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@microblag
Copy link
Copy Markdown
Contributor

This looks like a good solution. The only thing I noticed was it assumes port 443 when port isn't specified and doesn't check the scheme of the url. I think this is reasonably safe as I can't imagine there are many situations where you'd have OIDC auth but no TLS. But I thought it worth mentioning all the same.

@kbukum1
Copy link
Copy Markdown
Contributor Author

kbukum1 commented Apr 3, 2026

This looks like a good solution. The only thing I noticed was it assumes port 443 when port isn't specified and doesn't check the scheme of the url. I think this is reasonably safe as I can't imagine there are many situations where you'd have OIDC auth but no TLS. But I thought it worth mentioning all the same.

@microblag
Good callout — we only default to port 443 when no port is explicitly specified, which is the standard default for HTTPS. If a custom port is configured (e.g. :8443), it's preserved and matched exactly. As you noted, OIDC without TLS would be a misconfiguration, and we want to ensure there's no risk of credential leakage over plaintext.

@kbukum1 kbukum1 requested a review from JamieMagee April 3, 2026 23:02
@kbukum1 kbukum1 merged commit ba19fa5 into main Apr 4, 2026
183 of 184 checks passed
@kbukum1 kbukum1 deleted the kamil/oidc-registry-generalized branch April 4, 2026 00:12
@kbukum1 kbukum1 restored the kamil/oidc-registry-generalized branch April 4, 2026 00:17
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.

OIDC credential collision when multiple registries share a host

4 participants