# auth

## Registry的Token认证

registry的认证方式有三种，本文介绍最常用的token认证机制。

### 认证流程

我们以`docker pull`操作描述一下token认证的流程

![](https://647570976-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M69DxQvdw1iBrD0AYRc%2Fsync%2F746544f65a644dcf473f6f5a3020c9b16e44d599.png?generation=1592396948316108\&alt=media)

1、docker-daemon向registry发起pull操作\
2、如果registry开启了认证，则会返回`401 Unauthorized`的响应，并且在响应头中携带如何去认证的信息\
3、docker-daemon向授权服务（authorization service）发起请求，获取token\
4、授权服务返回一个token，该token中携带了权限信息\
5、docker-daemon重新向registry发起同样的请求，并且在头部中携带token\
6、registry对token进行验证，然后开始正常的pull流程

### registry开启token认证

registry要开启token认证，需要在配置文件中添加如下内容，以下四个参数都为必填。

```
auth:
    token:
        realm: {token-realm}
        service: {token-service}
        issuer: {registry-token-issuer}
        rootcertbundle: {/root/certs/bundle}
```

* realm：授权服务器的url
* service：被认证的服务的名字
* issuer：token签发者的名字，授权服务器签发的token中也有该字段；registry与授权服务的该字段要配置成一致，以便registry验证token是否由目标授权服务签发
* rootcertbundle：根证书的绝对路径

以下为本地harbor的registry配置文件中关于token配置的样例：

```
auth:
  token:
    realm: http://192.168.1.103:8021/service/token
    service: token-service
    issuer: registry-token-issuer
    rootcertbundle: /etc/registry/root.crt
```

### 如何认证

当docker-daemon没有携带任何认证信息向registry发起某个API请求时（对应上图中的步骤1），registry会返回`401 Unauthorized`，并且在响应头的参数`WWW-Authenticate`中给出如何去获取token的详细信息。

比如，pull镜像的第一步是获取manifest，那么我们没有携带任何认证信息去拉取本地harbor中的`library/registry:2.5.0`镜像的manifest文件，会得到如下的响应头（docker-daemon也是请求registry的API，这里我们使用curl模拟下载镜像的API请求）：

```
$ curl -I 192.168.1.103:8021/v2/library/registry/manifests/2.5.0
HTTP/1.1 401 Unauthorized
Server: nginx/1.11.5
Date: Wed, 05 Sep 2018 06:55:23 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 148
Connection: keep-alive
Docker-Distribution-Api-Version: registry/2.0
Www-Authenticate: Bearer realm="http://192.168.1.103:8021/service/token",service="token-service",scope="repository:library/registry:pull"
```

注意头部中的`Www-Authenticate`字段的值，告诉我们应该如何去获取token：

```
Www-Authenticate: Bearer realm="http://192.168.1.103:8021/service/token",service="token-service",scope="repository:library/registry:pull"
```

其中`realm`与`service`字段的值就是registry的配置文件中的值；`scope`字段的值，是registry根据接收到的API请求生成的，关于scope字段的语法请参考[官网](https://pshizhsysu.gitbook.io/docker/registry/auth)

### 请求Token

**QUERY参数**

* `service`：使用registry返回的值
* `scope`：使用registry返回的值
* `client_id`：（可选）发起请求的客户端的标识，比如docker-daemon发起的请求会将该字段设置为`docker`，harbor的复制策略中会将该字段设置为`harbor-registry-client`

**响应BODY字段**

* `token`：授权服务器返回的带有授权信息的token
* `issued_at`：（可选）token的签发时间，UTC标准时间格式
* `expires_in`：（可选）token在多少秒以后过期，如果没有说明则默认为60秒

**Example**

接下来，我们去获取token

```
$ curl 192.168.1.103:8021/service/token?service=token-service\&scope=repository:library/registry:pull\&client_id=curl
{
  "expires_in": 1800,
  "issued_at": "2018-09-05T08:34:40Z",
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkhNNjY6NkNYUzpaQlBROk1ENVo6QlJZVTpTVE9EOkNCUEs6Uk5ORjpYN0VDOkZMUUw6TFNFMjpLUUtTIn0.eyJpc3MiOiJyZWdpc3RyeS10b2tlbi1pc3N1ZXIiLCJzdWIiOiIiLCJhdWQiOiJ0b2tlbi1zZXJ2aWNlIiwiZXhwIjoxNTM2MTM4MjgwLCJuYmYiOjE1MzYxMzY0ODAsImlhdCI6MTUzNjEzNjQ4MCwianRpIjoiZFpxVkgxVDFjZkhXdnFZTiIsImFjY2VzcyI6W3sidHlwZSI6InJlcG9zaXRvcnkiLCJuYW1lIjoibGlicmFyeS9yZWdpc3RyeSIsImFjdGlvbnMiOlsicHVsbCJdfV19.ilCKa2-oJ9bKQpAo8ntcx1lHpbs0BcWYtbRrvItHAProaDEDpll9EZrzkzg6XR9OOLByFm_oJKKk8Y_wYwQfxYdjvhLbFjCNXzE6MckY8dEcSR5BmYxOK54zAqNVkw24ugUcagGFi7p8Gy0YZqBqf7AP8qCarhuWhKsZ7B4esMQk2xBEn1hh8r_9tb6wnZOkDl7trW0IWbPkqKSaP8ycq8oS9J0T6zaItyTLnERsV_GFJOh6DdfhSYzGwoWUFQH6cmp05ZHXF_-4O6N6d8tosGH9gTsam-ffeVHmWp8da_gpS_R15z3ELR5I2FO0s4gWo1UbTI3yuyV8stSURrCs6GHZSMb2C9_2R2r_Q-uDKmdpoazw2G1DxM3PgfXEwANWEjPJMjD0areUXmjwz_hefSMqYFxLi26TaQinG0th7pNz5m0qroefOy1AGyhRZK-t8rsduZJ9EWQCqtXHrPbTES0FoItJmcMqcJZcvQsrJsBMirtijvGdNn55l44-eDFyrIuExerHzU1dJoSijCtqIYxbdnclLE8HSP-vnBD5TOAJoUUdUfA1N8TvF2QqDjr_LATUOctahrFoWiuDjrFXH-ptmcJJ6lPjo1oCOne3ImKe_mieRR7YCOQLejuCbItIIweuqwBzJU5d33k3Drra0qvbvk-MkO7iBNgpCtfWqD8"
}
```

得到的token是一个很长的字符串。不过我们可以复制上面的token字段的内容到[jwt.io](https://github.com/pshizhsysu/docker/tree/f8e99cafd95650f9fc654ebc5fc9df01b2a5c0e2/registry/jwt.io)，查看token的明文，得到payload的内容如下：，查看token的明文，得到payload的内容如下：

```
{
 "iss": "registry-token-issuer",
 "sub": "",
 "aud": "token-service",
 "exp": 1536138280,
 "nbf": 1536136480,
 "iat": 1536136480,
 "jti": "dZqVH1T1cfHWvqYN",
 "access": [
  {
   "type": "repository",
   "name": "library/registry",
   "actions": [
    "pull"
   ]
  }
 ]
}
```

从`access`字段可以看出，该token对`library/registry`这个`repository`具有`pull`权限。

**携带用户信息**

在上面获取token的请求中，没有携带任何的用户信息，所以payload的`sub`字段为空。但是harbor的授权服务器返回的token还是具有pull权限，这是因为harbor允许匿名用户pull `library`中的镜像。不过我们可以携带harbor的admin用户信息去获取token，如下：

```
$ curl -H "Authorization: Basic YWRtaW46SGFyYm9yMTIzNDU=" 192.168.1.103:8021/service/token?service=token-service\&scope=repository:library/registry:pull\&client_id=curl
```

其中`YWRtaW46SGFyYm9yMTIzNDU=`是`admin:Harbor12345`使用base64加密后的字符串：

```
$ echo -n "admin:Harbor12345" | base64
YWRtaW46SGFyYm9yMTIzNDU=
```

### 使用Token

在得到token后，我们就可以在API请求的Header中添加token信息，比如下载镜像的manifest，这次请求就会正常返回了：

```
curl -H "Authorization: Bearer [token]" 192.168.1.103:8021/v2/library/registry/manifests/2.5.0
```

## 扩展知识

### 说说docker-login

docker login命令的使用方法如下：

```
docker login -u admin -p Harbor12345 192.168.1.103:8021
```

当docker login登录成功后，会在当前用户的 `~/.docker/config.json`中出现如下的内容：

```
{
    "auths": {
        "192.168.1.103:8021": {
            "auth": "YWRtaW46SGFyYm9yMTIzNDU="
        }
    }
}
```

其中字符串`YWRtaW46SGFyYm9yMTIzNDU=`是通过base64算法加密得到的字符串。关于base64算法的加密与解密如下：

```
$ echo -n "admin:Harbor12345" | base64
YWRtaW46SGFyYm9yMTIzNDU=
$ echo -n "YWRtaW46SGFyYm9yMTIzNDU=" | base64 -d
admin:Harbor12345
```

当docker login成功后，我们再执行docker pull或push操作的时候，docker client便会从当前用户下查找registry的登录信息，并携带给docker daemon，docker-daemon便会在向Authorization Service请求token的API请求头中携带这个字符串。
