Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

{Role} Keep --sdk-auth, for now #19872

Merged
merged 2 commits into from
Oct 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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