buildx で push したマルチアーキテクチャイメージの Amazon Inspector 検出結果をタグで追跡する
docker buildx で push したマルチアーキテクチャイメージの Amazon Inspector の検出結果を、イメージタグから追跡できるようにする
はじめに
コンテナイメージの脆弱性管理に Amazon Inspector を使用している方も多いのではないでしょうか。
当社でも Amazon ECR で管理しているコンテナイメージに対して Amazon Inspector による脆弱性管理を実施しているケースがあります。
Inspector の検出結果を確認していたところ、ECR にはイメージタグをつけて push したはずが、Inspector では <untagged> と表示される場合があることに気がつきました。

この状態では、どのバージョン (タグ) のイメージに対する検出結果なのか、Inspector 側から判断ができません。
調査したところ、docker buildx でマルチアーキテクチャイメージを push した際のイメージの構造が関係していることが分かりました。
今回の記事では、buildx で push したマルチアーキテクチャのイメージについて、Inspector の検出結果をタグから追跡する方法を検証した結果をご紹介します。
検証環境
本記事の内容は、以下の環境で確認しています。
- Amazon ECR のスキャン設定は拡張スキャン (Amazon Inspector) を有効にしている
- イメージは
linux/amd64とlinux/arm64の 2 つのアーキテクチャ向けにビルドする - ビルドには
docker-containerドライバーの buildx ビルダーを使用する - 検証用のイメージは、検出結果が出るように、サポートが終了したベースイメージを使用する
FROM node:16-bullseye-slimなお、以降のコマンド例に含まれるアカウント ID は 123456789012 に置き換えています。
何が起きているか
まず、一般的な方法でマルチアーキテクチャのイメージを push します。
docker buildx build --platform linux/amd64,linux/arm64 -t $REPO:v1 --push .push したイメージの構造を docker buildx imagetools inspect で確認すると、以下のようになっています。
$ docker buildx imagetools inspect $REPO:v1
Name: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/verify-index:v1
MediaType: application/vnd.oci.image.index.v1+json
Digest: sha256:eb425caf55fbe61845a183d9fe78494eebb44a7238d8cdfcdbda717d11df19cf
Manifests:
Name: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/verify-index:v1@sha256:a98a56059acdfd657355a178e765ba35e7ca979a90365354d6d786719c528110
MediaType: application/vnd.oci.image.manifest.v1+json
Platform: linux/amd64
Name: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/verify-index:v1@sha256:bf8103057072081b4a264bfc3670b4abaa232b5d1521520ef1c96e0a32d7a653
MediaType: application/vnd.oci.image.manifest.v1+json
Platform: linux/arm64
Name: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/verify-index:v1@sha256:390152150fe4242a51760f70ef6717a68fdf7828eafd6bdef1e5272fa6720b43
MediaType: application/vnd.oci.image.manifest.v1+json
Platform: unknown/unknown
Annotations:
vnd.docker.reference.digest: sha256:a98a56059acdfd657355a178e765ba35e7ca979a90365354d6d786719c528110
vnd.docker.reference.type: attestation-manifest
Name: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/verify-index:v1@sha256:578e52194d313be54a400b21963fb818815fd5211057100cb1a1de30afd8429c
MediaType: application/vnd.oci.image.manifest.v1+json
Platform: unknown/unknown
Annotations:
vnd.docker.reference.digest: sha256:bf8103057072081b4a264bfc3670b4abaa232b5d1521520ef1c96e0a32d7a653
vnd.docker.reference.type: attestation-manifest
タグ v1 がついているのはイメージインデックス (application/vnd.oci.image.index.v1+json) で、その中に以下の 4 つのマニフェストが含まれています。
linux/amd64のイメージ (sha256:a98a...)linux/arm64のイメージ (sha256:bf81...)- 各イメージのアテステーション (
unknown/unknown)- buildx が push 時にデフォルトで付与する provenance (ビルドの来歴) です
vnd.docker.reference.digestで、どのイメージのアテステーションかを示しています
イメージインデックスは、アーキテクチャごとのイメージを digest で参照する「目次」のようなものです。タグはイメージインデックスにのみついており、アーキテクチャごとのイメージにはタグがついていません。
Inspector の検出結果一覧に表示されていたのは、このうち sha256:a98a... と sha256:bf81... の 2 つでした。つまり、検出結果はタグのついていないアーキテクチャごとのイメージに紐づいていたことになります。
タグを指定して検出結果を取得した場合
push したタグを指定して aws ecr describe-image-scan-findings を実行すると、以下の結果が返ります。
$ aws ecr describe-image-scan-findings --repository-name verify-index --image-id imageTag=v1
{
"imageScanFindings": {
"findings": []
},
"registryId": "123456789012",
"repositoryName": "verify-index",
"imageId": {
"imageDigest": "sha256:eb425caf55fbe61845a183d9fe78494eebb44a7238d8cdfcdbda717d11df19cf",
"imageTag": "v1"
},
"imageScanStatus": {
"status": "UNSUPPORTED_IMAGE",
"description": "UnsupportedImageError: The media type, operating system and/or package manager are not supported."
}
}
コマンド自体はエラーにならず、imageScanStatus.status が UNSUPPORTED_IMAGE となり、検出結果は空で返ります。タグが指しているイメージインデックスはスキャンの対象外となっているためです。
Inspector のカバレッジ (aws inspector2 list-coverage) でも、イメージインデックスとアテステーションは UNSUPPORTED_MEDIA_TYPE としてスキャンの対象外になっていることが確認できます。
タグから検出結果を辿る方法
タグからイメージインデックスを取得し、目的のアーキテクチャのイメージの digest を取り出せば、検出結果を取得できます。
REPO_NAME=...
TAG=...
# イメージインデックスから linux/amd64 のイメージの digest を取り出す
DIGEST=$(aws ecr batch-get-image \
--repository-name "$REPO_NAME" --image-ids imageTag="$TAG" \
--accepted-media-types \
application/vnd.oci.image.index.v1+json \
application/vnd.docker.distribution.manifest.list.v2+json \
--query 'images[0].imageManifest' --output text \
| jq -r '.manifests[] | select(.platform.os=="linux" and .platform.architecture=="amd64") | .digest')
# 深刻度ごとの件数を集計する
aws ecr describe-image-scan-findings --repository-name "$REPO_NAME" \
--image-id imageDigest="$DIGEST" \
--query 'imageScanFindings.enhancedFindings[].severity' --output json \
| jq 'group_by(.) | map({(.[0]): length}) | add'上記を実行すると、linux/amd64 のイメージの検出結果を取得できます。アテステーションは platform が unknown/unknown となっているため、プラットフォームで絞り込むことで自然に除外されます。
{
"CRITICAL": xx,
"HIGH": xx,
"LOW": xx,
"MEDIUM": xx,
"UNTRIAGED": xx
}
このような方法でタグに関連する検出結果を取得することができますが、Inspector のコンソールからもタグで追跡ができるのかが気になり、アーキテクチャごとのイメージにもタグをつける方法を検証しました。
アーキテクチャごとにタグをつける
イメージインデックスは、アーキテクチャごとのイメージを digest で参照しているだけです。そのため、アーキテクチャごとのイメージを先にタグつきで push し、それらを参照するイメージインデックスを後から作成することができます。
まず、アーキテクチャごとにタグをつけて push します。
docker buildx build --platform linux/amd64 --provenance=false \
-t $REPO:v1-amd64 --push .
docker buildx build --platform linux/arm64 --provenance=false \
-t $REPO:v1-arm64 --push .ここでは --provenance=false を指定して、アテステーションをつけずに push しています。アテステーションをつけると、アーキテクチャごとのタグもイメージインデックスにつき、スキャンされるイメージにタグがつかなくなるためです。詳しくは後述します。
次に、上記のイメージを参照するイメージインデックスを作成します。
docker buildx imagetools create -t $REPO:v1 $REPO:v1-amd64 $REPO:v1-arm64docker buildx imagetools create は、イメージの再ビルドやダウンロードは行わず、指定したイメージの digest とプラットフォームを並べたイメージインデックスをレジストリに push します。
--provenance=false が必要な理由
--provenance=false を指定せずに、同じ手順でアーキテクチャごとに push した場合の結果も確認しました。なお、この検証では別のイメージを使用しているため、digest は前述のものとは異なります。
v1-amd64 の中身を確認すると、amd64 のイメージとそのアテステーションを含むイメージインデックスになっていました。
$ docker buildx imagetools inspect $REPO:v1-amd64 --raw
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.index.v1+json",
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:d0463ce4ef72e3c095a3b098f1acfe7ec96542c9799692948a7cb63a73fd523d",
"size": 480,
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:b34a51fea10bae53913c55959d7fd71533d6eefca640c3c09d15b32da10fba49",
"size": 836,
"annotations": {
"vnd.docker.reference.digest": "sha256:d0463ce4ef72e3c095a3b098f1acfe7ec96542c9799692948a7cb63a73fd523d",
"vnd.docker.reference.type": "attestation-manifest"
},
"platform": {
"architecture": "unknown",
"os": "unknown"
}
}
]
}
この状態で imagetools create を実行し、Inspector のカバレッジを確認した結果は以下のとおりです。
| 中身 | タグ | カバレッジの理由 |
|---|---|---|
イメージインデックス (imagetools create で作成) | v1 | UNSUPPORTED_MEDIA_TYPE |
| イメージインデックス (amd64 + アテステーション) | v1-amd64 | UNSUPPORTED_MEDIA_TYPE |
| イメージインデックス (arm64 + アテステーション) | v1-arm64 | UNSUPPORTED_MEDIA_TYPE |
| amd64 のイメージ | なし | スキャン済み |
| arm64 のイメージ | なし | スキャン済み |
| アテステーション | なし | UNSUPPORTED_MEDIA_TYPE |
アーキテクチャごとのタグ v1-amd64 / v1-arm64 は、アーキテクチャごとのイメージではなく、スキャンの対象外となるイメージインデックスについていました。スキャンされたイメージにはタグがつかないため、通常のマルチアーキテクチャの push と同じ状態になってしまいます。
アーキテクチャごとのイメージにタグをつけるためには、--provenance=false を指定して、アテステーションをつけずに push する必要があります。
Inspector で表示されるタグの変化
--provenance=false を指定して push したリポジトリ (verify-per-arch) のイメージを確認すると、アーキテクチャごとのイメージにタグがついていることが確認できます。
$ aws ecr describe-images --repository-name verify-per-arch \
--query 'imageDetails[].[imageTags, imageDigest, imageManifestMediaType]' \
--output table
-----------------------------------------------------------------------------
| DescribeImages |
+---------------------------------------------------------------------------+
| v1 |
| sha256:cb3c968e7de53cfd04672d0c2ffc5714b1592489c9285d549b9ffade38bf9891 |
| application/vnd.oci.image.index.v1+json |
| v1-arm64 |
| sha256:bf8103057072081b4a264bfc3670b4abaa232b5d1521520ef1c96e0a32d7a653 |
| application/vnd.oci.image.manifest.v1+json |
| v1-amd64 |
| sha256:a98a56059acdfd657355a178e765ba35e7ca979a90365354d6d786719c528110 |
| application/vnd.oci.image.manifest.v1+json |
+---------------------------------------------------------------------------+
v1-amd64 と v1-arm64 は、同じ Dockerfile からビルドしており、前述の verify-index リポジトリのイメージと同じ digest になっています。
Inspector の検出結果一覧では、同じ digest のイメージが、verify-index では <untagged>、アーキテクチャごとにタグをつけた verify-per-arch では v1-amd64 / v1-arm64 として表示されました。

CLI でも、タグを指定して検出結果を取得できます。
aws ecr describe-image-scan-findings --repository-name verify-per-arch \
--image-id imageTag=v1-amd64 \
--query 'imageScanFindings.enhancedFindings[].severity' --output json \
| jq 'group_by(.) | map({(.[0]): length}) | add'件数は linux/amd64、linux/arm64 ともに、digest を指定して取得した verify-index の結果と一致しました。
利用者側への影響
イメージを利用する側は、これまでどおりイメージインデックスのタグ v1 を pull するだけで、アーキテクチャに合ったイメージが選択されます。
$ docker pull --platform linux/arm64 $REPO:v1
v1: Pulling from verify-per-arch
...
Digest: sha256:cb3c968e7de53cfd04672d0c2ffc5714b1592489c9285d549b9ffade38bf9891
Status: Downloaded newer image for 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/verify-per-arch:v1
$ docker image inspect $REPO:v1 --format '{{.Os}}/{{.Architecture}}'
linux/arm64
アーキテクチャ別のタグは検出結果を追跡するためのもので、利用者が意識する必要はありません。
留意点
アーキテクチャごとにタグをつける方法には、以下の留意点があります。
- provenance のアテステーションを付与しなくなる
- ビルドの来歴の証跡が必要な場合は、cosign などを利用して別途署名やアテステーションを行う必要があります
- アーキテクチャ別のタグを誤って利用しないようにする
- アーキテクチャ別のタグを指定すると、実行環境に関わらずそのアーキテクチャのイメージが使用されます。通常はイメージインデックスのタグを使用します
また、Inspector には、ECR のイメージを Amazon ECS や Amazon EKS で実行中のコンテナに対応づける機能があります (ドキュメント)。稼働中のイメージの脆弱性を把握したい場合は、こちらの機能も有用です。ただし、この情報は 24 時間に 1 回の更新となるなどの制限があります。
おわりに
今回の記事では、docker buildx で push したマルチアーキテクチャのイメージについて、Amazon Inspector の検出結果をタグから追跡する方法を検証した結果をご紹介しました。
マルチアーキテクチャのイメージを扱う際は、タグがどのマニフェストについているのかを意識しておくと、今回のような事象に気づきやすくなると思います。