-
Notifications
You must be signed in to change notification settings - Fork 207
Description
Describe the bug
acquire_token_interactive
fails in Cloud Shell: Audience GUID/.default is not a supported MSI token audience.
As an example, we use ARM's app ID 797f4846-ba00-4fd7-ba43-dac1f8f63013
(can be retrieved by az ad sp show --id "https://management.azure.com/"
). MSAL can't get an access token for it:
$ az account get-access-token --resource "797f4846-ba00-4fd7-ba43-dac1f8f63013"
A Cloud Shell credential problem occurred. When you report the issue with the error below, please mention the hostname 'SandboxHost-638732994281562992'
Audience 797f4846-ba00-4fd7-ba43-dac1f8f63013/.default is not a supported MSI token audience.
Azure CLI passes 797f4846-ba00-4fd7-ba43-dac1f8f63013/.default
as scope
to MSAL, but due to the incorrect msal.cloudshell._scope_to_resource
logic:
microsoft-authentication-library-for-python/msal/cloudshell.py
Lines 26 to 37 in 3f3d133
def _scope_to_resource(scope): # This is an experimental reasonable-effort approach | |
cloud_shell_supported_audiences = [ | |
"https://analysis.windows.net/powerbi/api", # Came from https://msazure.visualstudio.com/One/_git/compute-CloudShell?path=/src/images/agent/env/envconfig.PROD.json | |
"https://pas.windows.net/CheckMyAccess/Linux/.default", # Cloud Shell accepts it as-is | |
] | |
for a in cloud_shell_supported_audiences: | |
if scope.startswith(a): | |
return a | |
u = urlparse(scope) | |
if u.scheme: | |
return "{}://{}".format(u.scheme, u.netloc) | |
return scope # There is no much else we can do here |
the /.default
suffix is not removed. This is because only when scope
is a URL, the /.default
suffix is removed. For GUID scope
, the /.default
suffix is preserved.
Here is a comparision of Azure CLI and MSAL's scope-to-resource conversion logic:
from azure.cli.core.auth.util import scopes_to_resource as cli_scopes_to_resource
from msal.cloudshell import _scope_to_resource as msal_scope_to_resource
guid_scope = '797f4846-ba00-4fd7-ba43-dac1f8f63013/.default'
print(cli_scopes_to_resource([guid_scope]))
print(msal_scope_to_resource(guid_scope))
url_scope = 'https://management.azure.com//.default'
print(cli_scopes_to_resource([url_scope]))
print(msal_scope_to_resource(url_scope))
Output:
797f4846-ba00-4fd7-ba43-dac1f8f63013
797f4846-ba00-4fd7-ba43-dac1f8f63013/.default
https://management.azure.com/
https://management.azure.com
Another issue: Incorrect handling of trailing slash
MSAL's scope-to-resource conversion logic has another issue: When scope
is https://management.azure.com//.default
, MSAL removes not only /.default
, but also the trailing slash /
, resulting in https://management.azure.com
. This will also trigger failure for resources that require a trailing slash. See https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc#trailing-slash-and-default
Azure CLI made a similar mistake before and fixed it in Azure/azure-cli#15698.
Azure CLI's implementation
The source code of azure.cli.core.auth.util.scopes_to_resource
:
def scopes_to_resource(scopes):
"""Convert MSAL scopes to ADAL resource by stripping the /.default suffix and return a str.
For example:
['https://management.core.windows.net//.default'] -> 'https://management.core.windows.net/'
['https://managedhsm.azure.com/.default'] -> 'https://managedhsm.azure.com'
:param scopes: The MSAL scopes. It can be a list or tuple of string
:return: The ADAL resource
:rtype: str
"""
if not scopes:
return None
scope = scopes[0]
suffixes = ['/.default', '/user_impersonation']
for s in suffixes:
if scope.endswith(s):
return scope[:-len(s)]
return scope
Instead of detecting whether scope
is a URL, it detects if the scope
ends with /.default
. If so, remove the /.default
suffix.