Merge pull request #973 from jlhawn/update_token_spec
[docs/spec/auth] Clarify the Token auth workflow
This commit is contained in:
commit
4271c60d98
1 changed files with 101 additions and 67 deletions
|
@ -46,7 +46,7 @@ the above process by using cryptographically signed tokens and no longer
|
||||||
require the client to authenticate each request with a username and password
|
require the client to authenticate each request with a username and password
|
||||||
stored locally in plain text.
|
stored locally in plain text.
|
||||||
|
|
||||||
The new registry workflow is more like this:
|
The v2 registry token workflow is more like this:
|
||||||
|
|
||||||
![v2 registry auth](https://docs.google.com/drawings/d/1EHZU9uBLmcH0kytDClBv6jv6WR4xZjE8RKEUw1mARJA/pub?w=480&h=360)
|
![v2 registry auth](https://docs.google.com/drawings/d/1EHZU9uBLmcH0kytDClBv6jv6WR4xZjE8RKEUw1mARJA/pub?w=480&h=360)
|
||||||
|
|
||||||
|
@ -55,19 +55,20 @@ The new registry workflow is more like this:
|
||||||
HTTP response with information on how to authenticate.
|
HTTP response with information on how to authenticate.
|
||||||
3. The registry client makes a request to the authorization service for a
|
3. The registry client makes a request to the authorization service for a
|
||||||
signed JSON Web Token.
|
signed JSON Web Token.
|
||||||
4. The authorization service returns a token.
|
4. The authorization service returns an opaque token representing the client's
|
||||||
|
authorized access.
|
||||||
5. The client retries the original request with the token embedded in the
|
5. The client retries the original request with the token embedded in the
|
||||||
request header.
|
request header.
|
||||||
6. The Registry authorizes the client and begins the push/pull session as
|
6. The Registry authorizes the client by validating the token and the claim set
|
||||||
usual.
|
embedded within it and begins the push/pull session as usual.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Registry Clients capable of generating key pairs which can be used to
|
- Registry clients which can understand and respond to token auth challenges
|
||||||
authenticate to an authorization server.
|
returned by the resource server.
|
||||||
- An authorization server capable of managing user accounts, their public keys,
|
- An authorization server capable of managing access controls to their
|
||||||
and access controls to their resources hosted by any given service (such as
|
resources hosted by any given service (such as repositories in a Docker
|
||||||
repositories in a Docker Registry).
|
Registry).
|
||||||
- A Docker Registry capable of trusting the authorization server to sign tokens
|
- A Docker Registry capable of trusting the authorization server to sign tokens
|
||||||
which clients can use for authorization and the ability to verify these
|
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.
|
tokens for single use or for use during a sufficiently short period of time.
|
||||||
|
@ -76,39 +77,55 @@ The new registry workflow is more like this:
|
||||||
|
|
||||||
This document borrows heavily from the [JSON Web Token Draft Spec](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32)
|
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
|
The described server is meant to serve as a standalone access control manager
|
||||||
centralized access control list for resources hosted by other services which
|
for resources hosted by other services which wish to authenticate and manage
|
||||||
wish to authenticate and manage authorizations using this services accounts and
|
authorizations using a separate access control manager.
|
||||||
their public keys.
|
|
||||||
|
|
||||||
Such a service could be used by the official docker registry to authenticate
|
Such a service could be used by the official Docker Registry to authenticate
|
||||||
clients and verify their authorization to docker image repositories.
|
clients and verify their authorization to Docker image repositories.
|
||||||
|
|
||||||
Docker will need to be updated to interact with an authorization server to get
|
As of Docker 1.6, the registry client within the Docker Engine has been updated
|
||||||
an authorization token.
|
to handle such an authorization workflow.
|
||||||
|
|
||||||
## How to authenticate
|
## How to authenticate
|
||||||
|
|
||||||
Today, registry clients first contact the index to initiate a push or pull.
|
Registry V1 clients first contact the index to initiate a push or pull. Under
|
||||||
For v2, clients should contact the registry first. If the registry server
|
the Registry V2 workflow, clients should contact the registry first. If the
|
||||||
requires authentication it will return a `401 Unauthorized` response with a
|
registry server requires authentication it will return a `401 Unauthorized`
|
||||||
`WWW-Authenticate` header detailing how to authenticate to this registry.
|
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
|
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
|
repository `samalba/my-app`. For the registry to authorize this, I will need
|
||||||
`push` access to the `samalba/my-app` repository or `push` access to the whole
|
`push` access to the `samalba/my-app` repository. The registry will first
|
||||||
`samalba` namespace in general. The registry will first return this response:
|
return this response:
|
||||||
|
|
||||||
```
|
```
|
||||||
HTTP/1.1 401 Unauthorized
|
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"
|
Content-Type: application/json; charset=utf-8
|
||||||
|
Docker-Distribution-Api-Version: registry/2.0
|
||||||
|
Www-Authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:samalba/my-app:pull,push"
|
||||||
|
Date: Thu, 10 Sep 2015 19:32:31 GMT
|
||||||
|
Content-Length: 235
|
||||||
|
Strict-Transport-Security: max-age=31536000
|
||||||
|
|
||||||
|
{"errors":[{"code":"UNAUTHORIZED","message":"access to the requested resource is not authorized","detail":[{"Type":"repository","Name":"samalba/my-app","Action":"pull"},{"Type":"repository","Name":"samalba/my-app","Action":"push"}]}]}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the HTTP Response Header indicating the auth challenge:
|
||||||
|
|
||||||
|
```
|
||||||
|
Www-Authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:samalba/my-app:pull,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)
|
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
|
This challenge indicates that the registry requires a token issued by the
|
||||||
`https://auth.docker.com/v2/token/` using the `service` and `scope` values from
|
specified token server and that the request the client is attempting will
|
||||||
the `WWW-Authenticate` header.
|
need to include sufficient access entries in its claim set. To respond to this
|
||||||
|
challenge, the client will need to make a `GET` request to the URL
|
||||||
|
`https://auth.docker.io/token` using the `service` and `scope` values from the
|
||||||
|
`WWW-Authenticate` header.
|
||||||
|
|
||||||
## Requesting a Token
|
## Requesting a Token
|
||||||
|
|
||||||
|
@ -132,45 +149,51 @@ the `WWW-Authenticate` header.
|
||||||
header. The above example would be specified as:
|
header. The above example would be specified as:
|
||||||
<code>scope=repository:samalba/my-app:push</code>.
|
<code>scope=repository:samalba/my-app:push</code>.
|
||||||
</dd>
|
</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>
|
</dl>
|
||||||
|
|
||||||
#### Description
|
#### Example Token Request
|
||||||
|
|
||||||
Requests an authorization token for access to a specific resource hosted by a
|
For this example, the client makes an HTTP GET request to the following URL:
|
||||||
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
|
https://auth.docker.io/token?service=registry.docker.io&scope=repository:samalba/my-app:pull,push
|
||||||
Host: auth.docker.com
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The server first inspects the client certificate to extract the subject key and
|
The token server should first attempt to authenticate the client using any
|
||||||
lookup which account it is associated with. The client is now authenticated
|
authentication credentials provided with the request. As of Docker 1.8, the
|
||||||
using that account.
|
registry client in the Docker Engine only supports Basic Authentication to
|
||||||
|
these token servers. If an attempt to authenticate to the token server fails,
|
||||||
|
the token server should return a `401 Unauthorized` response indicating that
|
||||||
|
the provided credentials are invalid.
|
||||||
|
|
||||||
The server next searches its access control list for the account's access to
|
Whether the token server requires authentication is up to the policy of that
|
||||||
the repository `samalba/my-app` hosted by the service `registry.docker.com`.
|
access control provider. Some requests may require authentication to determine
|
||||||
|
access (such as pushing or pulling a private repository) while others may not
|
||||||
|
(such as pulling from a public repository).
|
||||||
|
|
||||||
|
After authenticating the client (which may simply be an anonymous client if
|
||||||
|
no attempt was made to authenticate), the token server must next query its
|
||||||
|
access control list to determine whether the client has the requested scope. In
|
||||||
|
this example request, if I have authenticated as user `jlhawn`, the token
|
||||||
|
server will determine what access I have to the repository `samalba/my-app`
|
||||||
|
hosted by the entity `registry.docker.io`.
|
||||||
|
|
||||||
|
Once the token server has determined what access the client has to the
|
||||||
|
resources requested in the `scope` parameter, it will take the intersection of
|
||||||
|
the set of requested actions on each resource and the set of actions that the
|
||||||
|
client has in fact been granted. If the client only has a subset of the
|
||||||
|
requested access **it must not be considered an error** as it is not the
|
||||||
|
responsibility of the token server to indicate authorization errors as part of
|
||||||
|
this workflow.
|
||||||
|
|
||||||
|
Continuing with the example request, the token server will find that the
|
||||||
|
client's set of granted access to the repository is `[pull, push]` which when
|
||||||
|
intersected with the requested access `[pull, push]` yields an equal set. If
|
||||||
|
the granted access set was found only to be `[pull]` then the intersected set
|
||||||
|
would only be `[pull]`. If the client has no access to the repository then the
|
||||||
|
intersected set would be empty, `[]`.
|
||||||
|
|
||||||
|
It is this intersected set of access which is placed in the returned token.
|
||||||
|
|
||||||
The server will now construct a JSON Web Token to sign and return. A JSON Web
|
The server will now construct a JSON Web Token to sign and return. A JSON Web
|
||||||
Token has 3 main parts:
|
Token has 3 main parts:
|
||||||
|
@ -214,14 +237,16 @@ Token has 3 main parts:
|
||||||
<code>sub</code> (Subject)
|
<code>sub</code> (Subject)
|
||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
The subject of the token; the id of the client which requested it.
|
The subject of the token; the name or id of the client which
|
||||||
|
requested it. This should be empty (`""`) if the client did not
|
||||||
|
authenticate.
|
||||||
</dd>
|
</dd>
|
||||||
<dt>
|
<dt>
|
||||||
<code>aud</code> (Audience)
|
<code>aud</code> (Audience)
|
||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
The intended audience of the token; the id of the service which
|
The intended audience of the token; the name or id of the service
|
||||||
will verify the token to authorize the client/subject.
|
which will verify the token to authorize the client/subject.
|
||||||
</dd>
|
</dd>
|
||||||
<dt>
|
<dt>
|
||||||
<code>exp</code> (Expiration)
|
<code>exp</code> (Expiration)
|
||||||
|
@ -305,6 +330,7 @@ Token has 3 main parts:
|
||||||
"type": "repository",
|
"type": "repository",
|
||||||
"name": "samalba/my-app",
|
"name": "samalba/my-app",
|
||||||
"actions": [
|
"actions": [
|
||||||
|
"pull",
|
||||||
"push"
|
"push"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -416,8 +442,16 @@ claim set within. The registry will:
|
||||||
level of access for the operation the client is attempting to perform.
|
level of access for the operation the client is attempting to perform.
|
||||||
- Verify that the signature of the token is valid.
|
- 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
|
If any of these requirements are not met, the registry will return a
|
||||||
the authorization server. If anything, it would only need to update a list of
|
`403 Forbidden` response to indicate that the token is invalid.
|
||||||
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
|
**Note**: it is only at this point in the workflow that an authorization error
|
||||||
server.
|
may occur. The token server should *not* return errors when the user does not
|
||||||
|
have the requested authorization. Instead, the returned token should indicate
|
||||||
|
whatever of the requested scope the client does have (the intersection of
|
||||||
|
requested and granted access). If the token does not supply proper
|
||||||
|
authorization then the registry will return the appropriate error.
|
||||||
|
|
||||||
|
At no point in this process should the registry need to call back to the
|
||||||
|
authorization server. The registry only needs to be supplied with the trusted
|
||||||
|
public keys to verify the token signatures.
|
||||||
|
|
Loading…
Reference in a new issue