教程:为容器镜像添加构建来源数据注释
注释提供了关于构建过程的有价值元数据。这些信息用于审计和可追溯性。在安全事件中,拥有详细的来源数据可以显著加快调查和修复过程。
本教程介绍如何设置 GitLab 管道,通过使用 Cosign 自动化构建、签名和注释容器镜像的过程。
你可以配置 .gitlab-ci.yml 文件来构建、推送和签名 Docker 镜像,并将其推送到 GitLab 容器注册表。
要为容器镜像添加注释:
当你完成所有步骤后,你的 .gitlab-ci.yml 文件应该与本教程末尾提供的示例配置相似。
开始之前
你必须具备以下条件:
- Cosign v2.0 或更高版本已安装。
- 对于 GitLab 自托管版,GitLab 容器注册表必须配置了元数据数据库 以显示签名。
设置镜像和服务镜像
在 .gitlab-ci.yml 文件中,使用 docker:latest 镜像并启用 Docker-in-Docker 服务,以允许在 CI/CD 作业中运行 Docker 命令。
build_and_sign:
stage: build
image: docker:latest
services:
- docker:dind # Enable Docker-in-Docker service to allow Docker commands inside the container定义 CI/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 output准备 OIDC 令牌
为 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:latest
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