Skip to content

Commit b7fd269

Browse files
Copilotgladjohn
andcommitted
Add MSI v2 (mTLS PoP) support with Windows KeyGuard attestation
Co-authored-by: gladjohn <90415114+gladjohn@users.noreply.github.com>
1 parent 72c1cf6 commit b7fd269

File tree

8 files changed

+2419
-0
lines changed

8 files changed

+2419
-0
lines changed

msal/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
SystemAssignedManagedIdentity, UserAssignedManagedIdentity,
3939
ManagedIdentityClient,
4040
ManagedIdentityError,
41+
MsiV2Error,
4142
ArcPlatformNotSupportedError,
4243
)
4344

msal/managed_identity.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ class ManagedIdentityError(ValueError):
2424
pass
2525

2626

27+
class MsiV2Error(ManagedIdentityError):
28+
"""Raised when the MSI v2 (mTLS PoP) flow fails."""
29+
pass
30+
31+
2732
class ManagedIdentity(UserDict):
2833
"""Feed an instance of this class to :class:`msal.ManagedIdentityClient`
2934
to acquire token for the specified managed identity.
@@ -259,6 +264,8 @@ def acquire_token_for_client(
259264
*,
260265
resource: str, # If/when we support scope, resource will become optional
261266
claims_challenge: Optional[str] = None,
267+
mtls_proof_of_possession: bool = False,
268+
with_attestation_support: bool = False,
262269
):
263270
"""Acquire token for the managed identity.
264271
@@ -278,6 +285,23 @@ def acquire_token_for_client(
278285
even if the app developer did not opt in for the "CP1" client capability.
279286
Upon receiving a `claims_challenge`, MSAL will attempt to acquire a new token.
280287
288+
:param bool mtls_proof_of_possession: (optional)
289+
When True, use the MSI v2 (mTLS Proof-of-Possession) flow to acquire an
290+
``mtls_pop`` token bound to a short-lived mTLS certificate issued by the
291+
IMDS ``/issuecredential`` endpoint.
292+
Without this flag the legacy IMDS v1 flow is used.
293+
Defaults to False.
294+
295+
MSI v2 is used only when both ``mtls_proof_of_possession`` and
296+
``with_attestation_support`` are True.
297+
298+
:param bool with_attestation_support: (optional)
299+
When True (and ``mtls_proof_of_possession`` is also True), attempt
300+
KeyGuard / platform attestation before credential issuance.
301+
On Windows this leverages ``AttestationClientLib.dll`` when available;
302+
on other platforms the parameter is silently ignored.
303+
Defaults to False.
304+
281305
.. note::
282306
283307
Known issue: When an Azure VM has only one user-assigned managed identity,
@@ -292,6 +316,27 @@ def acquire_token_for_client(
292316
client_id_in_cache = self._managed_identity.get(
293317
ManagedIdentity.ID, "SYSTEM_ASSIGNED_MANAGED_IDENTITY")
294318
now = time.time()
319+
# MSI v2 is opt-in: use it only when BOTH mtls_proof_of_possession and
320+
# with_attestation_support are explicitly requested by the caller.
321+
# No auto-fallback: if MSI v2 is requested and fails, the error is raised.
322+
use_msi_v2 = bool(mtls_proof_of_possession and with_attestation_support)
323+
324+
if with_attestation_support and not mtls_proof_of_possession:
325+
raise ManagedIdentityError(
326+
"attestation_requires_pop",
327+
"with_attestation_support=True requires mtls_proof_of_possession=True (mTLS PoP)."
328+
)
329+
330+
if use_msi_v2:
331+
from .msi_v2 import obtain_token as _obtain_token_v2
332+
result = _obtain_token_v2(
333+
self._http_client, self._managed_identity, resource,
334+
attestation_enabled=True,
335+
)
336+
if "access_token" in result and "error" not in result:
337+
result[self._TOKEN_SOURCE] = self._TOKEN_SOURCE_IDP
338+
return result
339+
295340
if True: # Attempt cache search even if receiving claims_challenge,
296341
# because we want to locate the existing token (if any) and refresh it
297342
matches = self._token_cache.search(

0 commit comments

Comments
 (0)