使用 Docker Compose 将主机的 SSH 密钥注入 Docker 机器

Inject host's SSH keys into Docker Machine with Docker Compose

我在 Mac OS X 上使用 Docker 和 Docker Machine(使用默认的 boot2docker 机器),我使用 docker-compose 来设置我的开发环境。

假设其中一个容器名为“stack”。现在我想做的是打电话:

docker-composer run stack ssh user@whosebug.com

我的 public 密钥(已添加到 whosebug.com 并将用于对我进行身份验证)位于主机上。我希望此密钥可用于 Docker Machine 容器,这样我就可以在容器内使用该密钥针对 Whosebug 验证自己。最好不要将我的密钥物理复制到 Docker Machine.

有什么办法吗?另外,如果我的密钥受密码保护,有什么方法可以解锁一次,这样每次注入后我就不必手动输入密码了吗?

您可以将此添加到您的 docker-compose.yml(假设容器内的用户是 root):

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

你也可以查看more advanced solution with ssh agent(我自己没试过)

您可以转发 SSH 代理:

something:
    container_name: something
    volumes:
        - $SSH_AUTH_SOCK:/ssh-agent # Forward local machine SSH key to docker
    environment:
        SSH_AUTH_SOCK: /ssh-agent

警告:此功能在 Docker Compose 中的支持似乎有限,更多是为 Docker Swarm 设计的。

(我还没有检查确定,但是)我目前的印象是:

  • 在 Docker 中,Compose 机密只是绑定安装卷,因此与卷相比没有额外的安全性
  • 使用 Linux 主机更改机密权限的能力可能受到限制

查看答案评论了解更多详情。


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

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

When you grant a newly-created or running service access to a secret, the decrypted secret is mounted into the container in an in-memory filesystem

更多详情请参考:

如果您使用 OS X 和加密密钥,这将是 PITA。 Here are the steps 我想通了。

直接的方法

有人可能会认为没有问题。只需挂载您的 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 代理基本上是一个服务器,它监听一个特殊的文件,unix 套接字,称为“ssh auth sock”。您可以在您的系统上看到它的位置:

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 中 Mac

的 unix socket mount 支持问题

对于 OS X 用户来说不幸的是,Mac 的 Docker 有许多缺点,其中之一是无法在 Mac 和 Linux。有一个open issue in D4M Github。截至 2019 年 2 月,它仍在营业。

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

SSH代理转发方案

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

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

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

接下来,ssh 服务器将我们的 ssh 密钥转发到该容器上的 ssh 代理。 SSH 代理有一个 Unix 套接字,它使用安装到 Linux VM 的位置。 IE。 Unix 套接字适用于 Linux。 Mac 中的非工作 Unix 套接字文件无效。

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

有很多脚本可以简化该过程: https://github.com/avsm/docker-ssh-agent-forward

结论

让 SSH 在 Docker 中工作本来可以更容易。但这是可以完成的。并且将来可能会得到改进。至少 Docker 开发人员意识到了这个问题。甚至 solved it for Dockerfiles with build time secrets. And there's a suggestion 如何支持 Unix 域套接字。

Docker Mac 现在 supports mounting the ssh agent socket 在 macOS 上。

您可以使用多阶段构建来构建容器这是您可以采用的方法:-

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

第 2 阶段:构建容器

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)"

并为了安全起见删除中间容器。这会帮你加油。