튜토리얼: 빌드 출처 데이터로 컨테이너 이미지에 주석 달기
주석은 빌드 프로세스에 대한 유용한 메타데이터를 제공합니다. 이 튜토리얼은 Cosign을 사용하여 컨테이너 이미지를 빌드, 서명 및 주석 추가 프로세스를 자동화하는 GitLab 파이프라인을 설정하는 방법을 설명합니다.
주석은 빌드 프로세스에 대한 유용한 메타데이터를 제공합니다. 이 정보는 감사와 추적에 사용됩니다. 보안 사고 발생 시, 상세한 출처 데이터가 있으면 조사 및 해결 프로세스를 상당히 빠르게 진행할 수 있습니다.
이 튜토리얼은 Cosign을 사용하여 컨테이너 이미지를 빌드, 서명 및 주석 추가 프로세스를 자동화하는 GitLab 파이프라인을 설정하는 방법을 설명합니다.
.gitlab-ci.yml 파일을 구성하여 Docker 이미지를 빌드, 푸시, 서명하고 GitLab 컨테이너 레지스트리에 푸시할 수 있습니다.
컨테이너 이미지에 주석을 달려면:
모든 내용을 합치면 .gitlab-ci.yml은 이 튜토리얼 끝에 제공된 샘플 구성과 비슷해야 합니다.
시작하기 전에#
다음이 필요합니다:
- Cosign v2.0 이상이 설치되어 있습니다.
- GitLab Self-Managed의 경우, 서명을 표시하기 위해 메타데이터 데이터베이스로 구성된 GitLab 컨테이너 레지스트리가 있습니다.
이미지 및 서비스 이미지 설정#
.gitlab-ci.yml 파일에서 docker:cli 이미지를 사용하고 Docker-in-Docker 서비스를 활성화하여 CI/CD 잡 내에서 Docker 명령어를 실행할 수 있도록 합니다.
build_and_sign:
stage: build
image: docker:cli
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: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
