Skip to content

Add CIMD storage decorator for embedded AS#5343

Open
amirejaz wants to merge 7 commits into
mainfrom
cimd-phase2-pr2-storage-decorator
Open

Add CIMD storage decorator for embedded AS#5343
amirejaz wants to merge 7 commits into
mainfrom
cimd-phase2-pr2-storage-decorator

Conversation

@amirejaz
Copy link
Copy Markdown
Contributor

@amirejaz amirejaz commented May 20, 2026

Summary

Phase 2 PR 2 of CIMD embedded AS support — depends on PR #5320 (merged).

The embedded authorization server currently requires clients to call /oauth/register (DCR) before the authorization flow. With this PR, clients can present an HTTPS URL as client_id and the server fetches and validates the CIMD document on demand, with no prior registration.

  • CIMDStorageDecorator wraps storage.Storage, overrides only GetClient, and delegates all other methods transparently. When fosite calls GetClient("https://vscode.dev/oauth/client-metadata.json"), the decorator fetches the document via pkg/oauthproto/cimd.FetchClientMetadataDocument, builds a fosite.Client, caches it with a configurable TTL via hashicorp/golang-lru/v2, and deduplicates concurrent fetches for the same URL via singleflight.
  • Unwrap() exposes the underlying storage so the DCRCredentialStore and RedisStorage type assertions in server_impl.go still reach the concrete backend through the decorator layer.
  • runLegacyMigration helper extracted from newServer to keep cyclomatic complexity under the lint limit after the Unwrap additions.

Type of change

  • New feature (non-breaking)

Test plan

  • Unit tests: go test ./pkg/authserver/storage/... — all pass including cache hit/miss, singleflight deduplication, TTL expiry, Unwrap, disabled path, DCR client unaffected
  • Full authserver suite: go test ./pkg/authserver/... — 13 packages pass

Special notes for reviewers

  • NewCIMDStorageDecorator returns base unchanged when enabled=false — no allocation, no behavior change for existing deployments
  • LoopbackClient wraps clients whose redirect_uris contain loopback addresses, enabling RFC 8252 §7.3 dynamic port matching for native app clients

Generated with Claude Code

@github-actions github-actions Bot added size/L Large PR: 600-999 lines changed and removed size/L Large PR: 600-999 lines changed labels May 20, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

❌ Patch coverage is 86.11111% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.77%. Comparing base (391f4f2) to head (52fe1dd).

Files with missing lines Patch % Lines
pkg/authserver/storage/cimd_decorator.go 90.36% 4 Missing and 4 partials ⚠️
pkg/authserver/server_impl.go 63.15% 4 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5343      +/-   ##
==========================================
+ Coverage   68.72%   68.77%   +0.04%     
==========================================
  Files         625      626       +1     
  Lines       63422    63520      +98     
==========================================
+ Hits        43588    43687      +99     
+ Misses      16585    16580       -5     
- Partials     3249     3253       +4     

☔ 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.

Base automatically changed from cimd-phase2-pr1-fetch-validate to main May 20, 2026 15:25
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Large PR Detected

This PR exceeds 1000 lines of changes and requires justification before it can be reviewed.

How to unblock this PR:

Add a section to your PR description with the following format:

## Large PR Justification

[Explain why this PR must be large, such as:]
- Generated code that cannot be split
- Large refactoring that must be atomic
- Multiple related changes that would break if separated
- Migration or data transformation

Alternative:

Consider splitting this PR into smaller, focused changes (< 1000 lines each) for easier review and reduced risk.

See our Contributing Guidelines for more details.


This review will be automatically dismissed once you add the justification section.

@github-actions github-actions Bot added size/XL Extra large PR: 1000+ lines changed and removed size/L Large PR: 600-999 lines changed labels May 20, 2026
The CIMDStorageDecorator wraps storage.Storage and intercepts GetClient
calls for HTTPS client_id values. When the embedded AS receives a
client_id like https://vscode.dev/oauth/client-metadata.json, the
decorator fetches the CIMD document via pkg/oauthproto/cimd, validates
it, builds a fosite.Client, caches the result with a configurable
fallback TTL, and deduplicates concurrent fetches for the same URL via
singleflight.

Key design decisions:
- Embeds storage.Storage so all ~30 other methods delegate transparently
- Unwrap() exposes the underlying storage for the DCRCredentialStore and
  RedisStorage type assertions in server_impl.go to reach the concrete
  backend through the decorator layer
- LoopbackClient wraps clients with loopback redirect URIs for RFC 8252
  §7.3 dynamic port matching
- NewCIMDStorageDecorator returns base unchanged when enabled=false (no
  allocation); fails loudly for invalid cacheMaxSize

runLegacyMigration extracted from newServer to keep the function under
the gocyclo limit after the Unwrap additions; both the DCRCredentialStore
assertion and the RedisStorage migration now use the same Unwrap pattern.

Incorporates all changes from PR 1 (pkg/oauthproto/cimd sub-package,
networking.FetchJSON with WithMaxResponseSize, IsPrivateIP reuse).

Relates to #4825

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@amirejaz amirejaz force-pushed the cimd-phase2-pr2-storage-decorator branch from 87e6996 to 24b7094 Compare May 20, 2026 16:18
@github-actions github-actions Bot dismissed their stale review May 20, 2026 16:19

PR size has been reduced below the XL threshold. Thank you for splitting this up!

@github-actions
Copy link
Copy Markdown
Contributor

✅ PR size has been reduced below the XL threshold. The size review has been dismissed and this PR can now proceed with normal review. Thank you for splitting this up!

@github-actions github-actions Bot added size/L Large PR: 600-999 lines changed and removed size/XL Extra large PR: 1000+ lines changed labels May 20, 2026
@amirejaz amirejaz marked this pull request as ready for review May 21, 2026 11:55
@github-actions github-actions Bot added size/L Large PR: 600-999 lines changed and removed size/L Large PR: 600-999 lines changed labels May 21, 2026
@amirejaz amirejaz requested a review from Copilot May 21, 2026 12:05
@github-actions github-actions Bot added size/L Large PR: 600-999 lines changed and removed size/L Large PR: 600-999 lines changed labels May 21, 2026
Copy link
Copy Markdown
Contributor

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 storage-layer decorator to enable CIMD (Client ID Metadata Document) client resolution for the embedded authorization server, allowing client_id values that are HTTPS URLs to be fetched/validated on-demand instead of requiring pre-registration via DCR.

Changes:

  • Introduces CIMDStorageDecorator that intercepts GetClient for HTTPS client_ids, fetches CIMD documents, and caches the resulting fosite.Client with LRU + TTL and singleflight deduplication.
  • Adds unit tests covering constructor behavior, delegation for opaque IDs, cache hit/miss/TTL/LRU eviction, singleflight behavior, and loopback redirect wrapping.
  • Updates server construction to support decorator-wrapped storages via Unwrap(), and extracts Redis legacy migration logic into runLegacyMigration.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.

File Description
pkg/authserver/storage/cimd_decorator.go Adds the CIMD-aware Storage decorator, caching, and CIMD→fosite client conversion logic (including loopback redirect handling).
pkg/authserver/storage/cimd_decorator_test.go Adds comprehensive unit tests for decorator behavior (caching, concurrency, unwrap, and client building).
pkg/authserver/server_impl.go Adds Unwrap() handling for storage type assertions and extracts Redis legacy migration into a helper.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/authserver/storage/cimd_decorator_test.go
Comment thread pkg/authserver/storage/cimd_decorator_test.go Outdated
Comment thread pkg/authserver/storage/cimd_decorator_test.go Outdated
Comment thread pkg/authserver/storage/cimd_decorator.go
Comment thread pkg/authserver/storage/cimd_decorator.go
Comment thread pkg/authserver/storage/cimd_decorator.go
Comment thread pkg/authserver/storage/cimd_decorator.go Outdated
Comment thread pkg/authserver/server_impl.go Outdated
Comment thread pkg/authserver/server_impl.go
cimd_decorator.go:
- Fix docstring: TTL is fixed (not from Cache-Control); Cache-Control
  parsing is a documented follow-up
- Force token_endpoint_auth_method to "none": the embedded AS only
  advertises "none" in discovery, so accepting other values creates an
  inconsistent client; always override regardless of what the document says
- Fix LoopbackClient dropping TokenEndpointAuthMethod: was passing
  defaultClient (no auth method) instead of openIDClient.DefaultClient
  (carries the auth method) to NewLoopbackClient
- Fix singleflight context: use context.WithoutCancel(ctx) so one
  caller cancelling does not abort the shared in-flight fetch for other
  waiters; HTTP client inside FetchClientMetadataDocument already
  enforces its own 5-second timeout

cimd_decorator_test.go:
- Replace flaky time.Sleep with a startBarrier channel: goroutines
  signal before calling fetchOrCached; draining all signals before
  closing ready makes singleflight deduplication deterministic
- Fix CIDM → CIMD typo in test name

server_impl.go:
- Fix error message: report baseStore type (concrete backend) not stor
  type (possibly a decorator) when DCRCredentialStore assertion fails
- Extract unwrapStorage helper to deduplicate the identical Unwrap()
  inline logic in newServer and runLegacyMigration

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added size/L Large PR: 600-999 lines changed and removed size/L Large PR: 600-999 lines changed labels May 21, 2026
Issue 1 (blocking): actually fix LoopbackClient dropping TokenEndpointAuthMethod.
  Change LoopbackClient to embed *fosite.DefaultOpenIDConnectClient instead of
  *fosite.DefaultClient, preserving TokenEndpointAuthMethod through the wrapper.
  Update NewLoopbackClient signature, registration.New(), buildFositeClient, and
  all test call sites accordingly.
  Previous "fix" was wrong: openIDClient.DefaultClient is the same pointer as
  defaultClient — the behaviour was identical to the original broken code.

Issue 2: remove dead `enabled bool` field from CIMDStorageDecorator.
  The constructor returns base unchanged when enabled=false so the struct is
  never instantiated with enabled=false; the field was never read.

Issue 3: add TestCIMDStorageDecorator_FetchOrCached_FetchFailureReturnsNotFound
  covering the error path and verifying errors.Is(err, fosite.ErrNotFound).

Issue 4: add CIMD decorator section to docs/arch/11-auth-server-storage.md
  covering what the decorator does, the Unwrap() pattern, and the air-gapped
  caveat.

Issue 5: replace //nolint:forcetypeassert with a safe type assertion that
  returns an observable error instead of panicking on type mismatch.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@amirejaz amirejaz requested a review from rdimitrov as a code owner May 21, 2026 12:52
@github-actions github-actions Bot added size/L Large PR: 600-999 lines changed and removed size/L Large PR: 600-999 lines changed labels May 21, 2026
- server_impl.go: remove stray blank comment line
- cimd_decorator_test.go: add TokenEndpointAuthMethod assertion to
  LoopbackClient test to catch any future regression

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added size/L Large PR: 600-999 lines changed and removed size/L Large PR: 600-999 lines changed labels May 21, 2026
Copy link
Copy Markdown
Contributor

@jhrozek jhrozek left a comment

Choose a reason for hiding this comment

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

One question on the token_endpoint_auth_method handling.

Comment thread pkg/authserver/storage/cimd_decorator.go Outdated
The embedded AS only supports "none" for CIMD clients. Previously,
buildFositeClient silently overrode any declared auth method to "none",
which was misleading: a client that claimed private_key_jwt would be
treated as public without any indication of the mismatch.

Now fetch() rejects documents that declare a method other than "none"
before buildFositeClient is reached. buildFositeClient only fills in
"none" as a default when the field is omitted, which matches the
comment on defaultCIMDTokenEndpointAuthMethod.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added size/L Large PR: 600-999 lines changed and removed size/L Large PR: 600-999 lines changed labels May 22, 2026
@github-actions github-actions Bot added size/L Large PR: 600-999 lines changed and removed size/L Large PR: 600-999 lines changed labels May 22, 2026
amirejaz added a commit that referenced this pull request May 22, 2026
- Fix CacheFallbackTTL comment to say it is a fixed TTL (not fallback);
  matches the fix already applied in PR #5343
- Add TODO(cimd) comment above CIMDRunConfig noting the CRD exposure gap
- Add discovery handler tests: CIMDEnabled=true advertises the flag,
  CIMDEnabled=false omits it, for both AS metadata and OIDC endpoints
- Add config defaults tests: CIMDEnabled=true fills in cache size/TTL
  defaults; CIMDEnabled=false leaves zero fields unchanged
- Add resolveCIMDConfig nil-guard test: nil input returns zero values,
  non-nil passes all fields through

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added size/L Large PR: 600-999 lines changed and removed size/L Large PR: 600-999 lines changed labels May 22, 2026
@amirejaz amirejaz requested a review from jhrozek May 22, 2026 11:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Large PR: 600-999 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants