[docs/spec/auth] Clarify the Token auth workflow
Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)master
							parent
							
								
									6bf5a049ff
								
							
						
					
					
						commit
						fb481ef843
					
				| 
						 | 
					@ -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:
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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 New Issue