Help us learn about your current experience with the documentation. Take the survey.

教程:为容器镜像添加构建来源数据注释

注释提供了关于构建过程的有价值元数据。这些信息用于审计和可追溯性。在安全事件中,拥有详细的来源数据可以显著加快调查和修复过程。

本教程介绍如何设置 GitLab 管道,通过使用 Cosign 自动化构建、签名和注释容器镜像的过程。 你可以配置 .gitlab-ci.yml 文件来构建、推送和签名 Docker 镜像,并将其推送到 GitLab 容器注册表。

要为容器镜像添加注释:

  1. 设置镜像和服务镜像
  2. 定义 CI/CD 变量
  3. 准备 OIDC 令牌
  4. 准备容器
  5. 构建并推送镜像
  6. 使用 Cosign 签名镜像
  7. 验证签名和注释

当你完成所有步骤后,你的 .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