Skip to content

Generalise to WorkloadIdentity: resource-first configuration, Managed Identity and Blob Storage - #10

Open
neotrow wants to merge 4 commits into
rename/workload-identityfrom
feat/workload-identity
Open

neotrow wants to merge 4 commits into
rename/workload-identityfrom
feat/workload-identity

Conversation

@neotrow

@neotrow neotrow commented Sep 18, 2026

Copy link
Copy Markdown

Summary

Rebuilds the generalisation from #2 (resource-first configuration, Azure Managed Identity as an alternative to Google federation, Azure Blob Storage as a second resource) on top of the hardened token pipeline, instead of the pre-hardening implementation in #2.

Model. A TokenScope (AzureSql → https://database.windows.net/.default, BlobStorage → https://storage.azure.com/.default) is configured per resource under Csag.WorkloadIdentity with a Provider of ManagedIdentity (system- or user-assigned) or Google (workload identity federation via a Google service account ID token, against an app registration or a user-assigned managed identity holding the federated credential):

{
  "Csag.WorkloadIdentity": {
    "AzureSql":    { "Provider": "Google", "Google": { "TenantId": "…", "ClientId": "…", "ServiceAccountEmail": "…" } },
    "BlobStorage": { "Provider": "ManagedIdentity", "ManagedIdentity": { "UseSystemAssignedIdentity": true } },
    "RefreshAheadWindow": "00:05:00",
    "EnableBackgroundRefresh": true
  }
}

Public surface. AddWorkloadIdentity() / (Action<WorkloadIdentityOptions>) / (IConfiguration) (BindConfiguration / ValidateOnStart / TryAdd*, as before); IAzureSqlTokenProvider (GetAzureSqlAccessTokenAsync unchanged) and IBlobStorageTokenProvider, both extending a small IAccessTokenProvider that returns the token with its real expiry; WorkloadIdentityTokenCredential, a TokenCredential adapter over any provider so Azure SDK clients (e.g. BlobServiceClient) can be wired without faking expiry; the options types; TokenScope with its metadata attribute. Resolving a provider for a resource that is not configured throws a clear InvalidOperationException naming the missing section; validation messages are path-qualified (AzureSql:Google:ServiceAccountEmail must be provided.).

What is shared, not duplicated. One internal ResourceTokenProvider (the hardened holder: coalesced exchange, refresh-ahead window, forced refresh, TimeProvider) per configured resource; one TokenRefreshService running the expiry-driven backoff loop per resource; exchangers create their SDK credential once per identity behind factory seams (IClientAssertionCredentialFactory, new IManagedIdentityCredentialFactory).

Removed: AddAzureSqlFederatedIdentity, AzureSqlFederatedIdentityOptions, IAzureSqlTokenExchanger (public), PR #2's single-valued IdentityProvider enum, IMemoryCache usage.

Stacked on #9. Supersedes #2 (which can be closed).

Tests

40 → 83 per TFM: validators (root / resource / each provider, path-qualified messages), DI graph (both providers, unconfigured resource, ValidateOnStart, consumer substitution), the shared holder (hit / miss / skew boundary / short-lived / 50 concurrent → one exchange / faulted exchange not cached / forced refresh), both exchangers through their seams (credential created once), TokenRefreshService refreshing two resources independently under FakeTimeProvider, TokenScope metadata, and the TokenCredential adapter's real ExpiresOn.

Validation

  • dotnet build -c Release warning-free; dotnet test 83/83 on net8.0 and net10.0; dotnet restore --locked-mode passes.
  • Adversarially verified, including mutation checks against the tests and race analysis of the shared holder (two callers, expiry, faulted exchange, cancellation mid-flight, forced vs ordinary refresh).
  • Changeset (minor) documents the configuration change with before/after JSON.

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings September 18, 2026 02:44

Copilot AI 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.

🔵 Needs a closer look

The broad API, configuration, DI, and authentication changes require final human review.

Pull request overview

Generalizes workload identity for Azure SQL and Blob Storage with Google federation, Managed Identity, shared token refresh, and updated configuration APIs.

Changes:

  • Adds resource-first options, validation, scopes, and providers.
  • Adds shared token caching, refresh infrastructure, and Azure SDK credential support.
  • Updates documentation, demo, CI, changeset, and comprehensive tests.
File summaries
File Summary
Csag.WorkloadIdentity/WorkloadIdentityTokenCredential.cs Adds Azure SDK credential adapter.
Csag.WorkloadIdentity/WorkloadIdentityServiceCollectionExtensions.cs Registers generalized services.
Csag.WorkloadIdentity/TokenScopeMetadataAttribute.cs Defines scope metadata.
Csag.WorkloadIdentity/TokenScope.cs Defines supported token scopes.
Csag.WorkloadIdentity/README.md Documents configuration and usage.
Csag.WorkloadIdentity/Options/WorkloadIdentityResourceOptions.cs Defines resource options.
Csag.WorkloadIdentity/Options/WorkloadIdentityProvider.cs Defines provider selection.
Csag.WorkloadIdentity/Options/WorkloadIdentityOptionsValidator.cs Validates generalized options.
Csag.WorkloadIdentity/Options/WorkloadIdentityOptions.cs Defines root configuration.
Csag.WorkloadIdentity/Options/ManagedIdentityOptions.cs Defines Managed Identity settings.
Csag.WorkloadIdentity/Options/GoogleOptions.cs Defines Google federation settings.
Csag.WorkloadIdentity/Options/AzureSqlFederatedIdentityOptionsValidator.cs Validates legacy options.
Csag.WorkloadIdentity/Options/AzureSqlFederatedIdentityOptions.cs Contains legacy options.
Csag.WorkloadIdentity/Internal/WorkloadIdentityOptionsExtensions.cs Provides option helpers.
Csag.WorkloadIdentity/Internal/TokenScopeExtensions.cs Provides scope helpers.
Csag.WorkloadIdentity/Internal/Services/TokenRefreshService.cs Refreshes resource tokens.
Csag.WorkloadIdentity/Internal/ResourceTokenProviderFactory.cs Creates resource providers.
Csag.WorkloadIdentity/Internal/ResourceTokenProvider.cs Implements shared token handling.
Csag.WorkloadIdentity/Internal/ManagedIdentityTokenExchanger.cs Exchanges Managed Identity tokens.
Csag.WorkloadIdentity/Internal/ManagedIdentityCredentialFactory.cs Creates Managed Identity credentials.
Csag.WorkloadIdentity/Internal/IWorkloadIdentityTokenExchanger.cs Defines exchanger contract.
Csag.WorkloadIdentity/Internal/ITokenRefresher.cs Defines refresh contract.
Csag.WorkloadIdentity/Internal/IManagedIdentityCredentialFactory.cs Defines credential factory contract.
Csag.WorkloadIdentity/Internal/IAzureSqlTokenRefresher.cs Defines SQL refresh contract.
Csag.WorkloadIdentity/Internal/GoogleIdTokenProvider.cs Provides Google ID tokens.
Csag.WorkloadIdentity/Internal/GoogleFederatedTokenExchanger.cs Exchanges Google federation tokens.
Csag.WorkloadIdentity/Internal/AzureSqlTokenExchanger.cs Exchanges Azure SQL tokens.
Csag.WorkloadIdentity/FederatedIdentityServiceCollectionExtensions.cs Provides federation registration.
Csag.WorkloadIdentity/Csag.WorkloadIdentity.csproj Updates project metadata.
Csag.WorkloadIdentity/BlobStorageTokenProvider.cs Adds Blob Storage provider.
Csag.WorkloadIdentity/AzureSqlTokenProvider.cs Uses shared SQL token handling.
Csag.WorkloadIdentity/Abstractions/IGoogleIdTokenProvider.cs Defines Google token contract.
Csag.WorkloadIdentity/Abstractions/IBlobStorageTokenProvider.cs Defines Blob Storage provider contract.
Csag.WorkloadIdentity/Abstractions/IAzureSqlTokenProvider.cs Defines Azure SQL provider contract.
Csag.WorkloadIdentity/Abstractions/IAzureSqlTokenExchanger.cs Defines SQL exchanger contract.
Csag.WorkloadIdentity/Abstractions/IAccessTokenProvider.cs Defines shared access token contract.
Csag.WorkloadIdentity.UnitTests/WorkloadIdentityTokenCredentialTests.cs Tests credential adaptation.
Csag.WorkloadIdentity.UnitTests/WorkloadIdentityServiceCollectionExtensionsTests.cs Tests service registration.
Csag.WorkloadIdentity.UnitTests/WorkloadIdentityOptionsValidatorTests.cs Tests generalized validation.
Csag.WorkloadIdentity.UnitTests/TokenScopeTests.cs Tests token scopes.
Csag.WorkloadIdentity.UnitTests/TokenRefreshServiceTests.cs Tests token refresh behavior.
Csag.WorkloadIdentity.UnitTests/ResourceTokenProviderTests.cs Tests shared token handling.
Csag.WorkloadIdentity.UnitTests/ManagedIdentityTokenExchangerTests.cs Tests Managed Identity exchange.
Csag.WorkloadIdentity.UnitTests/GoogleIdTokenProviderTests.cs Tests Google ID token handling.
Csag.WorkloadIdentity.UnitTests/GoogleFederatedTokenExchangerTests.cs Tests Google federation exchange.
Csag.WorkloadIdentity.UnitTests/FederatedIdentityServiceCollectionExtensionsTests.cs Tests federation registration.
Csag.WorkloadIdentity.UnitTests/BlobStorageTokenProviderTests.cs Tests Blob Storage provider.
Csag.WorkloadIdentity.UnitTests/AzureSqlTokenProviderTests.cs Tests Azure SQL provider.
Csag.WorkloadIdentity.UnitTests/AzureSqlTokenExchangerTests.cs Tests SQL token exchange.
Csag.WorkloadIdentity.UnitTests/AzureSqlFederatedIdentityOptionsValidatorTests.cs Tests legacy validation.
Csag.WorkloadIdentity.Demo/Program.cs Updates demo setup.
Csag.WorkloadIdentity.Demo/appsettings.json Updates demo configuration.
.github/workflows/ci.yml Updates CI configuration.
.changeset/workload-identity.md Documents the release changes.
Review details
  • Files reviewed: 54/54 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@neotrow
neotrow added this pull request to stack #13 September 18, 2026 15:30
@neotrow
neotrow force-pushed the feat/workload-identity branch from 36b8d21 to dd8310d Compare September 18, 2026 16:02
Copilot AI review requested due to automatic review settings September 18, 2026 16:02

Copilot AI 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.

🟡 Changes recommended

The CI demo lacks the required Provider setting and will fail startup validation.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

Csag.WorkloadIdentity/README.md:20

  • The new feature description advertises managed identity support, but the README's prerequisites below still present Google Cloud, IAM Credentials, Application Default Credentials, and Google federation as mandatory for every installation. This makes the documented managed-identity/Blob Storage path misleading; update the prerequisites and setup guidance to distinguish Google-federated deployments from Azure managed-identity deployments (and include the Blob Storage authorization requirements).
  • Files reviewed: 54/54 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread .github/workflows/ci.yml
Copilot AI review requested due to automatic review settings September 18, 2026 16:37
@neotrow
neotrow force-pushed the feat/workload-identity branch from dd8310d to d4a5f80 Compare September 18, 2026 16:37

Copilot AI 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.

🟢 Approval recommended

No unresolved review issues were identified.

Review details
  • Files reviewed: 54/54 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 18, 2026 17:03
@neotrow
neotrow force-pushed the feat/workload-identity branch from d4a5f80 to 3afba02 Compare September 18, 2026 17:03

Copilot AI 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.

🔵 Needs a closer look

One or more issues must be addressed before approval.

Review details

Suppressed comments (1)

Csag.WorkloadIdentity/README.md:20

  • This README section still names IAzureSqlTokenExchanger as a public service and says it can be replaced, but this PR deletes that interface and registers only the internal IWorkloadIdentityTokenExchanger implementations. Please update the feature/DI documentation so consumers are not directed to an API that no longer exists.
  • Files reviewed: 54/54 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

neotrow and others added 4 commits September 18, 2026 14:27
…y and Blob Storage

Rebuilds the model of PR #2 on the hardened token provider:

- WorkloadIdentityOptions holds one section per resource (AzureSql, BlobStorage),
  each selecting a Provider (ManagedIdentity or Google) with that provider's
  settings; validated at host start with messages that name the offending path
- internal IWorkloadIdentityTokenExchanger with a Google-federated and a
  managed-identity implementation; credentials are created once per identity
  behind the IClientAssertionCredentialFactory and IManagedIdentityCredentialFactory seams
- one internal ResourceTokenProvider holds each resource's token (coalescing,
  refresh-ahead window, forced refresh); AzureSqlTokenProvider and
  BlobStorageTokenProvider are thin wrappers, both interfaces expose
  GetAccessTokenAsync and WorkloadIdentityTokenCredential adapts a provider for
  Azure SDK clients
- TokenRefreshService runs the expiry-driven refresh loop once per configured
  resource and skips a consumer-substituted provider that is not refreshable
- AddWorkloadIdentity replaces AddAzureSqlFederatedIdentity; both providers are
  always registered and an unconfigured resource fails with a message naming
  its section
- Demo, CI smoke test, package metadata and README follow the new configuration
  shape; changeset describes the migration

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…eplaceable Try* registration

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 18, 2026 17:27
@neotrow
neotrow force-pushed the feat/workload-identity branch from 3afba02 to 62b8382 Compare September 18, 2026 17:27

Copilot AI 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.

🟡 Changes recommended

Forced refresh may reuse cached SDK tokens, and the README contains outdated public API guidance.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

Csag.WorkloadIdentity/Internal/ResourceTokenProvider.cs:95

  • force: true only bypasses ResourceTokenProvider's holder check; both exchangers call the same cached Azure TokenCredential instance with a normal TokenRequestContext. ClientAssertionCredential and ManagedIdentityCredential can therefore return their cached access token, so a large configured RefreshAheadWindow (for example 30 minutes) is not actually honored by the background refresh—the holder receives the old expiry and schedules again from it. Please either make the refresh contract account for the SDK credential cache or use a refresh path that guarantees a new credential/token when a forced refresh is requested.
  • Files reviewed: 54/54 changed files
  • Comments generated: 1
  • Review effort level: Lite

## Features

- Exchanges a Google-signed ID token for a Microsoft Entra ID access token for Azure SQL; there are no secrets to store or rotate.
- Also serves Azure Blob Storage (`IBlobStorageTokenProvider`), and an application that runs on Azure can obtain the tokens from its managed identity instead (`"Provider": "ManagedIdentity"`). `WorkloadIdentityTokenCredential` presents a provider to Azure SDK clients as a `TokenCredential`.
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.

2 participants