Skip to content

Commit 016092e

Browse files
Copilotgladjohn
andauthored
Forward MSAL client metadata headers through IMDS to ESTS (#902)
* Initial plan * Add MSAL client metadata headers to IMDS managed identity requests Add x-client-SKU, x-client-Ver, and x-ms-client-request-id headers to _obtain_token_on_azure_vm() for IMDS token requests. Update existing IMDS tests to assert the new headers and validate UUID correlation IDs. Agent-Logs-Url: https://github.com/AzureAD/microsoft-authentication-library-for-python/sessions/3f0bb4c2-0e45-471e-9ab3-2a502ef8cddf Co-authored-by: gladjohn <90415114+gladjohn@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: gladjohn <90415114+gladjohn@users.noreply.github.com>
1 parent 67136fe commit 016092e

File tree

2 files changed

+42
-6
lines changed

2 files changed

+42
-6
lines changed

msal/managed_identity.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
import os
99
import sys
1010
import time
11+
import uuid
1112
from urllib.parse import urlparse # Python 3+
1213
from collections import UserDict # Python 3+
1314
from typing import List, Optional, Union # Needed in Python 3.7 & 3.8
1415
from .token_cache import TokenCache
1516
from .individual_cache import _IndividualCache as IndividualCache
1617
from .throttled_http_client import ThrottledHttpClientBase, RetryAfterParser
1718
from .cloudshell import _is_running_in_cloud_shell
19+
from .sku import SKU, __version__
1820

1921

2022
logger = logging.getLogger(__name__)
@@ -480,7 +482,12 @@ def _obtain_token_on_azure_vm(http_client, managed_identity, resource):
480482
"AZURE_POD_IDENTITY_AUTHORITY_HOST", "http://169.254.169.254"
481483
).strip("/") + "/metadata/identity/oauth2/token",
482484
params=params,
483-
headers={"Metadata": "true"},
485+
headers={
486+
"Metadata": "true",
487+
"x-client-SKU": SKU,
488+
"x-client-Ver": __version__,
489+
"x-ms-client-request-id": str(uuid.uuid4()),
490+
},
484491
)
485492
try:
486493
payload = json.loads(resp.text)

tests/test_mi.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import sys
55
import time
6+
import uuid
67
from typing import List, Optional
78
import unittest
89
try:
@@ -32,6 +33,8 @@
3233
)
3334
from msal.token_cache import is_subdict_of
3435

36+
EXPECTED_SKU = "MSAL.Python" # Hardcoded constant, not imported from product
37+
3538

3639
class ManagedIdentityTestCase(unittest.TestCase):
3740
def test_helper_class_should_be_interchangable_with_dict_which_could_be_loaded_from_file_or_env_var(self):
@@ -181,14 +184,23 @@ def _test_happy_path(self) -> callable:
181184
return mocked_method
182185

183186
def test_happy_path_of_vm(self):
184-
self._test_happy_path().assert_called_with(
187+
mock_get = self._test_happy_path()
188+
mock_get.assert_called_with(
185189
# The last call contained claims_challenge
186190
# but since IMDS doesn't support token_sha256_to_refresh,
187191
# the request shall remain the same as before
188192
'http://169.254.169.254/metadata/identity/oauth2/token',
189193
params={'api-version': '2018-02-01', 'resource': 'R'},
190-
headers={'Metadata': 'true'},
194+
headers={
195+
'Metadata': 'true',
196+
'x-client-SKU': EXPECTED_SKU,
197+
'x-client-Ver': ANY,
198+
'x-ms-client-request-id': ANY,
199+
},
191200
)
201+
# Validate correlation ID is a valid UUID
202+
corr_id = mock_get.call_args.kwargs["headers"]["x-ms-client-request-id"]
203+
uuid.UUID(corr_id)
192204

193205
@patch.object(ManagedIdentityClient, "_ManagedIdentityClient__instance", "MixedCaseHostName")
194206
def test_happy_path_of_theoretical_mixed_case_hostname(self):
@@ -200,11 +212,20 @@ def test_happy_path_of_theoretical_mixed_case_hostname(self):
200212

201213
@patch.dict(os.environ, {"AZURE_POD_IDENTITY_AUTHORITY_HOST": "http://localhost:1234//"})
202214
def test_happy_path_of_pod_identity(self):
203-
self._test_happy_path().assert_called_with(
215+
mock_get = self._test_happy_path()
216+
mock_get.assert_called_with(
204217
'http://localhost:1234/metadata/identity/oauth2/token',
205218
params={'api-version': '2018-02-01', 'resource': 'R'},
206-
headers={'Metadata': 'true'},
219+
headers={
220+
'Metadata': 'true',
221+
'x-client-SKU': EXPECTED_SKU,
222+
'x-client-Ver': ANY,
223+
'x-ms-client-request-id': ANY,
224+
},
207225
)
226+
# Validate correlation ID is a valid UUID
227+
corr_id = mock_get.call_args.kwargs["headers"]["x-ms-client-request-id"]
228+
uuid.UUID(corr_id)
208229

209230
def test_vm_error_should_be_returned_as_is(self):
210231
raw_error = '{"raw": "error format is undefined"}'
@@ -229,8 +250,16 @@ def test_vm_resource_id_parameter_should_be_msi_res_id(self):
229250
mocked_method.assert_called_with(
230251
'http://169.254.169.254/metadata/identity/oauth2/token',
231252
params={'api-version': '2018-02-01', 'resource': 'R', 'msi_res_id': '1234'},
232-
headers={'Metadata': 'true'},
253+
headers={
254+
'Metadata': 'true',
255+
'x-client-SKU': EXPECTED_SKU,
256+
'x-client-Ver': ANY,
257+
'x-ms-client-request-id': ANY,
258+
},
233259
)
260+
# Validate correlation ID is a valid UUID
261+
corr_id = mocked_method.call_args.kwargs["headers"]["x-ms-client-request-id"]
262+
uuid.UUID(corr_id)
234263

235264

236265
@patch.dict(os.environ, {"IDENTITY_ENDPOINT": "http://localhost", "IDENTITY_HEADER": "foo"})

0 commit comments

Comments
 (0)