最近,我们遇到了一个Docker私有镜像拉取的错误,花费了数小时研究Docker私有仓库(Registry)凭证存储的细节才能弄清楚。尽管最终修复起来很容易,但是我们在凭据存储设计的过程中了解了一两件事,以及如何对Docker私有仓库(Registry)凭证进行安全配置。
Docker私有镜像拉取失败的情况如下:
Blimp有时需要从Docker私有仓库(Registry)中拉取私有镜像。这通常可以正常工作,但是不幸的是,有时拉取私有镜像时,会收到以下错误消息:
Get https://1234.dkr.ecr.us-east-1.amazonaws.com/v2/blimp/blimp/manifests/v0.1: no basic auth credentials
要了解我们是如何解决的,首先你需要了解Docker凭证。
Docker的外部证书存储
一般建议,存储Docker凭证要放在外部凭证存储中。通常位于~/.docker/config.json的Docker配置文件中。
企业中使用私有镜像仓库,通常都需要开启认证,认证凭据可能是用户在企业中通用的账户。但docker login以后,会在~/.docker/config.json中保存base64以后的用户名、密码,这样,在一些多人使用的服务器上,就会出现账号泄露的问题。
有没有解决方法呢?
docker提供credentials store,也就是讲密码存储到外置的credentials store中。
目前支持如下几种:
- D-Bus Secret Service: https://github.com/docker/docker-credential-helpers/releases
- Apple macOS keychain: https://github.com/docker/docker-credential-helpers/releases
- Microsoft Windows Credential Manager: https://github.com/docker/docker-credential-helpers/releases
- pass: https://github.com/docker/docker-credential-helpers/releases
对于linux服务器来说,只能选择pass,因为D-Bus需要X11支持,而Apple和Microsoft看上去就不像是给Linux准备的。
因此,为了提高Docker凭据的安全性,你可以在Docker配置文件中使用两个字段来配置Docker获取凭据和存储凭据的方式:credsStore和credHelpers。
credsStore告诉Docker使用哪个帮助程序与凭证存储(credentials store)进行交互。所有帮助程序的名称都以docker-credential-开头,credsStore的值为帮助程序名称的后缀。
如果你使用Mac笔记本电脑,则可能会使用Mac OS钥匙串。使用Mac OS钥匙串后的帮助程序的名称是docker-credential-osxkeychain。因此,你Docker配置文件config.json将包括以下内容:
{ "credsStore": "osxkeychain" }
如果要查看Docker当前为你提供的凭据是什么,可以使用list命令。例如:
docker-credential-osxkeychain list
输出信息是一对服务器和用户名列表。例如:
{ "http://quay.io":"kklin", "https://index.docker.io":"kevinklin" }
credHelpers是帮助程序( helpers ),用于生成短暂的凭据。例如,如果你使用gcr,gcloud会安装一个credHelper来使用你的Google登录名获取令牌的。采用这种方式后,Docker永远不会直接拥有你的Google凭据, docker-credential-gcloud充当Docker和Google凭据之间的中间人。
但即使我们对Docker login密码加密保存,仍然得到如下的错误消息:
Get https://1234.dkr.ecr.us-east-1.amazonaws.com/v2/blimp/blimp/manifests/v0.1: no basic auth credentials
通过上文,我们知道可以通过运行docker-credential-osxkeychain list和get命令来查看1234.dkr.ecr.us-east-1.amazonaws.com的凭据,分析判断那么为什么会收到一个错误消息-没有任何凭据(no basic auth credentials)?
Docker1.11版本前:私有仓库密码存储在配置文件中
Docker1.11版本前,Docker配置文件config.json通过auths字段来存储凭据,直到2016年1.11版本才能够使用外部凭据存储。
每当你登录私有仓库时,Docker都会将auths的值设置为你的密码。你的配置文件可能包含以下内容:
{ "auths": { "https://index.docker.io/v1/": { "auth": "YW11cmRhY2E6c3VwZXJzZWNyZXRwYXNzd29yZA==" }, "localhost:5001": { "auth": "aGVzdHVzZXI6dGVzdHBhc3N3b3Jk" } } }
因此在采用credsStore后,当你使用docker login进行登录时,你的凭据还会被存储,但其中的字段auths不包含用户名或密码。结果看起来像这样:
{ "auths": { "https://index.docker.io/v1/": {} }
但Docker拉取镜像时从auths 和 credsStore两者同时获取凭据。因此,Docker拉取镜像时会有两份凭据,一份具有正确的用户名和密码,而一份则完全没有密码。
不幸的是,Docker偏爱使用auths中的https://的凭证,并尝试使用空凭证拉取镜像。因此,会有no basic auth credentials错误。
通过上文,我们确定了问题是一个空凭证被添加到 Docker配置文件config.json 中,我们就很容易解决该问题。我们需要做的就是添加一条if语句以跳过空凭据:
具体参考: https://github.com/kelda/blimp/blob/master/cli/up/up.go
addCredentials := func(authConfigs map[string]clitypes.AuthConfig) { for host, cred := range authConfigs { // Don't add empty config sections. if cred.Username != "" || cred.Password != "" || cred.Auth != "" || cred.Email != "" || cred.IdentityToken != "" || cred.RegistryToken != "" { creds[host] = types.AuthConfig{ Username: cred.Username, Password: cred.Password, Auth: cred.Auth, Email: cred.Email, ServerAddress: cred.ServerAddress, IdentityToken: cred.IdentityToken, RegistryToken: cred.RegistryToken, } } } }
Docker凭证安全风险
很多使用Docker的组织,可能仍在使用传统的auths方法。如果是这样,你的~/.docker/config.json可能看起来像这样:
{ "auths": { "https://index.docker.io/v1/": { "auth": "YW11cmRhY2E6c3VwZXJzZWNyZXRwYXNzd29yZA==" }, "localhost:5001": { "auth": "aGVzdHVzZXI6dGVzdHBhc3N3b3Jk" } } }
这看起来很安全,密码似乎是一堆乱码。你们会想,这些密码肯定是加密的吧?
其实,Docker所做的只是使用base64对密码进行编码。正如David Rieger在Hacker Noon上指出的那样,
Base64乍一看可能看起来像加密,但事实并非如此。Base64是一种编码方案,不是加密方案。你只需复制Base64字符串,然后在几秒钟内将其转换为ASCII。
那个看似安全的密码aGVzdHVzZXI6dGVzdHBhc3N3b3Jk,读取密码所需要做的只是对base64进行解码:
$ echo aGVzdHVzZXI6dGVzdHBhc3N3b3Jk| base64 -D hestuser:testpassword
因此,如果未对Docker配置文件中的密码加密,则Docker将以纯文本格式存储你的密码。纯文本格式的好消息是,解决问题很容易,但是也容易被泄露和攻击。
为此,Docker提供了credentials store,也就是把密码存储到外置的credentials store中。
译文链接: https://dzone.com/articles/what-a-mysterious-bug-taught-us-about-how-docker-s