Skip to content

oras copy can not handle separate authentication for same registry #1892

@MalteHei

Description

@MalteHei

What happened in your environment?

When copying an artifact, it seems that oras uses only the target authentication for both the source and target artifact if both artifacts are in the same registry:

# copy from docker hub to harbor demo, works as expected
$ oras copy \
    'docker.io/library/alpine:latest' \
    'demo.goharbor.io/oras-src/alpine:latest' \
    --to-username='robot_oras-src+src' --to-password='DX37nuS39P7Hb7AbhaC9Ed1TtuulcSYG'

Copied [registry] docker.io/library/alpine:latest => [registry] demo.goharbor.io/oras-src/alpine:latest


# copy from harbor demo to harbor demo, fails with authorization error
$ oras copy \
    'demo.goharbor.io/oras-src/alpine:latest' \
    --from-username='robot_oras-src+src' --from-password='DX37nuS39P7Hb7AbhaC9Ed1TtuulcSYG' \
    'demo.goharbor.io/oras-dst/alpine:latest' \
    --to-username='robot_oras-dst+dst' --to-password='COhhaKcEy2i1IMwHVjQ00a8GyVgDTrc3'

Error from source registry for "demo.goharbor.io/oras-src/alpine:latest": unauthorized: unauthorized to access repository: oras-src/alpine, action: pull: unauthorized to access repository: oras-src/alpine, action: pull
Debug Logs
$ oras copy --debug \
    'demo.goharbor.io/oras-src/alpine:latest' \
    --from-username='robot_oras-src+src' --from-password='DX37nuS39P7Hb7AbhaC9Ed1TtuulcSYG' \
    'demo.goharbor.io/oras-dst/alpine:latest' \
    --to-username='robot_oras-dst+dst' --to-password='COhhaKcEy2i1IMwHVjQ00a8GyVgDTrc3'

[2025-11-05T14:40:40.115127814+01:00][DEBUG]: --> Request #0
> Request URL: "https://demo.goharbor.io/v2/oras-src/alpine/manifests/latest"
> Request method: "GET"
> Request headers:
   "Accept": "application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, application/vnd.oci.artifact.manifest.v1+json"
   "User-Agent": "oras/1.3.0"


[2025-11-05T14:40:40.198292177+01:00][DEBUG]: <-- Response #0
< Response Status: "401 Unauthorized"
< Response headers:
   "Strict-Transport-Security": "max-age=31536000; includeSubDomains"
   "Date": "Wed, 05 Nov 2025 13:36:17 GMT"
   "Content-Type": "application/json; charset=utf-8"
   "Content-Length": "152"
   "Docker-Distribution-Api-Version": "registry/2.0"
   "Set-Cookie": "*****"
   "Www-Authenticate": "Bearer realm=\"https://demo.goharbor.io/service/token\",service=\"harbor-registry\",scope=\"repository:oras-src/alpine:pull\""
   "X-Request-Id": "26993d607654ddbc23e3ef82b646af75"
< Response body:
{"errors":[{"code":"UNAUTHORIZED","message":"authorize header needed to send HEAD to repository: authorize header needed to send HEAD to repository"}]}



[2025-11-05T14:40:40.198417478+01:00][DEBUG]: --> Request #1
> Request URL: "https://demo.goharbor.io/service/token?scope=repository%3Aoras-dst%2Falpine%3Apull%2Cpush&scope=repository%3Aoras-src%2Falpine%3Apull&service=harbor-registry"
> Request method: "GET"
> Request headers:
   "Authorization": "*****"
   "User-Agent": "oras/1.3.0"


[2025-11-05T14:40:40.231140378+01:00][DEBUG]: <-- Response #1
< Response Status: "200 OK"
< Response headers:
   "Set-Cookie": "*****"
   "X-Request-Id": "31ae690e60b9787d8280dc8f08ae7996"
   "Strict-Transport-Security": "max-age=31536000; includeSubDomains"
   "Date": "Wed, 05 Nov 2025 13:36:17 GMT"
   "Content-Type": "application/json; charset=utf-8"
< Response body:
   Response body redacted due to potential credentials


[2025-11-05T14:40:40.231308662+01:00][DEBUG]: --> Request #2
> Request URL: "https://demo.goharbor.io/v2/oras-src/alpine/manifests/latest"
> Request method: "GET"
> Request headers:
   "Accept": "application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, application/vnd.oci.artifact.manifest.v1+json"
   "Authorization": "*****"
   "User-Agent": "oras/1.3.0"


[2025-11-05T14:40:40.254262499+01:00][DEBUG]: <-- Response #2
< Response Status: "401 Unauthorized"
< Response headers:
   "Content-Length": "180"
   "Docker-Distribution-Api-Version": "registry/2.0"
   "Set-Cookie": "*****"
   "Www-Authenticate": "Basic realm=\"harbor\""
   "X-Request-Id": "ba4e559bc202e7472ec357a640722e3f"
   "Strict-Transport-Security": "max-age=31536000; includeSubDomains"
   "Date": "Wed, 05 Nov 2025 13:36:17 GMT"
   "Content-Type": "application/json; charset=utf-8"
< Response body:
{"errors":[{"code":"UNAUTHORIZED","message":"unauthorized to access repository: oras-src/alpine, action: pull: unauthorized to access repository: oras-src/alpine, action: pull"}]}



Error from source registry for "demo.goharbor.io/oras-src/alpine:latest": unauthorized: unauthorized to access repository: oras-src/alpine, action: pull: unauthorized to access repository: oras-src/alpine, action: pull

What did you expect to happen?

oras copy should use the from-credentials for the source artifact and the to-credentials for the target artifact, even if both artifacts share the same registry (hostname).

How can we reproduce it?

  1. Create two private projects at https://demo.goharbor.io/
  2. Create a robot account with full permissions in each project
  3. Push an artifact into the first project
  4. oras copy the artifact into the second project

What is the version of your ORAS CLI?

$ oras version
Version:        1.3.0
Go version:     go1.25.0
OS/Arch:        linux/amd64
Git commit:     40530fe4c68e5825b868cd874bd46fc0cdd0f432
Git tree state: clean

What is your OS environment?

rhel 8.8

Are you willing to submit PRs to fix it?

  • Yes, I am willing to fix it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingstaleInactive issues or pull requests

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions