Skip to content

[Bug] acquire_token_interactive fails in Cloud Shell: Audience GUID/.default is not a supported MSI token audience. #784

@jiasli

Description

@jiasli

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:

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:

https://github.com/Azure/azure-cli/blob/464a79cb0f9c474da1f9d426b9aaf56afcffc47a/src/azure-cli-core/azure/cli/core/auth/util.py#L87-L106

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions