Skip to content

Spike #6042: delegate IMDSv2 token leg to internal TokenClient exchange#6062

Closed
Robbie-Microsoft wants to merge 2 commits into
mainfrom
rginsburg/imdsv2-cca-delegation-spike
Closed

Spike #6042: delegate IMDSv2 token leg to internal TokenClient exchange#6062
Robbie-Microsoft wants to merge 2 commits into
mainfrom
rginsburg/imdsv2-cca-delegation-spike

Conversation

@Robbie-Microsoft

@Robbie-Microsoft Robbie-Microsoft commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Spike #6042 — IMDSv2: delegate the token leg to MSAL's internal exchange path

This is a throwaway spike PR for #6042. All production behavior is gated behind a default-off flag (s_delegateTokenLegToInternalExchange); it is not intended to merge as-is.

What & why

Per the discussion on #6042, this validates replacing IMDSv2's hand-rolled post-mint token POST with delegation to the internal TokenClient exchange. The driver is unblocking MSIv2 claims/NSP support (#5982), so claims→body, CP1 merge, claims-in-cache-key, and ESTS error handling are inherited rather than re-ported.

Deliverables

  • Design doc: docs/imdsv2_cca_delegation_spike.md (current-state map, the delegation seam, two integration depths — recommends option A, feasibility Q&A, risks, measurement).
  • PoC (behind default-off flag):
    • Extracted cert-mint into AcquireMtlsBindingAsync (behavior-neutral); added a mint-only delegation entrypoint + invalid_client cert eviction.
    • ManagedIdentityClient.AcquireImdsV2MtlsBindingAsync mints without the bespoke POST.
    • ManagedIdentityAuthRequest delegation branch → new TokenClient(reqParams).SendTokenRequestAsync(body, scopeOverride, tokenEndpointOverride) with the minted cert + mtls_pop scheme, wrapped in invalid_client re-mint-and-retry-once.

Feasibility: confirmed — no TokenClient change needed

tokenEndpointOverride hits ESTS-R with zero instance discovery; canonical client_id overrides AppConfig.ClientId via body params; the minted cert flows as the mTLS transport cert; MI caching is preserved (lookup runs upstream, branch only fires on cache miss).

Validation

  • New test mTLSPop_DelegatedTokenLeg_HappyPath_Spike6042 passes (IdP then Cache, cert-bound).
  • Core lib builds 0-warning (warnings-as-errors). All 424 ManagedIdentity unit tests pass.

Measurement (the issue''s ask)

+163/−24 across 3 prod files: ~50 moved (refactor), ~95 net-new glue, vs ~26 bespoke token-path lines bypassed. Happy-path line count is ~net-neutral, but the qualitative win is inheriting the shared exchange machinery for MSIv2 NSP (#5982).

Spike only — tracking issue: #6042.

Robbie-Microsoft and others added 2 commits June 11, 2026 18:25
Phase 1 of spike #6042. Documents delegating the MSI v2 post-mint token leg to MSAL's internal ClientCredentialRequest/TokenClient exchange path (not a public ConfidentialClientApplication), to unblock MSIv2 claims/NSP support (#5982). Captures current-state map, the tokenEndpointOverride seam, the invalid_client remint wrapper, risks, and the lines-removed-vs-added measurement methodology.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Phase 2 throwaway PoC for spike #6042. Behind a default-off flag (s_delegateTokenLegToInternalExchange), delegates the MSI v2 post-mint token request to MSAL's internal TokenClient exchange path while keeping the cert-mint flow unchanged. Injects the IMDS-minted cert, targets the ESTS-R endpoint via tokenEndpointOverride (no instance discovery), and wraps an invalid_client re-mint-and-retry-once. Adds mTLSPop_DelegatedTokenLeg_HappyPath_Spike6042; all 424 ManagedIdentity tests pass. Records the lines-removed-vs-added measurement in the design doc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This spike explores delegating the IMDSv2 (Managed Identity v2) post-mint token exchange to MSAL’s internal TokenClient path (instead of the bespoke managed-identity token POST), gated behind a default-off flag for experimentation and measurement tied to #6042.

Changes:

  • Added a default-off flag and a delegation branch in ManagedIdentityAuthRequest that mints an IMDSv2 binding certificate and calls TokenClient.SendTokenRequestAsync(..., tokenEndpointOverride: ...), with a one-time invalid_client re-mint retry.
  • Refactored IMDSv2 cert minting into a reusable helper (AcquireMtlsBindingAsync) and introduced a mint-only delegation entry point.
  • Added a unit test proving the delegated happy path + caching behavior, and added a design/measurement doc describing the approach and tradeoffs.

Reviewed changes

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

Show a summary per file
File Description
tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs Adds a spike test covering delegated token-leg happy path and cache hit behavior.
src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs Extracts cert-mint into a helper and adds a mint-only entrypoint used by the delegated path.
src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityClient.cs Adds an internal helper to acquire the IMDSv2 mTLS binding without sending the bespoke token request.
src/client/Microsoft.Identity.Client/Internal/Requests/ManagedIdentityAuthRequest.cs Adds the flag-gated delegated token-leg branch and invalid_client re-mint retry wrapper.
docs/imdsv2_cca_delegation_spike.md Documents current state, delegation seam, risks, and measurement for the spike.

Comment on lines +253 to +270
// Cert-mint mocks (unchanged path) + delegated ESTS-R token mock.
httpManager.AddMockHandler(MockHelpers.MockCsrResponse());
httpManager.AddMockHandler(MockHelpers.MockCertificateRequestResponse());
httpManager.AddMockHandler(CreateDelegatedEntraTokenMock());

// Act
var result = await managedIdentityApp.AcquireTokenForManagedIdentity(ManagedIdentityTests.Resource)
.WithMtlsProofOfPossession()
.WithAttestationSupport()
.ExecuteAsync().ConfigureAwait(false);

// Assert - token acquired from the delegated exchange path and cert-bound.
Assert.IsNotNull(result);
Assert.IsNotNull(result.AccessToken);
Assert.AreEqual(MTLSPoP, result.TokenType);
Assert.IsNotNull(result.BindingCertificate);
Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);

Comment on lines +283 to +285
catch (MsalServiceException ex) when (
string.Equals(ex.ErrorCode, "invalid_client", StringComparison.OrdinalIgnoreCase))
{
@Robbie-Microsoft

Copy link
Copy Markdown
Contributor Author

Implemented in #6070

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