如果你有一下需求,请阅读本文:
- 想要理解Docker Registry V2认证机制
- 想要根据自己的业务构建企业级镜像仓库
- 想要理解Haboar这类工具的实现方式,不甘只是工具的使用者
Docker Registry V2的认证过程
首先我们来了解一下当我们尝试从docker registry拉取镜像时实际的流程是什么样的?如下图所示
- docker daemon尝试从docker registry拉取镜像;
- 如果docker registry需要进行授权时,registry将会放回401 Unauthorized响应,同时在返回的头信息中包含了docker client如何进行认证的信息
- docker client根据registry返回的信息,向auth server发送请求获取认证token
- auth server则根据自己的业务实现去验证提交的用户信息查询用户数据仓库中是否存在相关信息(数据库或者LDAP)
- 用户数据仓库返回用户的相关信息
- auth server将会根据查询的用户信息,生成token令牌,以及当前用户所具有的相关权限信息
- docker client携带auth server返回的token令牌再次尝试访问docker registry.
- docker registry验证用户提交的token令牌信息,通过后则开始镜像的pull或者push动作
配置并启用Docker Registry启用用户认证
默认情况下docker registry将会从/etc/docker/registry/config.yml读取所有的配置信息。
完整的registry配置信息如下:
1 | version: 0.1 |
在本文当中我们主要关注auth部分配置
1 | auth: |
auth配置部分是可选的,docker registry当前支持3种认证实现方式:silly,token,htpasswd;registry默认不开启auth配置。用户可以自定义其中一种实现来完成registry的认证配置。
我们有两种方式可以实现自定义配置:
- 创建新的config.xml,并覆盖默认配置
1 | docker run -d -p 5000:5000 --restart=always --name registry \ |
- 使用环境变量覆盖默认registry配置,以docker-compose为例:
1 | registry: |
为了实现registry与业务系统的集成,我们配置registry auth的实现方式为token
1 | auth: |
- auth.token.realm: auth server用于认证的Endpoint地址
- auth.token.service: 用于请求auth server的携带的service名称
- auth.token.issuer: registry信任的auth server名称
- auth.token.rootcertbundle: 用户验证token签名的公钥文件
其中auth.crt为使用openssl生成的公钥文件,用于registry验证token的合法性
以以上配置为例,我们来看看registry与auth server的实际交互过程:
例如当用户尝试向registry push镜像samalba/my-app时,为了完成当前操作,用户需要对repository samalba/my-app具有push的权限,registry将会返回401 Unuthorized信息
1 | HTTP/1.1 401 Unauthorized |
其中需要注意的内容是:
1 | Www-Authenticate: Bearer realm="http://172.16.137.217:8080/auth",service="Docker registry",scope="repository:samalba/my-app:pull,push" |
这里registry告诉docker client你需要从http://172.16.137.217:8080/auth获取认证信息,并且携带请求参数service以及scope
返回信息根据用户设置的auth配置产生
Docker Client提供用户输入用户名和密码后向auth server的Endpoint发送请求:
1 | http://172.16 :8080/auth?service=Docker registry&scope=repository:samalba/my-app:pull,push |
同时在http head中包含用户相关的登录信息
1 | authorized: Basic YWtaW46cGzc3dvmcQ= |
此时我们自己实现的auth server只需要从http head中通过base64获取登录的用户名和密码,并且验证登录信息的合法性,同时根据业务数据返回用户的实际权限(pull, push)即可.
基于JWT协议规范使用私钥对返回内容签名生成相应的Token即可。
1 | HTTP/1.1 200 OK |
当docker client获取到token之后,client会将得到的token作为http请求头信息再次尝试访问registry,registry使用公钥解密并验证token内容,并根据token包含的权限信息完成实际的操作
如何生成符合Docker Registry规范的Json Web Token详解
生成用户加密的公私钥
1 | openssl req -newkey rsa:4096 -nodes -sha256 -keyout auth.key -x509 -days 365 -out auth.crt |
此时我们将得到两个文件加密文件auth.cert和auth.key
Docker Registry端
auth.cert对应docker registry的auth.token.rootcertbundle配置项,用户验证docker client请求时提供的token是否合法
1 | auth: |
Auth Server端
当Auth Server拦截到到认证请求
1 | http://172.16 :8080/auth?service=Docker registry&scope=repository:samalba/my-app:pull,push |
根据请求信息验证授权完成之后,我们将根据以下规则生成json web token内容。
生成token主要由3个部分组成:
- 生成jwt的Header信息
1 | { |
- typ: 当使用JWT时,typ固定为“JWT”
- alg: 对应私钥文件的加密方式,本示例中即对应auth.key文件的加密方式,可以通过代码读取私钥文件获取
- kid: 根据docker提供的规则生成公钥文件的kid,registry会根据同样的算法获取公钥的kid,如果匹配失败则认证失败
1 | - Take the DER encoded public key which the JWT token was signed against. |
对于基于golang开发的同学而言可以直接使用https://github.com/docker/libtrust/blob/master/key.go提供的KeyID方法获取公钥的keyid
- 设置jwt的payload信息Claim Set
1 | { |
3,最后使用auth.key私钥进行签名
1 | RSASHA256( |
从代码来看应该更容易理解
1 | Map<String, Object> header = getJWTHeader(); |
代码实现简化版
添加Endpoint用于响应docker client认证请求
1 |
|
添加RegistryAuthServer实现授权验证以及生成Token令牌
备注:作为示例keyid我们直接使用docker提供的libtrust库从公钥文件生成,另外代码中的硬编码,字符常量请忽略~just demo..
1 | @Component |