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

[Core] Drop Track 1 SDK authentication support #29631

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
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
11 changes: 3 additions & 8 deletions src/azure-cli-core/azure/cli/core/_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,18 +328,14 @@ def logout_all(self):
identity.logout_all_users()
identity.logout_all_service_principal()

def get_login_credentials(self, resource=None, client_id=None, subscription_id=None, aux_subscriptions=None,
aux_tenants=None):
def get_login_credentials(self, client_id=None, subscription_id=None, aux_subscriptions=None, aux_tenants=None):
"""Get a CredentialAdaptor instance to be used with both Track 1 and Track 2 SDKs.

:param resource: The resource ID to acquire an access token. Only provide it for Track 1 SDKs.
:param client_id:
:param subscription_id:
:param aux_subscriptions:
:param aux_tenants:
"""
resource = resource or self.cli_ctx.cloud.endpoints.active_directory_resource_id

if aux_tenants and aux_subscriptions:
raise CLIError("Please specify only one of aux_subscriptions and aux_tenants, not both")

Expand Down Expand Up @@ -368,11 +364,10 @@ def get_login_credentials(self, resource=None, client_id=None, subscription_id=N
for external_tenant in external_tenants:
external_credentials.append(self._create_credential(account, external_tenant, client_id=client_id))
from azure.cli.core.auth.credential_adaptor import CredentialAdaptor
cred = CredentialAdaptor(credential,
auxiliary_credentials=external_credentials,
resource=resource)
cred = CredentialAdaptor(credential, auxiliary_credentials=external_credentials)
else:
# managed identity
# TODO: Migrate MSIAuthentication to MSAL
cred = MsiAccountTypes.msi_auth_factory(managed_identity_type, managed_identity_id, resource)
return (cred,
str(account[_SUBSCRIPTION_ID]),
Expand Down
43 changes: 8 additions & 35 deletions src/azure-cli-core/azure/cli/core/auth/credential_adaptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,69 +3,42 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import requests
from knack.log import get_logger
from knack.util import CLIError

from .util import resource_to_scopes, _normalize_scopes
from .util import _normalize_scopes

logger = get_logger(__name__)


class CredentialAdaptor:
def __init__(self, credential, resource=None, auxiliary_credentials=None):
def __init__(self, credential, auxiliary_credentials=None):
"""
Adaptor to both
- Track 1: msrest.authentication.Authentication, which exposes signed_session
- Track 2: azure.core.credentials.TokenCredential, which exposes get_token
Cross-tenant credential adaptor. It takes a main credential and auxiliary credentials.

It implements Track 2 SDK's azure.core.credentials.TokenCredential by exposing get_token.

:param credential: Main credential from .msal_authentication
:param resource: AAD resource for Track 1 only
:param auxiliary_credentials: Credentials from .msal_authentication for cross tenant authentication.
Details about cross tenant authentication:
https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/authenticate-multi-tenant
"""

self._credential = credential
self._auxiliary_credentials = auxiliary_credentials
self._resource = resource

def _get_token(self, scopes=None, **kwargs):
external_tenant_tokens = []
# If scopes is not provided, use CLI-managed resource
scopes = scopes or resource_to_scopes(self._resource)
try:
token = self._credential.get_token(*scopes, **kwargs)
if self._auxiliary_credentials:
external_tenant_tokens = [cred.get_token(*scopes) for cred in self._auxiliary_credentials]
return token, external_tenant_tokens
except requests.exceptions.SSLError as err:
from azure.cli.core.util import SSLERROR_TEMPLATE
raise CLIError(SSLERROR_TEMPLATE.format(str(err)))

def signed_session(self, session=None):
logger.debug("CredentialAdaptor.signed_session")
session = session or requests.Session()
token, external_tenant_tokens = self._get_token()
header = "{} {}".format('Bearer', token.token)
session.headers['Authorization'] = header
if external_tenant_tokens:
aux_tokens = ';'.join(['{} {}'.format('Bearer', tokens2.token) for tokens2 in external_tenant_tokens])
session.headers['x-ms-authorization-auxiliary'] = aux_tokens
return session

def get_token(self, *scopes, **kwargs):
"""Get an access token from the main credential."""
logger.debug("CredentialAdaptor.get_token: scopes=%r, kwargs=%r", scopes, kwargs)

# SDK azure-keyvault-keys 4.5.0b5 passes tenant_id as kwargs, but we don't support tenant_id for now,
# so discard it.
kwargs.pop('tenant_id', None)

scopes = _normalize_scopes(scopes)
token, _ = self._get_token(scopes, **kwargs)
return token
return self._credential.get_token(*scopes, **kwargs)

def get_auxiliary_tokens(self, *scopes, **kwargs):
"""Get access tokens from auxiliary credentials."""
# To test cross-tenant authentication, see https://github.com/Azure/azure-cli/issues/16691
if self._auxiliary_credentials:
return [cred.get_token(*scopes, **kwargs) for cred in self._auxiliary_credentials]
Expand Down

This file was deleted.

1 change: 1 addition & 0 deletions src/azure-cli-core/azure/cli/core/auth/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def scopes_to_resource(scopes):


def _normalize_scopes(scopes):
# TODO: Drop this function
Copy link
Member Author

Choose a reason for hiding this comment

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

This function is to support very old Track 2 SDKs that doesn't comply with the current authentication interface. We should consider dropping their support as well.

"""Normalize scopes to workaround some SDK issues."""

# Track 2 SDKs generated before https://github.com/Azure/autorest.python/pull/239 don't maintain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,6 @@ def _get_mgmt_service_client(cli_ctx,
subscription_id=None,
api_version=None,
base_url_bound=True,
resource=None,
sdk_profile=None,
aux_subscriptions=None,
aux_tenants=None,
Expand All @@ -222,10 +221,6 @@ def _get_mgmt_service_client(cli_ctx,
from azure.cli.core._profile import Profile
logger.debug('Getting management service client client_type=%s', client_type.__name__)

# Track 1 SDK doesn't maintain the `resource`. The `resource` of the token is the one passed to
# get_login_credentials.
resource = resource or cli_ctx.cloud.endpoints.active_directory_resource_id

if credential:
# Use a custom credential
if not subscription_id:
Expand All @@ -234,8 +229,7 @@ def _get_mgmt_service_client(cli_ctx,
# Get a credential for the current `az login` context
profile = Profile(cli_ctx=cli_ctx)
credential, subscription_id, _ = profile.get_login_credentials(
subscription_id=subscription_id, resource=resource,
aux_subscriptions=aux_subscriptions, aux_tenants=aux_tenants)
subscription_id=subscription_id, aux_subscriptions=aux_subscriptions, aux_tenants=aux_tenants)

client_kwargs = {}
if base_url_bound:
Expand Down
Loading