키 없는 서명 및 검증에 Sigstore 사용
GitLab v19.5Offering: GitLab.com
요약
Sigstore 프로젝트는 Cosign이라는 CLI를 제공하며, 이 CLI는 GitLab CI/CD로 빌드한 컨테이너 이미지에 대해 키 없는 서명을 수행하는 데 사용할 수 있습니다. GitLab OIDC 클레임과 Fulcio 인증서 확장 항목 간의 매핑에 대한 자세한 내용은 OIDC 토큰 클레임과 Fulcio OID 매핑 문서의 GitLab 열을 참조합니다.
Sigstore 프로젝트는 Cosign이라는 CLI를 제공하며, 이 CLI는 GitLab CI/CD로 빌드한 컨테이너 이미지에 대해 키 없는 서명을 수행하는 데 사용할 수 있습니다. 키 없는 서명에는 개인 키를 관리, 보호, 교체할 필요가 없어진다는 것을 비롯해 여러 이점이 있습니다. Cosign은 서명에 사용할 단기(short-lived) 키 쌍을 요청하고, 이를 인증서 투명성 로그(certificate transparency log)에 기록한 뒤 폐기합니다. 이 키는 파이프라인을 실행한 사용자의 OIDC ID를 사용해 GitLab 서버에서 획득한 토큰을 통해 생성됩니다. 이 토큰에는 해당 토큰이 CI/CD 파이프라인에서 생성되었음을 증명하는 고유한 클레임(claim)이 포함됩니다. 자세한 내용은 키 없는 서명에 대한 Cosign 문서를 참조합니다.
GitLab OIDC 클레임과 Fulcio 인증서 확장 항목 간의 매핑에 대한 자세한 내용은 OIDC 토큰 클레임과 Fulcio OID 매핑 문서의 GitLab 열을 참조합니다.
셀프 호스팅 Sigstore 인프라를 사용하는 GitLab Self-Managed의 경우 셀프 호스팅 Sigstore로 아티팩트 및 컨테이너 이미지 서명을 참조합니다.
사전 요구 사항:
- GitLab.com을 사용하고 있어야 합니다.
- 프로젝트의 CI/CD 설정 파일이 해당 프로젝트 내에 있어야 합니다.
Cosign을 사용해 컨테이너 이미지 및 빌드 아티팩트 서명 또는 검증#
Cosign을 사용해 컨테이너 이미지와 빌드 아티팩트를 서명하고 검증할 수 있습니다.
사전 요구 사항:
- Cosign
>= 2.0.1버전을 사용해야 합니다.
알려진 문제
- CI/CD 설정 파일의
id_tokens부분은 빌드 및 서명 대상 프로젝트 내에 있어야 합니다. Auto DevOps, 다른 리포지터리에서 포함(include)한 CI 파일, 하위 파이프라인(child pipeline)은 지원되지 않습니다. 이 제약을 제거하는 작업은 에픽 11637에서 추적하고 있습니다.
모범 사례:
- 서명되기 전에 변조되는 것을 방지하기 위해 이미지/아티팩트는 동일한 job에서 빌드하고 서명합니다.
- 컨테이너 이미지에 서명할 때는 태그 대신 불변(immutable)값인 다이제스트(digest)에 서명합니다.
GitLab ID 토큰은 Cosign의
키 없는 서명에 사용할 수 있습니다. 이때 토큰의
aud 클레임이 sigstore로 설정되어 있어야 합니다. 토큰을 SIGSTORE_ID_TOKEN 환경 변수에
설정하면 Cosign이 이를 자동으로 사용할 수 있습니다.
Cosign 설치 방법에 대한 자세한 내용은 Cosign 설치 문서를 참조합니다.
서명#
컨테이너 이미지#
Cosign.gitlab-ci.yml
템플릿을 사용하면 GitLab CI에서 컨테이너 이미지를 빌드하고 서명할 수 있습니다. 서명은 이미지와 동일한
컨테이너 리포지터리에 자동으로 저장됩니다.
include:
- template: Cosign.gitlab-ci.yml
컨테이너 서명에 대한 자세한 내용은 Cosign 컨테이너 서명 문서를 참조합니다.
빌드 아티팩트#
다음 예시는 GitLab CI에서 빌드 아티팩트에 서명하는 방법을 보여줍니다. cosign sign-blob이 생성하는 cosign.bundle 파일은
서명 검증에 사용되므로 저장해 두어야 합니다.
아티팩트 서명에 대한 자세한 내용은 Cosign Blob 서명 문서를 참조합니다.
build_and_sign_artifact:
stage: build
image: alpine:latest
variables:
COSIGN_YES: "true"
id_tokens:
SIGSTORE_ID_TOKEN:
aud: sigstore
before_script:
- apk add --update cosign
script:
- echo "This is a build artifact" > artifact.txt
- cosign sign-blob artifact.txt --bundle cosign.bundle
artifacts:
paths:
- artifact.txt
- cosign.bundle
검증#
명령줄 인수
| Name | Value |
|---|---|
--certificate-identity |
Fulcio가 발급한 서명 인증서의 SAN입니다. 이미지/아티팩트가 서명된 프로젝트의 다음 정보로 구성할 수 있습니다: GitLab 인스턴스 URL + 프로젝트 경로 + // + CI 설정 파일 경로 + @ + ref 경로. |
--certificate-oidc-issuer |
이미지/아티팩트가 서명된 GitLab 인스턴스 URL입니다. 예: https://gitlab.com. |
--bundle |
cosign sign-blob이 생성하는 bundle 파일입니다. 빌드 아티팩트 검증에만 사용됩니다. |
서명된 이미지/아티팩트 검증에 대한 자세한 내용은 Cosign 검증 문서를 참조합니다.
컨테이너 이미지#
다음 예시는 GitLab CI에서 서명된 컨테이너 이미지를 검증하는 방법을 보여줍니다. 앞서 설명한 명령줄 인수를 사용합니다.
verify_image:
image: alpine:3.20
stage: verify
before_script:
- apk add --update cosign docker
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- cosign verify "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" --certificate-identity "https://gitlab.com/my-group/my-project//path/to/.gitlab-ci.yml@refs/heads/main" --certificate-oidc-issuer "https://gitlab.com"
추가 정보:
- 프로젝트 경로와
.gitlab-ci.yml경로 사이의 이중 슬래시는 오류가 아니며 검증이 성공하려면 반드시 필요합니다. 단일 슬래시를 사용하면 일반적으로Error: none of the expected identities matched what was in the certificate, got subjects오류가 발생하며, 그 뒤에 프로젝트 경로와.gitlab-ci.yml경로 사이에 슬래시 두 개가 있는 서명된 URL이 표시됩니다. - 서명과 동일한 파이프라인에서 검증이 이루어지는 경우,
--certificate-identity에는"${CI_PROJECT_URL}//.gitlab-ci.yml@refs/heads/${CI_COMMIT_REF_NAME}"를,--certificate-oidc-issuer에는"${CI_SERVER_URL}"를 사용할 수 있습니다.
빌드 아티팩트#
다음 예시는 GitLab CI에서 서명된 빌드 아티팩트를 검증하는 방법을 보여줍니다. 아티팩트를 검증하려면 아티팩트 자체와
cosign sign-blob이 생성하는 cosign.bundle 파일이 모두 필요합니다. 앞서 설명한 명령줄 인수를 사용합니다.
verify_artifact:
stage: verify
image: alpine:latest
before_script:
- apk add --update cosign
script:
- cosign verify-blob artifact.txt --bundle cosign.bundle --certificate-identity "https://gitlab.com/my-group/my-project//path/to/.gitlab-ci.yml@refs/heads/main" --certificate-oidc-issuer "https://gitlab.com"
추가 정보:
- 프로젝트 경로와
.gitlab-ci.yml경로 사이의 이중 슬래시는 오류가 아니며 검증이 성공하려면 반드시 필요합니다. 단일 슬래시를 사용하면 일반적으로Error: none of the expected identities matched what was in the certificate, got subjects오류가 발생하며, 그 뒤에 프로젝트 경로와.gitlab-ci.yml경로 사이에 슬래시 두 개가 있는 서명된 URL이 표시됩니다. - 서명과 동일한 파이프라인에서 검증이 이루어지는 경우,
--certificate-identity에는"${CI_PROJECT_URL}//.gitlab-ci.yml@refs/heads/${CI_COMMIT_REF_NAME}"를,--certificate-oidc-issuer에는"${CI_SERVER_URL}"를 사용할 수 있습니다.
Sigstore와 npm을 사용해 키 없는 출처 증명(provenance) 생성#
GitLab CI/CD와 함께 Sigstore와 npm을 사용하면 키 관리에 따른 부담 없이 빌드 아티팩트에 디지털 서명을 할 수 있습니다.
npm 출처 증명 정보#
npm CLI를 사용하면 패키지 관리자가 사용자에게 출처 증명서(provenance attestation)를 제공할 수 있습니다. npm CLI의 출처 증명 생성 기능을 사용하면 사용자가 다운로드하여 사용하는 패키지가 실제로 해당 관리자와 이를 빌드한 빌드 시스템에서 나온 것임을 신뢰하고 검증할 수 있습니다.
npm 패키지를 게시하는 방법에 대한 자세한 내용은 GitLab npm 패키지 레지스트리를 참조합니다.
Sigstore#
Sigstore는 패키지 관리자와 보안 전문가가 소프트웨어 공급망을 공격으로부터 보호하는 데 사용할 수 있는 도구 모음입니다. Fulcio, Cosign, Rekor 등 무료로 사용할 수 있는 오픈소스 기술을 결합해, 오픈소스 소프트웨어를 더 안전하게 배포하고 사용하는 데 필요한 디지털 서명, 검증, 출처 증명 확인을 처리합니다.
관련 주제:
GitLab CI/CD에서 출처 증명 생성#
앞서 설명한 것처럼 이제 Sigstore가 GitLab OIDC를 지원하므로, GitLab CI/CD와 Sigstore를 npm 출처 증명 기능과 함께 사용해 GitLab CI/CD 파이프라인에서 npm 패키지의 출처 증명을 생성하고 서명할 수 있습니다.
사전 요구 사항#
- GitLab ID 토큰의
aud를sigstore로 설정합니다. - npm publish에
--provenance플래그를 추가합니다.
.gitlab-ci.yml 파일에 추가할 예시 내용:
build:
image: node:latest
id_tokens:
SIGSTORE_ID_TOKEN:
aud: sigstore
script:
- npm publish --provenance --access public
npm GitLab 템플릿에서도 이 기능을 제공합니다. 예시는 템플릿 문서에 있습니다.
npm 출처 증명 검증#
npm CLI는 최종 사용자가 패키지의 출처 증명을 검증할 수 있는 기능도 제공합니다.
npm audit signatures
audited 1 package in 0s
1 package has a verified registry signature
출처 증명 메타데이터 확인#
Rekor 투명성 로그(transparency log)는 출처 증명과 함께 게시되는 모든 패키지의 인증서와 증명서(attestation)를 저장합니다. 예를 들어, 다음 예시에 대한 항목은 여기에서 확인할 수 있습니다.
npm이 생성하는 출처 증명 문서 예시:
_type: https://in-toto.io/Statement/v0.1
subject:
- name: pkg:npm/%40strongjz/strongcoin@0.0.13
digest:
sha512: >-
924a134a0fd4fe6a7c87b4687bf0ac898b9153218ce9ad75798cc27ab2cddbeff77541f3847049bd5e3dfd74cea0a83754e7686852f34b185c3621d3932bc3c8
predicateType: https://slsa.dev/provenance/v0.2
predicate:
buildType: https://github.com/npm/CLI/gitlab/v0alpha1
builder:
id: https://gitlab.com/strongjz/npm-provenance-example/-/runners/12270835
invocation:
configSource:
uri: git+https://gitlab.com/strongjz/npm-provenance-example
digest:
sha1: 6e02e901e936bfac3d4691984dff8c505410cbc3
entryPoint: deploy
parameters:
CI: 'true'
CI_API_GRAPHQL_URL: https://gitlab.com/api/graphql
CI_API_V4_URL: https://gitlab.com/api/v4
CI_COMMIT_BEFORE_SHA: 7d3e913e5375f68700e0c34aa90b0be7843edf6c
CI_COMMIT_BRANCH: main
CI_COMMIT_REF_NAME: main
CI_COMMIT_REF_PROTECTED: 'true'
CI_COMMIT_REF_SLUG: main
CI_COMMIT_SHA: 6e02e901e936bfac3d4691984dff8c505410cbc3
CI_COMMIT_SHORT_SHA: 6e02e901
CI_COMMIT_TIMESTAMP: '2023-05-19T10:17:12-04:00'
CI_COMMIT_TITLE: trying to publish to gitlab reg
CI_CONFIG_PATH: .gitlab-ci.yml
CI_DEFAULT_BRANCH: main
CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX: gitlab.com:443/strongjz/dependency_proxy/containers
CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX: gitlab.com:443/strongjz/dependency_proxy/containers
CI_DEPENDENCY_PROXY_SERVER: gitlab.com:443
CI_DEPENDENCY_PROXY_USER: gitlab-ci-token
CI_JOB_ID: '4316132595'
CI_JOB_NAME: deploy
CI_JOB_NAME_SLUG: deploy
CI_JOB_STAGE: deploy
CI_JOB_STARTED_AT: '2023-05-19T14:17:23Z'
CI_JOB_URL: https://gitlab.com/strongjz/npm-provenance-example/-/jobs/4316132595
CI_NODE_TOTAL: '1'
CI_PAGES_DOMAIN: gitlab.io
CI_PAGES_URL: https://strongjz.gitlab.io/npm-provenance-example
CI_PIPELINE_CREATED_AT: '2023-05-19T14:17:21Z'
CI_PIPELINE_ID: '872773336'
CI_PIPELINE_IID: '40'
CI_PIPELINE_SOURCE: push
CI_PIPELINE_URL: https://gitlab.com/strongjz/npm-provenance-example/-/pipelines/872773336
CI_PROJECT_CLASSIFICATION_LABEL: ''
CI_PROJECT_DESCRIPTION: ''
CI_PROJECT_ID: '45821955'
CI_PROJECT_NAME: npm-provenance-example
CI_PROJECT_NAMESPACE: strongjz
CI_PROJECT_NAMESPACE_SLUG: strongjz
CI_PROJECT_NAMESPACE_ID: '36018'
CI_PROJECT_PATH: strongjz/npm-provenance-example
CI_PROJECT_PATH_SLUG: strongjz-npm-provenance-example
CI_PROJECT_REPOSITORY_LANGUAGES: javascript,dockerfile
CI_PROJECT_ROOT_NAMESPACE: strongjz
CI_PROJECT_TITLE: npm-provenance-example
CI_PROJECT_URL: https://gitlab.com/strongjz/npm-provenance-example
CI_PROJECT_VISIBILITY: public
CI_REGISTRY: registry.gitlab.com
CI_REGISTRY_IMAGE: registry.gitlab.com/strongjz/npm-provenance-example
CI_REGISTRY_USER: gitlab-ci-token
CI_RUNNER_DESCRIPTION: 3-blue.shared.runners-manager.gitlab.com/default
CI_RUNNER_ID: '12270835'
CI_RUNNER_TAGS: >-
["gce", "east-c", "linux", "ruby", "mysql", "postgres", "mongo",
"git-annex", "shared", "docker", "saas-linux-small-amd64"]
CI_SERVER_HOST: gitlab.com
CI_SERVER_NAME: GitLab
CI_SERVER_PORT: '443'
CI_SERVER_PROTOCOL: https
CI_SERVER_REVISION: 9d4873fd3c5
CI_SERVER_SHELL_SSH_HOST: gitlab.com
CI_SERVER_SHELL_SSH_PORT: '22'
CI_SERVER_URL: https://gitlab.com
CI_SERVER_VERSION: 16.1.0-pre
CI_SERVER_VERSION_MAJOR: '16'
CI_SERVER_VERSION_MINOR: '1'
CI_SERVER_VERSION_PATCH: '0'
CI_TEMPLATE_REGISTRY_HOST: registry.gitlab.com
GITLAB_CI: 'true'
GITLAB_FEATURES: >-
elastic_search,ldap_group_sync,multiple_ldap_servers,seat_link,usage_quotas,zoekt_code_search,repository_size_limit,admin_audit_log,auditor_user,custom_file_templates,custom_project_templates,db_load_balancing,default_branch_protection_restriction_in_groups,extended_audit_events,external_authorization_service_api_management,geo,instance_level_scim,ldap_group_sync_filter,object_storage,pages_size_limit,project_aliases,password_complexity,enterprise_templates,git_abuse_rate_limit,required_ci_templates,runner_maintenance_note,runner_performance_insights,runner_upgrade_management,runner_jobs_statistics
GITLAB_USER_ID: '31705'
GITLAB_USER_LOGIN: strongjz
environment:
name: 3-blue.shared.runners-manager.gitlab.com/default
architecture: linux/amd64
server: https://gitlab.com
project: strongjz/npm-provenance-example
job:
id: '4316132595'
pipeline:
id: '872773336'
ref: .gitlab-ci.yml
metadata:
buildInvocationId: https://gitlab.com/strongjz/npm-provenance-example/-/jobs/4316132595
completeness:
parameters: true
environment: true
materials: false
reproducible: false
materials:
- uri: git+https://gitlab.com/strongjz/npm-provenance-example
digest:
sha1: 6e02e901e936bfac3d4691984dff8c505410cbc3