使用Docker Compose将主机的SSH密钥注入到Docker Machine中

60

我正在使用Docker Machine在Mac OS X上进行Docker操作(使用默认的boot2docker machine),并且我使用docker-compose来设置我的开发环境。

假设其中一个容器称为"stack",现在我想调用:

docker-composer run stack ssh user@stackoverflow.com

我的公钥(已添加到stackoverflow.com并将用于认证我)位于主机上。我希望这个密钥可以在Docker Machine容器中使用,以便我可以在容器内使用该密钥对自己进行身份验证,进而连接stackoverflow。最好的情况是,无需将我的密钥复制到Docker Machine中即可完成此操作。
是否有任何方法可以实现这一点?另外,如果我的密钥受密码保护,是否有任何方法可以仅解锁一次,以便在每次注入后我不必手动输入密码?
6个回答

70

如果您的容器内用户为root,您可以将以下内容添加到docker-compose.yml中:

volumes:
    - ~/.ssh:/root/.ssh

你还可以查看使用ssh代理的更高级解决方案(我自己没有尝试过)。


20
请注意,如果您的SSH密钥属于主机机器上的用户,则此解决方案可能会失败。 - Josh Bodah
16
虽然你可以在运行时执行这个操作,但你不能在构建时访问这个卷。 - Daniel van Flymen
5
在Windows上也无法工作。"/root/.ssh/id_rsa" 的权限为 0755,太开放了。 - spirit
2
您可以在此代码片段末尾添加“:ro”以只读方式挂载密钥,这通常会绕过SSH有关权限的警告,但如果您的密钥有密码(它确实应该有),您仍然需要使用ssh-agent进行一些技巧操作。 - dragon788
另一个重要的注意事项是,随着这个作为一个体积。 - ctatro85

52

警告:这个功能在Docker Compose中似乎有限的支持,更适用于Docker Swarm。

(我还没有核实,但) 我目前的印象是:

  • 在Docker Compose中,secrets只是绑定挂载卷,因此与卷相比没有额外的安全性。
  • 在Linux主机上更改secrets权限的能力可能有限。

详情请见回答评论。


Docker有一个名为secrets的功能,在这里很有帮助。要使用它,可以将以下代码添加到docker-compose.yml中:

---
version: '3.1' # Note the minimum file version for this feature to work
services:
  stack:
    ...
    secrets:
      - host_ssh_key

secrets:
  host_ssh_key:
    file: ~/.ssh/id_rsa

然后,可以像这样在Dockerfile中访问新的秘密文件:

RUN mkdir ~/.ssh && ln -s /run/secrets/host_ssh_key ~/.ssh/id_rsa

保密文件不会被复制到容器中:

当您授予新创建或正在运行的服务访问秘密的权限时,解密后的秘密会被挂载到容器中的内存文件系统中。

更多详情请参见:


1
嗨astyagun:我同意使用Secrets功能是解决这个问题的好方法,但为了使你的答案更好,包含答案的关键部分并提供参考链接会很有用。 - Vince Bowdren
1
不幸的是,链接id_rsa是行不通的,SSH会返回错误,因为链接具有不同的权限。因此,您仍需要复制该文件并保持RO权限。但好的一点是,您可以使用ssh -i /run/secrets/host_ssh_key ...参数,因为秘密文件具有RO权限。 - Kostanos
1
@Kostanos 在创建链接后添加 chown -h $(id -u):$(id -g) ~/.ssh/id_rsa 会有帮助吗? - Anton Styagun
2
请注意,密钥是“swarm模式功能”,如 https://github.com/docker/compose/issues/4994 和 https://dev59.com/MFUL5IYBdhLWcg3wr5x6 中所指出的一样。它不会在docker-compose up中挂载密钥。 - Roxana Tapia
1
@AntonStyagun 更新了有关 secrets 文档的链接:https://docs.docker.com/compose/compose-file/compose-file-v3/#secrets - Jack Westmore
显示剩余6条评论

40

如果你正在使用OS X并且使用加密密钥,那么这将是个麻烦的事情。 这里 是我找出解决方法经历的步骤。

简单的方法

有人可能会认为这没有问题。只需挂载你的ssh文件夹:

...
volumes:
  - ~/.ssh:/root/.ssh:ro
...

这应该能正常工作,对吧?

用户问题

接下来我们会注意到我们正在使用错误的用户 ID。没关系,我们将编写一个脚本来复制并更改 ssh 密钥的所有者。我们还将在配置文件中设置 ssh 用户,以便 ssh 服务器知道谁在连接。

...
volumes:
  - ~/.ssh:/root/.ssh-keys:ro
command: sh -c ‘./.ssh-keys.sh && ...’
environment:
  SSH_USER: $USER
...

# ssh-keys.sh
mkdir -p ~/.ssh
cp -r /root/.ssh-keys/* ~/.ssh/
chown -R $(id -u):$(id -g) ~/.ssh

cat <<EOF >> ~/.ssh/config
  User $SSH_USER
EOF

SSH密钥密码问题

我们公司使用密码保护SSH密钥。但在Docker中这不太实用,因为每次启动容器都需要输入密码。

我们可以移除密码(如下面的例子),但存在安全风险。

openssl rsa -in id_rsa -out id_rsa2
# enter passphrase
# replace passphrase-encrypted key with plaintext key:
mv id_rsa2 id_rsa

SSH代理解决方案

您可能已经注意到,本地使用ssh时不需要每次输入密码。为什么呢?这就是SSH代理的作用。 SSH代理基本上是一个服务器,它监听一个名为“ssh auth sock”的特殊文件,即Unix套接字。您可以在系统中看到其位置:

echo $SSH_AUTH_SOCK
# /run/user/1000/keyring-AvTfL3/ssh

SSH客户端通过此文件与SSH代理进行通信,以便您只需输入一次口令。 一旦它被解密,SSH代理将在内存中存储它并在请求时向SSH客户端发送。 我们可以在Docker中使用吗?当然可以,只需挂载该特殊文件并指定相应的环境变量:

environment:
  SSH_AUTH_SOCK: $SSH_AUTH_SOCK
  ...
volumes:
  - $SSH_AUTH_SOCK:$SSH_AUTH_SOCK

在这种情况下,我们甚至不需要复制密钥。 要确认密钥是否可用,可以使用ssh-add实用程序:

if [ -z "$SSH_AUTH_SOCK" ]; then
  echo "No ssh agent detected"
else
  echo $SSH_AUTH_SOCK
  ssh-add -l
fi

在Docker for Mac中使用Unix套接字挂载的问题

不幸的是,对于OS X用户来说,Docker for Mac存在许多缺点之一就是无法在Mac和Linux之间共享Unix套接字。在D4M Github上有一个开放的问题(链接),截至2019年2月它仍然未解决。

那么这是死路吗?不是的,有一个hacky的解决方法。

SSH代理转发解决方案

幸运的是,这个问题并不是新问题。早在Docker之前,有一种方法可以在远程ssh会话中使用本地ssh密钥,称为ssh代理转发。思路很简单:你通过ssh连接到远程服务器,你可以在那里使用所有相同的远程服务器,从而共享你的密钥。

使用Docker for Mac我们可以使用一个巧妙的技巧:使用TCP ssh连接将ssh代理共享到docker虚拟机,并将该文件从虚拟机挂载到需要该SSH连接的另一个容器中。以下是演示解决方案的图片:

SSH forwarding

首先,我们通过TCP端口在一个linux VM中的容器内部创建一个ssh会话到ssh服务器。在这里,我们使用真实的ssh auth sock。

接下来,ssh服务器将我们的ssh密钥转发到该容器上的ssh代理。SSH代理有一个Unix套接字,它使用挂载到Linux VM的位置。也就是说,Unix套接字在Linux中有效,Mac中不起作用。

然后我们创建一个带有SSH客户端的有用容器。我们共享本地SSH会话使用的Unix套接字文件。

有一堆脚本简化了该过程:(链接)

结论

让SSH在Docker中工作可能本可以更简单。但是它可以做到。并且未来很可能会得到改善。至少Docker开发人员已经意识到了这个问题。甚至为具有构建时机密的Dockerfiles解决了这个问题。并且有一些建议如何支持Unix域套接字。


7
您可以转发SSH代理:
something:
    container_name: something
    volumes:
        - $SSH_AUTH_SOCK:/ssh-agent # Forward local machine SSH key to docker
    environment:
        SSH_AUTH_SOCK: /ssh-agent

7
你已经在这里提供了答案 https://dev59.com/t2Ml5IYBdhLWcg3wyJUe#36648428。另外需要注意的是,这个方法不适用于Mac,因为像@joe-saw指出的那样,Unix域套接字不会被代理。 - 23tux
如何获取本地机器ssh代理目录/ssh-agent? - famas23
@famas23 这是你问题的答案:https://medium.com/trabe/use-your-local-ssh-keys-inside-a-docker-container-ea1d117515dc#3b26 - ismailarilik

5

您可以使用多阶段构建来构建容器。以下是您可以采取的方法:

Stage 1 building an image with ssh

FROM ubuntu as sshImage
LABEL stage=sshImage
ARG SSH_PRIVATE_KEY
WORKDIR /root/temp

RUN apt-get update && \
    apt-get install -y git npm 

RUN mkdir /root/.ssh/ &&\
    echo "${SSH_PRIVATE_KEY}" > /root/.ssh/id_rsa &&\
    chmod 600 /root/.ssh/id_rsa &&\
    touch /root/.ssh/known_hosts &&\
    ssh-keyscan github.com >> /root/.ssh/known_hosts

COPY package*.json ./

RUN npm install

RUN cp -R node_modules prod_node_modules

阶段二:构建你的容器

FROM node:10-alpine

RUN mkdir -p /usr/app

WORKDIR /usr/app

COPY ./ ./

COPY --from=sshImage /root/temp/prod_node_modules ./node_modules

EXPOSE 3006

CMD ["npm", "run", "dev"] 

在您的组合文件中添加env属性:

environment:
      - SSH_PRIVATE_KEY=${SSH_PRIVATE_KEY}

然后像这样从构建脚本传递参数:

docker-compose build --build-arg SSH_PRIVATE_KEY="$(cat ~/.ssh/id_rsa)"

为了安全起见,您需要删除中间容器it。这将帮助您实现目标。


最后一部分可以添加到docker_compose.yml的build.args中。 - Keto
3
请注意,密钥现在储存在镜像中,即使您使用多阶段构建,密钥仍然存在于其中一个层中,可能会带来安全风险。 - SomeOne_1
在从Github克隆存储库后,应添加一个步骤来删除密钥。 - Dung Le

4

3
很遗憾,Docker方面的文档已经更改,没有提到osxfs。像Docker一样,文档维护不佳或者被重定向了。但至少,在这里可以找到你的链接的archive.org版本:https://web.archive.org/web/20200414203352/https://docs.docker.com/docker-for-mac/osxfs/#ssh-agent-forwarding供后人参考的关键点是:在卷挂载中需要使用 type:bind - patricknelson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接