在Docker容器中使用ssh密钥

Using SSH keys inside docker container

我有一个应用程序可以用git执行各种有趣的东西(比如运行git clone&git push),我正在尝试将其Docker化。

不过,我遇到了一个问题,我需要向容器添加一个ssh密钥,以便容器"用户"使用。

我尝试将它复制到/root/.ssh/,更改$HOME,创建一个git ssh包装器,但仍然没有运气。

以下是Dockerfile供参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#DOCKER-VERSION 0.3.4                                                          

from  ubuntu:12.04                                                              

RUN  apt-get update                                                            
RUN  apt-get install python-software-properties python g++ make git-core openssh-server -y
RUN  add-apt-repository ppa:chris-lea/node.js                                  
RUN  echo"deb http://archive.ubuntu.com/ubuntu precise universe">> /etc/apt/sources.list
RUN  apt-get update                                                            
RUN  apt-get install nodejs -y                                                  

ADD . /src                                                                      
ADD ../../home/ubuntu/.ssh/id_rsa /root/.ssh/id_rsa                            
RUN   cd /src; npm install                                                      

EXPOSE  808:808                                                                

CMD   ["node","/src/app.js"]

app.js运行类似git pull的git命令


如果您需要在构建时使用ssh,这将是一个更困难的问题。例如,如果您使用git clone,或者在我的情况下,使用pipnpm从私有存储库下载。

我发现的解决方案是使用--build-arg标志添加密钥。然后,您可以使用新的实验--squash命令(添加了1.13)合并层,以便删除后键不再可用。我的解决方案是:

建立命令

1
$ docker build -t example --build-arg ssh_prv_key="$(cat ~/.ssh/id_rsa)" --build-arg ssh_pub_key="$(cat ~/.ssh/id_rsa.pub)" --squash .

文档文件

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
35
36
FROM python:3.6-slim

ARG ssh_prv_key
ARG ssh_pub_key

RUN apt-get update && \
    apt-get install -y \
        git \
        openssh-server \
        libmysqlclient-dev

# Authorize SSH Host
RUN mkdir -p /root/.ssh && \
    chmod 0700 /root/.ssh && \
    ssh-keyscan github.com > /root/.ssh/known_hosts

# Add the keys and set permissions
RUN echo"$ssh_prv_key"> /root/.ssh/id_rsa && \
    echo"$ssh_pub_key"> /root/.ssh/id_rsa.pub && \
    chmod 600 /root/.ssh/id_rsa && \
    chmod 600 /root/.ssh/id_rsa.pub

# Avoid cache purge by adding requirements first
ADD ./requirements.txt /app/requirements.txt

WORKDIR /app/

RUN pip install -r requirements.txt

# Remove SSH keys
RUN rm -rf /root/.ssh/

# Add the rest of the files
ADD . .

CMD python manage.py runserver

更新:如果您使用docker 1.13,并且在上有实验特性,那么可以将--squash附加到build命令,该命令将合并层,删除ssh键并将它们隐藏在docker history中。


结果发现,使用Ubuntu时,ssh配置不正确。你需要添加

1
RUN  echo"    IdentityFile ~/.ssh/id_rsa">> /etc/ssh/ssh_config

到你的dockerfile,以便让它识别你的ssh密钥。


Note: only use this approach for images that are private and will always be!

ssh密钥仍然存储在映像中,即使在添加后删除layer命令中的密钥(请参阅本文中的注释)。

在我的情况下,这是可以的,所以这是我正在使用的:

1
2
3
4
5
6
7
# Setup for ssh onto github
RUN mkdir -p /root/.ssh
ADD id_rsa /root/.ssh/id_rsa
RUN chmod 700 /root/.ssh/id_rsa
RUN echo"Host github.com
\tStrictHostKeyChecking no
">> /root/.ssh/config


如果您使用docker compose,一个简单的选择是像这样转发ssh代理:

1
2
3
4
5
6
something:
    container_name: something
    volumes:
        - $SSH_AUTH_SOCK:/ssh-agent # Forward local machine SSH key to docker
    environment:
        SSH_AUTH_SOCK: /ssh-agent


为了向您注入ssh密钥,在一个容器中,您有多个解决方案:

  • 使用dockerfile和ADD指令,您可以在构建过程中注入它。

  • 只是做一些像EDOCX1[1]

  • 使用docker cp命令,允许您在容器运行时插入文件。


  • 扩展了PeterGrainger的答案,我可以使用Docker 17.05以后提供的多阶段构建。官方页面声明:

    With multi-stage builds, you use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, leaving behind everything you don’t want in the final image.

    记住这一点,这里是我的Dockerfile示例,包括三个构建阶段。它旨在创建客户机Web应用程序的生产映像。

    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
    # 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}
    " | 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"]

    .dockerignore重复.gitignore文件的内容(防止node_modules和由此产生的项目dist目录被复制):

    1
    2
    3
    4
    .idea
    dist
    node_modules
    *.log

    构建图像的命令示例:

    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密钥没有密码短语,只需指定空的SSH_KEY_PASSPHRASE参数。

    这就是它的工作原理:

    1)。在第一阶段,只有package.jsonyarn.lock文件和专用ssh密钥被复制到名为sources的第一个中间映像。为了避免进一步的ssh密钥口令提示,它会自动添加到ssh-agent中。最后,yarn命令从NPM安装所有必需的依赖项,并通过ssh从bitback克隆私有git存储库。

    2)。第二阶段构建和缩小Web应用程序的源代码,并将其放在名为production的下一中间映像的dist目录中。请注意,安装的node_modules的源代码是从该行在第一阶段生成的名为sources的映像中复制的:

    1
    COPY --from=sources /app/ /app/

    可能它也可以是以下行:

    1
    COPY --from=sources /app/node_modules/ /app/node_modules/

    我们只有来自第一个中间图像的node_modules目录,不再有SSH_KEYSSH_KEY_PASSPHRASE参数。构建所需的所有其余部分都从我们的项目目录中复制。

    3)。在第三阶段,我们通过只包括名为production的第二中间映像中的dist目录和安装用于启动Web服务器的node express,来减小最终映像的大小,该映像将被标记为ezze/geoport:0.6.0

    列出图像会得到如下输出:

    1
    2
    3
    4
    5
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    ezze/geoport        0.6.0               8e8809c4e996        3 hours ago         717MB
    <none>              <none>              1f6518644324        3 hours ago         1.1GB
    <none>              <none>              fa00f1182917        4 hours ago         1.63GB
    node                carbon              b87c2ad8344d        4 weeks ago         676MB

    其中非标记图像对应于第一个和第二个中间构建阶段。

    如果你跑

    1
    $ docker history ezze/geoport:0.6.0 --no-trunc

    在最终的图像中,您不会看到任何提到SSH_KEYSSH_KEY_PASSPHRASE


    这一行有问题:

    1
    ADD ../../home/ubuntu/.ssh/id_rsa /root/.ssh/id_rsa

    指定要复制到图像中的文件时,只能使用相对路径(相对于DockerFile所在的目录)。因此,您应该使用:

    1
    ADD id_rsa /root/.ssh/id_rsa

    把id_rsa文件放在dockerfile所在的目录中。

    有关更多详细信息,请查看:http://docs.docker.io/reference/builder/add


    码头集装箱应被视为自己的"服务"。要分离关注点,您应该分离功能:

    1)数据应在数据容器中:使用链接卷将repo克隆到中。然后,可以将该数据容器链接到需要它的服务。

    2)使用容器运行git克隆任务(即它的唯一任务是克隆),在运行数据容器时将其链接到它。

    3)ssh密钥也一样:把它放在一个卷上(如上建议),需要时链接到git clone服务。

    这样,克隆任务和密钥都是短暂的,只有在需要时才是活动的。

    现在,如果您的应用程序本身是一个Git接口,那么您可能需要直接考虑GitHub或BitBucket REST API来完成您的工作:这就是它们的设计目的。


    在Docker构建期间进行NPM安装时,我们也遇到了类似的问题。

    从Daniel van Flymen的解决方案中得到启发,并将其与Git URL Rewrite结合,我们发现了一种更简单的方法来验证从私有Github Repos安装的NPM,我们使用OAuth2令牌而不是密钥。

    在我们的例子中,NPM依赖项被指定为"git+https://github.com/…"

    对于容器中的身份验证,需要重写URL以适用于ssh身份验证(ssh://[email protected]/)或令牌身份验证(https://[email protected]/)

    构建命令:

    1
    docker build -t sometag --build-arg GITHUB_TOKEN=$GITHUB_TOKEN .

    不幸的是,我在docker 1.9上,所以--squash选项还没有出现,最终需要添加它

    Dockerfile:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    FROM node:5.10.0

    ARG GITHUB_TOKEN

    #Install dependencies
    COPY package.json ./

    # add rewrite rule to authenticate github user
    RUN git config --global url."https://${GITHUB_TOKEN}@github.com/".insteadOf"https://github.com/"

    RUN npm install

    # remove the secret token from the git config file, remember to use --squash option for docker build, when it becomes available in docker 1.13
    RUN git config --global --unset url."https://${GITHUB_TOKEN}@github.com/".insteadOf

    # Expose the ports that the app uses
    EXPOSE 8000

    #Copy server and client code
    COPY server /server
    COPY clients /clients


    这个问题真的很烦人。因为您不能在docker file上下文之外添加/复制任何文件,这意味着不可能只将~/.ssh/id_rsa链接到映像的/root/.ssh/id_rsa中,并且在构建docker映像的过程中,当您确实需要一个密钥来执行某些sshed操作时,例如从私有repo链接执行git克隆…。

    不管怎样,我找到了一个解决办法,不是那么说服人,而是为我工作。

  • 在你的文件里:

    • 将此文件添加为/root/.ssh/id_rsa
    • 做你想做的,比如Git克隆,作曲家…
    • 最后是rm/root/.ssh/id_rsa
  • 一次拍摄的脚本:

    • 把你的钥匙放在存放dockerfile的文件夹里
    • 码头施工
    • rm复制的密钥
  • 每当您需要从这个映像运行一个容器,并且有一些ssh需求时,只需为run命令添加-v,比如:

    docker run-v~/.ssh/id_rsa:/root/.ssh/id_rsa--name container image命令

  • 这个解决方案在您的项目源代码和构建的Docker映像中都不会产生私钥,因此不再需要担心安全问题。


    将ssh身份验证套接字转发到容器:

    1
    2
    3
    4
    5
    docker run --rm -ti \
            -v $SSH_AUTH_SOCK:/tmp/ssh_auth.sock \
            -e SSH_AUTH_SOCK=/tmp/ssh_auth.sock \
            -w /src \
            my_image

    您的脚本将能够执行git clone

    额外:如果您希望克隆的文件属于某个特定用户,则需要使用chown,因为在容器内使用除根用户以外的其他用户会使git失败。

    您可以将一些附加变量发布到容器的环境中:

    1
    2
    3
    4
    docker run ...
            -e OWNER_USER=$(id -u) \
            -e OWNER_GROUP=$(id -g) \
            ...

    克隆之后,必须在离开容器之前执行chown $OWNER_USER:$OWNER_GROUP -R以设置正确的所有权,以便非根用户可以访问容器外的文件。


    一种解决方案是使用以下选项将主机的ssh密钥装载到Docker中:

    1
    docker run -v /home/<host user>/.ssh:/home/<docker user>/.ssh <image>

    类似于上述溶液。但是可以与非根用户一起使用。与Github完美合作。


    '您可以有选择地让远程服务器像在服务器上运行一样访问本地ssh代理'

    https://developer.github.com/guides/using-ssh-agent-forwarding/


    您也可以在主机和容器之间链接.ssh目录,我不知道这个方法是否有任何安全隐患,但它可能是最简单的方法。这样的方法应该有效:

    1
    $ sudo docker run -it -v /root/.ssh:/root/.ssh someimage bash

    请记住,docker使用sudo运行(除非您不使用),如果是这种情况,您将使用根ssh密钥。


    今天我遇到了同样的问题,以前的文章中有一些修改过的版本,我发现这种方法对我更有用。

    1
    docker run -it -v ~/.ssh/id_rsa:/root/.my-key:ro image /bin/bash

    (注意readonly标志,这样容器在任何情况下都不会弄乱我的ssh密钥。)

    在容器内,我现在可以运行:

    1
    ssh-agent bash -c"ssh-add ~/.my-key; git clone <gitrepourl> <target>"

    所以我不知道@kross指出的EDOCX1[0]错误


    您可以使用共享文件夹将授权密钥传入容器,并使用Docker文件设置权限,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    FROM ubuntu:16.04
    RUN apt-get install -y openssh-server
    RUN mkdir /var/run/sshd
    EXPOSE 22
    RUN cp /root/auth/id_rsa.pub /root/.ssh/authorized_keys
    RUN rm -f /root/auth
    RUN chmod 700 /root/.ssh
    RUN chmod 400 /root/.ssh/authorized_keys
    RUN chown root. /root/.ssh/authorized_keys
    CMD /usr/sbin/sshd -D

    您的docker运行包含如下内容:与容器共享主机上的auth目录(保存授权的_密钥),然后打开ssh端口,该端口可以通过主机上的端口7001访问。

    1
    -d -v /home/thatsme/dockerfiles/auth:/root/auth -–publish=127.0.0.1:7001:22

    您可能需要查看https://github.com/jpetazzo/nsenter,这似乎是在容器上打开shell并在容器内执行命令的另一种方法。


    在Docker(17.05)的较新版本中,您可以使用多阶段构建。这是最安全的选择,因为以前的构建只能由后续的构建使用,然后被销毁

    有关详细信息,请参阅我的stackoverflow问题的答案。


    如果您不关心ssh密钥的安全性,这里有很多好的答案。如果你这样做了,我找到的最佳答案是从上面的评论链接到Diegosandrim的这个Github评论。因此,其他人更可能看到它,只是为了防止回购协议消失,下面是该答案的编辑版本:

    这里的大多数解决方案最终都会将私钥留在图像中。这很糟糕,因为任何有权访问映像的人都可以访问您的私钥。因为我们对squash的行为还不够了解,所以即使删除键并挤压该层,情况也可能如此。

    我们生成了一个预签名的URL来使用AWS S3 CLI访问密钥,并限制访问大约5分钟,我们将这个预签名的URL保存到repo目录中的一个文件中,然后在dockerfile中将其添加到映像中。

    在dockerfile中,我们有一个执行所有这些步骤的run命令:使用预先命名的url获取ssh密钥,运行npm安装,然后删除ssh密钥。

    通过在一个命令中这样做,ssh密钥不会存储在任何层中,但会存储预签名的URL,这不是问题,因为该URL在5分钟后将无效。

    生成脚本如下所示:

    1
    2
    3
    # build.sh
    aws s3 presign s3://my_bucket/my_key --expires-in 300 > ./pre_sign_url
    docker build -t my-service .

    Dockerfile如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    FROM node

    COPY . .

    RUN eval"$(ssh-agent -s)" && \
        wget -i ./pre_sign_url -q -O - > ./my_key && \
        chmod 700 ./my_key && \
        ssh-add ./my_key && \
        ssh -o StrictHostKeyChecking=no [email protected] || true && \
        npm install --production && \
        rm ./my_key && \
        rm -rf ~/.ssh/*

    ENTRYPOINT ["npm","run"]

    CMD ["start"]


    我试图用另一种方式解决这个问题:向映像添加公共ssh密钥。但在我的试验中,我发现"docker cp"是用于从容器复制到主机的。creak回答中的第3项似乎是说可以使用docker cp将文件注入容器。请参阅https://docs.docker.com/engine/reference/commandline/cp/

    摘录

    Copy files/folders from a container's filesystem to the host path.
    Paths are relative to the root of the filesystem.

    1
    2
    3
      Usage: docker cp CONTAINER:PATH HOSTPATH

      Copy files/folders from the PATH to the HOSTPATH


    You can use secrets to manage any sensitive data which a container
    needs at runtime but you don’t want to store in the image or in source
    control, such as:

    • Usernames and passwords
    • TLS certificates and keys
    • SSH keys
    • Other important data such as the name of a database or internal server
    • Generic strings or binary content (up to 500 kb in size)

    https://docs.docker.com/engine/swarm/secrets/

    我试图弄清楚如何将签名密钥添加到一个容器中,以便在运行时(而不是构建)使用,然后遇到了这个问题。Docker秘密似乎是我的用例的解决方案,因为还没有人提到它,所以我会添加它。


    毫无疑问,迟到了,这会让您的主机操作系统密钥可以在容器内即时根目录,如何?

    1
    docker run -v ~/.ssh:/mnt -it my_image /bin/bash -c"ln -s /mnt /root/.ssh; ssh [email protected]"

    我不赞成使用dockerfile来安装密钥,因为容器的迭代可能会留下私钥。


    实现这一点的一个简单而安全的方法是,不在Docker图像层中保存密钥,也不通过ssh_代理体操:

  • 作为Dockerfile中的步骤之一,通过添加以下内容创建.ssh目录:

    RUN mkdir -p /root/.ssh

  • 下面指示您要将ssh目录作为卷装载:

    VOLUME ["/root/.ssh" ]

  • 通过添加以下行,确保容器的ssh_config知道在何处查找公钥:

    RUN echo" IdentityFile /root/.ssh/id_rsa">> /etc/ssh/ssh_config

  • 运行时向容器公开本地用户的.ssh目录:

    docker run -v ~/.ssh:/root/.ssh -it image_name

    或者在您的dockerCompose.yml中,在服务的音量键下添加:

    -"~/.ssh:/root/.ssh"

  • 最终的Dockerfile应该包含如下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    FROM node:6.9.1

    RUN mkdir -p /root/.ssh
    RUN  echo"    IdentityFile /root/.ssh/id_rsa">> /etc/ssh/ssh_config

    VOLUME ["/root/.ssh" ]

    EXPOSE 3000

    CMD ["launch" ]

    正如Eczajk在Daniel van Flymen的回答中所评论的那样,移除钥匙并使用--squash似乎并不安全,因为它们在历史上仍然可见(docker history --no-trunc)。

    与Docker 18.09不同,您现在可以使用"构建秘密"功能。在我的例子中,我使用主机ssh密钥克隆了一个私有的git repo,在我的dockerfile中包含以下内容:

    1
    2
    3
    4
    5
    6
    7
    # syntax=docker/dockerfile:experimental

    [...]

    RUN --mount=type=ssh git clone [...]

    [...]

    要使用它,您需要在运行docker build之前启用新的buildkit后端:

    1
    export DOCKER_BUILDKIT=1

    您需要将--ssh default参数添加到docker build中。

    有关此的详细信息,请访问:https://medium.com/@tonistigi/build-secrets-and-ssh-forwarding-in-docker-18-09-ae8161d066


    最简单的方法是,获取一个launchpad帐户并使用:ssh import id


    在一个运行中的docker容器中,可以使用docker-i(交互)选项发布ssh keygen。这将转发容器提示以在Docker容器内创建密钥。