Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package azure-cli-core for openSUSE:Factory checked in at 2024-10-10 22:14:58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/azure-cli-core (Old) and /work/SRC/openSUSE:Factory/.azure-cli-core.new.19354 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "azure-cli-core" Thu Oct 10 22:14:58 2024 rev:73 rq:1206705 version:2.65.0 Changes: -------- --- /work/SRC/openSUSE:Factory/azure-cli-core/azure-cli-core.changes 2024-09-06 17:19:19.905852801 +0200 +++ /work/SRC/openSUSE:Factory/.azure-cli-core.new.19354/azure-cli-core.changes 2024-10-10 22:15:02.927187278 +0200 @@ -1,0 +2,9 @@ +Tue Oct 8 09:43:03 UTC 2024 - John Paul Adrian Glaubitz <adrian.glaub...@suse.com> + +- New upstream release + + Version 2.65.0 + + For detailed information about changes see the + HISTORY.rst file provided with this package +- Update Requires from setup.py + +------------------------------------------------------------------- Old: ---- azure_cli_core-2.64.0.tar.gz New: ---- azure_cli_core-2.65.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ azure-cli-core.spec ++++++ --- /var/tmp/diff_new_pack.bCr4lW/_old 2024-10-10 22:15:03.667218076 +0200 +++ /var/tmp/diff_new_pack.bCr4lW/_new 2024-10-10 22:15:03.667218076 +0200 @@ -24,7 +24,7 @@ %global _sitelibdir %{%{pythons}_sitelib} Name: azure-cli-core -Version: 2.64.0 +Version: 2.65.0 Release: 0 Summary: Microsoft Azure CLI Core Module License: MIT @@ -50,8 +50,9 @@ Requires: %{pythons}-jmespath Requires: %{pythons}-knack < 1.0.0 Requires: %{pythons}-knack >= 0.11.0 +Requires: %{pythons}-microsoft-security-utilities-secret-masker >= 1.0.0~b2 Requires: %{pythons}-msal < 2.0.0 -Requires: %{pythons}-msal >= 1.30.0 +Requires: %{pythons}-msal >= 1.31.0 Requires: %{pythons}-msal-extensions < 2.0.0 Requires: %{pythons}-msal-extensions >= 1.2.0 Requires: %{pythons}-msrestazure < 0.7.0 ++++++ azure_cli_core-2.64.0.tar.gz -> azure_cli_core-2.65.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/HISTORY.rst new/azure_cli_core-2.65.0/HISTORY.rst --- old/azure_cli_core-2.64.0/HISTORY.rst 2024-08-28 07:44:58.000000000 +0200 +++ new/azure_cli_core-2.65.0/HISTORY.rst 2024-09-25 13:32:48.000000000 +0200 @@ -3,6 +3,10 @@ Release History =============== +2.65.0 +++++++ +* Minor fixes + 2.64.0 ++++++ * Minor fixes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/PKG-INFO new/azure_cli_core-2.65.0/PKG-INFO --- old/azure_cli_core-2.64.0/PKG-INFO 2024-08-28 07:45:13.234815400 +0200 +++ new/azure_cli_core-2.65.0/PKG-INFO 2024-09-25 13:33:13.158123700 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: azure-cli-core -Version: 2.64.0 +Version: 2.65.0 Summary: Microsoft Azure Command-Line Tools Core Module Home-page: https://github.com/Azure/azure-cli Author: Microsoft Corporation @@ -28,7 +28,7 @@ Requires-Dist: jmespath Requires-Dist: knack~=0.11.0 Requires-Dist: msal-extensions==1.2.0 -Requires-Dist: msal[broker]==1.30.0 +Requires-Dist: msal[broker]==1.31.0 Requires-Dist: msrestazure~=0.6.4 Requires-Dist: packaging>=20.9 Requires-Dist: paramiko<4.0.0,>=2.0.8 @@ -37,6 +37,7 @@ Requires-Dist: PyJWT>=2.1.0 Requires-Dist: pyopenssl>=17.1.0 Requires-Dist: requests[socks] +Requires-Dist: microsoft-security-utilities-secret-masker~=1.0.0b2 Microsoft Azure CLI Core Module ================================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/azure/cli/core/__init__.py new/azure_cli_core-2.65.0/azure/cli/core/__init__.py --- old/azure_cli_core-2.64.0/azure/cli/core/__init__.py 2024-08-28 07:44:58.000000000 +0200 +++ new/azure_cli_core-2.65.0/azure/cli/core/__init__.py 2024-09-25 13:32:48.000000000 +0200 @@ -4,7 +4,7 @@ # -------------------------------------------------------------------------------------------- # pylint: disable=line-too-long -__version__ = "2.64.0" +__version__ = "2.65.0" import os import sys diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/azure/cli/core/_profile.py new/azure_cli_core-2.65.0/azure/cli/core/_profile.py --- old/azure_cli_core-2.64.0/azure/cli/core/_profile.py 2024-08-28 07:44:58.000000000 +0200 +++ new/azure_cli_core-2.65.0/azure/cli/core/_profile.py 2024-09-25 13:32:48.000000000 +0200 @@ -11,7 +11,7 @@ from azure.cli.core._session import ACCOUNT from azure.cli.core.azclierror import AuthenticationError from azure.cli.core.cloud import get_active_cloud, set_cloud_subscription -from azure.cli.core.util import in_cloud_console, can_launch_browser +from azure.cli.core.util import in_cloud_console, can_launch_browser, is_github_codespaces from knack.log import get_logger from knack.util import CLIError @@ -166,6 +166,10 @@ logger.info('No web browser is available. Fall back to device code.') use_device_code = True + if not use_device_code and is_github_codespaces(): + logger.info('GitHub Codespaces is detected. Fall back to device code.') + use_device_code = True + if use_device_code: user_identity = identity.login_with_device_code(scopes=scopes, **kwargs) else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/azure/cli/core/aaz/_arg.py new/azure_cli_core-2.65.0/azure/cli/core/aaz/_arg.py --- old/azure_cli_core-2.64.0/azure/cli/core/aaz/_arg.py 2024-08-28 07:44:58.000000000 +0200 +++ new/azure_cli_core-2.65.0/azure/cli/core/aaz/_arg.py 2024-09-25 13:32:48.000000000 +0200 @@ -6,6 +6,7 @@ import copy from azure.cli.core import azclierror +from azure.cli.core.commands.arm import add_usage, remove_usage, set_usage from knack.arguments import CLICommandArgument, CaseInsensitiveList from knack.preview import PreviewItem from knack.experimental import ExperimentalItem @@ -626,12 +627,11 @@ class AAZGenericUpdateSetArg(AAZGenericUpdateArg): - _example = '--set property1.property2=<value>' def __init__( self, options=('--set',), arg_group='Generic Update', help='Update an object by specifying a property path and value to set.' - ' Example: {}'.format(_example), + ' Example: {}'.format(set_usage), **kwargs): super().__init__( options=options, @@ -653,12 +653,11 @@ class AAZGenericUpdateAddArg(AAZGenericUpdateArg): - _example = '--add property.listProperty <key=value, string or JSON string>' def __init__( self, options=('--add',), arg_group='Generic Update', help='Add an object to a list of objects by specifying a path and key value pairs.' - ' Example: {}'.format(_example), + ' Example: {}'.format(add_usage), **kwargs): super().__init__( options=options, @@ -680,12 +679,11 @@ class AAZGenericUpdateRemoveArg(AAZGenericUpdateArg): - _example = '--remove property.list <indexToRemove> OR --remove propertyToRemove' def __init__( self, options=('--remove', ), arg_group='Generic Update', help='Remove a property or an element from a list.' - ' Example: {}'.format(_example), + ' Example: {}'.format(remove_usage), **kwargs): super().__init__( options=options, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/azure/cli/core/auth/identity.py new/azure_cli_core-2.65.0/azure/cli/core/auth/identity.py --- old/azure_cli_core-2.64.0/azure/cli/core/auth/identity.py 2024-08-28 07:44:58.000000000 +0200 +++ new/azure_cli_core-2.65.0/azure/cli/core/auth/identity.py 2024-09-25 13:32:48.000000000 +0200 @@ -13,15 +13,20 @@ from knack.util import CLIError from msal import PublicClientApplication, ConfidentialClientApplication -# Service principal entry properties -from .msal_authentication import _CLIENT_ID, _TENANT, _CLIENT_SECRET, _CERTIFICATE, _CLIENT_ASSERTION, \ - _USE_CERT_SN_ISSUER -from .msal_authentication import UserCredential, ServicePrincipalCredential +from .msal_credentials import UserCredential, ServicePrincipalCredential from .persistence import load_persisted_token_cache, file_extensions, load_secret_store from .util import check_result AZURE_CLI_CLIENT_ID = '04b07795-8ddb-461a-bbee-02f9e1bf7b46' +# Service principal entry properties. Names are taken from OAuth 2.0 client credentials flow parameters: +# https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow +_TENANT = 'tenant' +_CLIENT_ID = 'client_id' +_CLIENT_SECRET = 'client_secret' +_CERTIFICATE = 'certificate' +_USE_CERT_SN_ISSUER = 'use_cert_sn_issuer' +_CLIENT_ASSERTION = 'client_assertion' # For environment credential AZURE_AUTHORITY_HOST = "AZURE_AUTHORITY_HOST" @@ -187,10 +192,9 @@ `credential` is a dict returned by ServicePrincipalAuth.build_credential """ sp_auth = ServicePrincipalAuth.build_from_credential(self.tenant_id, client_id, credential) - - # This cred means SDK credential object - cred = ServicePrincipalCredential(sp_auth, **self._msal_app_kwargs) - result = cred.acquire_token_for_client(scopes) + client_credential = sp_auth.get_msal_client_credential() + cca = ConfidentialClientApplication(client_id, client_credential, **self._msal_app_kwargs) + result = cca.acquire_token_for_client(scopes) check_result(result) # Only persist the service principal after a successful login @@ -246,32 +250,47 @@ def get_service_principal_credential(self, client_id): entry = self._service_principal_store.load_entry(client_id, self.tenant_id) - sp_auth = ServicePrincipalAuth(entry) - return ServicePrincipalCredential(sp_auth, **self._msal_app_kwargs) + client_credential = ServicePrincipalAuth(entry).get_msal_client_credential() + return ServicePrincipalCredential(client_id, client_credential, **self._msal_app_kwargs) def get_managed_identity_credential(self, client_id=None): raise NotImplementedError -class ServicePrincipalAuth: - +class ServicePrincipalAuth: # pylint: disable=too-many-instance-attributes def __init__(self, entry): + # Initialize all attributes first, so that we don't need to call getattr to check their existence + self.client_id = None + self.tenant = None + # secret + self.client_secret = None + # certificate + self.certificate = None + self.use_cert_sn_issuer = None + # federated identity credential + self.client_assertion = None + + # Internal attributes for certificate + # They are computed at runtime and not persisted in the service principal entry. + self._certificate_string = None + self._thumbprint = None + self._public_certificate = None + self.__dict__.update(entry) - if _CERTIFICATE in entry: + if self.certificate: from OpenSSL.crypto import load_certificate, FILETYPE_PEM, Error - self.public_certificate = None try: with open(self.certificate, 'r') as file_reader: - self.certificate_string = file_reader.read() - cert = load_certificate(FILETYPE_PEM, self.certificate_string) - self.thumbprint = cert.digest("sha1").decode().replace(':', '') + self._certificate_string = file_reader.read() + cert = load_certificate(FILETYPE_PEM, self._certificate_string) + self._thumbprint = cert.digest("sha1").decode().replace(':', '') if entry.get(_USE_CERT_SN_ISSUER): # low-tech but safe parsing based on # https://github.com/libressl-portable/openbsd/blob/master/src/lib/libcrypto/pem/pem.h match = re.search(r'-----BEGIN CERTIFICATE-----(?P<cert_value>[^-]+)-----END CERTIFICATE-----', - self.certificate_string, re.I) - self.public_certificate = match.group() + self._certificate_string, re.I) + self._public_certificate = match.group() except (UnicodeDecodeError, Error) as ex: raise CLIError('Invalid certificate, please use a valid PEM file. Error detail: {}'.format(ex)) @@ -307,8 +326,42 @@ return entry def get_entry_to_persist(self): + """Get a service principal entry that can be persisted by ServicePrincipalStore.""" persisted_keys = [_CLIENT_ID, _TENANT, _CLIENT_SECRET, _CERTIFICATE, _USE_CERT_SN_ISSUER, _CLIENT_ASSERTION] - return {k: v for k, v in self.__dict__.items() if k in persisted_keys} + # Only persist certain attributes whose values are not None + return {k: v for k, v in self.__dict__.items() if k in persisted_keys and v} + + def get_msal_client_credential(self): + """Get a client_credential that can be consumed by msal.ConfidentialClientApplication.""" + client_credential = None + + # client_secret + # "your client secret" + if self.client_secret: + client_credential = self.client_secret + + # certificate + # { + # "private_key": "...-----BEGIN PRIVATE KEY-----... in PEM format", + # "thumbprint": "A1B2C3D4E5F6...", + # "public_certificate": "...-----BEGIN CERTIFICATE-----...", + # } + if self.certificate: + client_credential = { + "private_key": self._certificate_string, + "thumbprint": self._thumbprint + } + if self._public_certificate: + client_credential['public_certificate'] = self._public_certificate + + # client_assertion + # { + # "client_assertion": "...a JWT with claims aud, exp, iss, jti, nbf, and sub..." + # } + if self.client_assertion: + client_credential = {'client_assertion': self.client_assertion} + + return client_credential class ServicePrincipalStore: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/azure/cli/core/auth/msal_authentication.py new/azure_cli_core-2.65.0/azure/cli/core/auth/msal_authentication.py --- old/azure_cli_core-2.64.0/azure/cli/core/auth/msal_authentication.py 2024-08-28 07:44:58.000000000 +0200 +++ new/azure_cli_core-2.65.0/azure/cli/core/auth/msal_authentication.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,140 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -""" -Credentials defined in this module are alternative implementations of credentials provided by Azure Identity. - -These credentials implement azure.core.credentials.TokenCredential by exposing `get_token` method for Track 2 -SDK invocation. - -If you want to implement your own credential, the credential must also expose `get_token` method. - -`get_token` method takes `scopes` as positional arguments and other optional `kwargs`, such as `claims`, `data`. -The return value should be a named tuple containing two elements: token (str), expires_on (int). You may simply use -azure.cli.core.auth.util.AccessToken to build the return value. See below credentials as examples. -""" - -from knack.log import get_logger -from knack.util import CLIError -from msal import PublicClientApplication, ConfidentialClientApplication - -from .util import check_result, build_sdk_access_token - -# OAuth 2.0 client credentials flow parameter -# https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow -_TENANT = 'tenant' -_CLIENT_ID = 'client_id' -_CLIENT_SECRET = 'client_secret' -_CERTIFICATE = 'certificate' -_CLIENT_ASSERTION = 'client_assertion' -_USE_CERT_SN_ISSUER = 'use_cert_sn_issuer' - -logger = get_logger(__name__) - - -class UserCredential(PublicClientApplication): - - def __init__(self, client_id, username, **kwargs): - """User credential implementing get_token interface. - - :param client_id: Client ID of the CLI. - :param username: The username for user credential. - """ - super().__init__(client_id, **kwargs) - - # Make sure username is specified, otherwise MSAL returns all accounts - assert username, "username must be specified, got {!r}".format(username) - - accounts = self.get_accounts(username) - - # Usernames are usually unique. We are collecting corner cases to better understand its behavior. - if len(accounts) > 1: - raise CLIError(f"Found multiple accounts with the same username '{username}': {accounts}\n" - "Please report to us via Github: https://github.com/Azure/azure-cli/issues/20168") - - if not accounts: - raise CLIError("User '{}' does not exist in MSAL token cache. Run `az login`.".format(username)) - - self._account = accounts[0] - - def get_token(self, *scopes, claims=None, **kwargs): - # scopes = ['https://pas.windows.net/CheckMyAccess/Linux/.default'] - logger.debug("UserCredential.get_token: scopes=%r, claims=%r, kwargs=%r", scopes, claims, kwargs) - - if claims: - logger.warning('Acquiring new access token silently for tenant %s with claims challenge: %s', - self.authority.tenant, claims) - result = self.acquire_token_silent_with_error(list(scopes), self._account, claims_challenge=claims, **kwargs) - - from azure.cli.core.azclierror import AuthenticationError - try: - # Check if an access token is returned. - check_result(result, scopes=scopes, claims=claims) - except AuthenticationError as ex: - # For VM SSH ('data' is passed), if getting access token fails because - # Conditional Access MFA step-up or compliance check is required, re-launch - # web browser and do auth code flow again. - # We assume the `az ssh` command is run on a system with GUI where a web - # browser is available. - if 'data' in kwargs: - logger.warning(ex) - logger.warning("\nThe default web browser has been opened at %s for scope '%s'. " - "Please continue the login in the web browser.", - self.authority.authorization_endpoint, ' '.join(scopes)) - - from .util import read_response_templates - success_template, error_template = read_response_templates() - - result = self.acquire_token_interactive( - list(scopes), login_hint=self._account['username'], port=8400 if self.authority.is_adfs else None, - success_template=success_template, error_template=error_template, **kwargs) - check_result(result) - - # For other scenarios like Storage Conditional Access MFA step-up, do not - # launch browser, but show the error message and `az login` command instead. - else: - raise - return build_sdk_access_token(result) - - -class ServicePrincipalCredential(ConfidentialClientApplication): - - def __init__(self, service_principal_auth, **kwargs): - """Service principal credential implementing get_token interface. - - :param service_principal_auth: An instance of ServicePrincipalAuth. - """ - client_credential = None - - # client_secret - client_secret = getattr(service_principal_auth, _CLIENT_SECRET, None) - if client_secret: - client_credential = client_secret - - # certificate - certificate = getattr(service_principal_auth, _CERTIFICATE, None) - if certificate: - client_credential = { - "private_key": getattr(service_principal_auth, 'certificate_string'), - "thumbprint": getattr(service_principal_auth, 'thumbprint') - } - public_certificate = getattr(service_principal_auth, 'public_certificate', None) - if public_certificate: - client_credential['public_certificate'] = public_certificate - - # client_assertion - client_assertion = getattr(service_principal_auth, _CLIENT_ASSERTION, None) - if client_assertion: - client_credential = {'client_assertion': client_assertion} - - super().__init__(service_principal_auth.client_id, client_credential=client_credential, **kwargs) - - def get_token(self, *scopes, **kwargs): - logger.debug("ServicePrincipalCredential.get_token: scopes=%r, kwargs=%r", scopes, kwargs) - - scopes = list(scopes) - result = self.acquire_token_for_client(scopes, **kwargs) - check_result(result) - return build_sdk_access_token(result) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/azure/cli/core/auth/msal_credentials.py new/azure_cli_core-2.65.0/azure/cli/core/auth/msal_credentials.py --- old/azure_cli_core-2.64.0/azure/cli/core/auth/msal_credentials.py 1970-01-01 01:00:00.000000000 +0100 +++ new/azure_cli_core-2.65.0/azure/cli/core/auth/msal_credentials.py 2024-09-25 13:32:48.000000000 +0200 @@ -0,0 +1,110 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Credentials defined in this module are alternative implementations of credentials provided by Azure Identity. + +These credentials implement azure.core.credentials.TokenCredential by exposing `get_token` method for Track 2 +SDK invocation. + +If you want to implement your own credential, the credential must also expose `get_token` method. + +`get_token` method takes `scopes` as positional arguments and other optional `kwargs`, such as `claims`, `data`. +The return value should be a named tuple containing two elements: token (str), expires_on (int). You may simply use +azure.cli.core.auth.util.AccessToken to build the return value. See below credentials as examples. +""" + +from knack.log import get_logger +from knack.util import CLIError +from msal import PublicClientApplication, ConfidentialClientApplication + +from .util import check_result, build_sdk_access_token + +logger = get_logger(__name__) + + +class UserCredential: # pylint: disable=too-few-public-methods + + def __init__(self, client_id, username, **kwargs): + """User credential implementing get_token interface. + + :param client_id: Client ID of the CLI. + :param username: The username for user credential. + """ + self._msal_app = PublicClientApplication(client_id, **kwargs) + + # Make sure username is specified, otherwise MSAL returns all accounts + assert username, "username must be specified, got {!r}".format(username) + + accounts = self._msal_app.get_accounts(username) + + # Usernames are usually unique. We are collecting corner cases to better understand its behavior. + if len(accounts) > 1: + raise CLIError(f"Found multiple accounts with the same username '{username}': {accounts}\n" + "Please report to us via Github: https://github.com/Azure/azure-cli/issues/20168") + + if not accounts: + raise CLIError("User '{}' does not exist in MSAL token cache. Run `az login`.".format(username)) + + self._account = accounts[0] + + def get_token(self, *scopes, claims=None, **kwargs): + # scopes = ['https://pas.windows.net/CheckMyAccess/Linux/.default'] + logger.debug("UserCredential.get_token: scopes=%r, claims=%r, kwargs=%r", scopes, claims, kwargs) + + if claims: + logger.warning('Acquiring new access token silently for tenant %s with claims challenge: %s', + self._msal_app.authority.tenant, claims) + result = self._msal_app.acquire_token_silent_with_error(list(scopes), self._account, claims_challenge=claims, + **kwargs) + + from azure.cli.core.azclierror import AuthenticationError + try: + # Check if an access token is returned. + check_result(result, scopes=scopes, claims=claims) + except AuthenticationError as ex: + # For VM SSH ('data' is passed), if getting access token fails because + # Conditional Access MFA step-up or compliance check is required, re-launch + # web browser and do auth code flow again. + # We assume the `az ssh` command is run on a system with GUI where a web + # browser is available. + if 'data' in kwargs: + logger.warning(ex) + logger.warning("\nThe default web browser has been opened at %s for scope '%s'. " + "Please continue the login in the web browser.", + self._msal_app.authority.authorization_endpoint, ' '.join(scopes)) + + from .util import read_response_templates + success_template, error_template = read_response_templates() + + result = self._msal_app.acquire_token_interactive( + list(scopes), login_hint=self._account['username'], + port=8400 if self._msal_app.authority.is_adfs else None, + success_template=success_template, error_template=error_template, **kwargs) + check_result(result) + + # For other scenarios like Storage Conditional Access MFA step-up, do not + # launch browser, but show the error message and `az login` command instead. + else: + raise + return build_sdk_access_token(result) + + +class ServicePrincipalCredential: # pylint: disable=too-few-public-methods + + def __init__(self, client_id, client_credential, **kwargs): + """Service principal credential implementing get_token interface. + + :param client_id: The service principal's client ID. + :param client_credential: client_credential that will be passed to MSAL. + """ + self._msal_app = ConfidentialClientApplication(client_id, client_credential, **kwargs) + + def get_token(self, *scopes, **kwargs): + logger.debug("ServicePrincipalCredential.get_token: scopes=%r, kwargs=%r", scopes, kwargs) + + result = self._msal_app.acquire_token_for_client(list(scopes), **kwargs) + check_result(result) + return build_sdk_access_token(result) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/azure/cli/core/breaking_change.py new/azure_cli_core-2.65.0/azure/cli/core/breaking_change.py --- old/azure_cli_core-2.64.0/azure/cli/core/breaking_change.py 2024-08-28 07:44:58.000000000 +0200 +++ new/azure_cli_core-2.65.0/azure/cli/core/breaking_change.py 2024-09-25 13:32:48.000000000 +0200 @@ -250,8 +250,10 @@ elif self.is_command_group(cli_ctx): command_group = cli_ctx.invocation.commands_loader.command_group_table[self.command_name] if not command_group: - self._register_to_direct_sub_cg_or_command(cli_ctx, self.command_name, self.to_tag(cli_ctx)) - else: + loader = self.find_suitable_command_loader(cli_ctx) + command_group = loader.command_group(self.command_name) + cli_ctx.invocation.commands_loader.command_group_table[self.command_name] = command_group + if command_group: command_group.group_kwargs['deprecate_info'] = \ self.appended_status_tag(cli_ctx, command_group.group_kwargs.get('deprecate_info'), self.to_tag(cli_ctx)) @@ -270,22 +272,11 @@ return old_status_tag return new_status_tag - def _register_to_direct_sub_cg_or_command(self, cli_ctx, cg_name, status_tag): - for key, command_group in cli_ctx.invocation.commands_loader.command_group_table.items(): - split_key = key.rsplit(maxsplit=1) - # If inpass command group name is empty, all first level command group should be registered to. - # Otherwise, we need to find all direct sub command groups. - if (not cg_name and len(split_key) == 1) or (len(split_key) == 2 and split_key[0] == cg_name): - from azure.cli.core.commands import AzCommandGroup - if isinstance(command_group, AzCommandGroup): - command_group.group_kwargs['deprecate_info'] = \ - self.appended_status_tag(cli_ctx, command_group.group_kwargs.get('deprecate_info'), status_tag) - else: - self._register_to_direct_sub_cg_or_command(cli_ctx, key, status_tag) - for key, command in cli_ctx.invocation.commands_loader.command_table.items(): - # If inpass command group name is empty, all first level command should be registered to. - if (not cg_name and ' ' not in key) or key.rsplit(maxsplit=1)[0] == cg_name: - command.deprecate_info = self.appended_status_tag(cli_ctx, command.deprecate_info, self.to_tag(cli_ctx)) + def find_suitable_command_loader(self, cli_ctx): + for name, loader in cli_ctx.invocation.commands_loader.cmd_to_loader_map.items(): + if name.startswith(self.command_name) and loader: + return loader[0] + return None def _register_option_deprecate(self, cli_ctx, arguments, option_name): for _, argument in arguments.items(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/azure/cli/core/commands/__init__.py new/azure_cli_core-2.65.0/azure/cli/core/commands/__init__.py --- old/azure_cli_core-2.64.0/azure/cli/core/commands/__init__.py 2024-08-28 07:44:58.000000000 +0200 +++ new/azure_cli_core-2.65.0/azure/cli/core/commands/__init__.py 2024-09-25 13:32:48.000000000 +0200 @@ -860,7 +860,7 @@ from ..credential_helper import distinguish_credential from ..telemetry import set_secrets_detected try: - containing_credential, secret_property_names = distinguish_credential(result) + containing_credential, secret_property_names, secret_names = distinguish_credential(result) if not containing_credential: set_secrets_detected(False) return @@ -869,10 +869,10 @@ if secret_property_names: message = sensitive_data_detailed_warning_message.format(', '.join(secret_property_names)) logger.warning(message) - set_secrets_detected(True, secret_property_names) - except Exception: # pylint: disable=broad-except + set_secrets_detected(True, secret_property_names, secret_names) + except Exception as ex: # pylint: disable=broad-except # ignore all exceptions, as this is just a warning - pass + logger.debug('Scan credentials failed with %s', str(ex)) def resolve_confirmation(self, cmd, parsed_args): confirm = cmd.confirmation and not parsed_args.__dict__.pop('yes', None) \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/azure/cli/core/credential_helper.py new/azure_cli_core-2.65.0/azure/cli/core/credential_helper.py --- old/azure_cli_core-2.64.0/azure/cli/core/credential_helper.py 2024-08-28 07:44:58.000000000 +0200 +++ new/azure_cli_core-2.65.0/azure/cli/core/credential_helper.py 2024-09-25 13:32:48.000000000 +0200 @@ -5,56 +5,22 @@ # pylint: disable=line-too-long -import re import json -from enum import Enum +from azure.cli.core import decorators sensitive_data_warning_message = '[Warning] This output may compromise security by showing secrets. Learn more at: https://go.microsoft.com/fwlink/?linkid=2258669' sensitive_data_detailed_warning_message = '[Warning] This output may compromise security by showing the following secrets: {}. Learn more at: https://go.microsoft.com/fwlink/?linkid=2258669' -class CredentialType(Enum): - # ([?&;]) - match character '?' or '&' or ';' as group 1, which is the prefix of signature within sas token - # sig= - match the literal string 'sig=' - # [\w%-/]+ - match any word character, '-', '%', or '/' one or more times. This is the signature which needs to be redacted - SAS_TOKEN = (r'([?&;])sig=[\w%-/]+', r'\1sig=_REDACTED_SAS_TOKEN_SIG_', 1, 'SAS token') - # key= - match the literal string 'key=', could be accountkey, primarykey, secondarykey, etc. - # [\w%+/=-]+ - match any word character, '%', '+', '/', '=', or '-' one or more times. - KEY = (r'key=[\w%+/=-]+', r'key=_REDACTED_KEY_', 1, 'Several types of keys/secrets are passed with a query parameter "key"') - # (?:eyJ0eXAi|eyJhbGci) - match the literal string 'eyJ0eXAi' or 'eyJhbGci' as group 1, which is the prefix of JWT token - # [\w\-.~+/%]* - match any word character, '-', '.', '~', '+', '/', '%', or '*' zero or more times. - JWT_TOKEN = (r'(?:eyJ0eXAi|eyJhbGci)[\w\-.~+/%]*', '_REDACTED_JWT_TOKEN_', 0, 'JWT token') - # (bearer |bearer%20) - match the literal string 'bearer ' or 'bearer%20' - # [\w\-.~+/]{100,} - match any word character, '-', '.', '~', '+', or '/' one hundred or more times. - BEARER_TOKEN = (r'(bearer |bearer%20)[\w\-.~+/]{100,}', r'\1_REDACTED_BEARER_TOKEN_', 0, 'Bearer token') - # (ssh-rsa ) - match the literal string 'ssh-rsa ' as group 1, which is the prefix of ssh key - # AAAA[\w\-.~+/]{100,} - match 'AAAA' followed by any word character, '-', '.', '~', '+', or '/' one hundred or more times. - SSH_KEY = (r'(ssh-rsa )AAAA[\w\-.~+/]{100,}', r'\1_REDACTED_SSH_KEY_', 1, 'SSH key') - # [\w.%#+-] - match any word character, '.', '%', '#', '+', or '-' one or more times. - # (%40|@) - match character '@' or '%40' as group 1 - # ([a-z0-9.-]*\.[a-z]{2,}) - match any word character, '.', or '-' zero or more times, followed by a '.' and two or more word characters. - EMAIL_ADDRESS = (r'[\w.%#+-]+(%40|@)([a-z0-9.-]*\.[a-z]{2,})', r'_REDACTED_EMAIL_\1\2', 99, 'Email address') - # [0-9a-f]{8} - match any character in the range '0' to '9' or 'a' to 'f' exactly eight times. - # -? - match character '-' zero or one time. - GUID = (r'([0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12})', '_REDACTED_GUID_', 999, 'GUID') - # below regexes are shared by ADO cred scan, see definition: - # https://github.com/microsoft/azure-pipelines-agent/blob/master/src/Microsoft.VisualStudio.Services.Agent/AdditionalMaskingRegexes.CredScan.cs - AAD_CLIENT_APP = (r'[0-9A-Za-z-_~.]{3}7Q~[0-9A-Za-z-_~.]{31}\b|\b[0-9A-Za-z-_~.]{3}8Q~[0-9A-Za-z-_~.]{34}', '_REDACTED_AAD_CLIENT_APP_', 99, 'AAD client app') - SYMMETRIC_KEY_512 = (r'[0-9A-Za-z+/]{76}(APIM|ACDb|\+(ABa|AMC|ASt))[0-9A-Za-z+/]{5}[AQgw]==', '_REDACTED_SYMMETRIC_KEY_', 1, '512-bit symmetric key') - SYMMETRIC_KEY_256 = (r'[0-9A-Za-z+/]{33}(AIoT|\+(ASb|AEh|ARm))[A-P][0-9A-Za-z+/]{5}=', '_REDACTED_SYMMETRIC_KEY_', 1, '256-bit symmetric key') - AZURE_FUNCTION_KEY = (r'[0-9A-Za-z_\-]{44}AzFu[0-9A-Za-z\-_]{5}[AQgw]==', '_REDACTED_AZURE_FUNCTION_KEY_', 1, 'Azure function key') - AZURE_SEARCH_KEY = (r'[0-9A-Za-z]{42}AzSe[A-D][0-9A-Za-z]{5}', '_REDACTED_AZURE_SEARCH_KEY_', 1, 'Azure search key') - AZURE_CONTAINER_REGISTRY_KEY = (r'[0-9A-Za-z+/]{42}\+ACR[A-D][0-9A-Za-z+/]{5}', '_REDACTED_AZURE_CONTAINER_REGISTRY_KEY_', 1, 'Azure container registry key') - AZURE_CACHE_FOR_REDIS_KEY = (r'[0-9A-Za-z]{33}AzCa[A-P][0-9A-Za-z]{5}=', '_REDACTED_AZURE_CACHE_FOR_REDIS_KEY_', 1, 'Azure cache for redis key') - - def __init__(self, regex, replacement, level=0, description=''): - self.regex = regex - self.replacement = replacement - self.level = level - self.description = description +@decorators.call_once +def get_secret_masker(): + # global secret_masker_instance + from microsoft_security_utilities_secret_masker import SecretMasker, load_regex_patterns_from_json_file + regex_patterns = load_regex_patterns_from_json_file('HighConfidenceSecurityModels.json') + return SecretMasker(regex_patterns) -def is_containing_credential(content, is_file=False, max_level=9): +def is_containing_credential(content, is_file=False): """Check if the given content contains credential or not. :param content: The content or the file path. @@ -75,11 +41,10 @@ content = str(content) except ValueError: raise ValueError('The content is not string or json object.') - return any(re.search(cred_type.regex, content, flags=re.IGNORECASE | re.MULTILINE) and cred_type.level <= max_level - for cred_type in CredentialType) + return get_secret_masker().detect_secrets(content) -def distinguish_credential(content, is_file=False, max_level=9): +def distinguish_credential(content, is_file=False): """Distinguish which property contains credential from the given content. :param content: The content(can be string or json object) or the file path. @@ -92,29 +57,35 @@ """ containing_credential = False secret_property_names = set() + secret_names = set() if is_file: with open(content, 'r') as f: content = json.load(f) if isinstance(content, list): for item in content: - _containing_credential, _secret_property_names = distinguish_credential(item, max_level=max_level) + _containing_credential, _secret_property_names, _secret_names = distinguish_credential(item) containing_credential = containing_credential or _containing_credential secret_property_names.update(_secret_property_names) - return containing_credential, secret_property_names + secret_names.update(_secret_names) + return containing_credential, secret_property_names, secret_names if isinstance(content, dict): for key, value in content.items(): - _containing_credential, _secret_property_names = distinguish_credential(value, max_level=max_level) + _containing_credential, _secret_property_names, _secret_names = distinguish_credential(value) containing_credential = containing_credential or _containing_credential secret_property_names.update(_secret_property_names) if _containing_credential: secret_property_names.add(key) - return containing_credential, secret_property_names + secret_names.update(_secret_names) + return containing_credential, secret_property_names, secret_names - if is_containing_credential(content, max_level=max_level): + detections = is_containing_credential(content) + if detections: containing_credential = True - return containing_credential, secret_property_names + for detection in detections: + secret_names.add(detection.name) + return containing_credential, secret_property_names, secret_names def redact_credential(content, is_file=False): @@ -146,7 +117,5 @@ raise ValueError('The content is not string or json object.') -def redact_credential_for_string(string): - for cred_type in CredentialType: - string = re.sub(cred_type.regex, cred_type.replacement, string, flags=re.IGNORECASE | re.MULTILINE) - return string +def redact_credential_for_string(content): + return get_secret_masker().mask_secrets(content) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/azure/cli/core/profiles/_shared.py new/azure_cli_core-2.65.0/azure/cli/core/profiles/_shared.py --- old/azure_cli_core-2.64.0/azure/cli/core/profiles/_shared.py 2024-08-28 07:44:58.000000000 +0200 +++ new/azure_cli_core-2.65.0/azure/cli/core/profiles/_shared.py 2024-09-25 13:32:48.000000000 +0200 @@ -104,7 +104,6 @@ MGMT_DATALAKE_STORE = ('azure.cli.command_modules.dls.vendored_sdks.azure_mgmt_datalake_store', None) MGMT_DATAMIGRATION = ('azure.mgmt.datamigration', None) MGMT_EVENTGRID = ('azure.mgmt.eventgrid', None) - MGMT_DEVTESTLABS = ('azure.mgmt.devtestlabs', None) MGMT_MAPS = ('azure.mgmt.maps', None) MGMT_POLICYINSIGHTS = ('azure.mgmt.policyinsights', None) MGMT_RDBMS = ('azure.mgmt.rdbms', None) @@ -261,7 +260,7 @@ ResourceType.MGMT_ARO: '2023-11-22', ResourceType.MGMT_DATABOXEDGE: '2021-02-01-preview', ResourceType.MGMT_CUSTOMLOCATION: '2021-03-15-preview', - ResourceType.MGMT_CONTAINERSERVICE: SDKProfile('2024-05-01'), + ResourceType.MGMT_CONTAINERSERVICE: SDKProfile('2024-07-01'), ResourceType.MGMT_APPCONTAINERS: '2022-10-01', }, '2020-09-01-hybrid': { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/azure/cli/core/telemetry.py new/azure_cli_core-2.65.0/azure/cli/core/telemetry.py --- old/azure_cli_core-2.64.0/azure/cli/core/telemetry.py 2024-08-28 07:44:58.000000000 +0200 +++ new/azure_cli_core-2.65.0/azure/cli/core/telemetry.py 2024-09-25 13:32:48.000000000 +0200 @@ -72,6 +72,7 @@ self.poll_end_time = None self.secrets_detected = None self.secret_keys = None + self.secret_names = None self.user_agent = None # authentication-related self.enable_broker_on_windows = None @@ -227,6 +228,7 @@ set_custom_properties(result, 'SecretsWarning', _get_secrets_warning_config()) set_custom_properties(result, 'SecretsDetected', str(self.secrets_detected)) set_custom_properties(result, 'SecretKeys', ','.join(self.secret_keys or [])) + set_custom_properties(result, 'SecretNames', ','.join(self.secret_names or [])) # authentication-related set_custom_properties(result, 'EnableBrokerOnWindows', str(self.enable_broker_on_windows)) set_custom_properties(result, 'MsalTelemetry', self.msal_telemetry) @@ -491,10 +493,12 @@ @decorators.suppress_all_exceptions() -def set_secrets_detected(secrets_detected, secret_keys=None): +def set_secrets_detected(secrets_detected, secret_keys=None, secret_names=None): _session.secrets_detected = secrets_detected if secret_keys: _session.secret_keys = secret_keys + if secret_names: + _session.secret_names = secret_names @decorators.suppress_all_exceptions() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/azure/cli/core/util.py new/azure_cli_core-2.65.0/azure/cli/core/util.py --- old/azure_cli_core-2.64.0/azure/cli/core/util.py 2024-08-28 07:44:58.000000000 +0200 +++ new/azure_cli_core-2.65.0/azure/cli/core/util.py 2024-09-25 13:32:48.000000000 +0200 @@ -754,6 +754,11 @@ return platform_name == 'windows' +def is_github_codespaces(): + # https://docs.github.com/en/codespaces/developing-in-a-codespace/default-environment-variables-for-your-codespace + return os.environ.get('CODESPACES') == 'true' + + def can_launch_browser(): import webbrowser platform_name, _ = _get_platform_info() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/azure_cli_core.egg-info/PKG-INFO new/azure_cli_core-2.65.0/azure_cli_core.egg-info/PKG-INFO --- old/azure_cli_core-2.64.0/azure_cli_core.egg-info/PKG-INFO 2024-08-28 07:45:13.000000000 +0200 +++ new/azure_cli_core-2.65.0/azure_cli_core.egg-info/PKG-INFO 2024-09-25 13:33:13.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: azure-cli-core -Version: 2.64.0 +Version: 2.65.0 Summary: Microsoft Azure Command-Line Tools Core Module Home-page: https://github.com/Azure/azure-cli Author: Microsoft Corporation @@ -28,7 +28,7 @@ Requires-Dist: jmespath Requires-Dist: knack~=0.11.0 Requires-Dist: msal-extensions==1.2.0 -Requires-Dist: msal[broker]==1.30.0 +Requires-Dist: msal[broker]==1.31.0 Requires-Dist: msrestazure~=0.6.4 Requires-Dist: packaging>=20.9 Requires-Dist: paramiko<4.0.0,>=2.0.8 @@ -37,6 +37,7 @@ Requires-Dist: PyJWT>=2.1.0 Requires-Dist: pyopenssl>=17.1.0 Requires-Dist: requests[socks] +Requires-Dist: microsoft-security-utilities-secret-masker~=1.0.0b2 Microsoft Azure CLI Core Module ================================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/azure_cli_core.egg-info/SOURCES.txt new/azure_cli_core-2.65.0/azure_cli_core.egg-info/SOURCES.txt --- old/azure_cli_core-2.64.0/azure_cli_core.egg-info/SOURCES.txt 2024-08-28 07:45:13.000000000 +0200 +++ new/azure_cli_core-2.65.0/azure_cli_core.egg-info/SOURCES.txt 2024-09-25 13:33:13.000000000 +0200 @@ -61,7 +61,7 @@ azure/cli/core/auth/binary_cache.py azure/cli/core/auth/credential_adaptor.py azure/cli/core/auth/identity.py -azure/cli/core/auth/msal_authentication.py +azure/cli/core/auth/msal_credentials.py azure/cli/core/auth/persistence.py azure/cli/core/auth/util.py azure/cli/core/auth/landing_pages/error.html diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/azure_cli_core.egg-info/requires.txt new/azure_cli_core-2.65.0/azure_cli_core.egg-info/requires.txt --- old/azure_cli_core-2.64.0/azure_cli_core.egg-info/requires.txt 2024-08-28 07:45:13.000000000 +0200 +++ new/azure_cli_core-2.65.0/azure_cli_core.egg-info/requires.txt 2024-09-25 13:33:13.000000000 +0200 @@ -6,7 +6,7 @@ jmespath knack~=0.11.0 msal-extensions==1.2.0 -msal[broker]==1.30.0 +msal[broker]==1.31.0 msrestazure~=0.6.4 packaging>=20.9 paramiko<4.0.0,>=2.0.8 @@ -14,6 +14,7 @@ PyJWT>=2.1.0 pyopenssl>=17.1.0 requests[socks] +microsoft-security-utilities-secret-masker~=1.0.0b2 [:sys_platform != "cygwin"] psutil>=5.9 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure_cli_core-2.64.0/setup.py new/azure_cli_core-2.65.0/setup.py --- old/azure_cli_core-2.64.0/setup.py 2024-08-28 07:44:58.000000000 +0200 +++ new/azure_cli_core-2.65.0/setup.py 2024-09-25 13:32:48.000000000 +0200 @@ -8,7 +8,7 @@ from codecs import open from setuptools import setup, find_packages -VERSION = "2.64.0" +VERSION = "2.65.0" # If we have source, validate that our version numbers match # This should prevent uploading releases with mismatched versions. @@ -54,7 +54,7 @@ 'jmespath', 'knack~=0.11.0', 'msal-extensions==1.2.0', - 'msal[broker]==1.30.0', + 'msal[broker]==1.31.0', 'msrestazure~=0.6.4', 'packaging>=20.9', 'paramiko>=2.0.8,<4.0.0', @@ -63,7 +63,8 @@ 'psutil>=5.9; sys_platform != "cygwin"', 'PyJWT>=2.1.0', 'pyopenssl>=17.1.0', # https://github.com/pyca/pyopenssl/pull/612 - 'requests[socks]' + 'requests[socks]', + 'microsoft-security-utilities-secret-masker~=1.0.0b2', ] with open('README.rst', 'r', encoding='utf-8') as f: