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

Client request only authorized for same repository: unauthorized when changing repo name #133

Open
borjamunozf opened this issue May 3, 2024 · 9 comments

Comments

@borjamunozf
Copy link

borjamunozf commented May 3, 2024

Hello.

We have started using the oras sdk but we got an strange behaviour. This is the following scenario:

Scenario

  • artifacts pushed to exampleregistry - repository quefacemos
  • artifacts pushed to same _exampleregistry- repository quefacemos2
  • authenticate oras client succesfully with access token from az acr registry

pseudocode

Actual behaviour

  • tags_quefacemos = cli.get_tags("exampleregistry/quefacemos") succesful request.
  • tags_quefacemos2 = cli.get_tags("exampleregistry/quefacemos2") fails and returns Unauthorized.
  • If we add a new line to login again between the get_tags call, it works!

fail

cli = oras.client.OrasClient()
cli.login(username=whatever, password=accessToken)
tags_quefacemos = cli.get_tags("exampleregistry/quefacemos")
tags_quefacemos2 = cli.get_tags("exampleregistry/quefacemos2")

ok

cli = oras.client.OrasClient()
cli.login(username=whatever, password=accessToken)
tags_quefacemos = cli.get_tags("exampleregistry/quefacemos")
cli.login(username=whatever, password=accessToken)
tags_quefacemos2 = cli.get_tags("exampleregistry/quefacemos2")

Expected behaviour

  • Running the same commands from the ORAS Cli works:
oras repo tags exampleregistry/quefacemos

OK

oras repo tags exampleregistry/quefacemos2

OK

Is this expected? Why the authentication seems to disappear or stop to being valid after changing the repository?

@borjamunozf
Copy link
Author

borjamunozf commented May 3, 2024

Ok, more info. It seems that after the first succesfull request, the headers changed from Basic to Bearer:

authHeaderRaw = originalResponse.headers.get("Www-Authenticate")

For the second request to quefacemos2 repository, the authHeaderRaw shows info about the Bearer:

  • the scope is assigned to the previous repo (quefacemos (in this case cct, does not matter).
  • complains about insufficient scope

imagen

UPDATE:

  • I checked other calls like getting the tags and then use
client.get_tags(whatever-samerepo)
client.remote.get_manifest(whatever-samerepo)

It fails also if you dont authenticate between again. So the behaviour is not because of changing the repository scope only, any time you do a new call.

@vsoch
Copy link
Contributor

vsoch commented May 3, 2024

Which registry is this? The challenge here is that there isn't a standard auth flow, and it's been tweaked over the years to fit niche registry cases.

@borjamunozf
Copy link
Author

borjamunozf commented May 3, 2024 via email

@vsoch
Copy link
Contributor

vsoch commented May 3, 2024

So azure just wants to keep the basic auth, or is it just missing the scope?

@borjamunozf
Copy link
Author

borjamunozf commented May 6, 2024

Not quite sure, but seems that it does not want neither basic auth.
If I'm not wrong, this the flow:

  • client.login(user, acces_token)
    this access token looks like this (omitted info)
{
  "iss": "Azure Container Registry",
  "aud": "rcsdockerregistry.azurecr.io",
  "version": "1.0",
  "grant_type": "refresh_token",
  "permissions": {
    "actions": [
      "read",
      "write",
      "delete",
      "deleted/read",
      "deleted/restore/action"
    ]
  },
  • the header initially is Basic Auth, but this basic request fails. After it gets the first 401, it goes through authenticate_request method, ends getting 200 response in line and updates the header from Basic Auth to Bearer.

oras-py/oras/provider.py

Lines 1050 to 1060 in cb575ab

authResponse = self.session.get(h.realm, headers=headers, params=params) # type: ignore
if authResponse.status_code != 200:
logger.debug(f"Auth response was not successful: {authResponse.text}")
return False
# Request the token
info = authResponse.json()
token = info.get("token") or info.get("access_token")
# Set the token to the original request and retry
self.headers.update({"Authorization": "Bearer %s" % token})

  • After the headers update, the request is succesfully authorized & response is processed.

This only works for the first request, as I mentioned. Any second request has an slighly different flow:

  • The header is already a Bearer Token.
  • However, the do_request() and authenticate_request brings 401.

@vsoch
Copy link
Contributor

vsoch commented May 6, 2024

Sounds like we need to keep the basic auth then and this registry does not have support for Bearer? Would that fix the issue?

@borjamunozf
Copy link
Author

borjamunozf commented May 8, 2024

I don't think that the registry is not supporting the Bearer. It does, but only seems to be valid for the each single request, so that's why I need to login each time. It's like the refreshing or handling of the next request does not take care properly of the Bearer header; the header transformation & update is only working if the Basic Auth is the original header.

Respecting the basic_auth could be an option to avoid login repeatedly because it looks like having it enforces the correct automatic refresh for the client/registry (transforming from Basic Auth to Bearer).

self.set_basic_auth(username, password)

  • If I set & call the _set_basic_auth function, which is what also the login method does internally, this works.
oras_client.login(hostname=self.server, username=OCI_USER, password=out_token["accessToken"])
oras_client.get_tags(whatever) 
oras_client.set_basic_auth(OCI_USER, self.auth_token)
oras_client.get_tags(whatever_repo2) 

I don't know if persisting or respecting the _basic_auth member variable for the Registry class could have side effects for others to be honest:

https://github.com/oras-project/oras-py/blob/cb575abaa0f45ef9f474f0971f579c12ef643170/oras/provider.py#L75C14-L75C26

@vsoch
Copy link
Contributor

vsoch commented May 8, 2024

What we probably need is to separate the auth flow into modules - so you can select a module that has a particular behavior. I'd be open to a PR for that - I won't have time myself imminently soon.

@vsoch
Copy link
Contributor

vsoch commented May 11, 2024

Hi @borjamunozf - I started #134 as an effort to refactor auth into modules. I stripped down the default (the token flow) so I'm interested in feedback about if that works for you now, and if not, what the issue is, and then if there were a "basic auth only" flow (which I added a skeleton for) what you'd like that to look like. This is a fairly big change so it might not go in quickly (we need feedback from folks that use other registries) but I wanted to get us started.

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

No branches or pull requests

2 participants