oras-project / oras-py

ORAS Python SDK
https://oras-project.github.io/oras-py/
Apache License 2.0
39 stars 33 forks source link

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

Open borjamunozf opened 5 months ago

borjamunozf commented 5 months ago

Hello.

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

Scenario

pseudocode

Actual behaviour

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

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 commented 5 months ago

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

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

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

imagen

UPDATE:

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 commented 5 months ago

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 commented 5 months ago

Azure OCI Registry

El vie, 3 may 2024 a las 20:25, Vanessasaurus @.***>) escribió:

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.

— Reply to this email directly, view it on GitHub https://github.com/oras-project/oras-py/issues/133#issuecomment-2093540933, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGFV5D3ZXOH22WQ7UD54FNLZAPJADAVCNFSM6AAAAABHFVWOGOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAOJTGU2DAOJTGM . You are receiving this because you authored the thread.Message ID: @.***>

vsoch commented 5 months ago

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

borjamunozf commented 5 months ago

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

{
  "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"
    ]
  },

https://github.com/oras-project/oras-py/blob/cb575abaa0f45ef9f474f0971f579c12ef643170/oras/provider.py#L1050-L1060

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

vsoch commented 5 months ago

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 commented 5 months ago

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).

https://github.com/oras-project/oras-py/blob/cb575abaa0f45ef9f474f0971f579c12ef643170/oras/client.py#L226

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 commented 5 months ago

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 commented 5 months ago

Hi @borjamunozf - I started https://github.com/oras-project/oras-py/pull/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.