Skip to content

Commit

Permalink
{Role} Keep --sdk-auth, for now (#19872)
Browse files Browse the repository at this point in the history
  • Loading branch information
jiasli authored Oct 15, 2021
1 parent 8966e1d commit 802f581
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 3 deletions.
52 changes: 52 additions & 0 deletions src/azure-cli-core/azure/cli/core/_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,58 @@ def refresh_accounts(self):

self._set_subscriptions(result, merge=False)

def get_sp_auth_info(self, subscription_id=None, name=None, password=None, cert_file=None):
"""Generate a JSON for --sdk-auth argument when used in:
- az ad sp create-for-rbac --sdk-auth
- az account show --sdk-auth
"""
from collections import OrderedDict
account = self.get_subscription(subscription_id)

# is the credential created through command like 'create-for-rbac'?
result = OrderedDict()
if name and (password or cert_file):
result['clientId'] = name
if password:
result['clientSecret'] = password
else:
result['clientCertificate'] = cert_file
result['subscriptionId'] = subscription_id or account[_SUBSCRIPTION_ID]
else: # has logged in through cli
user_type = account[_USER_ENTITY].get(_USER_TYPE)
if user_type == _SERVICE_PRINCIPAL:
client_id = account[_USER_ENTITY][_USER_NAME]
result['clientId'] = client_id
identity = Identity(tenant_id=account[_TENANT_ID])
sp_entry = identity.get_service_principal_entry(client_id)

from .auth.msal_authentication import _CLIENT_SECRET, _CERTIFICATE
secret = sp_entry.get(_CLIENT_SECRET)
if secret:
result['clientSecret'] = secret
else:
# we can output 'clientCertificateThumbprint' if asked
result['clientCertificate'] = sp_entry.get(_CERTIFICATE)
result['subscriptionId'] = account[_SUBSCRIPTION_ID]
else:
raise CLIError('SDK Auth file is only applicable when authenticated using a service principal')

result[_TENANT_ID] = account[_TENANT_ID]
endpoint_mappings = OrderedDict() # use OrderedDict to control the output sequence
endpoint_mappings['active_directory'] = 'activeDirectoryEndpointUrl'
endpoint_mappings['resource_manager'] = 'resourceManagerEndpointUrl'
endpoint_mappings['active_directory_graph_resource_id'] = 'activeDirectoryGraphResourceId'
endpoint_mappings['sql_management'] = 'sqlManagementEndpointUrl'
endpoint_mappings['gallery'] = 'galleryEndpointUrl'
endpoint_mappings['management'] = 'managementEndpointUrl'
from azure.cli.core.cloud import CloudEndpointNotSetException
for e in endpoint_mappings:
try:
result[endpoint_mappings[e]] = getattr(get_active_cloud(self.cli_ctx).endpoints, e)
except CloudEndpointNotSetException:
result[endpoint_mappings[e]] = None
return result

def get_installation_id(self):
installation_id = self._storage.get(_INSTALLATION_ID)
if not installation_id:
Expand Down
4 changes: 4 additions & 0 deletions src/azure-cli-core/azure/cli/core/auth/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ def get_service_principal_credential(self, client_id):
sp_auth = ServicePrincipalAuth(entry)
return ServicePrincipalCredential(sp_auth, **self._msal_app_kwargs)

def get_service_principal_entry(self, client_id):
"""This method is only used by --sdk-auth. DO NOT use it elsewhere."""
return self._msal_secret_store.load_entry(client_id, self.tenant_id)

def get_managed_identity_credential(self, client_id=None):
raise NotImplementedError

Expand Down
69 changes: 69 additions & 0 deletions src/azure-cli-core/azure/cli/core/tests/test_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1368,6 +1368,75 @@ def test_login_common_tenant_mfa_warning(self, get_user_credential_mock, create_

# With pytest, use -o log_cli=True to manually check the log

# Tests for get_sp_auth_info
def test_get_auth_info_fail_on_user_account(self):
cli = DummyCli()
storage_mock = {'subscriptions': None}
profile = Profile(cli_ctx=cli, storage=storage_mock)

consolidated = profile._normalize_properties(self.user1,
[self.subscription1],
False)
profile._set_subscriptions(consolidated)

# testing dump of existing logged in account
self.assertRaises(CLIError, profile.get_sp_auth_info)

@mock.patch('azure.cli.core.auth.identity.Identity.get_service_principal_entry', autospec=True)
@mock.patch('azure.cli.core._profile.SubscriptionFinder._create_subscription_client', autospec=True)
@mock.patch('azure.cli.core.auth.identity.Identity.get_service_principal_credential', autospec=True)
@mock.patch('azure.cli.core.auth.identity.Identity.login_with_service_principal', autospec=True)
def test_get_auth_info_for_logged_in_service_principal(self, login_with_service_principal_mock,
get_service_principal_credential_mock,
create_subscription_client_mock,
get_service_principal_entry_mock):
cli = DummyCli()
mock_subscription_client = mock.MagicMock()
mock_subscription_client.tenants.list.return_value = [TenantStub(self.tenant_id)]
mock_subscription_client.subscriptions.list.return_value = [deepcopy(self.subscription1_raw)]
create_subscription_client_mock.return_value = mock_subscription_client

storage_mock = {'subscriptions': []}
profile = Profile(cli_ctx=cli, storage=storage_mock)
profile._management_resource_uri = 'https://management.core.windows.net/'
profile.login(False, '1234', 'my-secret', True, self.tenant_id, use_device_code=False,
allow_no_subscriptions=False)
# action
get_service_principal_entry_mock.return_value = {
"tenant": self.tenant_id,
"client_id": '1234',
"client_secret": 'my-secret'
}
extended_info = profile.get_sp_auth_info()
# assert
self.assertEqual(self.id1.split('/')[-1], extended_info['subscriptionId'])
self.assertEqual('1234', extended_info['clientId'])
self.assertEqual('my-secret', extended_info['clientSecret'])
self.assertEqual('https://login.microsoftonline.com', extended_info['activeDirectoryEndpointUrl'])
self.assertEqual('https://management.azure.com/', extended_info['resourceManagerEndpointUrl'])

def test_get_auth_info_for_newly_created_service_principal(self):
cli = DummyCli()
storage_mock = {'subscriptions': []}
profile = Profile(cli_ctx=cli, storage=storage_mock)
consolidated = profile._normalize_properties(self.user1, [self.subscription1], False)
profile._set_subscriptions(consolidated)

# certificate
extended_info = profile.get_sp_auth_info(name='1234', cert_file='/tmp/123.pem')

self.assertEqual(self.id1.split('/')[-1], extended_info['subscriptionId'])
self.assertEqual(self.tenant_id, extended_info['tenantId'])
self.assertEqual('1234', extended_info['clientId'])
self.assertEqual('/tmp/123.pem', extended_info['clientCertificate'])
self.assertIsNone(extended_info.get('clientSecret', None))
self.assertEqual('https://login.microsoftonline.com', extended_info['activeDirectoryEndpointUrl'])
self.assertEqual('https://management.azure.com/', extended_info['resourceManagerEndpointUrl'])

# secret
extended_info = profile.get_sp_auth_info(name='1234', password='very_secret')
self.assertEqual('very_secret', extended_info['clientSecret'])


class FileHandleStub(object): # pylint: disable=too-few-public-methods

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def load_arguments(self, command):

with self.argument_context('account show') as c:
c.argument('show_auth_for_sdk', options_list=['--sdk-auth'], action='store_true',
deprecate_info=c.deprecate(target='--sdk-auth', expiration='3.0.0'),
deprecate_info=c.deprecate(target='--sdk-auth'),
help='Output result to a file compatible with Azure SDK auth. Only applicable when authenticating with a Service Principal.')

with self.argument_context('account get-access-token') as c:
Expand Down
3 changes: 1 addition & 2 deletions src/azure-cli/azure/cli/command_modules/role/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ def load_arguments(self, _):
help='Skip creating the default assignment, which allows the service principal to access resources under the current subscription. '
'When specified, --scopes will be ignored. You may use `az role assignment create` to create '
'role assignments for this service principal later.')
c.argument('show_auth_for_sdk', options_list='--sdk-auth',
deprecate_info=c.deprecate(target='--sdk-auth', expiration='3.0.0'),
c.argument('show_auth_for_sdk', options_list='--sdk-auth', deprecate_info=c.deprecate(target='--sdk-auth'),
help='output result in compatible with Azure SDK auth file', arg_type=get_three_state_flag())

with self.argument_context('ad sp owner list') as c:
Expand Down

0 comments on commit 802f581

Please sign in to comment.