# Docker Registry v2 authentication via central service

Today a Docker Registry can run in standalone mode in which there are no
authorization checks. While adding your own HTTP authorization requirements in
a proxy placed between the client and the registry can give you greater access
control, we'd like a native authorization mechanism that's public key based
with access control lists managed separately with the ability to have fine
granularity in access control on a by-key, by-user, by-namespace, and
by-repository basis. In v1 this can be configured by specifying an
`index_endpoint` in the registry's config. Clients present tokens generated by
the index and tokens are validated on-line by the registry with every request.
This results in a complex authentication and authorization loop that occurs
with every registry operation. Some people are very familiar with this image:

![index auth](https://docs.docker.com/static_files/docker_pull_chart.png)

The above image outlines the 6-step process in accessing the Official Docker
Registry.

1. Contact the Docker Hub to know where I should download “samalba/busybox”
2. Docker Hub replies:
    a. samalba/busybox is on Registry A
    b. here are the checksums for samalba/busybox (for all layers)
    c. token
3. Contact Registry A to receive the layers for samalba/busybox (all of them to
   the base image). Registry A is authoritative for “samalba/busybox” but keeps
   a copy of all inherited layers and serve them all from the same location.
4. Registry contacts Docker Hub to verify if token/user is allowed to download
   images.
5. Docker Hub returns true/false lettings registry know if it should proceed or
   error out.
6. Get the payload for all layers.

The goal of this document is to outline a way to eliminate steps 4 and 5 from
the above process by using cryptographically signed tokens and no longer
require the client to authenticate each request with a username and password
stored locally in plain text.

The new registry workflow is more like this:

![v2 registry auth](https://docs.google.com/drawings/d/1EHZU9uBLmcH0kytDClBv6jv6WR4xZjE8RKEUw1mARJA/pub?w=480&h=360)

1. Attempt to begin a push/pull operation with the registry.
2. If the registry requires authorization it will return a `401 Unauthorized`
   HTTP response with information on how to authenticate.
3. The registry client makes a request to the authorization service for a
   signed JSON Web Token.
4. The authorization service returns a token.
5. The client retries the original request with the token embedded in the
   request header.
6. The Registry authorizes the client and begins the push/pull session as
   usual. 

## Requirements

- Registry Clients capable of generating key pairs which can be used to
  authenticate to an authorization server.
- An authorization server capable of managing user accounts, their public keys,
  and access controls to their resources hosted by any given service (such as
  repositories in a Docker Registry).
- A Docker Registry capable of trusting the authorization server to sign tokens
  which clients can use for authorization and the ability to verify these
  tokens for single use or for use during a sufficiently short period of time.

## Authorization Server Endpoint Descriptions

This document borrows heavily from the [JSON Web Token Draft Spec](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32)

The described server is meant to serve as a user account and key manager and a
centralized access control list for resources hosted by other services which
wish to authenticate and manage authorizations using this services accounts and
their public keys.

Such a service could be used by the official docker registry to authenticate
clients and verify their authorization to docker image repositories.

Docker will need to be updated to interact with an authorization server to get
an authorization token.

## How to authenticate

Today, registry clients first contact the index to initiate a push or pull.
For v2, clients should contact the registry first. If the registry server
requires authentication it will return a `401 Unauthorized` response with a
`WWW-Authenticate` header detailing how to authenticate to this registry.

For example, say I (username `jlhawn`) am attempting to push an image to the
repository `samalba/my-app`. For the registry to authorize this, I either need
`push` access to the `samalba/my-app` repository or `push` access to the whole
`samalba` namespace in general. The registry will first return this response:

```
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="https://auth.docker.com/v2/token/",service="registry.docker.com",scope="repository:samalba/my-app:push"
```

This format is documented in [Section 3 of RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://tools.ietf.org/html/rfc6750#section-3)

The client will then know to make a `GET` request to the URL
`https://auth.docker.com/v2/token/` using the `service` and `scope` values from
the `WWW-Authenticate` header.

## Requesting a Token

#### Query Parameters

<dl>
    <dt>
        <code>service</code>
    </dt>
    <dd>
        The name of the service which hosts the resource.
    </dd>
    <dt>
        <code>scope</code>
    </dt>
    <dd>
        The resource in question, formatted as one of the space-delimited
        entries from the <code>scope</code> parameters from the <code>WWW-Authenticate</code> header
        shown above. This query parameter should be specified multiple times if
        there is more than one <code>scope</code> entry from the <code>WWW-Authenticate</code>
        header. The above example would be specified as:
        <code>scope=repository:samalba/my-app:push</code>.
    </dd>
    <dt>
        <code>account</code>
    </dt>
    <dd>
        The name of the account which the client is acting as. Optional if it
        can be inferred from client authentication.
    </dd>
</dl>

#### Description

Requests an authorization token for access to a specific resource hosted by a
specific service provider. Requires the client to authenticate either using a
TLS client certificate or using basic authentication (or any other kind of
digest/challenge/response authentication scheme if the client doesn't support
TLS client certs). If the key in the client certificate is linked to an account
then the token is issued for that account key. If the key in the certificate is
linked to multiple accounts then the client must specify the `account` query
parameter. The returned token is in JWT (JSON Web Token) format, signed using
the authorization server's private key.

#### Example

For this example, the client makes an HTTP request to the following endpoint
over TLS using a client certificate with the server being configured to allow a
non-verified issuer during the handshake (i.e., a self-signed client cert is
okay).

```
GET /v2/token/?service=registry.docker.com&scope=repository:samalba/my-app:push&account=jlhawn HTTP/1.1
Host: auth.docker.com
```

The server first inspects the client certificate to extract the subject key and
lookup which account it is associated with. The client is now authenticated
using that account.

The server next searches its access control list for the account's access to
the repository `samalba/my-app` hosted by the service `registry.docker.com`.

The server will now construct a JSON Web Token to sign and return. A JSON Web
Token has 3 main parts:

1.  Headers

    The header of a JSON Web Token is a standard JOSE header. The "typ" field
    will be "JWT" and it will also contain the "alg" which identifies the
    signing algorithm used to produce the signature. It will also usually have
    a "kid" field, the ID of the key which was used to sign the token.

    Here is an example JOSE Header for a JSON Web Token (formatted with
    whitespace for readability):

    ```
    {
        "typ": "JWT",
        "alg": "ES256",
        "kid": "PYYO:TEWU:V7JH:26JV:AQTZ:LJC3:SXVJ:XGHA:34F2:2LAQ:ZRMK:Z7Q6"
    }
    ```

    It specifies that this object is going to be a JSON Web token signed using
    the key with the given ID using the Elliptic Curve signature algorithm
    using a SHA256 hash.

2.  Claim Set

    The Claim Set is a JSON struct containing these standard registered claim
    name fields:

    <dl>
        <dt>
            <code>iss</code> (Issuer)
        </dt>
        <dd>
            The issuer of the token, typically the fqdn of the authorization
            server.
        </dd>
        <dt>
            <code>sub</code> (Subject)
        </dt>
        <dd>
            The subject of the token; the id of the client which requested it.
        </dd>
        <dt>
            <code>aud</code> (Audience)
        </dt>
        <dd>
            The intended audience of the token; the id of the service which
            will verify the token to authorize the client/subject.
        </dd>
        <dt>
            <code>exp</code> (Expiration)
        </dt>
        <dd>
            The token should only be considered valid up to this specified date
            and time.
        </dd>
        <dt>
            <code>nbf</code> (Not Before)
        </dt>
        <dd>
            The token should not be considered valid before this specified date
            and time.
        </dd>
        <dt>
            <code>iat</code> (Issued At)
        </dt>
        <dd>
            Specifies the date and time which the Authorization server
            generated this token.
        </dd>
        <dt>
            <code>jti</code> (JWT ID)
        </dt>
        <dd>
            A unique identifier for this token. Can be used by the intended
            audience to prevent replays of the token.
        </dd>
    </dl>

    The Claim Set will also contain a private claim name unique to this
    authorization server specification:

    <dl>
        <dt>
            <code>access</code>
        </dt>
        <dd>
            An array of access entry objects with the following fields:

            <dl>
                <dt>
                    <code>type</code>
                </dt>
                <dd>
                    The type of resource hosted by the service.
                </dd>
                <dt>
                    <code>name</code>
                </dt>
                <dd>
                    The name of the recource of the given type hosted by the
                    service.
                </dd>
                <dt>
                    <code>actions</code>
                </dt>
                <dd>
                    An array of strings which give the actions authorized on
                    this resource.
                </dd>
            </dl>
        </dd>
    </dl>

    Here is an example of such a JWT Claim Set (formatted with whitespace for
    readability):

    ```
    {
        "iss": "auth.docker.com",
        "sub": "jlhawn",
        "aud": "registry.docker.com",
        "exp": 1415387315,
        "nbf": 1415387015,
        "iat": 1415387015,
        "jti": "tYJCO1c6cnyy7kAn0c7rKPgbV1H1bFws",
        "access": [
            {
                "type": "repository",
                "name": "samalba/my-app",
                "actions": [
                    "push"
                ]
            }
        ]
    }
    ```

3.  Signature

    The authorization server will produce a JOSE header and Claim Set with no
    extraneous whitespace, i.e., the JOSE Header from above would be

    ```
    {"typ":"JWT","alg":"ES256","kid":"PYYO:TEWU:V7JH:26JV:AQTZ:LJC3:SXVJ:XGHA:34F2:2LAQ:ZRMK:Z7Q6"}
    ```

    and the Claim Set from above would be

    ```
    {"iss":"auth.docker.com","sub":"jlhawn","aud":"registry.docker.com","exp":1415387315,"nbf":1415387015,"iat":1415387015,"jti":"tYJCO1c6cnyy7kAn0c7rKPgbV1H1bFws","access":[{"type":"repository","name":"samalba/my-app","actions":["push"]}]}
    ```

    The utf-8 representation of this JOSE header and Claim Set are then
    url-safe base64 encoded (sans trailing '=' buffer), producing:

    ```
    eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0
    ```

    for the JOSE Header and

    ```
    eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0
    ```

    for the Claim Set. These two are concatenated using a '.' character,
    yielding the string:

    ```
    eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0
    ```

    This is then used as the payload to a the `ES256` signature algorithm
    specified in the JOSE header and specified fully in [Section 3.4 of the JSON Web Algorithms (JWA)
    draft specification](https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-38#section-3.4)

    This example signature will use the following ECDSA key for the server:

    ```
    {
        "kty": "EC",
        "crv": "P-256",
        "kid": "PYYO:TEWU:V7JH:26JV:AQTZ:LJC3:SXVJ:XGHA:34F2:2LAQ:ZRMK:Z7Q6",
        "d": "R7OnbfMaD5J2jl7GeE8ESo7CnHSBm_1N2k9IXYFrKJA",
        "x": "m7zUpx3b-zmVE5cymSs64POG9QcyEpJaYCD82-549_Q",
        "y": "dU3biz8sZ_8GPB-odm8Wxz3lNDr1xcAQQPQaOcr1fmc"
    }
    ```

    A resulting signature of the above payload using this key is:

    ```
    QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w
    ```

    Concatenating all of these together with a `.` character gives the
    resulting JWT:

    ```
    eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0.QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w
    ```

This can now be placed in an HTTP response and returned to the client to use to
authenticate to the audience service:


```
HTTP/1.1 200 OK
Content-Type: application/json

{"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlBZWU86VEVXVTpWN0pIOjI2SlY6QVFUWjpMSkMzOlNYVko6WEdIQTozNEYyOjJMQVE6WlJNSzpaN1E2In0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJqbGhhd24iLCJhdWQiOiJyZWdpc3RyeS5kb2NrZXIuY29tIiwiZXhwIjoxNDE1Mzg3MzE1LCJuYmYiOjE0MTUzODcwMTUsImlhdCI6MTQxNTM4NzAxNSwianRpIjoidFlKQ08xYzZjbnl5N2tBbjBjN3JLUGdiVjFIMWJGd3MiLCJhY2Nlc3MiOlt7InR5cGUiOiJyZXBvc2l0b3J5IiwibmFtZSI6InNhbWFsYmEvbXktYXBwIiwiYWN0aW9ucyI6WyJwdXNoIl19XX0.QhflHPfbd6eVF4lM9bwYpFZIV0PfikbyXuLx959ykRTBpe3CYnzs6YBK8FToVb5R47920PVLrh8zuLzdCr9t3w"}
```

## Using the signed token

Once the client has a token, it will try the registry request again with the
token placed in the HTTP `Authorization` header like so:

```
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IkJWM0Q6MkFWWjpVQjVaOktJQVA6SU5QTDo1RU42Ok40SjQ6Nk1XTzpEUktFOkJWUUs6M0ZKTDpQT1RMIn0.eyJpc3MiOiJhdXRoLmRvY2tlci5jb20iLCJzdWIiOiJCQ0NZOk9VNlo6UUVKNTpXTjJDOjJBVkM6WTdZRDpBM0xZOjQ1VVc6NE9HRDpLQUxMOkNOSjU6NUlVTCIsImF1ZCI6InJlZ2lzdHJ5LmRvY2tlci5jb20iLCJleHAiOjE0MTUzODczMTUsIm5iZiI6MTQxNTM4NzAxNSwiaWF0IjoxNDE1Mzg3MDE1LCJqdGkiOiJ0WUpDTzFjNmNueXk3a0FuMGM3cktQZ2JWMUgxYkZ3cyIsInNjb3BlIjoiamxoYXduOnJlcG9zaXRvcnk6c2FtYWxiYS9teS1hcHA6cHVzaCxwdWxsIGpsaGF3bjpuYW1lc3BhY2U6c2FtYWxiYTpwdWxsIn0.Y3zZSwaZPqy4y9oRBVRImZyv3m_S9XDHF1tWwN7mL52C_IiA73SJkWVNsvNqpJIn5h7A2F8biv_S2ppQ1lgkbw
```

This is also described in [Section 2.1 of RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://tools.ietf.org/html/rfc6750#section-2.1)

## Verifying the token

The registry must now verify the token presented by the user by inspecting the
claim set within. The registry will:

- Ensure that the issuer (`iss` claim) is an authority it trusts.
- Ensure that the registry identifies as the audience (`aud` claim).
- Check that the current time is between the `nbf` and `exp` claim times.
- If enforcing single-use tokens, check that the JWT ID (`jti` claim) value has
  not been seen before.
  - To enforce this, the registry may keep a record of `jti`s it has seen for
    up to the `exp` time of the token to prevent token replays.
- Check the `access` claim value and use the identified resources and the list
  of actions authorized to determine whether the token grants the required
  level of access for the operation the client is attempting to perform.
- Verify that the signature of the token is valid.

At no point in this process should the registry need to <em>call back</em> to
the authorization server. If anything, it would only need to update a list of
trusted public keys for verifying token signatures or use a separate API
(still to be spec'd) to add/update resource records on the authorization
server.
