Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
jiasli opened this issue Jan 24, 2025 · 2 comments · Fixed by Azure/azure-cli#30727 · May be fixed by #785

Comments

@jiasli
Copy link
Contributor

jiasli commented Jan 24, 2025

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.

@bgavrilMS
Copy link
Member

Does this affect only public client 'acquire_token_interactive' or all the other flows? If only interactive, does broker based auth work?

@rayluo
Copy link
Collaborator

rayluo commented Jan 24, 2025

Does this affect only public client 'acquire_token_interactive' or all the other flows? If only interactive, does broker based auth work?

This is in the cloud shell module, so it affects public client's acquire_token_interactive() and acquire_token_silent() only when running inside Cloud Shell.

In Cloud Shell, MSAL does not invoke any broker. In a sense, Cloud Shell itself is a broker that provides token for the signed-in user (and NOT for a managed identity, for that matter).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants