本文主要通过如何在 Docker Build 时使用 SSH 私钥进行认证,比如拉取私有仓库时就很有用。包括18.09版本之前的使用多阶段构建
方式,以及 18.09版本后的 --ssh
方式。
- 概述
在实际工作中,Build Docker 镜像时,经常碰上需要在 Docker 镜像内用到 SSH Private Key 的场景。比如构建镜像时要从 GitHub、GitLab 的私有库 Clone 代码,或者要安装私有库的 Gem、NPM Package 等。
而如果直接把自己的 SSH Private Key 打包到 Docker 镜像中的话,是存在很大安全风险的 。如何解决这个问题?
- 多阶段构建方式
通过参数将私钥传递到容器里,同时配合多阶段构建以解决直接把私钥打包进容器带来的安全风险。
使用多阶段构建,只要私钥不出现在最后一阶段,都是比较安全的,中间过程的镜像只会存放在本机,不会公开,因此问题也不大。
Dockerfile 如下:
分为三个阶段:
阶段一:拿到私钥并写入到/root/.ssh/id_rsa文件用于认证 阶段二:yarn build 阶段三:将编译好的产物 COPY 到 node 环境运行 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Stage 1: get sources from npm and git over ssh
FROM node:carbon AS sources
ARG SSH_KEY
ARG SSH_KEY_PASSPHRASE
RUN mkdir -p /root/.ssh &&
chmod 0700 /root/.ssh &&
ssh-keyscan bitbucket.org > /root/.ssh/known_hosts &&
echo ” ${ SSH_KEY } ” > /root/.ssh/id_rsa &&
chmod 600 /root/.ssh/id_rsa
WORKDIR /app/
COPY package*.json yarn.lock /app/
RUN eval ssh-agent -s &&
printf ” ${ SSH_KEY_PASSPHRASE } \n” | ssh-add $HOME /.ssh/id_rsa &&
yarn —pure-lockfile —mutex file —network-concurrency 1 &&
rm -rf /root/.ssh/
Stage 2: build minified production code
FROM node:carbon AS production
WORKDIR /app/
COPY —from= sources /app/ /app/
COPY . /app/
RUN yarn build:prod
Stage 3: include only built production files and host them with Node Express server
FROM node:carbon
WORKDIR /app/
RUN yarn add express
COPY —from= production /app/dist/ /app/dist/
COPY server.js /app/
EXPOSE 33330
CMD [ “node” , “server.js” ]
build 命令
1
2
3
4
docker build -t ezze/geoport:0.6.0
—build-arg SSH_KEY = ” $( cat ~/.ssh/id_rsa) ”
—build-arg SSH_KEY_PASSPHRASE = “my_super_secret”
./
- SSH mount type
同时 Docker 在18.09
版本后,推出了 BuildKit 的 SSH mount type ,我们也可以用这个特性来解决该问题。
Enable BuildKit
由于是 BuildKit 的特性,因此需要设置这个环境来开启 BuildKit
1
export DOCKER_BUILDKIT = 1
或者修改 /etc/docker/daemon.json 文件并重启 docker 服务永久开启 BuildKit,添加内容如下所示:
1
2
3
4
5
{
“features” : {
“buildkit” : true
}
}
Dockerfile 修改
首先需要在 Dockerfile 首行开启特性:
1
syntax=docker/dockerfile:1
这句话意思是用 docker/dockerfile:1
这个镜像来解析 Dockerfile
然后添加下面的内容,下载对应网站的公钥:
1
2
Download public key for github.com
RUN —mount= type = ssh mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
注意替换域名
然后,在 Dockerfile 中需要使用 SSH Private Key 的地方都加上--mount=type=ssh
这个 flag 指定该命令运行时有权限访问对应的 ssh 私钥,即:其他没有指定的命令是无法使用该私钥的。
比如 go 下载依赖就像这样:
1
RUN —mount= type = ssh go mod download
比如,Rails 项目安装有私有库的 Gem 包时,就写成这样:
1
RUN —mount= type = ssh bundle install
Docker build 命令
然后 docker build 时通过 --ssh
指定私钥:
1
docker build -f Dockerfile -t helloworld:1.0.0 . —ssh default = /root/.ssh/id_rsa
这种方式,既能正常使用上 SSH Private Key,又能使其在镜像中不留痕迹。完美!
完整版
完整 Dockerfile 内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
syntax=docker/dockerfile:1
Build the manager binary
FROM golang:1.19 as builder
ARG TARGETOS
ARG TARGETARCH
ENV GOPROXY = https://goproxy.cn
WORKDIR /workspace
Copy the go source
COPY . /workspace
Download public key for github.com
RUN —mount= type = ssh mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
cache deps before building and copying source so that we don’t need to re-download as much
and so that source changes don’t invalidate our downloaded layer
RUN —mount= type = ssh go mod download
Build
the GOARCH has not a default value to allow the binary be built according to the host where the command
was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
RUN CGO_ENABLED = 0 GOOS = ${ TARGETOS :- linux } GOARCH = ${ TARGETARCH } go build -a -o manager main.go
Use distroless as minimal base image to package the manager binary
Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY —from= builder /workspace/manager .
USER 65532:65532
ENTRYPOINT [ “/manager” ]
使用以下命令进行构建:
1
docker build -t ${ IMG } . —ssh default = ~/.ssh/id_rsa
FAQ
关闭 SSH 严格模式
在测试时出现以下错误:
1
2
#0 0.831 Host key verification failed.
#0 0.831 fatal: Could not read from remote repository.
这是因为 ssh 不能识别远程主机提供的秘钥,默认情况下会询问是否信任该秘钥,但是在非交互的环境里会直接拒绝掉。
Dockerfile 里的 ssh-keycan github.com > known_hosts
这句就是添加到信任列表,也有可能没生效。可以试试加上下面这句,直接关闭 SSH 的严格模式:
1
2
3
Configure ssh to trust unknown host keys:
RUN sed /^StrictHostKeyChecking/d /etc/ssh/ssh_config;
echo StrictHostKeyChecking no >> /etc/ssh/ssh_config
buildx
使用 docker buildx 进行多架构编译时也可以使用同样的方式对 Dockerfile 进行修改,然后在 build 时指定私钥。
1
docker buildx build —push —platform “linux/amd64,linux/arm64” -t helloworld:1.0.0 —ssh default = ~/.ssh/id_rsa -f cross.Dockerfile .
多私钥
如果有多个私有仓库并且需要不同的私钥进行认证的话也是支持的,需要额外处理一下。
首先是 Dockerfile 里每条命令需要指定使用的私钥
1
2
RUN —mount= type = ssh,id= github_ssh_key go mod download
RUN —mount= type = ssh,id= gitlab_ssh_key bundle install
相应的需要信任多个 hosts
1
2
ssh-keyscan github.com >> ~/.ssh/known_hosts
ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
最后 build 时需要传递多个私钥
1
docker build —ssh github_ssh_key = /path/to/.ssh/github_ssh_id_rsa —ssh gitlab_ssh_key = /path/to/.ssh/gitlab_ssh_id_rsa .
- 小结
在 Docker build 时使用 SSH 私钥进行认证有两种比较好的解决方案:
1)Docker 18.09 之前的版本,使用多阶段构建,将私钥以参数形式传递进容器,需要保证私钥不出现在最后一阶段即可 2)Docker 18.09 及以后,原生支持 --ssh
参数,推荐使用 如果不使用 Docker 的话就只能用方案一了。
- 参考
using-ssh-keys-inside-docker-container
docker 官方文档
dockerfile-run-mount-type-ssh-doesnt-seem-to-work
Build secrets and SSH forwarding in Docker 18.09