@@ -128,6 +128,18 @@ def _obtain_token(http_client, managed_identity, resource):
128128 managed_identity ,
129129 resource ,
130130 )
131+ if "IDENTITY_ENDPOINT" in os .environ and "IMDS_ENDPOINT" in os .environ :
132+ if ManagedIdentity .is_user_assigned (managed_identity ):
133+ raise ValueError ( # Note: Azure Identity for Python raised exception too
134+ "Ignoring managed_identity parameter. "
135+ "Azure Arc supports only system-assigned managed identity, "
136+ "See also "
137+ "https://learn.microsoft.com/en-us/azure/service-fabric/configure-existing-cluster-enable-managed-identity-token-service" )
138+ return _obtain_token_on_arc (
139+ http_client ,
140+ os .environ ["IDENTITY_ENDPOINT" ],
141+ resource ,
142+ )
131143 return _obtain_token_on_azure_vm (http_client , managed_identity , resource )
132144
133145
@@ -248,6 +260,44 @@ def _obtain_token_on_service_fabric(
248260 raise
249261
250262
263+ def _obtain_token_on_arc (http_client , endpoint , resource ):
264+ # https://learn.microsoft.com/en-us/azure/azure-arc/servers/managed-identity-authentication
265+ logger .debug ("Obtaining token via managed identity on Azure Arc" )
266+ resp = http_client .get (
267+ endpoint ,
268+ params = {"api-version" : "2020-06-01" , "resource" : resource },
269+ headers = {"Metadata" : "true" },
270+ )
271+ www_auth = "www-authenticate" # Header in lower case
272+ challenge = {
273+ # Normalized to lowercase, because header names are case-insensitive
274+ # https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
275+ k .lower (): v for k , v in resp .headers .items () if k .lower () == www_auth
276+ }.get (www_auth , "" ).split ("=" ) # Output will be ["Basic realm", "content"]
277+ if not ( # https://datatracker.ietf.org/doc/html/rfc7617#section-2
278+ len (challenge ) == 2 and challenge [0 ].lower () == "basic realm" ):
279+ raise ValueError ("Irrecognizable WWW-Authenticate header: {}" .format (resp .headers ))
280+ with open (challenge [1 ]) as f :
281+ secret = f .read ()
282+ response = http_client .get (
283+ endpoint ,
284+ params = {"api-version" : "2020-06-01" , "resource" : resource },
285+ headers = {"Metadata" : "true" , "Authorization" : "Basic {}" .format (secret )},
286+ )
287+ payload = json .loads (response .text )
288+ if payload .get ("access_token" ) and payload .get ("expires_in" ):
289+ # Example: https://learn.microsoft.com/en-us/azure/azure-arc/servers/media/managed-identity-authentication/bash-token-output-example.png
290+ return {
291+ "access_token" : payload ["access_token" ],
292+ "expires_in" : int (payload ["expires_in" ]),
293+ "token_type" : payload .get ("token_type" , "Bearer" ),
294+ "resource" : payload .get ("resource" ),
295+ }
296+ return {
297+ "error" : "invalid_request" ,
298+ "error_description" : response .text ,
299+ }
300+
251301
252302class ManagedIdentityClient (object ):
253303 _instance , _tenant = socket .getfqdn (), "managed_identity" # Placeholders
0 commit comments