Posted at

Docker の BuildKit を使ってセキュアなビルドを試す


はじめに

Docker 18.09 で BuildKit が正式に採用されました。BuildKit によって、パフォーマンスの向上、ビルドキャッシュの改良、 鍵ファイルや SSH 秘密鍵の安全なマウントなどの改良がされました。今回は、BuildKit の鍵ファイル、SSH 秘密鍵の安全なマウントに着目して、セキュアなビルドを試してみます。


環境


  • macOS High Sierra Version 10.13.6

  • Docker for Mac 2.0.0.3 (Engine: 18.09.2)


BuildKit を有効にする

環境変数から BuildKit を有効にするには、DOCKER_BUILDKIT=1 を実行して有効にします。

$ export DOCKER_BUILDKIT=1

$ docker build .

デフォルトで有効にする場合は、/etc/docker/daemon.json に以下の設定を記述して再起動することで有効になります。

本記事での環境は Docker for Mac なので、Docker for Mac の設定から Daemon - Advanced を開いて設定を記述します。

{

"features": {
"buildkit": true
}
}

以降は、BuildKit が有効である前提で進めます。


鍵ファイルのマウント

今まで Dockerfile 内からプライベートな Git リポジトリーや AWS などのクラウドサービスに接続するにはちょっとした工夫をする必要がありました。

まずは単純に鍵ファイルを COPY 命令でビルドした例をみてみましょう。

以下のような Dockerfile を用意します。


Dockerfile

FROM ubuntu:18.04

COPY id_rsa /root/.ssh/

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

RUN ssh-keyscan -H github.com >> /root/.ssh/known_hosts \
&& git clone git@github.com:[ユーザー名]/[プライベートリポジトリ名].git

RUN rm -rf /root/.ssh/id_rsa


鍵ファイルを用意したディレクトリでビルドしてみます。

$ docker build -t sample .

ビルドした Docker イメージの履歴を表示してみます。

$ docker history sample

IMAGE CREATED CREATED BY SIZE COMMENT
4a841c8c9d87 2 minutes ago /bin/sh -c rm -rf /root/.ssh/id_rsa 0B
de2dc4ca7089 2 minutes ago /bin/sh -c ssh-keyscan -H github.com >> /roo… 22.6kB
16ced59e0844 2 minutes ago /bin/sh -c apt-get update && apt-get install… 118MB
207d964e6f0b 3 minutes ago /bin/sh -c #(nop) COPY file:53ba8782ad73011c… 1.68kB
47b19964fb50 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 3 weeks ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 3 weeks ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0B
<missing> 3 weeks ago /bin/sh -c set -xe && echo '
#!/bin/sh' > /… 745B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:529264c6593975a61… 88.1MB

履歴を見ると鍵ファイルを削除する前のレイヤーが残っていることが分かります。

削除する前のレイヤーにアクセスできるということはパブリックなレジストリにプッシュしてしまうと、第三者が鍵ファイルにアクセスできてしまうことになります。

以前のバージョンでこのレイヤーへアクセスできないようにするには、マルチステージビルドや docker build --squash を用いて不要なレイヤを削除する必要があります。

マルチステージビルドを試した場合の Dockerfile は以下のとおりです。


Dockerfile.multi

FROM ubuntu:18.04 as builder

COPY id_rsa /root/.ssh/

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

RUN ssh-keyscan -H github.com >> /root/.ssh/known_hosts \
&& git clone git@github.com:[ユーザー名]/[プライベートリポジトリ名].git

RUN rm -rf /root/.ssh/id_rsa

FROM ubuntu:18.04

COPY --from=builder /private-test /private-test


マルチステージビルドの Dockerfile をビルドします。

$ docker build -f Dockerfile.multi -t sample-multi .

マルチステージビルドした Docker イメージの履歴を表示してみます。

$ docker history sample-multi

IMAGE CREATED CREATED BY SIZE COMMENT
8ec225a84208 3 minutes ago /bin/sh -c #(nop) COPY dir:681f0d3bca9cd752e… 21.7kB
47b19964fb50 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 3 weeks ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 3 weeks ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0B
<missing> 3 weeks ago /bin/sh -c set -xe && echo '
#!/bin/sh' > /… 745B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:529264c6593975a61… 88.1MB

COPY したレイヤだけが表示されているので鍵ファイルにアクセスできないことが分かります。

次に Docker の experimental を有効にして、 docker build --squash を試した場合の Dockerfile は以下のとおりです。


Dockerfile.squash

FROM ubuntu:18.04

COPY id_rsa /root/.ssh/

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

RUN ssh-keyscan -H github.com >> /root/.ssh/known_hosts \
&& git clone git@github.com:[ユーザー名]/[プライベートリポジトリ名].git

RUN rm -rf /root/.ssh/id_rsa


docker build --squash でビルドしてみます。

$ docker build --squash -f Dockerfile.squash -t sample-squash .

docker build --squash した Docker イメージの履歴を表示してみます。

$ docker history sample-squash

IMAGE CREATED CREATED BY SIZE COMMENT
d447056e816f About a minute ago 118MB merge sha256:4a841c8c9d871dbe8f44c3624cb41bb3c0f65ff76b1b6ea29516d3cb945f0f4d to sha256:47b19964fb500f3158ae57f20d16d8784cc4af37c52c49d3b4f5bc5eede49541
<missing> 3 hours ago /bin/sh -c rm -rf /root/.ssh/id_rsa 0B
<missing> 3 hours ago /bin/sh -c ssh-keyscan -H github.com >> /roo… 0B
<missing> 3 hours ago /bin/sh -c apt-get update && apt-get install… 0B
<missing> 3 hours ago /bin/sh -c #(nop) COPY file:53ba8782ad73011c… 0B
<missing> 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 3 weeks ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 3 weeks ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0B
<missing> 3 weeks ago /bin/sh -c set -xe && echo '
#!/bin/sh' > /… 745B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:529264c6593975a61… 88.1MB

docker build --squash を利用するとマージされ不要なレイヤが削除されていることが分かります。

以上のように以前のバージョンでは注意して対応する必要がありました。

そこで BuildKit では、RUN --mount=type=secret 命令を使用することでマウントできるようになりました。

試した例が以下の Dockerfile です。


Dockerfile.secret

# syntax = docker/dockerfile:experimental

FROM ubuntu:18.04

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

RUN --mount=type=secret,id=ssh,dst=/root/.ssh/id_rsa \
ssh-keyscan -H github.com >> /root/.ssh/known_hosts \
&& git clone git@github.com:[ユーザー名]/[プライベートリポジトリ名].git


Dockerfile の新しい構文を利用するため、1 行目に # syntax = docker/dockerfile:experimental を指定する必要があります。

そして、RUN --mount=type=secret 命令を用いて鍵ファイルをイメージに残す危険性を考えずにマウントできます。

id には docker build 時に渡すキーとなる任意の同じ ID 、dst には  Docker コンテナ内のマウント先を指定します。dst は、targetdestination のいずれかでも動作します。

以下のコマンドでビルドします。

$ docker build --secret id=ssh,src=$HOME/.ssh/i d_rsa -f Dockerfile.secret -t sample-secret .

--secret を有効にし、id に Dockerfile の RUN --mount=type=secret 命令でも使用した同じ ID、src にマウント元を指定します。srcsource でも動作します。

RUN --mount=type=secret 命令を使用した Docker イメージの履歴を表示してみます。

$ docker history sample-secret

IMAGE CREATED CREATED BY SIZE COMMENT
f2f83f34ae1c 4 minutes ago RUN /bin/sh -c ssh-keyscan -H github.com >> … 22.6kB buildkit.dockerfile.v0
<missing> 30 hours ago RUN /bin/sh -c apt-get update && apt-get ins… 118MB buildkit.dockerfile.v0
<missing> 4 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 4 weeks ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 4 weeks ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0B
<missing> 4 weeks ago /bin/sh -c set -xe && echo '
#!/bin/sh' > /… 745B
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:529264c6593975a61… 88.1MB

鍵ファイルが残っていないことが見て取れます。

実際にコンテナを起動して確認して見ると、コンテナの中には空のファイルが残っていることが分かります。

$ docker run --rm --name sample-secret -it -d sample-secret bash

$ docker attach sample-secret
root@9d4b16c1ba71:/# ls /root/.ssh/
id_rsa known_hosts
root@9d4b16c1ba71:/# cat /root/.ssh/id_rsa
root@9d4b16c1ba71:/#


SSH 秘密鍵のマウント

RUN --mount=type=secret 命令では、パスフレーズ付きの SSH 秘密鍵には対応できません。

そこで代わりに RUN --mount=type=ssh 命令が対応しています。

試した例が以下の Dockerfile です。


Dockerfile.ssh

# syntax = docker/dockerfile:experimental

FROM ubuntu:18.04

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

RUN --mount=type=ssh \
mkdir -p -m 0600 ~/.ssh \
&& ssh-keyscan -H github.com >> ~/.ssh/known_hosts \
&& git clone git@github.com:[ユーザー名]/[プライベートリポジトリ名].git


1 行目に # syntax = docker/dockerfile:experimental を指定し、RUN --mount=type=ssh 命令を用いて SSH 秘密鍵をマウントします。

事前にパスフレーズ入力するため、ssh-agent を実行して SSH 秘密鍵を登録します。

$ ssh-add ~/.ssh/id_rsa

Enter passphrase for /Users/[ユーザー名]/.ssh/id_rsa: [パスフレーズを入力する]
Identity added: /Users/[ユーザー名]/.ssh/id_rsa (/Users/[ユーザー名]/.ssh/id_rsa)

以下のコマンドでビルドします。

$ docker build --ssh default -f Dockerfile.ssh -t sample-ssh .

--ssh を有効にし、default を渡すことで ssh-agent に接続し解決してくれます。


さいごに

BuildKit を用いて鍵ファイルや SSH 秘密鍵をマウントしてイメージに認証情報を残さずビルドできることが分かりました。

リリースサイクルを考えると Docker 19.03 が控えているので次のリリースが楽しみです。


参考

Build Enhancements for Docker | Docker Documentation

Docker v18.09 新機能 (イメージビルド&セキュリティ) – nttlabs – Medium

Docker セキュリティ: 今すぐ役に立つテクニックから,次世代技術まで - SlideShare

Access Private Repositories from Your Dockerfile Without Leaving Behind Your SSH Keys - vsupalov

Docker Engine 18.09 から使える Build-time secrets を試してみた - はったりエンジニアの備忘録

GitHub - moby/buildkit: concurrent, cache-efficient, and Dockerfile-agnostic builder toolkit