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

Scope to resource #785

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open

Scope to resource #785

wants to merge 3 commits into from

Conversation

rayluo
Copy link
Collaborator

@rayluo rayluo commented Jan 25, 2025

This PR fixes the #784

As an implementation detail, this PR does not find and remove a magic string /.default. Instead, it still attempts to semantically strip the "path(s)" and keep the "domain" or "guid". Regardless of such detail, please focus on reviewing the test cases.

CC @jiasli : Can you test from this PR? Please give me a reply and then I will merge it and ship it.

Comment on lines +38 to +40
trailer = ( # https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc#trailing-slash-and-default
"/" if u.path.startswith("//") else "")
return "{}://{}{}".format(u.scheme, u.netloc, trailer)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If resource is provided to Azure CLI, Azure CLI just adds a trailing /.default to the resource:

https://github.com/Azure/azure-cli/blob/967983c749c70e5e7dec5f1f84dae9032dc1008d/src/azure-cli-core/azure/cli/core/_profile.py#L451

            scopes = resource_to_scopes(resource)

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

    scope = resource + '/.default'

So this scope-to-resource conversion is still not exactly the reverse of Azure CLI's process.

Copy link
Contributor

@jiasli jiasli Jan 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an edge case where the resource contains more than 2 trailing slashes.

Imagine resource is https://myresource.com//, CLI will convert it to scope as https://myresource.com///.default. The converted-back resource will be https://myresource.com/, losing the last slash.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conversion logic will remove the path part.

As mentioned in Azure/azure-cli#18312, valid identifier URI can also be

  • https://{verifiedCustomerDomain}/{string}
  • https://{string}.{verifiedCustomerDomain}/{string}

The trailing /{string} will be removed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...
So this scope-to-resource conversion is still not exactly the reverse of Azure CLI's process.

Not that the current msal.cloudshell._scope_to_resource() conversion algorithm is perfect, but logically speaking, the goal is not reversing Azure CLI's process, but to match Cloud Shell's quirk.

Imagine resource is https://myresource.com// ...

Theoretically, yes, but we never saw that before. And, most importantly, the "Cloud Shell's quirk" above contains several resources with one trailing slash, but never a double (or more) trailing slash.

https://myresource.com/mypath will also be converted to https://myresource.com.

That is arguably an expected behavior. Again, we have never encountered a Cloud Shell resource containing path, except the two known outliners which have already been handled properly.

I will suggest we focus on this test case and verify its solving the "GUID/scope" use case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seem to be a lot of special cases here. Will there be other customers for this code path?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No other customer uses this code path. It is for Cloud Shell only, and not for any other managed identity.

tests/test_cloudshell.py Outdated Show resolved Hide resolved
@rayluo rayluo force-pushed the scope-to-resource branch from 83533ad to f183da5 Compare January 26, 2025 04:43
@rayluo rayluo force-pushed the scope-to-resource branch from f183da5 to 8891464 Compare January 26, 2025 04:54
@rayluo rayluo force-pushed the scope-to-resource branch from 8891464 to 9a320f2 Compare January 26, 2025 06:20
tests/test_cloudshell.py Outdated Show resolved Hide resolved
@rayluo rayluo force-pushed the scope-to-resource branch from 9a320f2 to 388b86d Compare January 27, 2025 05:14
"https://pas.windows.net/CheckMyAccess/Linux/.default", # Special case
"https://double-slash.com//scope": "https://double-slash.com/",
"https://single-slash.com/scope": "https://single-slash.com",
"guid/some/scope": "guid",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is guid really the answer here? I think you should only strip away .\default

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semantically, since the Cloud Shell's token endpoint is expecting a resource, so we consider the resource of a potential "GUID/path/subpath" input to be only the leading part i.e. the "GUID".

Ideally, Cloud Shell will support scope directly in the future and then we will be able to remove all these workarounds.

@@ -32,8 +32,12 @@ def _scope_to_resource(scope): # This is an experimental reasonable-effort appr
if scope.startswith(a):
return a
u = urlparse(scope)
if not u.scheme and not u.netloc: # Typically the "GUID/scope" case
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't agree with this logic. It can potentially strip important scope information and be seen as a security issue.

Since this is managed identity, we only deal with app scopes and there is 1:1 mapping between app scope and resource. i.e. scope = resource + "./default"

As such this logic should take it into account - i.e. strip "./default" if it exists. It should not attempt to remove anything else.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed offline. The code path is for Azure CLI running inside Cloud Shell to get a token for the signed-in user. It is never about a managed machine. (That is also why it is misleading to expose Cloud Shell token acquisition as part of the managed identity.)

Currently, Cloud Shell's token endpoint accepts a resource parameter which shall not contain any scope; Cloud Shell also has special case that accepts "https://pas.windows.net/CheckMyAccess/Linux/.default" as-is. That .Net logic will not work here.

Copy link
Member

@bgavrilMS bgavrilMS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restrict the logic to strings ending in "./default"

@rayluo
Copy link
Collaborator Author

rayluo commented Jan 28, 2025

Per the agreement in a meeting today, this PR is now shipped as MSAL Python 1.31.2b1 to meet a release schedule. Another round of discussion with broader audience will be scheduled in the near future; further changes can be made accordingly.

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