如何自然的将Harbor集成到kubernetes集群中
by 伊布
我们计划使用Harbor来做镜像管理。相对原生的docker registry,Harbor有一些优势,例如可以集成用户认证、提供项目的概念、项目间可以隔离、镜像复制等功能。
Harbor的认证一般可以采用DB或者LDAP的方式,在安装Harbor时指定 auth_mode
为 db_auth
或 ldap_auth
。以LDAP为例,安装后,用户可以使用LDAP账户/密码,登录到Harbor的web页面,也可以 使用LDAP账户/密码, docker login
到Harbor,Harbor会去LDAP服务器那里去bind做用户认证。
但在将Harbor集成到kubernetes时,会遇到一个问题。
我们希望在容器平台上可以获取到用户自己的项目、镜像,Harbor提供了这些API,但调用API时,只支持了basic auth和session两种方式,而容器平台、kubernetes都是OIDC token的方式。
所以,用户可以调用容器平台来获取自己的项目(使用用户自己的token),但容器平台无法使用该token来调用Harbor的API,因为Harbor并不能识别这个token。
一个办法是让用户在容器平台上保存自己的用户名、密码,容器平台请求Harbor API时,使用这个账户密码。但在企业应用中,通常账户密码都是ERP账户,其绑定了很多权限,用户无法接受密码泄露的风险。
有什么别的方法吗?
有的。实际上,从架构上来说,从容器平台调用Harbor API,和,从容器平台调用k8s API,本质上是一致的,所以只要Hack Harbor的源码,让它能支持OIDC token认证,就可以了。
用户从web登录Harbor、用户docker login,仍然是走之前Harbor的LDAP认证(注意Harbor会自己在DB中存储一份不包含密码的用户信息);容器平台调用Harbor API时,则使用新的OIDC token。
先来看看Harbor目前支持的认证(v1.5.1):
src/ui/filter/security.go
- secret: 内置secret,例如 ui、jobserver 调用 adminserver时,使用 -H “Authorization: Harbor-Secret xxx”。secret在adminserver创建时生成,可以去adminserver容器里看环境变量。
- basic auth:输入用户名、密码(docker login时使用?)。harbor针对basic auth,有3种认证方式:DB、LDAP、UAA。
- session:会话,web访问harbor时使用。
- token:Admiral时使用。Admiral是Vmware的一个容器管理平台。用户可以在Admiral上配置使用Harbor作为Reigistry;用户使用Harbor API时,携带token到harbor;harbor会再去Admiral这里去验证token合法性,验证方式就是拿token调用Admiral的endpoint后检查返回值。
其实,OIDC token的认证方式,和Admiral token是比较类似的:用户登录外部认证平台后获取token;携带该token调用Harbor API;Harbor调用外部认证平台鉴别该token是否合法。
因此,只要给 reqCtxModifiers
增加一种认证方式就可以了。注意,不要加在 unauthorizedReqCtxModifier
后面,这个是switch 的 default分支。
// Init ReqCtxMofiers list
func Init() {
// standalone
reqCtxModifiers = []ReqCtxModifier{
&secretReqCtxModifier{config.SecretStore},
&basicAuthReqCtxModifier{},
&sessionReqCtxModifier{},
&oidcTokenReqCtxModifier{},
&unauthorizedReqCtxModifier{}}
那么Harbor是怎么支持多种用户认证方式呢?其实就是轮询,只要有一个能通过就行。
// SecurityFilter authenticates the request and passes a security context
// and a project manager with it which can be used to do some authN & authZ
func SecurityFilter(ctx *beegoctx.Context) {
if ctx == nil {
return
}
req := ctx.Request
if req == nil {
return
}
// add security context and project manager to request context
for _, modifier := range reqCtxModifiers {
if modifier.Modify(ctx) {
break
}
}
}
简要写一下 oidcTokenReqCtxModifier
的逻辑。
func (t *oidcTokenReqCtxModifier) Modify(ctx *beegoctx.Context) bool {
token := getBearerToken(ctx.Request)
if len(token) == 0 {
return false
}
idToken, err := config.Verifier.Verify(ctx.Request.Context(), token)
if err != nil {
return false
}
var claims Claims
err = idToken.Claims(&claims)
if err != nil {
log.Info("token claim failed, ", err)
return false
}
user := &models.User{
Username: claims.Name,
}
pm := config.GlobalProjectMgr
secureCtx := local.NewSecurityContext(user, pm)
setSecurCtxAndPM(ctx.Request, secureCtx, pm)
return true
简单来说,就是从req中拿到Token,再去找 OIDC Provider(在我们的案例中是dex)Verify该token的合法性,并获取用户的一些基本信息,如用户名、email等。
pm(ProjectManager) 的作用是什么呢?走读一下代码会发现,它主要负责为一些api调用提供数据,例如查询用户的Project。
为什么这里直接使用了 GlobalProjectMgr
呢?实际上,我们这里只是 增加 做了OIDC的认证,用户仍然还是可以通过之前的LDAP方式登录到Harbor上去 docker login/push/pull的,也就是说,用户、项目、镜像等信息,仍然是存储在Harbor的数据库中的,因此,Project Manager直接使用 GlobalProjectMgr
即可,它在查询信息时,只需要从 secureCtx 中获取用户名就行了。
Subscribe via RSS