How can I use the Docker Registry API to pull information about a container? Getting UNAUTHORIZED
Asked Answered
E

2

5

I came across an article awhile back titled Inspecting Docker Images without pulling them that gets into the nitty-gritty of the specific API calls needed to essentially do a docker inspect with REST calls. However, I'm wondering if perhaps something has changed with the Docker registry API since that article was written.

The article breaks down that you need to make three REST calls, in order, to get information about a container. In the case of the public Docker registry, they are as follows:

  1. A GET request to auth.docker.io to get a token

    curl "https://auth.docker.io/token?scope=repository:<image>:pull&service=registry.docker.io"
    

    In this case image could be something like nginx or docker - basically whatever image you're looking up. This REST call returns a token to use in subsequent requests.

  2. A GET request to retrieve the manifest listings

    curl -H "Accept: application/vnd.docker.distribution.manifest.v2+json"
    -H "Authorization: Bearer <token-from-step-1>"
    "https://registry-1.docker.io/v2/<image>/manifests/<tag>"
    

    Here image is the same as in Step 1, and tag could be something like latest. This call returns some JSON; the key is that we need to extract the value at .config.digest. This is the digest string that we use in the final request.

  3. Finally a GET request to retrieve the container config, using the digest we received in step 2

    curl -H "Accept: application/vnd.docker.distribution.manifest.v2+json"
    -H "Authorization: Bearer <token-from-step-1>"
    "https://registry-1.docker.io/v2/<image>/blobs/<digest-from-step-2>"
    

    This returns some JSON, and the field I care about is .config

I was able to test this successfully on a private Docker registry, although there I had to do something different for auth. However I have the opposite problem when I try to follow along with the guide (which I've outlined in these steps above) for the public Docker registry: Step 1 gives me a token, but that token is worthless. Whenever I try to use it, in Steps 2 or 3, I get this back:

{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"repository","Class":"","Name":"docker","Action":"pull"}]}]}

Any ideas on how to get this working?

Exstipulate answered 27/3, 2019 at 20:48 Comment(2)
What is the image parameter you are using?Expiation
It doesn't seem to matter as they all produce tokens which don't work on subsequent steps. For testing purposes I had been trying both docker and nginx.Exstipulate
E
8

Using the following steps you can retrieve the configuration of any public container-image.

  1. Get the respective token for the image. Note that you must specify the full name of the image (official images use the library repository). Thereby, the NGINX image should be referred to as: library/nginx.

    curl \
        --silent \
        "https://auth.docker.io/token?scope=repository:library/nginx:pull&service=registry.docker.io" \
        | jq -r '.token'
    

    Token shortened for brevity: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXV...

  2. Retrieve the image digest from the manifest. For this request, you also need to specify a valid tag (this example uses the latest tag).

    curl \
        --silent \
        --header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
        --header "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXV..." \
        "https://registry-1.docker.io/v2/library/nginx/manifests/latest" \
        | jq -r '.config.digest'
    

    sha256:2bcb04bdb83f7c5dc30f0edaca1609a716bda1c7d2244d4f5fbbdfef33da366c

  3. Finally, get the container configuration by using the following request. Within the URL, the digest from step two must be specified.

    curl \
        --silent \
        --location \
        --header "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXV..." \
        "https://registry-1.docker.io/v2/library/nginx/blobs/sha256:2bcb04bdb83f7c5dc30f0edaca1609a716bda1c7d2244d4f5fbbdfef33da366c" \
        | jq -r '.container_config'
    

    Output shortened for brevity:

    {
        "Hostname": "6c02a05b3d09",
        "Env": [
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
            "NGINX_VERSION=1.15.10-1~stretch",
            "NJS_VERSION=1.15.10.0.3.0-1~stretch"
        ],
        "Labels": {
            "maintainer": "NGINX Docker Maintainers <[email protected]>"
        },
        "StopSignal": "SIGTERM"
    }
    
Expiation answered 1/4, 2019 at 18:32 Comment(2)
Adding the library/ prefix was what I was missing. Thank you. I'll award the bounty as soon as it lets me.Exstipulate
If you have any time, I have a closely-related question here: https://mcmap.net/q/111831/-what-token-endpoint-does-jfrog-artifactory-use-for-its-docker-registriesExstipulate
M
0

The article breaks down that you need to make three REST calls, in order, to get information about a container.

about an image

Finally a GET request to retrieve the container config

the image config

A script that displays an image config. It works with private registries, and Docker Hub. For basic auth registries you need to provide the second argument (user:pass).

First it gets $registry/v2/. If it returns 401, it does the authentication.

In case of Docker Hub it takes realm and service from the WWW-Authenticate header, gets $realm?service=$service&scope=repository:$image:pull, and takes .token from the result to use in the following requests (-H "Authorization: Bearer $token").

In other cases it uses the basic auth (-u "$user:$pass").

#!/usr/bin/env bash
set -eu
image=$1
creds=${2-}

# https://github.com/moby/moby/blob/v20.10.18/vendor/github.com/docker/distribution/reference/normalize.go#L29-L57
# https://github.com/moby/moby/blob/v20.10.18/vendor/github.com/docker/distribution/reference/normalize.go#L88-L105
registry=${image%%/*}
if [ "$registry" = "$image" ] \
|| { [ "`expr index "$registry" .:`" = 0 ] && [ "$registry" != localhost ]; }; then
    registry=docker.io
else
    image=${image#*/}
fi
if [ "$registry" = docker.io ] && [ "`expr index "$image" /`" = 0 ]; then
    image=library/$image
fi
if [ "`expr index "$image" :`" = 0 ]; then
    tag=latest
else
    tag=${image#*:}
    image=${image%:*}
fi
if [ "$registry" = docker.io ]; then
    registry=https://registry-1.docker.io
elif ! [[ "$registry" =~ ^localhost(:[0-9]+)$ ]]; then
    registry=https://$registry
fi

r=`curl -sS "$registry/v2/" \
    -o /dev/null \
    -w '%{http_code}:%header{www-authenticate}'`
http_code=`echo "$r" | cut -d: -f1`
curl_args=(-sS -H 'Accept: application/vnd.docker.distribution.manifest.v2+json')
if [ "$http_code" = 401 ]; then
    if [ "$registry" = https://registry-1.docker.io ]; then
        header_www_authenticate=`echo "$r" | cut -d: -f2-`
        header_www_authenticate=`echo "$header_www_authenticate" | sed -E 's/^Bearer +//'`
        split_into_lines() {
            sed -Ee :1 -e 's/^(([^",]|"([^"]|\")*")*),/\1\n/; t1'
        }
        header_www_authenticate=`echo "$header_www_authenticate" | split_into_lines`
        extract_value() {
            sed -E 's/^[^=]+="(([^"]|\")*)"$/\1/; s/\\(.)/\1/g'
        }
        realm=$(echo "$header_www_authenticate" | grep '^realm=' | extract_value)
        service=$(echo "$header_www_authenticate" | grep '^service=' | extract_value)
        scope=repository:$image:pull
        token=`curl -sS "$realm?service=$service&scope=$scope" | jq -r .token`
        curl_args+=(-H "Authorization: Bearer $token")
    else
        curl_args+=(-u "$creds")
    fi
fi
manifest=`curl "${curl_args[@]}" "$registry/v2/$image/manifests/$tag"`
config_digest=`echo "$manifest" | jq -r .config.digest`
curl "${curl_args[@]}" -L "$registry/v2/$image/blobs/$config_digest" | jq -C

Usage:

$ ./image-config.sh ruby
$ ./image-config.sh library/ruby            # same as the one above
$ ./image-config.sh docker.io/library/ruby  # same as the previous two
$ ./image-config.sh docker.io/library/ruby:3.0.4
$ ./image-config.sh myregistry.com/hello-world testuser:testpassword
$ ./image-config.sh localhost:5000/hello-world

A couple of remarks about using the API:

  • A registry is a collection of repositories. Think GitHub. Generally repository names are of the form user/name (e.g. nginxproxy/nginx-proxy). But with official repositories (that all in fact start with library/) the first segment may be omitted (library/ruby -> ruby). Also there might be just one segment (e.g. in private registries), and occasionaly the first part alone is called a repository (example). Or the second.
  • A repository is a collection of images. Some of them are tagged. Untagged images usually come into being when you push a new version of an image using the same tag.
  • An old, but maybe somewhat up-to-date relevant article (at least the beginning).
  • For the Docker Hub's registry URL see this answer.
  • Don't expect everything in the spec to work. E.g. Docker Hub doesn't implement the /v2/_catalog route.
Mountebank answered 7/10, 2022 at 3:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.