Docker Content Trust 101

# はじめに
[NIST800-190 (Application Container Security Guide) ](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-190.pdf)をベースにコンテナセキュリティについてまとめている中で、**Docker Content Trust**について簡単に検証しました。
知識の整理も兼ねて、検証結果と調べた内容を簡単にまとめています。

# Docker Content Trust(DCT)とは?
Docker version 1.8から提供されている、イメージに対する署名を用いた検証機能です。
DCTにより、ユーザーは「イメージの完全性の検証」と「イメージ提供者の検証」が可能になりました。

## なぜDCTが必要?
DCTの必要性を理解するには、イメージにおけるセキュリティリスクを理解する必要があります。
[NIST800-190 (Application Container Security Guide)](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-190.pdf)の「3.1.5 Use of Untrusted Images」 には、下記のように記載されています。

>The portability and ease of reuse of containers increase the temptation for teams **to run images from external sources that may not be well validated or trustworthy.**
>(コンテナの可搬性と再利用性によって、チーム内では**検証もしくは信頼されていない外部ソースから提供されているイメージを実行する**誘惑が強くなります。) 

記載されている通り、Docker環境は[DockerHub](https://hub.docker.com/)などを用いて第三者提供のイメージを利用できるため、可搬性と再利用性の面で非常に優れています。

一方で、DCTの機能提供以前はイメージは改ざんに弱く、ユーザーはイメージを扱う際に中間者攻撃(MITM)のリスクに晒されていました。

これらのリスクに対応する機能として、DCTが提供されるようになりました。
(イメージダイジェストの生成機能はDocker1.6から提供されていたようですが、ダイジェストを利用したイメージの検証機能は提供されていないようです。)

- イメージの完全性の検証
    -  署名を用いてイメージを検証し、**改ざんされていないイメージのみを取得できるようにする。**
   <br>( = docker pullにより取得できるイメージの完全性を担保する)
- イメージ提供者の検証
    - イメージへの署名と検証をユーザーに強制することで、**不正なユーザーによるイメージのpushなどを判別する。(もしくは、処理自体を拒否する)**

# DCTの仕組み
Docker success centerの記事では、DCTの一連のフローについて下記のように紹介しています。
https://success.docker.com/article/introduction-to-docker-content-trust

>When a publisher using Docker Content Trust pushes an image to a remote registry, Docker Engine signs the image locally with the publisher’s private key. 
(Publisherがイメージをレジストリにpushする際に、Publisherの秘密鍵でイメージに署名する)

>When a user later pulls this image, Docker Engine uses the publisher’s public key to verify that the image is exactly what the publisher created, has not been tampered with, and is up to date.
(ユーザーがイメージをpullする際には、Publisherの公開鍵を利用して、「そのイメージがPublisherが作成したものか」「イメージが改ざんされていないか」「最新のイメージかどうか」を検証する)

下記のリンクでも記載されている通り、DCTはデジタル署名と同じ仕組みでイメージの完全性の担保を行なっていると考えられます。
http://pocketstudio.jp/log3/2015/08/14/content-trust-docker-1-8-ja/

>とりわけ初回の信頼に関して HTTPS を使う公開 PKI に対応することで、セキュリティに対する利便性をもたらします

上記の和訳記事に掲載されていた画像がDCTの仕組みを端的に表しています。
(図中では、署名用の鍵が**offline key**、検証用の鍵が**tagging key**として記載されています)
![dct1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/170549/69b22f9e-f085-99bf-0529-9d51b09628fe.png)

# 検証
下記の内容について、DCTに対応しているレジストリサービスを利用しながら検証を行います。

```
- DCTの一連のフロー
    - レジストリでのDCTの有効化
    - レジストリへのイメージのpush
    - イメージのpull
- DCTを有効化していないクライアント利用時の挙動
- 公式イメージのpull
```

今回は、下記のような環境で検証を行います。

- レジストリサービス：[IBM Cloud Registry(ICR)](https://cloud.ibm.com/docs/services/Registry?topic=registry-getting-started&locale=ja)
<br>(ライトユーザーのオプションとしてDocker Content Trustの有効化が可能です。)
- Docker Engine：19.03



## DCTのフロー検証
### レジストリ側の設定

レジストリ内で検証用のNamespaceを用意し、クライアント側ではDCTを有効化しておきます。

```
# レジストリのセットアップ
$ ibmcloud cr region-set us-south
地域は「us-south」に設定されました。地域は「us.icr.io」です。

OK
$ ibmcloud cr login
「registry.ng.bluemix.net」にログインしています...
「registry.ng.bluemix.net」にログインしました。

「us.icr.io」にログインしています...
「us.icr.io」にログインしました。


# DCTの有効化
$ export DOCKER_CONTENT_TRUST=1
$ export DOCKER_CONTENT_TRUST_SERVER=https://us.icr.io:4443

$ ibmcloud cr namespace-add sign_test

$ ibmcloud cr namespace-list
レジストリー「us.icr.io」のアカウント「xxx's Account」用の名前空間をリストしています...

名前空間
sign_test

OK
```

### イメージのpush
事前にイメージに対して、下記の形式でタグを付与しておきます。
`us.icr.io/ Namespace名(sign_test)/ レポジトリ名(myhello_repo):バージョン(1.0)`

タグを付与した後にDCTを有効化して、イメージのpushを行います。
ICRでは、offline keyは**root key**、tagging keyは**repository key**として記載されます。

```
# イメージへのタグ付け
$ docker tag myhello:latest us.icr.io/sign_test/myhello_repo:1.0

# タグにイメージが付与されていることを確認
$ docker image ls
us.icr.io/sign_test/myhello_repo     1.0                 73d3bebec135        7 months ago        6.5MB


# タグ付けしたイメージのpush
$ docker push us.icr.io/sign_test/myhello_repo:1.0
The push refers to repository [us.icr.io/sign_test/myhello_repo]
8c7811fbf293: Pushed
1.0: digest: sha256:9ae3c4a73834ea96f86808b3d95780d7727f8005f1bf4429f852d3a5258c6558 size: 528
Signing and pushing trust metadata

You are about to create a new root signing key passphrase. 
This passphrasewill 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 thekey 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.


# 初回のpush時にoffline key、tagging keyを作成するためにパスフレーズを入力する
Enter passphrase for new root key with ID b924e5e:
Repeat passphrase for new root key with ID b924e5e:
Enter passphrase for new repository key with ID 2b960ef:
Repeat passphrase for new repository key with ID 2b960ef:
Finished initializing "us.icr.io/sign_test/myhello_repo"

Successfully signed us.icr.io/sign_test/myhello_repo:1.0
```


### イメージのpush(2回目)
バージョンを上げた同じイメージをpushする際には、tagging key(**repository key**)のパスフレーズのみが求められます。

```
# タグ付けしたイメージのpush
$ docker push us.icr.io/sign_test/myhello_repo:1.1
The push refers to repository [us.icr.io/sign_test/myhello_repo]
8c7811fbf293: Layer already exists
1.1: digest: sha256:9ae3c4a73834ea96f86808b3d95780d7727f8005f1bf4429f852d3a5258c6558 size: 528
Signing and pushing trust metadata


# tagging keyのパスフレーズを入力する
Enter passphrase for repository key with ID 2b960ef:
Successfully signed us.icr.io/sign_test/myhello_repo:1.1
```

### イメージのpull
push後に手元のイメージを削除して、同じイメージをリモートからpullできる状態にします。
その上でイメージをpullすると、付与されているイメージの署名が検証されます。

```
# イメージのpull
$ docker pull us.icr.io/sign_test/myhello_repo:1.1

# イメージのダイジェストを突き合わせて、完全性の検証を行う
Pull (1 of 1): us.icr.io/sign_test/myhello_repo:1.1@sha256:9ae3c4a73834ea96f86808b3d95780d7727f8005f1bf4429f852d3a5258c6558
sha256:9ae3c4a73834ea96f86808b3d95780d7727f8005f1bf4429f852d3a5258c6558: Pulling from sign_test/myhello_repo
Digest: sha256:9ae3c4a73834ea96f86808b3d95780d7727f8005f1bf4429f852d3a5258c6558

# イメージがレポジトリ内で最新かどうかを確認
Status: Image is up to date for us.icr.io/sign_test/myhello_repo@sha256:9ae3c4a73834ea96f86808b3d95780d7727f8005f1bf4429f852d3a5258c6558
Tagging us.icr.io/sign_test/myhello_repo@sha256:9ae3c4a73834ea96f86808b3d95780d7727f8005f1bf4429f852d3a5258c6558 as us.icr.io/sign_test/myhello_repo:1.1

us.icr.io/sign_test/myhello_repo:1.1
```

## DCTを有効化していないクライアント利用時の挙動
別の端末で同じユーザーの認証情報を入力してregistryに接続できる状態にしておき、DCTを有効化していない状態でイメージをpushすると処理が拒否されます。

```
$ ibmcloud cr login

「registry.ng.bluemix.net」にログインしています...
「registry.ng.bluemix.net」にログインしました。

OK
$ docker push us.icr.io/sign_test/alpine_repo:1.0
The push refers to repository [us.icr.io/sign_test/alpine_repo]
77cae8ab23bf: Preparing

# イメージをpushすると、「Unauthorized」としてpushの実行が拒否される
unauthorized: authentication required
```

## 公式イメージのpull
クライアント側でDCTを有効化した状態で、Docker Hubにある公式イメージ(`alpine`)を取得します。
注意点としてはDCTを有効化した状態だと、公式のイメージには自身の署名が付与されていないため、**公式のイメージであってもpullができなくなります。**

今回は、一時的にDCTをクライアント側で無効化することで、署名の検証を回避して公式のイメージを取得しています。

```
# 署名が付与されていないので、イメージのpullに失敗する
$ docker pull alpine
Using default tag: latest
Error: remote trust data does not exist for docker.io/library/alpine: us.icr.io:4443 does not have trust data for docker.io/library/alpine

# DCTを無効化する
$ export DOCKER_CONTENT_TRUST=0
$ export DOCKER_CONTENT_TRUST_SERVER=https://registry.ng.bluemix.net:4443

#同じイメージをpullすると、取得できるようになる
$ docker pull alpine
Using default tag: latest
latest: Pulling from library/alpine
Digest: sha256:c19173c5ada610a5989151111163d28a67368362762534d8a8121ce95cf2bd5a
Status: Image is up to date for alpine:latest
docker.io/library/alpine:latest
```

# 各クラウドベンダーの対応状況(2018/10月末時点)
## AWS
AWSのレジストリサービスである[ECR](https://cloud.google.com/container-registry/?hl=ja)は、DCT未対応です。

AWSのContainer-roadmapでは、「Content Trust / Notary support for ECS/ECR」がissueとして挙がっており、9/28の段階で「We're Working On It」のステータスになっています。
https://github.com/aws/containers-roadmap/issues/43

## GCP
GCPのレジストリサービスである[Container Registry(GCR)](https://aws.amazon.com/jp/ecr/)は、DCT未対応です。

実装予定なども不明で、Stack overflowに投稿されたDCTの対応状況に関する質問にも未対応という回答がついていました。
https://stackoverflow.com/questions/53380782/pushing-signed-docker-images-to-gcr

## Azure
Azure Container Registryでは、DCTの有効化がオプションとして提供されています。
ユーザーに`AcrImageSigner`のロールを付与することで、IBM Cloud Registry同様にイメージを署名してpushすることが可能になります。
https://docs.microsoft.com/ja-jp/azure/container-registry/container-registry-content-trust

## IBM Cloud
IBM Cloud Registryでは、DCTの有効化がオプションとして提供されています。
ユーザーへのRoleの付与は必要なく、クライアント側でDCTを有効化することでイメージへの署名が可能になります。
https://cloud.ibm.com/docs/services/Registry?topic=registry-registry_trustedcontent&locale=ja

# 終わりに
Docker Content Trustについて、検証結果と調査した内容をまとめていきました。
自分の勉強した内容の整理のためにも、[NIST800-190 (Application Container Security Guide) ](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-190.pdf)をベースにしたコンテナセキュリティ周りの記事はこれからも書いていこうと思います。