从源码看Docker Registry v2中的Token认证实现机制

■ 文/ 天云软件 容器技术团队

概述

Docker Registry v2在认证方面提供了基于Token的认证方式,允许用户使用自己的认证服务来进行认证,这就使得在企业内部利用现有的认证系统来实现对访问Docker Registry的用户进行认证成为可能。那么该如何使用企业内部已有的认证系统来实现Token认证,本文将从阅读源码的角度来对Token认证的实现过程进行说明。本次分析的Docker Registry版本号:v2.4.0。

认证流程

我们以在命令行中输入docker login https://reg.skycloud.com为例来看一下认证流程:

  1. docker client接受用户的输入命令,将命令转化为调用engine-api的RegistryLogin方法;
  2. 在api的RegistryLogin方法中通过http调用registry中的auth方法;
  3. 在auth方法中由于是v2版本的,所以会调用loginV2方法;
  4. 在loginv2方法中会进行调用/v2/接口,该接口会对请求进行认证。,此时的请求中并没有包含token信息,认证失败,会返回401错误,同时会在header中返回去哪里认证的服务器的地址;
  5. registry client收到该返回结果后,便会去返回的认证服务器那里进去认证,向认证服务器发送的request的header中包含有加密的用户名和密码;
  6. 认证服务器从header中获取到加密的用户名和密码,这个时候就可以结合实际的认证系统进行认证;
  7. 认证成功后,需要生成一个token,并返回;
  8. registry client会拿着返回的token再次尝试向registry server发生请求,这次由于带有token,请求验证成功,返回状态码为200;
  9. docker client端接受到返回的状态码为200,说明操作成功,控制台会出现“Login Succeeded”。

至此,登录完成。整个交互过程如下图所示:

token01Token生成

从以上的流程中我们可以看出,生成用户验证的Token是整个流程的关键,Docker Registry已经帮我们完成了请求认证服务器以及对token进行验证,我们只需要按照它的要求生成一个token即可,下面我们一起看一下如何生成token。

我们可以在docker registry中看到token是如何定义的,文件的路径在:distribution/registry/token/token.go中,有对token的结构体定义,从作者的注释上看出,token是通过JWT(JSON Web Token)来实现的,那我们就来看看JSON Web Token是什么。具体的关于JWT的解释参考这里:json-web-token。总的来说,JWT由header、Claims Set和根据header和Claims Set的签名。下面我们分别来看一下各个部分的详细内容。

1. Header

在该部分中描述了token的类型以及使用的hash算法,除此之外,还加入了一些与认证相关的信息,例如:KeyID、X5c等。header的定义如下:

type Header struct { 
 Type string json:”typ” 
 SigningAlg string json:”alg” 
 KeyID string json:”kid,omitempty” 
 X5c []string json:”x5c,omitempty” 
 RawJWK *json.RawMessage json:”jwk,omitempty” 
}

其中,KeyID是采用github.com/docker/libtrust根据公钥生成,格式为:

ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP 

2. ClaimSet

该部分为整个token的核心,包含了进行认证的必要信息,具体包括:

– “iss” (Issuer):代表了发起请求的实体,是一个大小写敏感的字符串
– “sub” (Subject):代表的是JWT的主题,该字段要求在上下文中或者是全局唯一,可以放入用户帐号;
– “aud” (Audience):代表的是JWT希望的接受者;
– “exp” (Expiration Time):代表的是token的过期时间;
– “nbf” (Not Before):代表的是JWT不能早于该时间来处理;
– “iat” (Issued At):代表的是JWT的签发时间。

docker registry在此基础上还增加2个字段:

– “JWTID”: 这是一个基于base64加密的随机长度的字符串;
– “Access”:代表了访问和操作权限

claimset定义的结构体如下:

type ClaimSet struct { 
 Issuer string json:”iss” 
 Subject string json:”sub” 
 Audience string json:”aud” 
 Expiration int64 json:”exp” 
 NotBefore int64 json:”nbf” 
 IssuedAt int64 json:”iat” 
 JWTID string json:”jti” 
 Access []*ResourceActions json:”access” 
}

具体详情可以参考这里:http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-19#section-4.1.1

3. Signature

这一部分是JWT根据第一部分和第二部分的签名。最后整个token按照这样的格式拼接组成:

Base64Url(header)+”.”+Base64Url(ClaimSet)+”.”+Signature.

Token验证

docker registry在收到认证服务器番返回的token后,会对token进行验证,具体的流程为:用”.”将token分割为三部分,分别对每一部分进行验证,这里需要重点关注下第二部分的验证,ClaimSet里定义了token的签发时间及过期时间,这里的时间都是utc时间,为了使验证通过,一定要保证认证服务器的系统时间与docker registry server的系统时间保持一致,否则,在进行时间判断时,会因为时间认证设置的时间区间内而导致认证失败。具体的验证token的代码文件为:distribution\registry\auth\token\token.go,方法为:Verify。有兴趣的读者可以自行阅读源码来进一步了解验证的过程。

总结

通过以上的分析,我们可以看出,docker registry为我们定义好了token的格式,并提供了配置认证服务器的地址。只要按照定义的格式来生成token,我们就可以提供企业级的docker registry服务。在私有云环境中,可以根据已有的多租户模型对用户访问、操作docker registry进行身份认证与权限控制,使得docker registry可以在私有云环境中提供企业级的镜像服务。