Skip to content

Commit d68ba44

Browse files
authored
[Core] PREVIEW: Support managed identity on Azure Arc-enabled Windows server (Azure#29187)
1 parent 5705899 commit d68ba44

File tree

2 files changed

+72
-8
lines changed

2 files changed

+72
-8
lines changed

src/azure-cli-core/azure/cli/core/_profile.py

+52-7
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ def login(self,
220220
return deepcopy(consolidated)
221221

222222
def login_with_managed_identity(self, identity_id=None, allow_no_subscriptions=None):
223+
if _on_azure_arc_windows():
224+
return self.login_with_managed_identity_azure_arc_windows(
225+
identity_id=identity_id, allow_no_subscriptions=allow_no_subscriptions)
226+
223227
import jwt
224228
from azure.mgmt.core.tools import is_valid_resource_id
225229
from azure.cli.core.auth.adal_authentication import MSIAuthenticationWrapper
@@ -282,6 +286,33 @@ def login_with_managed_identity(self, identity_id=None, allow_no_subscriptions=N
282286
self._set_subscriptions(consolidated)
283287
return deepcopy(consolidated)
284288

289+
def login_with_managed_identity_azure_arc_windows(self, identity_id=None, allow_no_subscriptions=None):
290+
import jwt
291+
identity_type = MsiAccountTypes.system_assigned
292+
from .auth.msal_credentials import ManagedIdentityCredential
293+
294+
cred = ManagedIdentityCredential()
295+
token = cred.get_token(*self._arm_scope).token
296+
logger.info('Managed identity: token was retrieved. Now trying to initialize local accounts...')
297+
decode = jwt.decode(token, algorithms=['RS256'], options={"verify_signature": False})
298+
tenant = decode['tid']
299+
300+
subscription_finder = SubscriptionFinder(self.cli_ctx)
301+
subscriptions = subscription_finder.find_using_specific_tenant(tenant, cred)
302+
base_name = ('{}-{}'.format(identity_type, identity_id) if identity_id else identity_type)
303+
user = _USER_ASSIGNED_IDENTITY if identity_id else _SYSTEM_ASSIGNED_IDENTITY
304+
if not subscriptions:
305+
if allow_no_subscriptions:
306+
subscriptions = self._build_tenant_level_accounts([tenant])
307+
else:
308+
raise CLIError('No access was configured for the managed identity, hence no subscriptions were found. '
309+
"If this is expected, use '--allow-no-subscriptions' to have tenant level access.")
310+
311+
consolidated = self._normalize_properties(user, subscriptions, is_service_principal=True,
312+
user_assigned_identity_id=base_name)
313+
self._set_subscriptions(consolidated)
314+
return deepcopy(consolidated)
315+
285316
def login_in_cloud_shell(self):
286317
import jwt
287318
from .auth.msal_credentials import CloudShellCredential
@@ -354,13 +385,18 @@ def get_login_credentials(self, resource=None, client_id=None, subscription_id=N
354385
# Cloud Shell
355386
from .auth.msal_credentials import CloudShellCredential
356387
from azure.cli.core.auth.credential_adaptor import CredentialAdaptor
357-
cs_cred = CloudShellCredential()
358-
# The cloud shell credential must be wrapped by CredentialAdaptor so that it can work with Track 1 SDKs.
359-
cred = CredentialAdaptor(cs_cred, resource=resource)
388+
# The credential must be wrapped by CredentialAdaptor so that it can work with Track 1 SDKs.
389+
cred = CredentialAdaptor(CloudShellCredential(), resource=resource)
360390

361391
elif managed_identity_type:
362392
# managed identity
363-
cred = MsiAccountTypes.msi_auth_factory(managed_identity_type, managed_identity_id, resource)
393+
if _on_azure_arc_windows():
394+
from .auth.msal_credentials import ManagedIdentityCredential
395+
from azure.cli.core.auth.credential_adaptor import CredentialAdaptor
396+
# The credential must be wrapped by CredentialAdaptor so that it can work with Track 1 SDKs.
397+
cred = CredentialAdaptor(ManagedIdentityCredential(), resource=resource)
398+
else:
399+
cred = MsiAccountTypes.msi_auth_factory(managed_identity_type, managed_identity_id, resource)
364400

365401
else:
366402
# user and service principal
@@ -415,9 +451,13 @@ def get_raw_token(self, resource=None, scopes=None, subscription=None, tenant=No
415451
# managed identity
416452
if tenant:
417453
raise CLIError("Tenant shouldn't be specified for managed identity account")
418-
from .auth.util import scopes_to_resource
419-
cred = MsiAccountTypes.msi_auth_factory(managed_identity_type, managed_identity_id,
420-
scopes_to_resource(scopes))
454+
if _on_azure_arc_windows():
455+
from .auth.msal_credentials import ManagedIdentityCredential
456+
cred = ManagedIdentityCredential()
457+
else:
458+
from .auth.util import scopes_to_resource
459+
cred = MsiAccountTypes.msi_auth_factory(managed_identity_type, managed_identity_id,
460+
scopes_to_resource(scopes))
421461

422462
else:
423463
cred = self._create_credential(account, tenant)
@@ -918,3 +958,8 @@ def _create_identity_instance(cli_ctx, *args, **kwargs):
918958
return Identity(*args, encrypt=encrypt, use_msal_http_cache=use_msal_http_cache,
919959
enable_broker_on_windows=enable_broker_on_windows,
920960
instance_discovery=instance_discovery, **kwargs)
961+
962+
963+
def _on_azure_arc_windows():
964+
# This indicates an Azure Arc-enabled Windows server
965+
return "IDENTITY_ENDPOINT" in os.environ and "IMDS_ENDPOINT" in os.environ

src/azure-cli-core/azure/cli/core/auth/msal_credentials.py

+20-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818

1919
from knack.log import get_logger
2020
from knack.util import CLIError
21-
from msal import PublicClientApplication, ConfidentialClientApplication
21+
from msal import (PublicClientApplication, ConfidentialClientApplication,
22+
ManagedIdentityClient, SystemAssignedManagedIdentity)
2223

2324
from .constants import AZURE_CLI_CLIENT_ID
2425
from .util import check_result, build_sdk_access_token
@@ -131,3 +132,21 @@ def get_token(self, *scopes, **kwargs):
131132
result = self._msal_app.acquire_token_interactive(list(scopes), prompt="none", **kwargs)
132133
check_result(result, scopes=scopes)
133134
return build_sdk_access_token(result)
135+
136+
137+
class ManagedIdentityCredential: # pylint: disable=too-few-public-methods
138+
"""Managed identity credential implementing get_token interface.
139+
Currently, only Azure Arc's system-assigned managed identity is supported.
140+
"""
141+
142+
def __init__(self):
143+
import requests
144+
self._msal_client = ManagedIdentityClient(SystemAssignedManagedIdentity(), http_client=requests.Session())
145+
146+
def get_token(self, *scopes, **kwargs):
147+
logger.debug("ManagedIdentityCredential.get_token: scopes=%r, kwargs=%r", scopes, kwargs)
148+
149+
from .util import scopes_to_resource
150+
result = self._msal_client.acquire_token_for_client(resource=scopes_to_resource(scopes))
151+
check_result(result)
152+
return build_sdk_access_token(result)

0 commit comments

Comments
 (0)