Docker私有镜像拉取错误no basic auth credentials

2023年 7月 9日 54.7k 0

最近,我们遇到了一个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

相关文章

KubeSphere 部署向量数据库 Milvus 实战指南
探索 Kubernetes 持久化存储之 Longhorn 初窥门径
征服 Docker 镜像访问限制!KubeSphere v3.4.1 成功部署全攻略
那些年在 Terraform 上吃到的糖和踩过的坑
无需 Kubernetes 测试 Kubernetes 网络实现
Kubernetes v1.31 中的移除和主要变更

发布评论