チュートリアル: ビルド来歴データでコンテナイメージにアノテーションを付与する
注釈は、ビルドプロセスに関する貴重なメタデータを提供します。この情報は、監査と追跡可能性に使用されます。セキュリティインシデントが発生した場合、詳細な来歴データがあれば、調査と修正プロセスを大幅にスピードアップできます。
このチュートリアルでは、Cosignを使用してコンテナイメージのビルド、署名、注釈の処理を自動化するGitLabパイプラインをセットアップする方法について説明します。.gitlab-ci.ymlファイルを設定して、Dockerイメージをビルド、プッシュ、署名し、GitLabレジストリにプッシュできます。
コンテナイメージに注釈を付けるには、次の手順に従います:
- イメージとサービスイメージを設定する。
- CI/CD変数を定義します。
- OIDCトークンを準備する。
- コンテナを準備する。
- イメージをビルドしてプッシュする。
- Cosignでイメージに署名する。
- 署名と注釈を確認する。
すべてをまとめると、.gitlab-ci.ymlは、このチュートリアルの最後に記載されているサンプル設定のようになります。
はじめる前
以下が必要です:
- Cosign v2.0以降がインストールされている必要があります。
- GitLab Self-Managedの場合、署名を表示するには、メタデータデータベースで構成されたGitLabコンテナレジストリが必要です。
イメージとサービスイメージを設定する
.gitlab-ci.ymlファイルで、docker:cliイメージを使用し、DockerコマンドをCI/CDジョブで実行できるようにDocker-in-Dockerサービスを有効にします。
build_and_sign:
stage: build
image: docker:cli
services:
- docker:dind # Enable Docker-in-Docker service to allow Docker commands inside the containerCI/CD変数を定義する
GitLab CI/CD定義済み変数を使用して、イメージタグ付けとURIの変数を定義します。
variables:
IMAGE_TAG: $CI_COMMIT_SHORT_SHA # Use the commit short SHA as the image tag
IMAGE_URI: $CI_REGISTRY_IMAGE:$IMAGE_TAG # Construct the full image URI with the registry, project path, and tag
COSIGN_YES: "true" # Automatically confirm actions in Cosign without user interaction
FF_SCRIPT_SECTIONS: "true" # Enables GitLab's CI script sections for better multi-line script outputOIDCトークンを準備する
Cosignでキーレス署名を行うためのOIDCトークンをセットアップします。
id_tokens:
SIGSTORE_ID_TOKEN:
aud: sigstore # Provide an OIDC token for keyless signing with Cosignコンテナを準備する
.gitlab-ci.ymlファイルのbefore_scriptセクションで以下を行います:
- Cosignとjq(JSON処理用)をインストールします:
apk add --no-cache cosign jq - CI/CDジョブトークンを使用してGitLabコンテナレジストリログインを有効にします:
docker login -u "gitlab-ci-token" -p "$CI_JOB_TOKEN" "$CI_REGISTRY"
パイプラインは、必要な環境をセットアップすることから開始します。
イメージをビルドしてプッシュする
.gitlab-ci.ymlファイルのscriptセクションに、Dockerイメージをビルドし、GitLabコンテナレジストリにプッシュするための次のコマンドを入力します。
- docker build --pull -t "$IMAGE_URI" .
- docker push "$IMAGE_URI"このコマンドは、現在のディレクトリのDockerfileを使用してイメージを作成し、レジストリにプッシュします。
Cosignでイメージに署名する
イメージをビルドしてGitLabコンテナレジストリにプッシュした後、Cosignを使用して署名します。
.gitlab-ci.ymlファイルのscriptセクションに、次のコマンドを入力します:
- IMAGE_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE_URI")
- |
cosign sign "$IMAGE_DIGEST" \
--registry-referrers-mode oci-1-1 \
--annotations "com.gitlab.ci.user.name=$GITLAB_USER_NAME" \
--annotations "com.gitlab.ci.pipeline.id=$CI_PIPELINE_ID" \
# Additional annotations removed for readability
--annotations "tag=$IMAGE_TAG"このステップでは、イメージダイジェストを取得します。次に、Cosignを使用してイメージに署名し、いくつかの注釈を追加します。
署名と注釈を確認する
イメージに署名した後、署名と追加された注釈を確認することが重要です。
.gitlab-ci.ymlファイルに、cosign verifyコマンドを使用した確認ステップを含めます:
- |
cosign verify \
--annotations "tag=$IMAGE_TAG" \
--certificate-identity "$CI_PROJECT_URL//.gitlab-ci.yml@refs/heads/$CI_COMMIT_REF_NAME" \
--certificate-oidc-issuer "$CI_SERVER_URL" \
"$IMAGE_URI" | jq .この確認ステップでは、イメージに添付されている来歴データが正しいこと、および改ざんされていないことを確認します。cosign verifyコマンドは、署名を確認し、注釈をチェックします。出力には、署名処理中にイメージに追加したすべての注釈が表示されます。
出力には、以前に追加されたすべての注釈(以下を含む)が表示されます:
- GitLabユーザー名
- パイプラインIDとURL
- ジョブIDとURL
- コミットSHAと参照名
- プロジェクトパス
- イメージソースとリビジョン
これらの注釈を確認することにより、イメージの来歴データがそのまま残り、ビルドプロセスに基づいて予想される内容と一致することを確認できます。
.gitlab-ci.ymlの設定例
上記のすべての手順に従うと、.gitlab-ci.ymlファイルは次のようになります:
stages:
- build
build_and_sign:
stage: build
image: docker:cli
services:
- docker:dind # Enable Docker-in-Docker service to allow Docker commands inside the container
variables:
IMAGE_TAG: $CI_COMMIT_SHORT_SHA # Use the commit short SHA as the image tag
IMAGE_URI: $CI_REGISTRY_IMAGE:$IMAGE_TAG # Construct the full image URI with the registry, project path, and tag
COSIGN_YES: "true" # Automatically confirm actions in Cosign without user interaction
FF_SCRIPT_SECTIONS: "true" # Enables GitLab's CI script sections for better multi-line script output
id_tokens:
SIGSTORE_ID_TOKEN:
aud: sigstore # Provide an OIDC token for keyless signing with Cosign
before_script:
- apk add --no-cache cosign jq # Install Cosign (mandatory) and jq (optional)
- docker login -u "gitlab-ci-token" -p "$CI_JOB_TOKEN" "$CI_REGISTRY" # Log in to the Docker registry using GitLab CI token
script:
# Build the Docker image using the specified tag and push it to the registry
- docker build --pull -t "$IMAGE_URI" .
- docker push "$IMAGE_URI"
# Retrieve the digest of the pushed image to use in the signing step
- IMAGE_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$IMAGE_URI")
# Sign the image using Cosign with annotations that provide metadata about the build and tag annotation to allow verifying
# the tag->digest mapping (https://github.com/sigstore/cosign?tab=readme-ov-file#tag-signing)
- |
cosign sign "$IMAGE_DIGEST" \
--registry-referrers-mode oci-1-1 \
--annotations "com.gitlab.ci.user.name=$GITLAB_USER_NAME" \
--annotations "com.gitlab.ci.pipeline.id=$CI_PIPELINE_ID" \
--annotations "com.gitlab.ci.pipeline.url=$CI_PIPELINE_URL" \
--annotations "com.gitlab.ci.job.id=$CI_JOB_ID" \
--annotations "com.gitlab.ci.job.url=$CI_JOB_URL" \
--annotations "com.gitlab.ci.commit.sha=$CI_COMMIT_SHA" \
--annotations "com.gitlab.ci.commit.ref.name=$CI_COMMIT_REF_NAME" \
--annotations "com.gitlab.ci.project.path=$CI_PROJECT_PATH" \
--annotations "org.opencontainers.image.source=$CI_PROJECT_URL" \
--annotations "org.opencontainers.image.revision=$CI_COMMIT_SHA" \
--annotations "tag=$IMAGE_TAG"
# Verify the image signature using Cosign to ensure it matches the expected annotations and certificate identity
- |
cosign verify \
--annotations "tag=$IMAGE_TAG" \
--certificate-identity "$CI_PROJECT_URL//.gitlab-ci.yml@refs/heads/$CI_COMMIT_REF_NAME" \
--certificate-oidc-issuer "$CI_SERVER_URL" \
"$IMAGE_URI" | jq . # Use jq to format the verification output for easier readability