Docker Content TrustでDockerイメージにデジタル署名をする
Docker Bench for SecurityでDockerコンテナのセキュリティ診断を行うでは運用しているコンテナのセキュリティをDocker社の提供するベンチマークツールで診断した。起動しているコンテナに関するセキュリティはこのようなベンチマークツールで診断することは可能だが、そもそもコンテナの元となるイメージは信頼できるものなのだろうか。そんな不安を覚えたエンジニアは少なくないと思う。この記事ではDocker 1.8から追加されているDockerイメージへのデジタル署名の仕組みDocker Content Trustを使い、イメージの信頼性の検証を行う。
【注】実際には最後のイメージの真正性の確認で想定した挙動とことなる挙動が起きている。現在問い合わせ中。備忘録として残しておく。
Docker Content Trustとは
Docker Content Trustはデジタル署名技術を利用したDocker イメージ発行者の真正性の検証とDocker イメージ自体の信頼性の検証を行う機能である。
MITM Attack、リプレイAttack、鍵の漏洩対策が施されており、信頼されないイメージに対して、docker pull
、docker push
、 docker build
、 docker create
、 docker run
コマンドが使えなくなる。
詳細な仕組みを理解したい場合はIntroducing Docker Content Trust | Docker BlogとContent trust in Docker を読んで頂きたい。
Notaryとは
Docker Content Trustを支えているツールにNotaryがある。Notaryは安全なイメージの公開と、イメージ内容を検証するためのDocker社のツールでOSSで公開されている。Notaryはイメージの信頼性の検証にはTUFが使っているとのこと。
実際に使ってみる
今回ははじめから構築するのではなく、Content Trustの検証をするためにDocker社が用意しているデモ用のコンテナを使う。
検証環境
➤ system_profiler SPSoftwareDataType Software: System Software Overview: System Version: OS X 10.10.5 (14F27) Kernel Version: Darwin 14.5.0 ➤ docker-machine -v docker-machine version 0.5.1 (7e8e38e) (Dockerホスト) * Docker version 1.9.1
構成
Dockerホストの構築
以下のVagrantfileを利用してDockerホストを構築する。
Vagrant.configure(2) do |config|
config.vm.define "trust-content-vm" do |trust|
trust.vm.provider :virtualbox do |virtualbox, override|
virtualbox.name = "trust"
virtualbox.customize ["modifyvm", :id, "--memory", "2048"]
override.vm.box = "takanabe/ubuntu1504"
override.vm.network "private_network", ip: "192.168.33.17"
end
trust.vm.provision "trigger" do |trigger|
trigger.fire do
puts "======= IN TRIGGER SECTION !!======="
`docker-machine status #{@machine.name}`
if $?.exitstatus != 0
if @machine.provider_name == :virtualbox
ip = `vagrant ssh #{@machine.name} -c "ip addr show dev eth1 | grep -w inet"`
.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)[0]
port = 22
else
ip = @machine.ssh_info[:host]
port = @machine.ssh_info[:port]
end
run "docker-machine -D create -d generic \
--generic-ip-address #{ip} \
--generic-ssh-key #{@machine.ssh_info[:private_key_path][0]} \
--generic-ssh-port #{port} \
--generic-ssh-user #{@machine.ssh_info[:username]} \
#{@machine.name}"
end
end
end
trust.trigger.before :destroy do
`docker-machine status #{@machine.name}`
if $?.exitstatus == 0
run "docker-machine rm #{@machine.name}"
end
end
end
end
vagrant ssh sudo sh -c 'echo "127.0.0.1 notaryserver" >> /etc/hosts' sudo sh -c 'echo "127.0.0.1 registry" >> /etc/hosts'
Notary Serverの構築
Notary Server、Notary Signer、MySQLコンテナからなるNotaryシステムを構築する。必要なイメージをdocker-compose build
で取得する。
➤ eval "$(docker-machine env content-trust-vm)" ➤ git clone -b trust-sandbox https://github.com/docker/notary.git ➤ cd notary ➤ cat docker-compose.yml notaryserver: build: . dockerfile: notary-server-Dockerfile links: - notarymysql - notarysigner ports: - "8080" - "4443:4443" environment: SERVICE_NAME: notary notarysigner: volumes: - /dev/bus/usb/003/010:/dev/bus/usb/002/010 - /var/run/pcscd/pcscd.comm:/var/run/pcscd/pcscd.comm build: . dockerfile: notary-signer-Dockerfile links: - notarymysql notarymysql: build: ./notarymysql/ ports: - "3306:3306" ➤ docker-compose build ➤ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE notary_notaryserver latest 7caf51d0db26 14 minutes ago 771.5 MB notary_notarysigner latest a896b6e24c4f 16 minutes ago 616.1 MB notary_notarymysql latest 08a49db35bcd 18 minutes ago 316.9 MB ubuntu 14.04 89d5d8e8bafb 2 weeks ago 187.9 MB golang latest 08ff0c215f8f 2 weeks ago 703.8 MB diogomonica/golang-softhsm2 latest b2faa32d5624 5 months ago 550.3 MB
取得したイメージを使ってNotaryシステムを構築する。
➤ docker-compose up -d ➤ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 95e0b84348e7 notary_notaryserver "notary-server -confi" 8 seconds ago Up 8 seconds 0.0.0.0:4443->4443/tcp, 0.0.0.0:32768->8080/tcp notary_notaryserver_1 2f053325fa03 notary_notarysigner "notary-signer -confi" 9 seconds ago Up 8 seconds 4443/tcp notary_notarysigner_1 1afbb6c9d7cc notary_notarymysql "/start" 9 seconds ago Up 9 seconds 0.0.0.0:3306->3306/tcp notary_notarymysql_1
Registryの構築
続いて、コンテナイメージを登録するRegistryコンテナを立てる。こちらもお手軽簡単にできる。
➤ cd .. ➤ git clone https://github.com/docker/distribution.git ➤ cd distribution ➤ docker build -t registry . ➤ docker run -p 5000:5000 --name registry registry & ➤ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE registry latest 7746865674c6 3 minutes ago 812.7 MB notary_notaryserver latest 7caf51d0db26 26 minutes ago 771.5 MB notary_notarysigner latest a896b6e24c4f 28 minutes ago 616.1 MB notary_notarymysql latest 08a49db35bcd 29 minutes ago 316.9 MB ubuntu 14.04 89d5d8e8bafb 2 weeks ago 187.9 MB golang 1.5.2 08ff0c215f8f 2 weeks ago 703.8 MB golang latest 08ff0c215f8f 2 weeks ago 703.8 MB diogomonica/golang-softhsm2 latest b2faa32d5624 5 months ago 550.3 MB
Operation用コンテナイメージの作成
Notary ServerやRegistryと接続してContent Trustの仕組みを検証するためのOperationコンテナをつくる。ディレクトリを移動する。
cd ..
以下の内容でDockerfileを作成する。
FROM debian:jessie
ADD https://master.dockerproject.org/linux/amd64/docker /usr/bin/docker
RUN chmod +x /usr/bin/docker \
&& apt-get update \
&& apt-get install -y \
tree \
vim \
git \
ca-certificates \
--no-install-recommends
WORKDIR /root
RUN git clone -b trust-sandbox https://github.com/docker/notary.git
RUN cp /root/notary/fixtures/root-ca.crt /usr/local/share/ca-certificates/root-ca.crt
RUN update-ca-certificates
ENTRYPOINT ["bash"]
続いてビルドしてイメージを作成する。
➤ docker build -t notary_ops . ➤ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE notary_ops latest 14880b4ca74f 3 minutes ago 333.7 MB registry latest 7746865674c6 10 minutes ago 812.7 MB notary_notaryserver latest 7caf51d0db26 33 minutes ago 771.5 MB notary_notarysigner latest a896b6e24c4f 35 minutes ago 616.1 MB notary_notarymysql latest 08a49db35bcd 36 minutes ago 316.9 MB ubuntu 14.04 89d5d8e8bafb 2 weeks ago 187.9 MB golang 1.5.2 08ff0c215f8f 2 weeks ago 703.8 MB golang latest 08ff0c215f8f 2 weeks ago 703.8 MB debian jessie 23cb15b0fcec 2 weeks ago 125.1 MB diogomonica/golang-softhsm2 latest b2faa32d5624 5 months ago 550.3 MB
notary_opsを起動する
作成したイメージを利用してRegistryとNotaryに接続した状態でコンテナを起動する。
➤ docker run -it -v /var/run/docker.sock:/var/run/docker.sock --link notary_notaryserver_1:notaryserver --link registry:registry notary_ops
Trust contentテスト用イメージをプルしようとしたが以下のエラーが出た。
root@9685575d7a50:~# docker pull hello-world Using default tag: latest Error response from daemon: client is newer than server (client API version: 1.22, server API version: 1.21)
ホストとコンテナ内のクライアントのAPIのバージョンが合っていないのが原因らしい。 DockerホストのAPIバージョンの確認。
$ docker version Client: Version: 1.9.1 API version: 1.21 Go version: go1.4.2 Git commit: a34a1d5 Built: Fri Nov 20 13:16:54 UTC 2015 OS/Arch: linux/amd64
sandbox containerのAPIバージョンの確認。どのコマンドを実行してもエラーが出る状態のようだ。
$ sudo docker exec eb85b4290804 docker version Client: Version: 1.10.0-dev API version: 1.22 Go version: go1.5.2 Git commit: 7ff61dd Built: Sat Dec 19 02:35:52 2015 OS/Arch: linux/amd64 Error response from daemon: client is newer than server (client API version: 1.22, server API version: 1.21)
ホストのバージョンとコンテナのAPIのバージョンがずれていることが確認された。この辺をみると、docker-machine upgrade machine_name
コマンドを使ってAPIのバージョンを上げる可能らしいが、この方法はワークしなかった。(APIのバージョンが1.21までしか上げられない)。そこで、Installation from binaries を参考にしてコンテナ側のDockerバイナリを古い1.9.1に入れ替える。
# apt-get install wget # wget https://get.docker.com/builds/Linux/x86_64/docker-1.9.1 # cp docker-1.9.1 $(which docker) # docker version Client: Version: 1.9.1 API version: 1.21 Go version: go1.4.3 Git commit: a34a1d5 Built: Fri Nov 20 17:56:04 UTC 2015 OS/Arch: linux/amd64 Server: Version: 1.9.1 API version: 1.21 Go version: go1.4.3 Git commit: a34a1d5 Built: Fri Nov 20 17:56:04 UTC 2015 OS/Arch: linux/amd64
APIのバージョンが一致してエラーが出なくなったので、もう一度Trust contentのテスト用イメージをプルする。またエラーが出る。
# docker pull hello-world # docker tag hello-world registry:5000/test/hello-world:latest # export DOCKER_CONTENT_TRUST=1 # export DOCKER_CONTENT_TRUST_SERVER=https://notaryserver:4443
レジストリにhello-worldのイメージが登録されてい無いことを確認する。
# docker pull registry:5000/test/hello-world Using default tag: latest no trust data available
レジストリにイメージを登録する。
docker push registry:5000/test/hello-world:latest
The push refers to a repository registry:5000/test/hello-world 0a6ba66e537a: Pushed b901d36b6f2f: Pushed latest: digest: sha256:bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858 size: 2749 Signing and pushing trust metadata You are about to create a new root signing key passphrase. This passphrase will be used to protect the most sensitive key in your signing system. Please choose a long, complex passphrase and be careful to keep the password and the key file itself secure and backed up. It is highly recommended that you use a password manager to generate the passphrase and keep it safe. There will be no way to recover this key. You can find the key in your config directory. Enter passphrase for new root key with id 347da56: Passphrase is too short. Please use a password manager to generate and store a good random passphrase. Enter passphrase for new root key with id 347da56: Repeat passphrase for new root key with id 347da56: Enter passphrase for new repository key with id registry:5000/test/hello-world (4d2561e): Repeat passphrase for new repository key with id registry:5000/test/hello-world (4d2561e): Finished initializing “registry:5000/test/hello-world”
docker pull registry:5000/test/hello-world Using default tag: latest Pull (1 of 1): registry:5000/test/hello-world:latest@sha256:bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858 sha256:bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858: Pulling from test/hello-world
Digest: sha256:bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858 Status: Downloaded newer image for registry:5000/test/hello-world@sha256:bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858 Tagging registry:5000/test/hello-world@sha256:bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858 as registry:5000/test/hello-world:latest
一度削除してpull出来ることを確認する
# docker images | grep hello registry:5000/test/hello-world latest 0a6ba66e537a 10 weeks ago 960 B hello-world latest 0a6ba66e537a 10 weeks ago 960 B registry:5000/test/hello-world0a6ba66e537a 10 weeks ago 960 B # docker rmi -f 0a6ba66e537a Untagged: hello-world:latest Untagged: registry:5000/test/hello-world:latest Untagged: registry:5000/test/hello-world@sha256:bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858 Deleted: 0a6ba66e537a53a5ea94f7c6a99c534c6adb12e3ed09326d4bf3b38f7c3ba4e7 Deleted: b901d36b6f2fd759c362819c595301ca981622654e7ea8a1aac893fbd2740b4c # docker pull registry:5000/test/hello-world Using default tag: latest Pull (1 of 1): registry:5000/test/hello-world:latest@sha256:bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858 sha256:bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858: Pulling from test/hello-world b901d36b6f2f: Pull complete 0a6ba66e537a: Pull complete Digest: sha256:bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858 Status: Downloaded newer image for registry:5000/test/hello-world@sha256:bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858 Tagging registry:5000/test/hello-world@sha256:bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858 as registry:5000/test/hello-world:latest
イメージを偽装する
登録したイメージを偽装するためにRegistryコンテナに入る。
➤ docker exec -it registry bash
pushした時にsha256の値が表示されるが、その値がパスに含まれていないパス使ってイメージのデータがあるディレクトリまで移動し偽装する。
# cd /var/lib/registry/docker/registry/v2/blobs/sha256/a3/a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4 # echo "Malicious data" > data
イメージの偽装が完了したら、Opeartion用コンテナからローカルに保存されている偽装前のイメージを削除する。
# docker images | grep hello hello-world latest 0a6ba66e537a 10 weeks ago 960 B registry:5000/test/hello-world latest 0a6ba66e537a 10 weeks ago 960 B # docker rmi -f 0a6ba66e537a Untagged: hello-world:latest Untagged: registry:5000/test/hello-world:latest Deleted: 0a6ba66e537a53a5ea94f7c6a99c534c6adb12e3ed09326d4bf3b38f7c3ba4e7 Deleted: b901d36b6f2fd759c362819c595301ca981622654e7ea8a1aac893fbd2740b4c
pullして失敗すればContent Trustの仕組みが有効であることが確認できる。
# docker pull registry:5000/test/hello-world Using default tag: latest Pull (1 of 1): registry:5000/test/hello-world:latest@sha256:bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858 sha256:bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858: Pulling from test/hello-world b901d36b6f2f: Pull complete 0a6ba66e537a: Verifying Checksum filesystem layer verification failed for digest sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
Content Trustによる真正性の検証により、偽装されたイメージが取得できなくなった。
余談:暗号系の基礎知識について
暗号の公開鍵暗号
とかデジタル署名
とかPKI
とかSSL/TLS
とか聞いてピンと来ない人は暗号の勉強が必要かもしれない。その際はこの本が圧倒的にわかりやすいのでお勧め。僕は第2版しか読んでないが、第3版は時代の流れを汲みとって大幅にアップデートされてるようなので読み直したい。
古典的な暗号理論ではなく、もう少し先端の暗号理論について知りたい人はこんな本もある。
余談2:
Notary Opretionコンテナ起動時に/var/run/docker.sockをマウントしているわけだがこれセキュリティ的に大丈夫だったんだっけ?。
余談3
コンテナイメージを偽装するときに、pushの時に表示されたsha256以下のblobを変更すると失敗する。
cd /var/lib/registry/docker/registry/v2/blobs/sha256/bb/bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858 echo "Malicious data" > data
こんな感じのエラーメッセージが表示される。
# docker pull registry:5000/test/hello-world Using default tag: latest Pull (1 of 1): registry:5000/test/hello-world:latest@sha256:bb39087fb4d12a5d9f2d179759a8599fa943cdc1922e1088215be5cd838b6858 Error response from daemon: manifest unknown: manifest unknown
ちなみに、blobとはBinary large objectのことである。
参考
公式
- Content trust in Docker
- Manage keys for content trust
- Automation with content trust
- Play in a content trust sandbox
Docker Content Trustについて
- Introducing Docker Content Trust | Docker Blog
- Docker adds digital signatures - Enterprise Times
- 【参考訳】 Docker Content Trust 入門 | Pocketstudio.jp log3
- Dockerが、ユーザー名前空間サポートなどのセキュリティ強化を発表