POC 2: MSI v2 mTLS PoP — WindowsCertificate + SchannelSession#930
Closed
gladjohn wants to merge 1 commit into
Closed
POC 2: MSI v2 mTLS PoP — WindowsCertificate + SchannelSession#930gladjohn wants to merge 1 commit into
gladjohn wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces an end-to-end Windows-only MSI v2 flow that acquires mTLS Proof-of-Possession (mtls_pop) tokens bound to non-exportable KeyGuard-backed private keys, and adds companion packages to support attestation and downstream mTLS calls via WinHTTP/SChannel.
Changes:
- Add
msal.msi_v2(ctypes-based KeyGuard + IMDSv2 + WinHTTP/SChannel) andWindowsCertificatefor platform-backed cert/key handle lifecycle. - Extend
ManagedIdentityClient.acquire_token_for_client()with opt-in flags for MSI v2 (mTLS PoP + attestation) and exposeMsiV2Error. - Add new companion packages (
msal-key-attestation,msal-schannel-transport), tests/docs, and update Azure Pipelines to run SDL + test matrix.
Reviewed changes
Copilot reviewed 32 out of 33 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_msi_v2.py | New unit tests for MSI v2 helpers (cnf binding, cache, IMDS helpers, gating). |
| tests/test_e2e.py | E2E gating + lab-config improvements for CI friendliness (esp. Azure DevOps). |
| tests/test_e2e_mtls_pop.py | New Windows-only E2E test for MSI v2 token + downstream mTLS call. |
| tests/lab_config.py | Add LAB_APP_CLIENT_ID constant and clean_env() to avoid ADO $(VAR) literal pitfalls. |
| setup.cfg | Configure pytest testpaths = tests. |
| sample/msi_v2_sample.py | New sample demonstrating MSI v2 token acquisition and header usage. |
| sample/MSI_V2_GUIDE.md | New guide for setup/usage of MSI v2 + attestation package. |
| sample/devapp_msi_v2_mtls/app.py | Dev app demonstrating public-API-only MSI v2 + downstream Schannel transport usage. |
| msal/windows_certificate.py | New WindowsCertificate wrapper for CERT_CONTEXT creation + NCrypt handle lifecycle. |
| msal/sku.py | Fix docstring typo and bump version string. |
| msal/msi_v2.py | New MSI v2 implementation (KeyGuard key, CSR, IMDS, cert binding, WinHTTP mTLS token acquisition). |
| msal/managed_identity.py | Public API gating for MSI v2 via new flags + MsiV2Error. |
| msal/init.py | Export MsiV2Error and WindowsCertificate in package namespace. |
| msal-schannel-transport/pyproject.toml | New package metadata for downstream WinHTTP/SChannel transport. |
| msal-schannel-transport/msal_schannel_transport/session.py | SchannelSession implementation for downstream mTLS with non-exportable keys. |
| msal-schannel-transport/msal_schannel_transport/init.py | Public exports for msal-schannel-transport. |
| msal-key-attestation/tests/test_attestation.py | Unit tests for in-memory MAA JWT caching and provider wrapper. |
| msal-key-attestation/setup.py | Minimal setup entrypoint. |
| msal-key-attestation/setup.cfg | Package metadata + dependency on msal. |
| msal-key-attestation/README.md | Usage + architecture docs for attestation package. |
| msal-key-attestation/pyproject.toml | Build metadata + packaging notes for native DLL sourcing. |
| msal-key-attestation/msal_key_attestation/attestation.py | ctypes bindings to AttestationClientLib.dll + JWT caching + provider factory. |
| msal-key-attestation/msal_key_attestation/init.py | Package exports/version. |
| msal-key-attestation/MANIFEST.in | Include license/readme, exclude tests. |
| msal-key-attestation/LICENSE | MIT license file. |
| docs/mTLS-PoP-Architecture-Decision.md | ADR documenting why WinHTTP/SChannel is required for non-exportable keys in Python. |
| docs/MSI_V2_API.md | Public API reference for MSI v2 and attestation package integration. |
| azure-pipelines.yml | Replace ad-hoc pipeline with template-driven PR gate + scheduled CI. |
| .Pipelines/template-pipeline-stages.yml | New shared template: SDL scans + test matrix + KV cert retrieval logic. |
| .Pipelines/pipeline-publish.yml | New manual release pipeline with version validation + build + publish stages. |
| .Pipelines/credscan-exclusion.json | CredScan suppressions for known test fixtures. |
| .Pipelines/CI-AND-RELEASE-PIPELINES.md | Documentation for CI/release pipeline structure and release process. |
| .gitignore | Ignore egg-info for msal-key-attestation editable installs. |
Comments suppressed due to low confidence (1)
msal/managed_identity.py:274
- This docstring claims the result is always cached, but the new MSI v2 branch returns before the token-cache lookup/store logic, so MSI v2 results are not cached the same way as MSI v1. Either cache MSI v2 results (if feasible) or document the difference to avoid misleading API consumers.
"""Acquire token for the managed identity.
The result will be automatically cached.
Subsequent calls will automatically search from cache first.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+92
to
+96
| try: | ||
| from msal_key_attestation import get_attestation_token | ||
| attestation_provider = get_attestation_token | ||
| except ImportError: | ||
| pass |
Comment on lines
+85
to
+88
| from msal.managed_identity import UserAssignedManagedIdentity | ||
| mi = UserAssignedManagedIdentity( | ||
| ManagedIdentity={"ManagedIdentityIdType": "ClientId", | ||
| "Id": E2E_CLIENT_ID}) |
Comment on lines
+180
to
+184
| try: | ||
| from msal_key_attestation import get_attestation_token | ||
| attestation_provider = get_attestation_token | ||
| except ImportError: | ||
| pass |
Comment on lines
+101
to
+103
| # Minimum remaining cert lifetime to cache (24 hours) | ||
| MIN_REMAINING_LIFETIME_SEC = 24 * 3600 | ||
|
|
Comment on lines
+242
to
+244
| # Not enough remaining lifetime (< 24h) | ||
| entry = self._make_entry(not_after=time.time() + 3600) | ||
| _cert_cache_set("k3", entry) |
…roof End-to-end mTLS Proof-of-Possession for Managed Identity v2 on Windows Azure VMs with Credential Guard / KeyGuard. ## Core (msal package) - msal/windows_certificate.py: Python X509Certificate2 (NCRYPT_KEY_HANDLE wrapper) - msal/msi_v2.py: Full MSI v2 flow (KeyGuard key, CSR, attestation, issuecredential, mTLS token) - msal/managed_identity.py: Public API (acquire_token_for_client + mtls_proof_of_possession) ## Separate packages - msal-schannel-transport/: WinHTTP/SChannel for downstream mTLS (bypasses OpenSSL) - msal-key-attestation/: MAA attestation wrapper (DLL from NuGet: Microsoft.Azure.Security.KeyGuardAttestation v1.1.5) ## Dev app + docs - sample/devapp_msi_v2_mtls/app.py: Uses ONLY public MSAL API - docs/mTLS-PoP-Architecture-Decision.md: Why WinHTTP (OpenSSL CNG gap) ## E2E Results (MSIV2 VM, June 2026) - Token: mtls_pop ✓ | Binding: cnf.x5t#S256 MATCH ✓ | Downstream: HTTP 200 ✓ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
f611587 to
f92d975
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
End-to-end mTLS Proof-of-Possession for Managed Identity v2 on Windows Azure VMs with Credential Guard / KeyGuard.
E2E proven on MSIV2 VM (June 2026): Token acquired → cert binding verified → downstream mTLS call to
tokenbinding.vault.azure.netreturned HTTP 200 with secret value.What's included
Core (
msalpackage)msal/windows_certificate.py— Python equivalent of .NETX509Certificate2. WrapsNCRYPT_KEY_HANDLE(non-exportable private key), thread-safeCERT_CONTEXTcreation, lifecycle management.msal/msi_v2.py—obtain_token()now returnsbinding_certificate(WindowsCertificate) in auth resultmsal/managed_identity.py— Public API:acquire_token_for_client(mtls_proof_of_possession=True, with_attestation_support=True)Separate packages
msal-schannel-transport/— WinHTTP/SChannel HTTP client for downstream mTLS. Bypasses OpenSSL (which cannot use non-exportable keys). Self-contained Win32 bindings.msal-key-attestation/— Python wrapper forAttestationClientLib.dllwith in-memory MAA JWT caching. Native DLL sourced from NuGet:Microsoft.Azure.Security.KeyGuardAttestation v1.1.5Dev app + docs
sample/devapp_msi_v2_mtls/app.py— Uses ONLY public MSAL API (no internal imports)docs/mTLS-PoP-Architecture-Decision.md— Why WinHTTP/SChannel (OpenSSL CNG provider gap research)Architecture
msal-schannel-transportexists because Python's OpenSSL cannot use non-exportable keysApp developer usage
E2E Results
mtls_pop, 86399sNotes
AttestationClientLib.dll(5.3MB) not included — sourced from NuGet at build time