From 2e67654b4b66d27f3d6f7e7c794aa8a509e2139f Mon Sep 17 00:00:00 2001 From: xuming-ms Date: Fri, 7 Aug 2026 13:00:32 +1000 Subject: [PATCH 1/2] [Ssh] `az ssh`: Allow SSH certificate flow in Cloud Shell (#33860) (cherry picked from commit 171de4a444be7f4e92959fabb973464d7737f027) --- src/azure-cli-core/azure/cli/core/_profile.py | 4 +-- .../azure/cli/core/tests/test_profile.py | 28 ++++++++++++------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/_profile.py b/src/azure-cli-core/azure/cli/core/_profile.py index 13f47ed417c..afd66504e2b 100644 --- a/src/azure-cli-core/azure/cli/core/_profile.py +++ b/src/azure-cli-core/azure/cli/core/_profile.py @@ -443,8 +443,8 @@ def get_msal_token(self, scopes, data): """ account = self.get_subscription() managed_identity_type, _ = Profile._parse_managed_identity_account(account) - if managed_identity_type or (in_cloud_console() and account[_USER_ENTITY].get(_CLOUD_SHELL_ID)): - raise AuthenticationError("VM SSH currently doesn't support managed identity or Cloud Shell.") + if managed_identity_type: + raise AuthenticationError("VM SSH currently doesn't support managed identity.") credential, _, _ = self.get_login_credentials(sdk_credential=False) from .auth.constants import ACCESS_TOKEN diff --git a/src/azure-cli-core/azure/cli/core/tests/test_profile.py b/src/azure-cli-core/azure/cli/core/tests/test_profile.py index d62464ca5d3..9254ca1744d 100644 --- a/src/azure-cli-core/azure/cli/core/tests/test_profile.py +++ b/src/azure-cli-core/azure/cli/core/tests/test_profile.py @@ -12,8 +12,8 @@ from unittest import mock from azure.cli.core._profile import (Profile, SubscriptionFinder, _attach_token_tenant, - _transform_subscription_for_multiapi, - _TENANT_LEVEL_ACCOUNT_NAME) + _transform_subscription_for_multiapi, + _TENANT_LEVEL_ACCOUNT_NAME) from azure.cli.core.azclierror import AuthenticationError from azure.cli.core.auth.util import AccessToken from azure.cli.core.mock import DummyCli @@ -75,10 +75,12 @@ def acquire_token(self, scopes, **kwargs): class CloudShellCredentialStub: def __init__(self): self.acquire_token_scopes = None + self.acquire_token_data = None super().__init__() def acquire_token(self, scopes, **kwargs): self.acquire_token_scopes = scopes + self.acquire_token_data = kwargs.get('data') return { 'access_token': TestProfile.test_cloud_shell_access_token, 'token_type': 'Bearer', @@ -1361,15 +1363,16 @@ def test_get_msal_token_mi_unsupported(self): profile._set_subscriptions(consolidated) with self.assertRaisesRegex(AuthenticationError, - "VM SSH currently doesn't support managed identity or Cloud Shell."): + "VM SSH currently doesn't support managed identity."): profile.get_msal_token(['https://pas.windows.net/CheckMyAccess/Linux/.default'], {'token_type': 'ssh-cert'}) @mock.patch('azure.cli.core._profile.in_cloud_console', autospec=True) @mock.patch('azure.cli.core.auth.msal_credentials.CloudShellCredential', autospec=True) - def test_get_msal_token_cloud_shell_unsupported(self, cloud_shell_credential_mock, mock_in_cloud_console): + def test_get_msal_token_cloud_shell(self, cloud_shell_credential_mock, mock_in_cloud_console): mock_in_cloud_console.return_value = True - cloud_shell_credential_mock.return_value = CloudShellCredentialStub() + credential_stub = CloudShellCredentialStub() + cloud_shell_credential_mock.return_value = credential_stub profile = Profile(cli_ctx=DummyCli(), storage={'subscriptions': None}) test_subscription_id = '12345678-1bf0-4dda-aec3-cb9272f09590' @@ -1378,14 +1381,19 @@ def test_get_msal_token_cloud_shell_unsupported(self, cloud_shell_credential_moc self.display_name1, self.state1, test_tenant_id) consolidated = profile._normalize_properties(self.user1, [cloud_shell_subscription], - True) + False) consolidated[0]['user']['cloudShellID'] = True profile._set_subscriptions(consolidated) - with self.assertRaisesRegex(AuthenticationError, - "VM SSH currently doesn't support managed identity or Cloud Shell."): - profile.get_msal_token(['https://pas.windows.net/CheckMyAccess/Linux/.default'], - {'token_type': 'ssh-cert'}) + scopes = ['https://pas.windows.net/CheckMyAccess/Linux/.default'] + data = {'token_type': 'ssh-cert', 'key_id': 'test_key_id', 'req_cnf': 'test_req_cnf'} + _, certificate_string = profile.get_msal_token(scopes, data) + + # The certificate request must reach the credential intact, since dropping `data` is what + # turns the SSH certificate into an ordinary access token. + assert credential_stub.acquire_token_scopes == scopes + assert credential_stub.acquire_token_data == data + assert certificate_string == TestProfile.test_cloud_shell_access_token @mock.patch('azure.cli.core.auth.identity.Identity.logout_service_principal') @mock.patch('azure.cli.core.auth.identity.Identity.logout_user') From 4af9e3aea13d12f4e5bd54a001814f00abc2e45e Mon Sep 17 00:00:00 2001 From: Naga Nandyala Date: Fri, 7 Aug 2026 14:20:06 +1000 Subject: [PATCH 2/2] {Release} Upgrade to Azure CLI 2.89.1 --- src/azure-cli-core/HISTORY.rst | 4 ++++ src/azure-cli-core/azure/cli/core/__init__.py | 2 +- src/azure-cli-core/azure/cli/core/commandIndex.latest.json | 2 +- src/azure-cli-core/azure/cli/core/helpIndex.latest.json | 2 +- src/azure-cli-core/setup.py | 2 +- src/azure-cli/HISTORY.rst | 7 +++++++ src/azure-cli/azure/cli/__main__.py | 2 +- src/azure-cli/requirements.py3.Darwin.txt | 4 ++-- src/azure-cli/requirements.py3.Linux.txt | 4 ++-- src/azure-cli/requirements.py3.windows.txt | 4 ++-- src/azure-cli/setup.py | 2 +- 11 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/azure-cli-core/HISTORY.rst b/src/azure-cli-core/HISTORY.rst index 75430175d2a..e0cdad7e5b4 100644 --- a/src/azure-cli-core/HISTORY.rst +++ b/src/azure-cli-core/HISTORY.rst @@ -3,6 +3,10 @@ Release History =============== +2.89.1 +++++++ +* Allow SSH certificate flow in Cloud Shell (#33860) + 2.89.0 ++++++ * Minor fixes diff --git a/src/azure-cli-core/azure/cli/core/__init__.py b/src/azure-cli-core/azure/cli/core/__init__.py index 7898c6e75f6..ef24b55770d 100644 --- a/src/azure-cli-core/azure/cli/core/__init__.py +++ b/src/azure-cli-core/azure/cli/core/__init__.py @@ -4,7 +4,7 @@ # -------------------------------------------------------------------------------------------- # pylint: disable=line-too-long -__version__ = "2.89.0" +__version__ = "2.89.1" import os import sys diff --git a/src/azure-cli-core/azure/cli/core/commandIndex.latest.json b/src/azure-cli-core/azure/cli/core/commandIndex.latest.json index ca036de84d5..aea05f80da8 100644 --- a/src/azure-cli-core/azure/cli/core/commandIndex.latest.json +++ b/src/azure-cli-core/azure/cli/core/commandIndex.latest.json @@ -1,5 +1,5 @@ { - "version": "2.89.0", + "version": "2.89.1", "cloudProfile": "latest", "commandIndex": { "account": [ diff --git a/src/azure-cli-core/azure/cli/core/helpIndex.latest.json b/src/azure-cli-core/azure/cli/core/helpIndex.latest.json index cab2be4da18..6209a9cf336 100644 --- a/src/azure-cli-core/azure/cli/core/helpIndex.latest.json +++ b/src/azure-cli-core/azure/cli/core/helpIndex.latest.json @@ -1,5 +1,5 @@ { - "version": "2.89.0", + "version": "2.89.1", "cloudProfile": "latest", "helpIndex": { "groups": { diff --git a/src/azure-cli-core/setup.py b/src/azure-cli-core/setup.py index 302b81a9f8a..e1ba7ee209a 100644 --- a/src/azure-cli-core/setup.py +++ b/src/azure-cli-core/setup.py @@ -8,7 +8,7 @@ from codecs import open from setuptools import setup, find_packages -VERSION = "2.89.0" +VERSION = "2.89.1" # If we have source, validate that our version numbers match # This should prevent uploading releases with mismatched versions. diff --git a/src/azure-cli/HISTORY.rst b/src/azure-cli/HISTORY.rst index f091f159e11..b448e89b0a8 100644 --- a/src/azure-cli/HISTORY.rst +++ b/src/azure-cli/HISTORY.rst @@ -3,6 +3,13 @@ Release History =============== +2.89.1 +++++++ + +**Core** + +* Allow SSH certificate flow in Cloud Shell (#33860) + 2.89.0 ++++++ diff --git a/src/azure-cli/azure/cli/__main__.py b/src/azure-cli/azure/cli/__main__.py index 436abd7e302..924711d0e95 100644 --- a/src/azure-cli/azure/cli/__main__.py +++ b/src/azure-cli/azure/cli/__main__.py @@ -17,7 +17,7 @@ from knack.log import get_logger __author__ = "Microsoft Corporation " -__version__ = "2.89.0" +__version__ = "2.89.1" logger = get_logger(__name__) diff --git a/src/azure-cli/requirements.py3.Darwin.txt b/src/azure-cli/requirements.py3.Darwin.txt index f7a64ab9e8e..f8858e7708c 100644 --- a/src/azure-cli/requirements.py3.Darwin.txt +++ b/src/azure-cli/requirements.py3.Darwin.txt @@ -4,9 +4,9 @@ argcomplete==3.5.2 asn1crypto==0.24.0 azure-appconfiguration==1.8.0 azure-batch==15.0.0b1 -azure-cli-core==2.89.0 +azure-cli-core==2.89.1 azure-cli-telemetry==1.1.0 -azure-cli==2.89.0 +azure-cli==2.89.1 azure-common==1.1.22 azure-core==1.39.0 azure-cosmos==3.2.0 diff --git a/src/azure-cli/requirements.py3.Linux.txt b/src/azure-cli/requirements.py3.Linux.txt index 15d7b8b458c..0b380866841 100644 --- a/src/azure-cli/requirements.py3.Linux.txt +++ b/src/azure-cli/requirements.py3.Linux.txt @@ -4,9 +4,9 @@ argcomplete==3.5.2 asn1crypto==0.24.0 azure-appconfiguration==1.8.0 azure-batch==15.0.0b1 -azure-cli-core==2.89.0 +azure-cli-core==2.89.1 azure-cli-telemetry==1.1.0 -azure-cli==2.89.0 +azure-cli==2.89.1 azure-common==1.1.22 azure-core==1.39.0 azure-cosmos==3.2.0 diff --git a/src/azure-cli/requirements.py3.windows.txt b/src/azure-cli/requirements.py3.windows.txt index 0e356532832..d2819a16542 100644 --- a/src/azure-cli/requirements.py3.windows.txt +++ b/src/azure-cli/requirements.py3.windows.txt @@ -4,9 +4,9 @@ argcomplete==3.5.2 asn1crypto==0.24.0 azure-appconfiguration==1.8.0 azure-batch==15.0.0b1 -azure-cli-core==2.89.0 +azure-cli-core==2.89.1 azure-cli-telemetry==1.1.0 -azure-cli==2.89.0 +azure-cli==2.89.1 azure-common==1.1.22 azure-core==1.39.0 azure-cosmos==3.2.0 diff --git a/src/azure-cli/setup.py b/src/azure-cli/setup.py index 82f627b96a2..27ebcb6774a 100644 --- a/src/azure-cli/setup.py +++ b/src/azure-cli/setup.py @@ -17,7 +17,7 @@ logging.warning("Wheel is not available, disabling bdist_wheel hook") cmdclass = {} -VERSION = "2.89.0" +VERSION = "2.89.1" # If we have source, validate that our version numbers match # This should prevent uploading releases with mismatched versions. try: