Docker Notaryで署名したイメージをDockerHubで公開する
はじめに
ソフトウェアをインストールするさいにHashの検証や署名の検証などで、ダウンロードしてきたバイナリの信頼性を検証するみたいなのはよくやると思います。Dockerのイメージに対してこれがどのように解決されるかというところに理解が浅かったので、Dockerのコンテントトラストのドキュメントを読みつつ、自分で作成したイメージに署名をするところまでやってみようかと思います。
どのようにDocker のコンテントトラストが実現されるか
DockerではDCT(Docker Content Trust)と呼ばれる機能でデジタル署名を利用してデータの整合性と公開者情報を検証できる仕組みを提供しているようです。
この機能を使うと特定のイメージタグに対して検証を行えるようになります。
DCTでは、タグ毎にサインを行いどのタグにサインを行なうかはイメージの公開者が決める必要があります。
また、1つのリポジトリで1つのイメージに対してサインされているタグは1つだけ存在するようです。
クライアント目線で言うと、DCTを有効にした場合実行できるイメージはサインされたイメージのみで、ほかは利用できなくなります。フィルターの概念が近いようです。
Notaryについて
Docker DCTの仕組みはNotrayという機能の上で実装されているみたいです。
Notrayはサーバサイドとクライアントサイドで提供されており、サーバサイドが利用するDockerリポジトリにアタッチされている必要があるようです。
このブログでは独自にリポジトリを用意してアタッチすることなどは行いません(そのやり方に付いてはこちらを確認ください)。
今回はDocker Hubを使います。
また、NotrayはTOFU(Trust On First Use)というモデルを採用しており、最初にダウンロードしたものを信じるという仕様になってます。
V2ではこれを改善するような議論がコミュニティで行われているようです(ソースを見つけられなかった...)
自分で作成したイメージに署名する
環境
今回は以下の環境で諸々を動かしてみます。
$ lsb_release -a LSB Version: core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch Distributor ID: Ubuntu Description: Ubuntu 20.04.2 LTS Release: 20.04 Codename: focal $ uname -srvmpio Linux 5.4.0-80-generic #90-Ubuntu SMP Fri Jul 9 22:49:44 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux $ docker version Client: Docker Engine - Community Version: 20.10.8 API version: 1.41 Go version: go1.16.6 Git commit: 3967b7d Built: Fri Jul 30 19:54:27 2021 OS/Arch: linux/amd64 Context: default Experimental: true Server: Docker Engine - Community Engine: Version: 20.10.8 API version: 1.41 (minimum version 1.12) Go version: go1.16.6 Git commit: 75249d8 Built: Fri Jul 30 19:52:33 2021 OS/Arch: linux/amd64 Experimental: true containerd: Version: 1.4.9 GitCommit: e25210fe30a0a703442421b0f60afac609f950a3 runc: Version: 1.0.1 GitCommit: v1.0.1-0-g4144b63 docker-init: Version: 0.19.0 GitCommit: de40ad0
イメージの作成
まずは署名を行なうイメージを作っておきます。
今回はnginxの公式イメージを使って作ります。
$ docker create nginx $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 896fa7872413 nginx "/docker-entrypoint.…" 27 seconds ago Created cranky_dewdney $ docker commit 896fa7872413 hirohiroyuya/nginx:singed sha256:ffcb0c4915f34a5d68f4eb6e8452db191d4c19e6ecfc4bc17a90b16689e0dfaa $ docker commit 896fa7872413 hirohiroyuya/nginx:non-singed sha256:8e321e701fd6f0f5bf730dfd55c464804f85570e0dcb31326f6e50e4c289b8a4 $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE hirohiroyuya/nginx non-singed 8e321e701fd6 About a minute ago 133MB hirohiroyuya/nginx singed ffcb0c4915f3 About a minute ago 133MB nginx latest 08b152afcfae 3 weeks ago 133MB
今回は2つのタグを作り片方は署名しもう片方は署名せずにPushしようと思います。 下準備は完了です。
鍵の作成とNotaryサーバへの設定
署名をする前に鍵を作る必要があります。
鍵を作る場合には以下のコマンドを実行します。
$ docker trust key generate henoheno Generating key for henoheno... Enter passphrase for new henoheno key with ID 3eb42d4: Repeat passphrase for new henoheno key with ID 3eb42d4: Successfully generated and loaded private key. Corresponding public key available: /home/someone/henoheno.pub
今回は試しませんがすでに鍵がある場合は以下のようにして既存のロードできるみたいです。
$ docker trust key load key.pem --name jeff
次に作成された公開鍵を公開鍵を Notary サーバーへ追加します。 今回はDockerHubを使うので特にドメインなどは指定してませんが、必要な場合は指定してください。
$ docker trust signer add --key /home/yuya-hirooka/henoheno.pub henoheno hirohiroyuya/nginx Adding signer "henoheno" to hirohiroyuya/nginx... Enter passphrase for repository key with ID 7d993ef: Successfully added signer: henoheno to hirohiroyuya/nginx
ここまでで鍵の生成とサーバへの設定は終了です。
署名したイメージとしてないイメージをリポジトリにPushする
署名は以下のコマンドで行なうことができます。
$ docker trust sign hirohiroyuya/nginx:singed Signing and pushing trust data for local image hirohiroyuya/nginx:singed, may overwrite remote trust data The push refers to repository [docker.io/hirohiroyuya/nginx] e3135447ca3e: Mounted from library/nginx b85734705991: Mounted from library/nginx 988d9a3509bb: Mounted from library/nginx 59b01b87c9e7: Mounted from library/nginx 7c0b223167b9: Mounted from library/nginx 814bff734324: Mounted from library/nginx singed: digest: sha256:505db062138c1e3dd094c9e5811c6cd9baae8c7beb77b1c010db809f2e0d8fd3 size: 1570 Signing and pushing trust metadata Enter passphrase for henoheno key with ID 3eb42d4: Successfully signed docker.io/hirohiroyuya/nginx:singed
イメージをPushします。
この際にDOCKER_CONTENT_TRUST=1
を環境変数に指定してコンテントトラストを有効にする必要があるようです。
$ DOCKER_CONTENT_TRUST=1 docker push hirohiroyuya/nginx:singed The push refers to repository [docker.io/hirohiroyuya/nginx] e3135447ca3e: Layer already exists b85734705991: Layer already exists 988d9a3509bb: Layer already exists 59b01b87c9e7: Layer already exists 7c0b223167b9: Layer already exists 814bff734324: Layer already exists singed: digest: sha256:505db062138c1e3dd094c9e5811c6cd9baae8c7beb77b1c010db809f2e0d8fd3 size: 1570 Signing and pushing trust metadata Enter passphrase for henoheno key with ID 3eb42d4: Successfully signed docker.io/hirohiroyuya/nginx:singed
これでDockerHubでの公開が完了しました。
Pushしたイメージの署名の情報を見るためには以下のコマンドを実行します。
$ docker trust inspect --pretty hirohiroyuya/nginx:singed Signatures for hirohiroyuya/nginx:singed SIGNED TAG DIGEST SIGNERS singed 12d3e6084e8af99509bd65b1d4583953cfb0791ddd66c4db199b725f6463327c henoheno List of signers and their keys for hirohiroyuya/nginx:singed SIGNER KEYS henoheno 3eb42d4ad775 Administrative keys for hirohiroyuya/nginx:singed Repository Key: 7d993ef2d41d5473aa8556e987cf8449bda7edd07b34856a13788a495bf70e3c Root Key: 9f0717638ac4ef0e113004354e2946c8010e2f6cb5b425af4d0779986ad45c74
Pushしたイメージを利用する
まずは、比較を行なうために先程のhirohiroyuya/nginx:non-singed
の方もPushしておきます。
また、ローカルのイメージも綺麗にしておきます。
$ docker push hirohiroyuya/nginx:non-singed The push refers to repository [docker.io/hirohiroyuya/nginx] e3135447ca3e: Layer already exists b85734705991: Layer already exists 988d9a3509bb: Layer already exists 59b01b87c9e7: Layer already exists 7c0b223167b9: Layer already exists 814bff734324: Layer already exists non-singed: digest: sha256:1ab4fc461a4c9028fa375aefec46c862d9317a2b2009321273c0135f7bdcb6ec size: 1570 $ docker rmi -f $(docker images -a -q) $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE
これでOK。
DockerクライアントではDCTはデフォルトで無効になっているようです。
これを有効にするためにはDOCKER_CONTENT_TRUST=1
を環境変数に指定してコマンドを実行する必要があるようです。
署名が行われていない状態のnginx:non-signed
をdocker run
してみます。
$ DOCKER_CONTENT_TRUST=1 docker run hirohiroyuya/nginx:non-singed docker: No valid trust data for non-singed.
実行できないようになってますね。
今度は、署名がされているものをdocker run
してみます。
$ DOCKER_CONTENT_TRUST=1 docker run hirohiroyuya/nginx:singed /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh 10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf 10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh /docker-entrypoint.sh: Configuration complete; ready for start up 2021/08/14 07:41:54 [notice] 1#1: using the "epoll" event method 2021/08/14 07:41:54 [notice] 1#1: nginx/1.21.1 2021/08/14 07:41:54 [notice] 1#1: built by gcc 8.3.0 (Debian 8.3.0-6) 2021/08/14 07:41:54 [notice] 1#1: OS: Linux 5.4.0-80-generic 2021/08/14 07:41:54 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576 2021/08/14 07:41:54 [notice] 1#1: start worker processes 2021/08/14 07:41:54 [notice] 1#1: start worker process 36 2021/08/14 07:41:54 [notice] 1#1: start worker process 37 2021/08/14 07:41:54 [notice] 1#1: start worker process 38 2021/08/14 07:41:54 [notice] 1#1: start worker process 39 2021/08/14 07:41:54 [notice] 1#1: start worker process 40 2021/08/14 07:41:54 [notice] 1#1: start worker process 41 2021/08/14 07:41:54 [notice] 1#1: start worker process 42 2021/08/14 07:41:54 [notice] 1#1: start worker process 43
きちんと実行ができました。
この通り、クライアントサイドではDCTを有効にしている場合、署名されたものしか実行されず更に検証も行われているようです。