diff --git a/src/azure-cli-core/azure/cli/core/_profile.py b/src/azure-cli-core/azure/cli/core/_profile.py index d53fea946a9..8b49653cc9f 100644 --- a/src/azure-cli-core/azure/cli/core/_profile.py +++ b/src/azure-cli-core/azure/cli/core/_profile.py @@ -222,9 +222,13 @@ def login(self, def login_with_managed_identity(self, identity_id=None, client_id=None, object_id=None, resource_id=None, allow_no_subscriptions=None): - if _on_azure_arc(): - return self.login_with_managed_identity_azure_arc( - identity_id=identity_id, allow_no_subscriptions=allow_no_subscriptions) + if _use_msal_managed_identity(self.cli_ctx): + if identity_id: + raise CLIError('--username is not supported by MSAL managed identity. ' + 'Use --client-id, --object-id or --resource-id instead.') + return self.login_with_managed_identity_msal( + client_id=client_id, object_id=object_id, resource_id=resource_id, + allow_no_subscriptions=allow_no_subscriptions) import jwt from azure.mgmt.core.tools import is_valid_resource_id @@ -304,13 +308,14 @@ def login_with_managed_identity(self, identity_id=None, client_id=None, object_i self._set_subscriptions(consolidated) return deepcopy(consolidated) - def login_with_managed_identity_azure_arc(self, identity_id=None, allow_no_subscriptions=None): + def login_with_managed_identity_msal(self, client_id=None, object_id=None, resource_id=None, + allow_no_subscriptions=None): import jwt - identity_type = MsiAccountTypes.system_assigned - from .auth.msal_credentials import ManagedIdentityCredential from .auth.constants import ACCESS_TOKEN - cred = ManagedIdentityCredential() + identity_id_type, identity_id_value = MsiAccountTypes.parse_ids( + client_id=client_id, object_id=object_id, resource_id=resource_id) + cred = MsiAccountTypes.msal_credential_factory(identity_id_type, identity_id_value) token = cred.acquire_token(self._arm_scope)[ACCESS_TOKEN] logger.info('Managed identity: token was retrieved. Now trying to initialize local accounts...') decode = jwt.decode(token, algorithms=['RS256'], options={"verify_signature": False}) @@ -318,8 +323,8 @@ def login_with_managed_identity_azure_arc(self, identity_id=None, allow_no_subsc subscription_finder = SubscriptionFinder(self.cli_ctx) subscriptions = subscription_finder.find_using_specific_tenant(tenant, cred) - base_name = ('{}-{}'.format(identity_type, identity_id) if identity_id else identity_type) - user = _USER_ASSIGNED_IDENTITY if identity_id else _SYSTEM_ASSIGNED_IDENTITY + base_name = ('{}-{}'.format(identity_id_type, identity_id_value) if identity_id_value else identity_id_type) + user = _USER_ASSIGNED_IDENTITY if identity_id_value else _SYSTEM_ASSIGNED_IDENTITY if not subscriptions: if allow_no_subscriptions: subscriptions = self._build_tenant_level_accounts([tenant]) @@ -399,10 +404,10 @@ def get_login_credentials(self, subscription_id=None, aux_subscriptions=None, au elif managed_identity_type: # managed identity - if _on_azure_arc(): - from .auth.msal_credentials import ManagedIdentityCredential + if _use_msal_managed_identity(self.cli_ctx): # The credential must be wrapped by CredentialAdaptor so that it can work with Track 1 SDKs. - sdk_cred = CredentialAdaptor(ManagedIdentityCredential()) + cred = MsiAccountTypes.msal_credential_factory(managed_identity_type, managed_identity_id) + sdk_cred = CredentialAdaptor(cred) else: # The resource is merely used by msrestazure to get the first access token. # It is not actually used in an API invocation. @@ -432,7 +437,8 @@ def get_login_credentials(self, subscription_id=None, aux_subscriptions=None, au str(account[_SUBSCRIPTION_ID]), str(account[_TENANT_ID])) - def get_raw_token(self, resource=None, scopes=None, subscription=None, tenant=None): + def get_raw_token(self, resource=None, scopes=None, subscription=None, tenant=None, credential_out=None): + # credential_out is only used by unit tests to inspect the credential. Do not use it! # Convert resource to scopes if resource and not scopes: from .auth.util import resource_to_scopes @@ -460,9 +466,11 @@ def get_raw_token(self, resource=None, scopes=None, subscription=None, tenant=No # managed identity if tenant: raise CLIError("Tenant shouldn't be specified for managed identity account") - if _on_azure_arc(): - from .auth.msal_credentials import ManagedIdentityCredential - sdk_cred = CredentialAdaptor(ManagedIdentityCredential()) + if _use_msal_managed_identity(self.cli_ctx): + cred = MsiAccountTypes.msal_credential_factory(managed_identity_type, managed_identity_id) + if credential_out: + credential_out['credential'] = cred + sdk_cred = CredentialAdaptor(cred) else: from .auth.util import scopes_to_resource sdk_cred = MsiAccountTypes.msi_auth_factory(managed_identity_type, managed_identity_id, @@ -810,6 +818,41 @@ def msi_auth_factory(cli_account_name, identity, resource): return MSIAuthenticationWrapper(resource=resource, msi_res_id=identity) raise ValueError("unrecognized msi account name '{}'".format(cli_account_name)) + @staticmethod + def parse_ids(client_id=None, object_id=None, resource_id=None): + id_arg_count = len([arg for arg in (client_id, object_id, resource_id) if arg]) + if id_arg_count > 1: + raise CLIError('Usage error: Provide only one of --client-id, --object-id, --resource-id.') + + id_type = None + id_value = None + if id_arg_count == 0: + id_type = MsiAccountTypes.system_assigned + id_value = None + elif client_id: + id_type = MsiAccountTypes.user_assigned_client_id + id_value = client_id + elif object_id: + id_type = MsiAccountTypes.user_assigned_object_id + id_value = object_id + elif resource_id: + id_type = MsiAccountTypes.user_assigned_resource_id + id_value = resource_id + return id_type, id_value + + @staticmethod + def msal_credential_factory(id_type, id_value): + from azure.cli.core.auth.msal_credentials import ManagedIdentityCredential + if id_type == MsiAccountTypes.system_assigned: + return ManagedIdentityCredential() + if id_type == MsiAccountTypes.user_assigned_client_id: + return ManagedIdentityCredential(client_id=id_value) + if id_type == MsiAccountTypes.user_assigned_object_id: + return ManagedIdentityCredential(object_id=id_value) + if id_type == MsiAccountTypes.user_assigned_resource_id: + return ManagedIdentityCredential(resource_id=id_value) + raise ValueError("Unrecognized managed identity ID type '{}'".format(id_type)) + class SubscriptionFinder: # An ARM client. It finds subscriptions for a user or service principal. It shouldn't do any @@ -976,7 +1019,9 @@ def _create_identity_instance(cli_ctx, authority, tenant_id=None, client_id=None instance_discovery=instance_discovery) -def _on_azure_arc(): +def _use_msal_managed_identity(cli_ctx): # This indicates an Azure Arc-enabled server from msal.managed_identity import get_managed_identity_source, AZURE_ARC - return get_managed_identity_source() == AZURE_ARC + # PREVIEW: Use core.use_msal_managed_identity=true to enable managed identity authentication with MSAL + use_msal_managed_identity = cli_ctx.config.getboolean('core', 'use_msal_managed_identity', fallback=False) + return use_msal_managed_identity or get_managed_identity_source() == AZURE_ARC diff --git a/src/azure-cli-core/azure/cli/core/auth/msal_credentials.py b/src/azure-cli-core/azure/cli/core/auth/msal_credentials.py index 01467188291..cdde91c4b7d 100644 --- a/src/azure-cli-core/azure/cli/core/auth/msal_credentials.py +++ b/src/azure-cli-core/azure/cli/core/auth/msal_credentials.py @@ -10,7 +10,7 @@ from knack.log import get_logger from knack.util import CLIError from msal import (PublicClientApplication, ConfidentialClientApplication, - ManagedIdentityClient, SystemAssignedManagedIdentity) + ManagedIdentityClient, SystemAssignedManagedIdentity, UserAssignedManagedIdentity) from .constants import AZURE_CLI_CLIENT_ID from .util import check_result @@ -131,9 +131,14 @@ class ManagedIdentityCredential: # pylint: disable=too-few-public-methods Currently, only Azure Arc's system-assigned managed identity is supported. """ - def __init__(self): + def __init__(self, client_id=None, resource_id=None, object_id=None): import requests - self._msal_client = ManagedIdentityClient(SystemAssignedManagedIdentity(), http_client=requests.Session()) + if client_id or resource_id or object_id: + managed_identity = UserAssignedManagedIdentity( + client_id=client_id, resource_id=resource_id, object_id=object_id) + else: + managed_identity = SystemAssignedManagedIdentity() + self._msal_client = ManagedIdentityClient(managed_identity, http_client=requests.Session()) def acquire_token(self, scopes, **kwargs): logger.debug("ManagedIdentityCredential.acquire_token: scopes=%r, kwargs=%r", scopes, kwargs) diff --git a/src/azure-cli-core/azure/cli/core/tests/test_profile.py b/src/azure-cli-core/azure/cli/core/tests/test_profile.py index dc797de79ed..710238c35c3 100644 --- a/src/azure-cli-core/azure/cli/core/tests/test_profile.py +++ b/src/azure-cli-core/azure/cli/core/tests/test_profile.py @@ -83,6 +83,23 @@ def acquire_token(self, scopes, **kwargs): } +class ManagedIdentityCredentialStub: + def __init__(self, client_id=None, resource_id=None, object_id=None): + self.client_id = client_id + self.resource_id = resource_id + self.object_id = object_id + self.get_token_scopes = None + + def acquire_token(self, scopes, **kwargs): + self.get_token_scopes = scopes + return { + 'access_token': TestProfile.test_mi_access_token, + 'token_type': 'Bearer', + 'expires_in': 1800, + 'token_source': 'cache' + } + + class MSRestAzureAuthStub: def __init__(self, *args, **kwargs): @@ -271,6 +288,11 @@ def setUpClass(cls): # A random GUID generated by uuid.uuid4() cls.test_mi_tenant = 'b6f04d88-9bff-45da-a9b4-a0b6d3cb1b2a' + cls.test_mi_client_id = 'e7bb3d6e-45a3-46c5-aa6a-51783b424587' + cls.test_mi_object_id = 'b4cf2750-5e3d-4085-8cb1-1ac4230ae3b6' + cls.test_mi_resource_id = ( + "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/testrg/providers/" + "Microsoft.ManagedIdentity/userAssignedIdentities/testmi") cls.test_mi_access_token = _build_test_jwt({'tid': cls.test_mi_tenant}) cls.test_mi_subscription_id = '796071f3-b997-48d8-87f8-dbc5869bd9c5' cls.test_mi_subscription_resource_id = '/subscriptions/{}'.format(cls.test_mi_subscription_id) @@ -716,6 +738,141 @@ def test_login_with_mi_user_assigned_resource_id(self, create_subscription_clien self.assertEqual(s['user']['type'], 'servicePrincipal') self.assertEqual(subscriptions[0]['user']['assignedIdentityInfo'], 'MSIResource-{}'.format(test_res_id)) + @mock.patch('azure.cli.core._profile.SubscriptionFinder._create_subscription_client', autospec=True) + @mock.patch('azure.cli.core.auth.msal_credentials.ManagedIdentityCredential', ManagedIdentityCredentialStub) + @mock.patch.dict('os.environ', {'AZURE_CORE_USE_MSAL_MANAGED_IDENTITY': 'true'}) + def test_login_with_mi_system_assigned_msal(self, create_subscription_client_mock): + mock_subscription_client = mock.MagicMock() + mock_subscription_client.subscriptions.list.return_value = [deepcopy(self.subscription1_raw)] + create_subscription_client_mock.return_value = mock_subscription_client + + cli = DummyCli() + storage_mock = {'subscriptions': None} + profile = Profile(cli_ctx=cli, storage=storage_mock) + subscriptions = profile.login_with_managed_identity() + + credential_instance = create_subscription_client_mock.call_args.args[1] + assert credential_instance.client_id is None + assert credential_instance.object_id is None + assert credential_instance.resource_id is None + assert credential_instance.get_token_scopes == ['https://management.core.windows.net//.default'] + + self.assertEqual(len(subscriptions), 1) + s = subscriptions[0] + self.assertEqual(s['user']['name'], 'systemAssignedIdentity') + self.assertEqual(s['user']['type'], 'servicePrincipal') + self.assertEqual(s['user']['assignedIdentityInfo'], 'MSI') + self.assertEqual(s['name'], self.display_name1) + self.assertEqual(s['id'], self.id1.split('/')[-1]) + self.assertEqual(s['tenantId'], self.test_mi_tenant) + + @mock.patch('azure.cli.core._profile.SubscriptionFinder._create_subscription_client', autospec=True) + @mock.patch('azure.cli.core.auth.msal_credentials.ManagedIdentityCredential', ManagedIdentityCredentialStub) + @mock.patch.dict('os.environ', {'AZURE_CORE_USE_MSAL_MANAGED_IDENTITY': 'true'}) + def test_login_with_mi_system_assigned_no_subscriptions_msal(self, create_subscription_client_mock): + mock_subscription_client = mock.MagicMock() + mock_subscription_client.subscriptions.list.return_value = [] + create_subscription_client_mock.return_value = mock_subscription_client + + cli = DummyCli() + storage_mock = {'subscriptions': None} + profile = Profile(cli_ctx=cli, storage=storage_mock) + subscriptions = profile.login_with_managed_identity(allow_no_subscriptions=True) + + credential_instance = create_subscription_client_mock.call_args.args[1] + assert credential_instance.client_id is None + assert credential_instance.object_id is None + assert credential_instance.resource_id is None + assert credential_instance.get_token_scopes == ['https://management.core.windows.net//.default'] + + self.assertEqual(len(subscriptions), 1) + s = subscriptions[0] + + self.assertEqual(s['name'], 'N/A(tenant level account)') + self.assertEqual(s['id'], self.test_mi_tenant) + self.assertEqual(s['tenantId'], self.test_mi_tenant) + + self.assertEqual(s['user']['name'], 'systemAssignedIdentity') + self.assertEqual(s['user']['type'], 'servicePrincipal') + self.assertEqual(s['user']['assignedIdentityInfo'], 'MSI') + + @mock.patch('azure.cli.core._profile.SubscriptionFinder._create_subscription_client', autospec=True) + @mock.patch('azure.cli.core.auth.msal_credentials.ManagedIdentityCredential', ManagedIdentityCredentialStub) + @mock.patch.dict('os.environ', {'AZURE_CORE_USE_MSAL_MANAGED_IDENTITY': 'true'}) + def test_login_with_mi_user_assigned_client_id_msal(self, create_subscription_client_mock): + mock_subscription_client = mock.MagicMock() + mock_subscription_client.subscriptions.list.return_value = [deepcopy(self.subscription1_raw)] + create_subscription_client_mock.return_value = mock_subscription_client + + cli = DummyCli() + storage_mock = {'subscriptions': None} + profile = Profile(cli_ctx=cli, storage=storage_mock) + subscriptions = profile.login_with_managed_identity(client_id=self.test_mi_client_id) + + credential_instance = create_subscription_client_mock.call_args.args[1] + assert credential_instance.client_id == self.test_mi_client_id + assert credential_instance.object_id is None + assert credential_instance.resource_id is None + assert credential_instance.get_token_scopes == ['https://management.core.windows.net//.default'] + + self.assertEqual(len(subscriptions), 1) + s = subscriptions[0] + self.assertEqual(s['name'], self.display_name1) + self.assertEqual(s['id'], self.id1.split('/')[-1]) + self.assertEqual(s['tenantId'], self.test_mi_tenant) + + self.assertEqual(s['user']['name'], 'userAssignedIdentity') + self.assertEqual(s['user']['type'], 'servicePrincipal') + self.assertEqual(s['user']['assignedIdentityInfo'], 'MSIClient-{}'.format(self.test_mi_client_id)) + + @mock.patch('azure.cli.core._profile.SubscriptionFinder._create_subscription_client', autospec=True) + @mock.patch('azure.cli.core.auth.msal_credentials.ManagedIdentityCredential', ManagedIdentityCredentialStub) + @mock.patch.dict('os.environ', {'AZURE_CORE_USE_MSAL_MANAGED_IDENTITY': 'true'}) + def test_login_with_mi_user_assigned_object_id_msal(self, create_subscription_client_mock): + mock_subscription_client = mock.MagicMock() + mock_subscription_client.subscriptions.list.return_value = [deepcopy(self.subscription1_raw)] + create_subscription_client_mock.return_value = mock_subscription_client + + cli = DummyCli() + storage_mock = {'subscriptions': None} + profile = Profile(cli_ctx=cli, storage=storage_mock) + subscriptions = profile.login_with_managed_identity(object_id=self.test_mi_object_id) + + credential_instance = create_subscription_client_mock.call_args.args[1] + assert credential_instance.client_id is None + assert credential_instance.object_id == self.test_mi_object_id + assert credential_instance.resource_id is None + assert credential_instance.get_token_scopes == ['https://management.core.windows.net//.default'] + + s = subscriptions[0] + self.assertEqual(s['user']['name'], 'userAssignedIdentity') + self.assertEqual(s['user']['type'], 'servicePrincipal') + self.assertEqual(s['user']['assignedIdentityInfo'], 'MSIObject-{}'.format(self.test_mi_object_id)) + + @mock.patch('azure.cli.core._profile.SubscriptionFinder._create_subscription_client', autospec=True) + @mock.patch('azure.cli.core.auth.msal_credentials.ManagedIdentityCredential', ManagedIdentityCredentialStub) + @mock.patch.dict('os.environ', {'AZURE_CORE_USE_MSAL_MANAGED_IDENTITY': 'true'}) + def test_login_with_mi_user_assigned_resource_id_msal(self, create_subscription_client_mock): + mock_subscription_client = mock.MagicMock() + mock_subscription_client.subscriptions.list.return_value = [deepcopy(self.subscription1_raw)] + create_subscription_client_mock.return_value = mock_subscription_client + + cli = DummyCli() + storage_mock = {'subscriptions': None} + profile = Profile(cli_ctx=cli, storage=storage_mock) + subscriptions = profile.login_with_managed_identity(resource_id=self.test_mi_resource_id) + + credential_instance = create_subscription_client_mock.call_args.args[1] + assert credential_instance.client_id is None + assert credential_instance.object_id is None + assert credential_instance.resource_id == self.test_mi_resource_id + assert credential_instance.get_token_scopes == ['https://management.core.windows.net//.default'] + + s = subscriptions[0] + self.assertEqual(s['user']['name'], 'userAssignedIdentity') + self.assertEqual(s['user']['type'], 'servicePrincipal') + self.assertEqual(subscriptions[0]['user']['assignedIdentityInfo'], 'MSIResource-{}'.format(self.test_mi_resource_id)) + @mock.patch('azure.cli.core._profile.SubscriptionFinder._create_subscription_client', autospec=True) @mock.patch('azure.cli.core.auth.identity.Identity.get_user_credential', autospec=True) @mock.patch('azure.cli.core.auth.identity.Identity.login_with_auth_code', autospec=True) @@ -1143,6 +1300,79 @@ def test_get_login_credentials_mi_user_assigned_with_res_id(self): self.assertTrue(cred.token_read_count) self.assertTrue(cred.msi_res_id, test_res_id) + @mock.patch('azure.cli.core.auth.msal_credentials.ManagedIdentityCredential', ManagedIdentityCredentialStub) + @mock.patch.dict('os.environ', {'AZURE_CORE_USE_MSAL_MANAGED_IDENTITY': 'true'}) + def test_get_login_credentials_mi_system_assigned_msal(self): + profile = Profile(cli_ctx=DummyCli(), storage={'subscriptions': None}) + consolidated = profile._normalize_properties('systemAssignedIdentity', + [deepcopy(self.test_mi_subscription)], + True, + user_assigned_identity_id="MSI") + profile._set_subscriptions(consolidated) + cred, subscription_id, _ = profile.get_login_credentials() + + assert subscription_id == self.test_mi_subscription_id + assert cred.get_token(profile._arm_scope).token == self.test_mi_access_token + assert cred._credential.client_id is None + assert cred._credential.object_id is None + assert cred._credential.resource_id is None + + @mock.patch('azure.cli.core.auth.msal_credentials.ManagedIdentityCredential', ManagedIdentityCredentialStub) + @mock.patch.dict('os.environ', {'AZURE_CORE_USE_MSAL_MANAGED_IDENTITY': 'true'}) + def test_get_login_credentials_mi_user_assigned_client_id_msal(self): + profile = Profile(cli_ctx=DummyCli(), storage={'subscriptions': None}) + consolidated = profile._normalize_properties( + 'userAssignedIdentity', + [deepcopy(self.test_mi_subscription)], + True, + user_assigned_identity_id='MSIClient-{}'.format(self.test_mi_client_id) + ) + profile._set_subscriptions(consolidated, secondary_key_name='name') + cred, subscription_id, _ = profile.get_login_credentials() + + assert subscription_id == self.test_mi_subscription_id + assert cred.get_token(profile._arm_scope).token == self.test_mi_access_token + assert cred._credential.client_id == self.test_mi_client_id + assert cred._credential.object_id is None + assert cred._credential.resource_id is None + + @mock.patch('azure.cli.core.auth.msal_credentials.ManagedIdentityCredential', ManagedIdentityCredentialStub) + @mock.patch.dict('os.environ', {'AZURE_CORE_USE_MSAL_MANAGED_IDENTITY': 'true'}) + def test_get_login_credentials_mi_user_assigned_object_id_msal(self): + profile = Profile(cli_ctx=DummyCli(), storage={'subscriptions': None}) + consolidated = profile._normalize_properties( + 'userAssignedIdentity', + [deepcopy(self.test_mi_subscription)], + True, + user_assigned_identity_id='MSIObject-{}'.format(self.test_mi_object_id) + ) + profile._set_subscriptions(consolidated, secondary_key_name='name') + cred, subscription_id, _ = profile.get_login_credentials() + + assert subscription_id == self.test_mi_subscription_id + assert cred.get_token(profile._arm_scope).token == self.test_mi_access_token + assert cred._credential.client_id is None + assert cred._credential.object_id == self.test_mi_object_id + assert cred._credential.resource_id is None + + @mock.patch('azure.cli.core.auth.msal_credentials.ManagedIdentityCredential', ManagedIdentityCredentialStub) + @mock.patch.dict('os.environ', {'AZURE_CORE_USE_MSAL_MANAGED_IDENTITY': 'true'}) + def test_get_login_credentials_mi_user_assigned_resource_id_msal(self): + profile = Profile(cli_ctx=DummyCli(), storage={'subscriptions': None}) + consolidated = profile._normalize_properties( + 'userAssignedIdentity', + [deepcopy(self.test_mi_subscription)], + True, + user_assigned_identity_id='MSIResource-{}'.format(self.test_mi_resource_id)) + profile._set_subscriptions(consolidated, secondary_key_name='name') + cred, subscription_id, _ = profile.get_login_credentials() + + assert subscription_id == self.test_mi_subscription_id + assert cred.get_token(profile._arm_scope).token == self.test_mi_access_token + assert cred._credential.client_id is None + assert cred._credential.object_id is None + assert cred._credential.resource_id == self.test_mi_resource_id + @mock.patch('azure.cli.core.auth.util._now_timestamp', new=_now_timestamp_mock) @mock.patch('azure.cli.core.auth.identity.Identity.get_user_credential') def test_get_raw_token(self, get_user_credential_mock): @@ -1270,6 +1500,137 @@ def mi_auth_factory(*args, **kwargs): with self.assertRaisesRegex(CLIError, "Tenant shouldn't be specified"): cred, subscription_id, _ = profile.get_raw_token(resource='http://test_resource', tenant=self.tenant_id) + @mock.patch('azure.cli.core.auth.util._now_timestamp', new=_now_timestamp_mock) + @mock.patch('azure.cli.core.auth.msal_credentials.ManagedIdentityCredential', ManagedIdentityCredentialStub) + @mock.patch.dict('os.environ', {'AZURE_CORE_USE_MSAL_MANAGED_IDENTITY': 'true'}) + def test_get_raw_token_mi_system_assigned_msal(self): + profile = Profile(cli_ctx=DummyCli(), storage={'subscriptions': None}) + consolidated = profile._normalize_properties('systemAssignedIdentity', + [deepcopy(self.test_mi_subscription)], + True, + user_assigned_identity_id='MSI') + profile._set_subscriptions(consolidated) + + credential_out = {'credential': None} + token_tuple, subscription_id, tenant_id = profile.get_raw_token(resource=self.adal_resource, + credential_out=credential_out) + + cred = credential_out['credential'] + assert cred.client_id is None + assert cred.object_id is None + assert cred.resource_id is None + # Verify correct scopes are passed to get_token + assert list(cred.get_token_scopes) == self.msal_scopes + + self.assertEqual(token_tuple[0], 'Bearer') + self.assertEqual(token_tuple[1], self.test_mi_access_token) + + # Make sure expires_on and expiresOn are set + self.assertEqual(token_tuple[2]['expires_on'], MOCK_EXPIRES_ON_INT) + self.assertEqual(token_tuple[2]['expiresOn'], MOCK_EXPIRES_ON_DATETIME) + self.assertEqual(subscription_id, self.test_mi_subscription_id) + self.assertEqual(tenant_id, self.test_mi_tenant) + + # verify tenant shouldn't be specified for MSI account + with self.assertRaisesRegex(CLIError, "Tenant shouldn't be specified"): + cred, subscription_id, _ = profile.get_raw_token(resource='http://test_resource', tenant=self.tenant_id) + + @mock.patch('azure.cli.core.auth.util._now_timestamp', new=_now_timestamp_mock) + @mock.patch('azure.cli.core.auth.msal_credentials.ManagedIdentityCredential', ManagedIdentityCredentialStub) + @mock.patch.dict('os.environ', {'AZURE_CORE_USE_MSAL_MANAGED_IDENTITY': 'true'}) + def test_get_raw_token_mi_user_assigned_client_id_msal(self): + profile = Profile(cli_ctx=DummyCli(), storage={'subscriptions': None}) + consolidated = profile._normalize_properties( + 'userAssignedIdentity', + [deepcopy(self.test_mi_subscription)], + True, + user_assigned_identity_id='MSIClient-{}'.format(self.test_mi_client_id) + ) + profile._set_subscriptions(consolidated) + + credential_out = {'credential': None} + token_tuple, subscription_id, tenant_id = profile.get_raw_token(resource=self.adal_resource, + credential_out=credential_out) + cred = credential_out['credential'] + assert cred.client_id == self.test_mi_client_id + assert cred.object_id is None + assert cred.resource_id is None + # Verify correct scopes are passed to get_token + assert list(cred.get_token_scopes) == self.msal_scopes + + self.assertEqual(token_tuple[0], 'Bearer') + self.assertEqual(token_tuple[1], self.test_mi_access_token) + + # Make sure expires_on and expiresOn are set + self.assertEqual(token_tuple[2]['expires_on'], MOCK_EXPIRES_ON_INT) + self.assertEqual(token_tuple[2]['expiresOn'], MOCK_EXPIRES_ON_DATETIME) + self.assertEqual(subscription_id, self.test_mi_subscription_id) + self.assertEqual(tenant_id, self.test_mi_tenant) + + @mock.patch('azure.cli.core.auth.util._now_timestamp', new=_now_timestamp_mock) + @mock.patch('azure.cli.core.auth.msal_credentials.ManagedIdentityCredential', ManagedIdentityCredentialStub) + @mock.patch.dict('os.environ', {'AZURE_CORE_USE_MSAL_MANAGED_IDENTITY': 'true'}) + def test_get_raw_token_mi_user_assigned_object_id_msal(self): + profile = Profile(cli_ctx=DummyCli(), storage={'subscriptions': None}) + consolidated = profile._normalize_properties( + 'userAssignedIdentity', + [deepcopy(self.test_mi_subscription)], + True, + user_assigned_identity_id='MSIObject-{}'.format(self.test_mi_object_id) + ) + profile._set_subscriptions(consolidated) + + credential_out = {'credential': None} + token_tuple, subscription_id, tenant_id = profile.get_raw_token(resource=self.adal_resource, + credential_out=credential_out) + cred = credential_out['credential'] + assert cred.client_id is None + assert cred.object_id == self.test_mi_object_id + assert cred.resource_id is None + # Verify correct scopes are passed to get_token + assert list(cred.get_token_scopes) == self.msal_scopes + + self.assertEqual(token_tuple[0], 'Bearer') + self.assertEqual(token_tuple[1], self.test_mi_access_token) + + # Make sure expires_on and expiresOn are set + self.assertEqual(token_tuple[2]['expires_on'], MOCK_EXPIRES_ON_INT) + self.assertEqual(token_tuple[2]['expiresOn'], MOCK_EXPIRES_ON_DATETIME) + self.assertEqual(subscription_id, self.test_mi_subscription_id) + self.assertEqual(tenant_id, self.test_mi_tenant) + + @mock.patch('azure.cli.core.auth.util._now_timestamp', new=_now_timestamp_mock) + @mock.patch('azure.cli.core.auth.msal_credentials.ManagedIdentityCredential', ManagedIdentityCredentialStub) + @mock.patch.dict('os.environ', {'AZURE_CORE_USE_MSAL_MANAGED_IDENTITY': 'true'}) + def test_get_raw_token_mi_user_assigned_resource_id_msal(self): + profile = Profile(cli_ctx=DummyCli(), storage={'subscriptions': None}) + consolidated = profile._normalize_properties( + 'userAssignedIdentity', + [deepcopy(self.test_mi_subscription)], + True, + user_assigned_identity_id='MSIResource-{}'.format(self.test_mi_resource_id) + ) + profile._set_subscriptions(consolidated) + + credential_out = {'credential': None} + token_tuple, subscription_id, tenant_id = profile.get_raw_token(resource=self.adal_resource, + credential_out=credential_out) + cred = credential_out['credential'] + assert cred.client_id is None + assert cred.object_id is None + assert cred.resource_id == self.test_mi_resource_id + # Verify correct scopes are passed to get_token + assert list(cred.get_token_scopes) == self.msal_scopes + + self.assertEqual(token_tuple[0], 'Bearer') + self.assertEqual(token_tuple[1], self.test_mi_access_token) + + # Make sure expires_on and expiresOn are set + self.assertEqual(token_tuple[2]['expires_on'], MOCK_EXPIRES_ON_INT) + self.assertEqual(token_tuple[2]['expiresOn'], MOCK_EXPIRES_ON_DATETIME) + self.assertEqual(subscription_id, self.test_mi_subscription_id) + self.assertEqual(tenant_id, self.test_mi_tenant) + @mock.patch('azure.cli.core.auth.util._now_timestamp', new=_now_timestamp_mock) @mock.patch('azure.cli.core._profile.in_cloud_console', autospec=True) @mock.patch('azure.cli.core.auth.msal_credentials.CloudShellCredential', autospec=True)