本文主要通过如何在 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 = { 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