Skip to content

Conversation

@jiasli
Copy link
Member

@jiasli jiasli commented Sep 16, 2020

Fix #15179

Issue

In

  • Track 1 SDK, scopes (resource) is managed by Azure CLI
  • Track 2 SDK, scopes (resource) is managed by SDK

For example, for Track 2 SDK mgmt-plane SubscriptionClientConfiguration:

self.credential_scopes = ['https://management.azure.com/.default']

For Track 2 SDK data-plane AzureAppConfigurationClient:

if aad_mode:
    scope = base_url.strip("/") + "/.default"

But in AdalAuthentication.get_token, scopes is not honored (discarded), resulting in getting a token for a wrong scopes (ARM https://management.core.windows.net/ by default):

# This method is exposed for Azure Core.
def get_token(self, *scopes, **kwargs): # pylint:disable=unused-argument
_, token, full_token, _ = self._get_token()
try:
return AccessToken(token, int(full_token['expiresIn'] + time.time()))
except KeyError: # needed to deal with differing unserialized MSI token payload
return AccessToken(token, int(full_token['expires_on']))

Fix

This PR fixes the issue by honoring the scopes specified by Track 2 SDK.

Testing Guide

User Identity

# Track 1 SDK mgmt-plan
> az group show -n fy --debug
DEBUG: AdalAuthentication.signed_session invoked by Track 1 SDK

# Track 2 SDK mgmt-plane
> az account subscription show --id 0b1f6471-1bf0-4dda-aec3-cb9272f09590 --debug
DEBUG: AdalAuthentication.get_token invoked by Track 2 SDK with scopes=('https://management.azure.com/.default',)

# Track 1 SDK data-plane
> az account subscription show --id 0b1f6471-1bf0-4dda-aec3-cb9272f09590 --debug
DEBUG: Profile.get_raw_token invoked with resource='https://storage.azure.com', subscription=None, tenant=None

# Track 2 SDK data-plane
az storage blob list --account-name st0921 -c con1 --auth-mode login --debug
DEBUG: AdalAuthentication.get_token invoked by Track 2 SDK with scopes=('https://storage.azure.com/.default',)

Managed Identity

# Track 1 SDK mgmt-plan
> az group show -n fy --debug
DEBUG: MSIAuthenticationWrapper.signed_session invoked by Track 1 SDK

# Track 2 SDK mgmt-plane
> az account subscription show --id 0b1f6471-1bf0-4dda-aec3-cb9272f09590 --debug
DEBUG: MSIAuthenticationWrapper.get_token invoked by Track 2 SDK with scopes=('https://management.azure.com/.default',)

# Track 1 SDK data-plane
> az account subscription show --id 0b1f6471-1bf0-4dda-aec3-cb9272f09590 --debug
DEBUG: Profile.get_raw_token invoked with resource='https://storage.azure.com', subscription=None, tenant=None

# Track 2 SDK data-plane
az storage blob list --account-name st0921 -c con1 --auth-mode login --debug
DEBUG: MSIAuthenticationWrapper.get_token invoked by Track 2 SDK with scopes=('https://storage.azure.com/.default',)

@yonzhan yonzhan added this to the S176 milestone Sep 16, 2020
@yonzhan
Copy link
Collaborator

yonzhan commented Sep 16, 2020

Core

profile = Profile(cli_ctx=cmd.cli_ctx)
n.token_credential, _, _ = profile.get_login_credentials(
resource="https://storage.azure.com", subscription_id=n._subscription)
n.token_credential, _, _ = profile.get_login_credentials(subscription_id=n._subscription)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scope is now managed by Track 2 Storage SDK (azure/multiapi/storagev2/blob/v2019_12_12/_shared/constants.py:25):

STORAGE_OAUTH_SCOPE = "https://storage.azure.com/.default"

@jiasli jiasli marked this pull request as ready for review September 21, 2020 10:07
@jiasli
Copy link
Member Author

jiasli commented Sep 24, 2020

Unfortunately, due an incorrect credential_scopes managing logic in existing SDKs (Azure/azure-sdk-for-python#12947), even though the bug has been fixed in Azure/autorest.python#745, merging this PR will break all commands using Track 2 SDKs with sovereign clouds, as credential_scopes will be passed to scopes as

['https://management.azure.com/.default', 'https://management.core.chinacloudapi.cn/.default']

'https://management.core.chinacloudapi.cn/.default' will be discarded.

Comment on lines +71 to +76
# Deal with an old Track 2 SDK issue where the default credential_scopes is extended with
# custom credential_scopes. Instead, credential_scopes should be replaced by custom credential_scopes.
# https://github.com/Azure/azure-sdk-for-python/issues/12947
# We simply remove the first one if there are multiple scopes provided.
if len(scopes) > 1:
scopes = scopes[1:]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a patch to handle issue Azure/azure-sdk-for-python#12947 in old Track 2 SDKs.

@yonzhan yonzhan modified the milestones: S176, S177 Sep 27, 2020
@jiasli jiasli requested a review from evelyn-ys as a code owner October 16, 2020 08:45
@yonzhan yonzhan modified the milestones: S177, S178 Oct 24, 2020
# Conflicts:
#	src/azure-cli-core/azure/cli/core/adal_authentication.py
@jiasli jiasli requested a review from bim-msft October 26, 2020 09:20
:rtype: str
"""
scope = scopes[0]
if scope.endswith(".default"):
Copy link
Member

@jsntcy jsntcy Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if scope.endswith(".default"): [](start = 4, length = 30)

Do we need make this case insensitive?

Copy link
Member Author

@jiasli jiasli Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. .default is the only supported form. See The /.default scope.


# Trim extra ending slashes. https://datalake.azure.net// -> https://datalake.azure.net/
scope = scope.rstrip('/') + '/'
return scope
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it expected that "scope" is https://management.core.windows.net/ or it should be https://management.core.windows.net?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. See

active_directory_resource_id='https://management.core.windows.net/',

:return: The ADAL resource
:rtype: str
"""
scope = scopes[0]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we only support one scope now as it is a list?

Copy link
Member Author

@jiasli jiasli Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per Request an access token,

The scopes must all be from a single resource, along with OIDC scopes (profile, openid, email)

:param resource: The ADAL resource ID
:return: A list of scopes
"""
if 'datalake' in resource or 'batch' in resource or 'database' in resource:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible that we move this tricky logic from core to module level?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Currently resource_to_scopes is only called by _get_mgmt_service_client. We should actually remove this tricky logic totally from this function. Will do in a separate PR.

Copy link
Member

@jsntcy jsntcy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

{Core} AdalAuthentication doesn't honor scopes for Track 2 SDK

7 participants