Artifact Registry で Immutable tags を利用する

Artifact Registry で Immutable tags の設定がリリースされました(記事執筆時点ではプレビュー)。
Artifact Registry の Docker リポジトリで Immutable tags を有効化することにより、当該リポジトリでは一度付与したタグを変更・削除することができなくなります。
一度付与したタグを変更・削除できなくなることで、誤って新しいイメージに古いタグを付与してタグを上書きしてしまったり、latest タグを本番利用して意図せずコンテナイメージが更新されてしまう、などのトラブルを予防することができます。

cloud.google.com

Immutable tags の設定はリポジトリ毎に行えるため、同じプロジェクト内の Artifact Registry の Docker リポジトリであっても、有効化するリポジトリを個別に選択することが可能です。
新規リポジトリの作成時に適用することも、既存リポジトリに後から適用することも可能です。

実際にどのような挙動になるのか、環境を構築して試してみた様子をご紹介します。

検証

まずはプロジェクトの Artifact Registry のページに移動し、サンプルの Docker リポジトリを作成します。

すでにコンソールからも設定できるようになっていますが、今回は挙動の違いを説明するため最初は Immutable tags を設定せずにリポジトリを作成します。

無事に作成されました。
それでは Cloud Shell をローカルの開発環境に見立てて、コンテナイメージの push や pull, それに伴うタグの変化を見ていきましょう。

Immutable tags の有効化されていないリポジトリ

まずは適当なコンテナイメージを作成します。
Dockerfile は次のとおりとしました。

$ cat Dockerfile
FROM scratch
LABEL version="1.0"

ビルドしてリポジトリに push します。
リポジトリにアクセスする前に、Artifact Registry に対するリクエストの認証 も行っておきましょう。

$ gcloud auth configure-docker us-central1-docker.pkg.dev
WARNING: Your config file at [/home/*******/.docker/config.json] contains these credential helper entries:

{
  "credHelpers": {
    "gcr.io": "gcloud",
    "us.gcr.io": "gcloud",
    "eu.gcr.io": "gcloud",
    "asia.gcr.io": "gcloud",
    "staging-k8s.gcr.io": "gcloud",
    "marketplace.gcr.io": "gcloud",
    "us-central1-docker.pkg.dev": "gcloud"
  }
}
Adding credentials for: us-central1-docker.pkg.dev
gcloud credential helpers already registered correctly.

$ docker build .
[+] Building 0.0s (3/3) FINISHED
 => [internal] load .dockerignore  0.0s
 => => transferring context: 2B  0.0s
 => [internal] load build definition from Dockerfile  0.0s
 => => transferring dockerfile: 70B  0.0s
 => exporting to image  0.0s
 => => writing image sha256:f81ebf003bbfd311ca96466afcea450c2eb5f1a99c6cff9d2f2eb6e28423244d  0.0s
 
$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED   SIZE
<none>       <none>    f81ebf003bbf   N/A       0B

$ docker tag f81ebf003bbf us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app

$ docker tag f81ebf003bbf us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app:1.0

$ docker images
REPOSITORY                                                                      TAG       IMAGE ID       CREATED   SIZE
us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app   1.0       f81ebf003bbf   N/A       0B
us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app   latest    f81ebf003bbf   N/A       0B

$ docker push us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app
Using default tag: latest
The push refers to repository [us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app]
latest: digest: sha256:ac784f1fc90581fdf4a3c799a0511267e7a885396671fc87a58fec3e5683d970 size: 313

$ docker push us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app:1.0
The push refers to repository [us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app]
1.0: digest: sha256:ac784f1fc90581fdf4a3c799a0511267e7a885396671fc87a58fec3e5683d970 size: 313

コンソールからも、Artifact Registry のリポジトリにイメージが push されたことが確認できます。
この時点で、sample-app のコンテナイメージ(イメージ ID f81ebf003bbf)には 1.0latest のタグが付与されています。

次に、更新版のコンテナイメージをビルドしてリポジトリに push します。
ここではバージョンを 1.1 としました。

$ cat Dockerfile
FROM scratch
LABEL version="1.1"

$ docker build .
[+] Building 0.0s (3/3) FINISHED
 => [internal] load .dockerignore  0.0s
 => => transferring context: 2B  0.0s
 => [internal] load build definition from Dockerfile  0.0s
 => => transferring dockerfile: 70B  0.0s
 => exporting to image  0.0s
 => => writing image sha256:b7fe6fa49cf475863c3f799d32167a28cc3a25a1b192640e43643b660fa8dda1  0.0s
 
$ docker images
REPOSITORY                                                                      TAG       IMAGE ID       CREATED   SIZE
us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app   1.0       f81ebf003bbf   N/A       0B
us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app   latest    f81ebf003bbf   N/A       0B
<none>                                                                          <none>    b7fe6fa49cf4   N/A       0B

$ docker tag b7fe6fa49cf4 us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app

$ docker tag b7fe6fa49cf4 us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app:1.1

$ docker images
REPOSITORY                                                                      TAG       IMAGE ID       CREATED   SIZE
us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app   1.0       f81ebf003bbf   N/A       0B
us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app   1.1       b7fe6fa49cf4   N/A       0B
us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app   latest    b7fe6fa49cf4   N/A       0B

$ docker push us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app
Using default tag: latest
The push refers to repository [us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app]
latest: digest: sha256:0270d6f43f79a1501be08239e133f760e000ff2cc9a31fdd0e60679fb56e61d4 size: 313

$ docker push us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app:1.1
The push refers to repository [us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app]
1.1: digest: sha256:0270d6f43f79a1501be08239e133f760e000ff2cc9a31fdd0e60679fb56e61d4 size: 313

コンソールからも、Artifact Registry のリポジトリにイメージが push されたことが確認できます。
latest のタグを付与し直したため、sample-app の最新のコンテナイメージ(イメージ ID b7fe6fa49cf4)に 1.1latest のタグが付与され、古いコンテナイメージ(イメージ ID f81ebf003bbf)からは latest タグが削除されています(レジストリ内のすべてのタグにユニーク制約があるため)。

Immutable tags の有効化されたリポジトリ

それでは、リポジトリの設定を変更して Immutable tags を有効化してみましょう。
今回はコンソールから変更します。

次に、更新版のコンテナイメージをビルドしてリポジトリに push します。
ここではバージョンを 1.2 としました。

$ cat Dockerfile
FROM scratch
LABEL version="1.2"

$ docker build .
[+] Building 0.0s (3/3) FINISHED
 => [internal] load build definition from Dockerfile  0.0s
 => => transferring dockerfile: 70B  0.0s
 => [internal] load .dockerignore  0.0s
 => => transferring context: 2B  0.0s
 => exporting to image  0.0s
 => => writing image sha256:b65e3f9f75d82ff6c5bf3faf7b66bdd9347d8876bc364d9ac52c686b46608b4d  0.0s
 
$ docker images
REPOSITORY                                                                      TAG       IMAGE ID       CREATED   SIZE
us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app   1.0       f81ebf003bbf   N/A       0B
us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app   1.1       b7fe6fa49cf4   N/A       0B
us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app   latest    b7fe6fa49cf4   N/A       0B
<none>                                                                          <none>    b65e3f9f75d8   N/A       0B

$ docker tag b65e3f9f75d8 us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app

$ docker tag b65e3f9f75d8 us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app:1.2

$ docker images
REPOSITORY                                                                      TAG       IMAGE ID       CREATED   SIZE
us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app   1.0       f81ebf003bbf   N/A       0B
us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app   1.1       b7fe6fa49cf4   N/A       0B
us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app   1.2       b65e3f9f75d8   N/A       0B
us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app   latest    b65e3f9f75d8   N/A       0B

$ docker push us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app
Using default tag: latest
The push refers to repository [us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app]
manifest invalid: cannot update tag latest. The repository has enabled tag immutability

$ docker push us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app:1.2
The push refers to repository [us-central1-docker.pkg.dev/titanium-atlas-382304/sample-repository/sample-app]
1.2: digest: sha256:ab999980dde7c3fd1208685ef5fd32b844ccd888cbbf4d4c75b9b146adc4333a size: 313

コンソールからも、Artifact Registry のリポジトリにイメージが push されたことが確認できます。
latest のタグを付与し直しましたが、Immutable tags が有効化されているためイメージの push 時にエラーが表示され、push に失敗していることがわかります(manifest invalid: cannot update tag latest. The repository has enabled tag immutability のエラーメッセージ)。
sample-app の最新のコンテナイメージ(イメージ ID b65e3f9f75d8)には 1.2 のタグが付与されましたが、latest タグは古いコンテナイメージ(イメージ ID b7fe6fa49cf4)に付与されたままとなります。

ちなみに、タグの削除が行えないことで、該当するコンテナイメージを削除する、あるいはパッケージごと削除するといった操作も行えなくなります。
タグを付与したイメージを削除する必要がある場合は、Immutable tags を一度無効化する必要があります。
gcr-cleaner などのツールを使ってリポジトリのハウスキーピングを行っている場合は注意が必要です。

タグの削除操作の失敗

イメージの削除操作の失敗

利用時の注意点

リポジトリ作成後も Immutable tags の有効・無効は切り替えられるため、セキュリティ対策としては Immutable tags の有効化と併せて、Artifact Registry のリポジトリ設定の変更ができる権限を付与するユーザを限定しておくことをおすすめします。
誰でもリポジトリの設定変更ができてしまうと、Immutable tags を無効化して悪意のあるコンテナイメージに正規のタグを付与して Immutable tags を再度有効化する、あるいは意図せず設定を変更してしまいいつの間にか Immutable tags が無効化されている、といった事態を招きかねないためです。
設定変更の操作は Audit logs に残るため、不正行為を行ったユーザを後から特定することは可能ですが、監査システムとしてリポジトリの設定変更を追跡していない場合は見逃したり、発覚が遅れる場合があるため、予防的な対策として IAM の最小権限の原則は守っておくと良いでしょう。

また、上記検証でも見たように、Immutable tags を有効化すると、それまでに付与されていた latest タグは Immutable tags をもう一度無効化しない限り削除できません。
latest タグのように付与先のイメージをローテーションするようなタグを利用していたリポジトリで、後から Immutable tags を有効化する場合は、誤った利用を防ぐため事前にそうしたタグを削除してから有効化すると良いでしょう。

雑感

Immutable tags は単体の機能としても有用ですが、今後 Binary Authorization を有効化した環境でタグによるイメージ指定に使えるようになるとより便利になるのではと思いました。
現状、Binary Authorization を有効化した環境では、デプロイするコンテナイメージをダイジェストで指定する必要がありますが、これはタグ指定では署名したイメージの特定を保証できないためと思われます。
信頼済みのイメージだけを GKE クラスタに展開できる安全性という点では魅力的な Binary Authorization ですが、ダイジェストでのイメージの指定は人間にとっての可読性が劣る面もあります。
そのため、今回の Artifact Registry のアップデートのように、タグの Immutability が保証されているリポジトリを対象として、タグ指定でも Binary Authorization が使えるようになるとより使い勝手が良くなりそうです。

また、組織ポリシーとして Immutable tags の有効化を必須とするようなポリシーがデフォルトで提供されれば、組織内でのリポジトリ設定の統制もより簡単になりそうです。

まとめ

Artifact Registry に Immutable tags の機能がリリースされました。
タグの付与・更新に際して故意・過失を問わず起こる可能性のあったトラブルを未然に防ぐ手段として有用です。
タグの Immutability が保証されているリポジトリを使うことで、チームの習慣付けとして「小さな変更もこまめにバージョニングする」、「latest タグの利用を抑制する」といったことにも自然とつながるため、今後機会があれば活用していきたいと思います。