Skip to content
Closed
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
2 changes: 1 addition & 1 deletion src/azure-cli-core/azure/cli/core/_identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def __init__(self, authority=None, tenant_id=None, client_id=None, **kwargs):
# - Access token
# - Service principal secret
# - Service principal certificate
# self._credential_kwargs['logging_enable'] = True
self._credential_kwargs['logging_enable'] = True

def _load_msal_cache(self):
# sdk/identity/azure-identity/azure/identity/_internal/msal_credentials.py:95
Expand Down
10 changes: 7 additions & 3 deletions src/azure-cli-core/azure/cli/core/azclierror.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
logger = get_logger(__name__)
# pylint: disable=unnecessary-pass

UNAUTHORIZED_MESSAGE = ("The access token has expired or been revoked due to being blocked by Continuous Access "
"Evaluation. To re-authenticate, please run `az login`. "
Comment on lines +15 to +16
Copy link
Member

Choose a reason for hiding this comment

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

My understanding is continuous access evaluation just makes revoke happen before AT is expired. The statement can be "blocked by access policy" or "not meet the criteria to access this resource"

"(Silent re-authentication will be attempted in the future.)")

# Error types in AzureCLI are from different sources, and there are many general error types like CLIError, AzureError.
# Besides, many error types with different names are actually showing the same kind of error.
Expand Down Expand Up @@ -169,7 +172,8 @@ class BadRequestError(UserFault):

class UnauthorizedError(UserFault):
""" Unauthorized request: 401 error """
pass
def __init__(self, error_msg, recommendation=UNAUTHORIZED_MESSAGE):
super().__init__(error_msg, recommendation)


class ForbiddenError(UserFault):
Expand Down Expand Up @@ -259,7 +263,7 @@ class RecommendationError(ClientError):
pass


class AuthenticationError(ServiceError):
""" Raised when AAD authentication fails. """
class AuthenticationError(AzCLIError):
""" Raised when credential.get_token fails. """

# endregion
18 changes: 12 additions & 6 deletions src/azure-cli-core/azure/cli/core/azlogging.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,18 @@ def __init__(self, name, cli_ctx=None):

def configure(self, args):
super(AzCliLogging, self).configure(args)
from knack.log import CliLogLevel
if self.log_level == CliLogLevel.DEBUG:
# As azure.core.pipeline.policies.http_logging_policy is a redacted version of
# azure.core.pipeline.policies._universal, disable azure.core.pipeline.policies.http_logging_policy
# when debug log is shown.
logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.CRITICAL)
if self.log_level:
# When invoked by pytest, configure() is skipped and log_level will not be set.
from knack.log import CliLogLevel
if self.log_level == CliLogLevel.DEBUG:
# As azure.core.pipeline.policies.http_logging_policy is a redacted version of
# azure.core.pipeline.policies._universal, disable azure.core.pipeline.policies.http_logging_policy
# when debug log is shown.
logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.CRITICAL)

if self.log_level <= CliLogLevel.WARNING:
# Disable warnings from Azure Identity
logging.getLogger("azure.identity").setLevel(logging.CRITICAL)

def get_command_log_dir(self):
return self.command_log_dir
Expand Down
2 changes: 1 addition & 1 deletion src/azure-cli-core/azure/cli/core/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ def from_json(cls, json_str):
'AzureCloud',
endpoints=CloudEndpoints(
management='https://management.core.windows.net/',
resource_manager='https://management.azure.com/',
resource_manager='https://eastus2euap.management.azure.com/',
Copy link
Member Author

@jiasli jiasli Feb 24, 2021

Choose a reason for hiding this comment

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

Using canary ARM endpoint causes CI failure:

https://dev.azure.com/azure-sdk/public/_build/results?buildId=750605&view=logs&j=75453f46-9c6c-5df4-6021-c5646ce3452d&t=4b9b4997-7a06-5274-d89c-915764f2e7f7&l=213892

____________________ TestCloud.test_metadata_url_endpoints _____________________
self = <azure.cli.core.tests.test_cloud.TestCloud testMethod=test_metadata_url_endpoints>

    @mock.patch.dict('os.environ', {'ARM_CLOUD_METADATA_URL': 'https://management.azure.com/metadata/endpoints?api-version=2019-05-01'})
    def test_metadata_url_endpoints(self):
        clouds = get_known_clouds(refresh=True)
        for cloud in HARD_CODED_CLOUD_LIST:
            metadata_url_cloud = next(c for c in clouds if c.name == cloud.name)
            for k, v1 in cloud.endpoints.__dict__.items():
                v2 = metadata_url_cloud.endpoints.__dict__[k]
                if v1:
>                   self.assertEqual(v1.strip('/'), v2.strip('/'))
E                   AssertionError: 'https://eastus2euap.management.azure.com' != 'https://management.azure.com'
E                   - https://eastus2euap.management.azure.com
E                   ?         ------------
E                   + https://management.azure.com

src/azure-cli-core/azure/cli/core/tests/test_cloud.py:260: AssertionError

This can't be fixed because

  1. There are tests like test_metadata_url_endpoints using https://management.azure.com.
  2. All YAML recordings are based on https://management.azure.com. Calling https://eastus2euap.management.azure.com will cause mismatch during playback.

sql_management='https://management.core.windows.net:8443/',
batch_resource_id='https://batch.core.windows.net/',
gallery='https://gallery.azure.com/',
Expand Down
29 changes: 17 additions & 12 deletions src/azure-cli-core/azure/cli/core/credential.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@

import requests
from azure.cli.core._identity import resource_to_scopes
from azure.cli.core.azclierror import AuthenticationError
from azure.cli.core.util import in_cloud_console
from azure.core.credentials import AccessToken
from azure.core.exceptions import ClientAuthenticationError
from azure.identity import AuthenticationRequiredError

from knack.log import get_logger
from knack.util import CLIError
Expand Down Expand Up @@ -48,27 +50,30 @@ def _get_token(self, scopes=None, **kwargs):
if in_cloud_console():
CredentialAdaptor._log_hostname()
raise err

except AuthenticationRequiredError as err:
refined_message = "Interactive authentication is required to get a token."
refined_message = refined_message + "\n\nError detail:\n" + err.error_details

recommendation = "The refresh token has been revoked due to password change or " \
"being blocked by Conditional Access policy. " \
"To re-authenticate, " +\
("please refresh Azure Portal." if in_cloud_console() else "please run `az login`.")
raise AuthenticationError(refined_message, recommendation) from err

except ClientAuthenticationError as err:
# pylint: disable=no-member
if in_cloud_console():
CredentialAdaptor._log_hostname()

err = getattr(err, 'message', None) or ''
if 'authentication is required' in err:
raise CLIError("Authentication is migrated to Microsoft identity platform (v2.0). {}".format(
"Please run 'az login' to login." if not in_cloud_console() else ''))
if 'AADSTS70008' in err: # all errors starting with 70008 should be creds expiration related
err_message = getattr(err, 'error_details', None) or ''
if 'AADSTS70008' in err_message: # all errors starting with 70008 should be creds expiration related
raise CLIError("Credentials have expired due to inactivity. {}".format(
"Please run 'az login'" if not in_cloud_console() else ''))
if 'AADSTS50079' in err:
if 'AADSTS50079' in err_message:
raise CLIError("Configuration of your account was changed. {}".format(
"Please run 'az login'" if not in_cloud_console() else ''))
if 'AADSTS50173' in err:
raise CLIError("The credential data used by CLI has been expired because you might have changed or "
"reset the password. {}".format(
"Please clear browser's cookies and run 'az login'"
if not in_cloud_console() else ''))
raise CLIError(err)
raise CLIError(err_message)
except requests.exceptions.SSLError as err:
from .util import SSLERROR_TEMPLATE
raise CLIError(SSLERROR_TEMPLATE.format(str(err)))
Expand Down
2 changes: 1 addition & 1 deletion src/azure-cli-core/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
'humanfriendly>=4.7,<10.0',
'jmespath',
'knack==0.8.0rc2',
'azure-identity==1.5.0b2',
'azure-identity==1.6.0b1',
# Dependencies of the vendored subscription SDK
# https://github.com/Azure/azure-sdk-for-python/blob/ab12b048ddf676fe0ccec16b2167117f0609700d/sdk/resources/azure-mgmt-resource/setup.py#L82-L86
'msrest>=0.5.0',
Expand Down
2 changes: 2 additions & 0 deletions src/azure-cli-testsdk/azure/cli/testsdk/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ def __init__(self, method_name):
self.kwargs = {}
self.test_resources_count = 0

patch_main_exception_handler(self)
Copy link
Member Author

@jiasli jiasli Feb 24, 2021

Choose a reason for hiding this comment

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

ScenarioTest (inherited from ReplayableTest) patches azure.cli.core.util.handle_exception so that the raw exception is thrown and can be captured by assertRaises. (handle_exception silences any exception.)

Do the same for LiveScenarioTest.


def cmd(self, command, checks=None, expect_failure=False):
command = self._apply_kwargs(command)
return execute(self.cli_ctx, command, expect_failure=expect_failure).assert_with_checks(checks)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from time import sleep

import jwt
from azure.cli.core.azclierror import AuthenticationError
from azure.cli.testsdk import LiveScenarioTest
from msrestazure.azure_exceptions import CloudError

ARM_URL = "https://eastus2euap.management.azure.com/" # ARM canary
ARM_RETRY_INTERVAL = 10


class CAEScenarioTest(LiveScenarioTest):

def test_client_capabilities(self):
self.cmd('login')

# Verify the access token has CAE enabled
out = self.cmd('account get-access-token').get_output_in_json()
access_token = out['accessToken']
decoded = jwt.decode(access_token, verify=False, algorithms=['RS256'])
self.assertEqual(decoded['xms_cc'], ['CP1']) # xms_cc: extension microsoft client capabilities
self.assertEqual(decoded['xms_ssm'], '1') # xms_ssm: extension microsoft smart session management

def _test_revoke_session(self, command, expected_error, checks=None):
self.test_client_capabilities()

# Test access token is working
self.cmd(command)

self._revoke_sign_in_sessions()

# CAE is currently only available in canary endpoint
# with mock.patch.object(self.cli_ctx.cloud.endpoints, "resource_manager", ARM_URL):
exit_code = 0
with self.assertRaises(expected_error) as ex:
while exit_code == 0:
exit_code = self.cmd(command).exit_code
sleep(ARM_RETRY_INTERVAL)
if checks:
checks(ex.exception)

def test_revoke_session_track2(self):
def check_aad_error_code(ex):
self.assertIn('AADSTS50173', str(ex))

self._test_revoke_session("storage account list", AuthenticationError, check_aad_error_code)

def test_revoke_session_track1(self):
def check_arm_error(ex):
self.assertEqual(ex.status_code, 401)
self.assertIsNotNone(ex.response.headers["WWW-Authenticate"])

self._test_revoke_session('group list', CloudError, check_arm_error)

def _revoke_sign_in_sessions(self):
# Manually revoke sign in sessions
self.cmd('rest -m POST -u https://graph.microsoft.com/v1.0/me/revokeSignInSessions')
4 changes: 2 additions & 2 deletions src/azure-cli/requirements.py3.Darwin.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ azure-cosmos==3.2.0
azure-datalake-store==0.0.49
azure-functions-devops-build==0.0.22
azure-graphrbac==0.60.0
azure-identity==1.5.0b2
azure-identity==1.6.0b1
azure-keyvault==1.1.0
azure-keyvault-administration==4.0.0b3
azure-keyvault==1.1.0
Expand All @@ -35,7 +35,7 @@ azure-mgmt-consumption==2.0.0
azure-mgmt-containerinstance==1.5.0
azure-mgmt-containerregistry==3.0.0rc17
azure-mgmt-containerservice==11.1.0
azure-mgmt-core==1.2.1
azure-core==1.12.0b1
azure-mgmt-cosmosdb==3.0.0
azure-mgmt-databoxedge==0.2.0
azure-mgmt-datalake-analytics==0.2.1
Expand Down
4 changes: 2 additions & 2 deletions src/azure-cli/requirements.py3.Linux.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ azure-cosmos==3.2.0
azure-datalake-store==0.0.49
azure-functions-devops-build==0.0.22
azure-graphrbac==0.60.0
azure-identity==1.5.0b2
azure-identity==1.6.0b1
azure-keyvault==1.1.0
azure-keyvault-administration==4.0.0b3
azure-keyvault==1.1.0
Expand All @@ -36,7 +36,7 @@ azure-mgmt-consumption==2.0.0
azure-mgmt-containerinstance==1.5.0
azure-mgmt-containerregistry==3.0.0rc17
azure-mgmt-containerservice==11.1.0
azure-mgmt-core==1.2.1
azure-core==1.12.0b1
azure-mgmt-cosmosdb==3.0.0
azure-mgmt-databoxedge==0.2.0
azure-mgmt-datalake-analytics==0.2.1
Expand Down
4 changes: 2 additions & 2 deletions src/azure-cli/requirements.py3.windows.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ azure-cosmos==3.2.0
azure-datalake-store==0.0.49
azure-functions-devops-build==0.0.22
azure-graphrbac==0.60.0
azure-identity==1.5.0b2
azure-identity==1.6.0b1
azure-keyvault==1.1.0
azure-keyvault-administration==4.0.0b3
azure-keyvault==1.1.0
Expand All @@ -36,7 +36,7 @@ azure-mgmt-consumption==2.0.0
azure-mgmt-containerinstance==1.5.0
azure-mgmt-containerregistry==3.0.0rc17
azure-mgmt-containerservice==11.1.0
azure-mgmt-core==1.2.1
azure-core==1.12.0b1
azure-mgmt-cosmosdb==3.0.0
azure-mgmt-databoxedge==0.2.0
azure-mgmt-datalake-analytics==0.2.1
Expand Down