컨테이너 이미지 출처와 구성요소를 검증하는 장면

Docker image attestations API로 SBOM과 SLSA 출처 검증하기

Docker image attestations API를 붙이면 SBOM과 SLSA provenance가 있는지 확인하고 내용까지 읽을 수 있어요. 다만 여기서 바로 “출처 검증 끝”이라고 하면 곤란합니다. 이 API는 attestation을 발견하고 조회하는 기능이지, 누가 서명했는지 인증하는 검증기는 아니거든요. 있음과 믿을 만함은 다른 문제. 이 글은 2026년 7월 31일 기준 공식 문서와 Moby 공개 구현을 교차 검토해 그 경계를 정리한 내용이며, 현재 조사 환경에는 Docker CLI와 daemon이 없어 실제 응답을 재현한 후기는 아닙니다.

핵심은 두 단계예요. Engine API에서 요구한 predicate와 statement 본문, subject digest를 검사하고, 그다음 별도 검증기에서 서명자 신원과 신뢰 정책을 확인해야 합니다. 앞 단계만 통과한 이미지는 정상 확정이 아니라 어디까지나 정상 후보임.

Docker Engine API의 발견·내용 검사와 별도 서명·신뢰 정책 검증을 나눈 2단계 배포 게이트

먼저 확인할 버전은 Engine 29가 아니라 29.6.0

GET /images/{name}/attestations가 추가된 정확한 시점은 Docker Engine 29.6.0, 날짜는 2026년 6월 18일입니다. 대응 Engine API는 v1.55예요. “Docker 29면 가능”이라고 뭉뚱그리면 29.0부터 29.5까지도 지원한다고 오해할 수 있습니다. Docker Engine 29 릴리스 노트Engine API 버전 이력에서 이 경계를 먼저 확인하는 게 안전합니다.

서버가 제공하는 API 버전부터 봅니다.

docker version
curl --unix-socket /var/run/docker.sock http://localhost/v1.55/version

두 번째 명령은 Unix socket에 직접 접근하는 Linux 예시예요. Docker context, TLS, 권한 구성에 따라 접속 방식은 달라질 수 있습니다. 그리고 조회 명령 자체가 읽기 전용이라고 마음 편-안 해지면 안 됨. Docker socket 접근 권한은 사실상 daemon 제어 권한이 될 수 있으니 실행 주체와 권한을 따로 통제해야 합니다.

클라이언트와 서버가 구버전 API로 협상하면 새 endpoint를 쓸 수 없다는 점도 체크 대상입니다. 버전 문자열만 보고 넘어가기보다 Server API가 v1.55 이상인지 확인하는 이유예요.

attestation이 보존되는 저장 경로인지 본다

BuildKit attestation은 이미지 index 아래 별도 manifest의 JSON blob으로 연결됩니다. endpoint는 선택한 플랫폼 manifest와 연결된 attestation manifest를 따라가 statement layer를 찾는 구조예요. 이미지가 OCI index가 아니면 sibling attestation manifest를 가질 수 없어 구현상 빈 결과로 귀결될 수 있습니다. Build attestationsattestation 저장 구조를 같이 봐야 하는 이유입니다.

여기서 꽤 잘 걸리는 함정이 하나 있어요. Buildx의 기본 image store는 attestation을 지원하지 않아 docker output이나 로컬 load 과정에서 metadata가 사라질 수 있습니다. registry로 직접 push하거나 containerd image store를 사용해야 하고, GitHub Actions에서도 load: true 또는 docker exporter는 attestation을 이미지에 붙이지 못합니다. 생성 로그는 성공인데 조회 결과는 텅 빈 상황, 충분히 가능함. docker buildx build 문서GitHub Actions attestation 문서에서 저장 경로 조건을 확인할 수 있습니다.

descriptor부터 보고 statement 본문을 읽는다

이미지 이름은 URL path component이므로 /, :, @ 같은 문자를 percent-encoding해야 합니다. 아래 명령은 실제 registry와 digest를 바꿔 넣는 개념 예시예요.

# 1) predicate descriptor와 type을 먼저 조회
curl --fail-with-body --silent --show-error \
  --unix-socket /var/run/docker.sock \
  'http://localhost/v1.55/images/registry.example.com%2Fteam%2Fapp%40sha256%3AIMAGE_DIGEST/attestations'

# 2) statement 원문까지 조회
curl --fail-with-body --silent --show-error \
  --unix-socket /var/run/docker.sock \
  'http://localhost/v1.55/images/registry.example.com%2Fteam%2Fapp%40sha256%3AIMAGE_DIGEST/attestations?statement=true'

# 3) 실제 predicate type URI로 필터링
curl --get --fail-with-body --silent --show-error \
  --unix-socket /var/run/docker.sock \
  --data-urlencode 'type=https://spdx.dev/Document' \
  --data-urlencode 'statement=true' \
  'http://localhost/v1.55/images/registry.example.com%2Fteam%2Fapp%40sha256%3AIMAGE_DIGEST/attestations'

응답은 AttestationStatement 배열이며 각 항목에는 OCI blob descriptor와 predicate type URI가 들어갑니다. statement=true를 줬을 때만 JSON statement 원문이 포함돼요. 기본값인 false는 statement blob을 읽지 않으므로 가벼운 inventory에는 적당하지만, subject digest나 SBOM package 목록, provenance materials를 최종 판정할 수는 없습니다. 목록이 보였다고 내용 검사까지 끝난 건 아님. Moby API 타입에도 응답 범위가 Descriptor, PredicateType, 선택적 Statement로 드러납니다.

type에는 sbom이나 provenance 같은 별칭이 아니라 in-toto predicate type URI를 넣습니다. Docker 예시의 SPDX SBOM은 https://spdx.dev/Document, 기본 provenance는 https://slsa.dev/provenance/v0.2예요. 다만 BuildKit은 SLSA v1 생성도 지원하고 도구에 따라 SBOM URI 버전이 달라질 수 있습니다. 처음부터 하나를 하드코딩하기보다 무필터 조회로 실제 PredicateType을 수집한 뒤 조직의 허용 목록과 비교하는 흐름이 덜 위험합니다.

무필터 조회로 PredicateType을 수집하고 허용 URI와 statement 본문 검사로 좁히는 API 호출 흐름

멀티 플랫폼은 배포 플랫폼별로 요청한다

platform을 생략하면 daemon host 플랫폼이 선택됩니다. 검증 노드는 AMD64인데 실제 배포 대상은 ARM64라면 엉뚱한 변형을 보고 통과시킬 수 있는 셈이에요. API v1.55에서 platform parameter는 미래 호환을 위해 반복형으로 선언됐지만 현재는 한 값만 받습니다. 여러 플랫폼 이미지는 플랫폼별 요청으로 순회해야 함.

요청한 플랫폼의 image manifest 자체가 없으면 Not Found가 될 수 있습니다. 반면 선택 플랫폼에 attestation manifest가 없거나 type 필터가 맞지 않는 경우는 구현상 빈 결과로 귀결될 수 있어요. 비슷해 보이지만 대응이 다릅니다.

SBOM은 존재보다 subject와 내용이 중요하다

BuildKit SBOM은 SPDX JSON을 in-toto SPDX predicate로 이미지에 연결합니다. 최소 검사는 다음 순서가 좋습니다.

  1. 실제 PredicateType이 조직에서 허용한 SPDX URI인지 확인합니다.
  2. statement의 _type, subject, predicateType, predicate가 있고 예상 schema와 맞는지 봅니다.
  3. subject[].digest가 배포할 플랫폼별 image manifest digest와 일치하는지 확인합니다. 바뀔 수 있는 tag 문자열만 비교하면 부족해요.
  4. SPDX document 필수 metadata와 package 목록이 조직 정책을 충족하는지 검사합니다.
  5. 필요하면 실제 filesystem scan 결과와 대조합니다.

SPDX 형식 JSON이 있다는 사실과 필요한 구성요소가 빠짐없이 들어 있다는 사실은 같지 않습니다. 더구나 SBOM은 작성자가 이미지 안에 무엇이 있다고 주장하는 자료예요. 신뢰할 수 있는 생성자의 인증이나 실제 filesystem 대조가 없다면 그 내용의 진실성까지 보장하지는 않습니다.

멀티 스테이지 빌드도 살짝 까다롭습니다. 기본 SBOM은 최종 stage만 scan해요. build context나 중간 stage까지 포함하려면 Dockerfile에 ARG BUILDKIT_SBOM_SCAN_CONTEXT=true 또는 ARG BUILDKIT_SBOM_SCAN_STAGE=...를 선언해야 합니다. CLI에서 --build-arg만 넘기고 Dockerfile의 ARG 선언을 빼면 효과 없음. 자세한 조건은 Docker SBOM attestation 문서에서 확인할 수 있습니다.

SLSA provenance는 출처 값과 인증을 나눠 본다

BuildKit provenance에는 build 시간, parameter와 environment, VCS, source, materials 등이 들어갈 수 있습니다. 기본 schema는 SLSA v0.2이고 version=v1을 선택할 수 있어요. 먼저 schema 버전을 구분한 뒤 그 버전에 맞춰 검사해야 합니다.

mode=min은 build argument 값, secret identity, 풍부한 layer metadata 등을 제외합니다. mode=max는 LLB definition, Dockerfile 정보, source map 등을 더 담지만 build argument 값이 노출될 수 있어요. secret을 build arg로 전달하면 안 되는 이유가 여기서 또 등장함. Docker provenance 문서가 두 mode의 범위를 설명합니다.

내용 검사는 이런 순서로 잡을 수 있습니다.

  1. subject digest와 배포 대상의 플랫폼 manifest digest를 맞춥니다.
  2. 허용한 provenance schema가 v0.2인지 v1인지 구분합니다.
  3. builder identity와 build type, source repository와 revision, resolved dependencies와 materials를 조직의 기대값과 비교합니다.
  4. completeness, reproducible, hermetic 필드를 생성 규칙과 함께 해석합니다.
  5. 승인한 attester의 서명, 인증 체인, 신뢰 루트와 투명성 증거는 별도 검증기로 확인합니다.

필드 이름이 그럴듯하다고 그대로 믿기는 어렵습니다. BuildKit 문서상 로컬 directory source는 materials가 불완전할 수 있고, 별도 VCS metadata는 BuildKit이 검증하지 않아서 hint로만 봐야 해요. reproducible=true 역시 실제로 재현 빌드에 성공했다는 결과로 확대하면 안 됩니다. 이 부분은 Docker SLSA definitions의 생성 규칙까지 같이 읽어야 판단 가능.

정상·누락·위조를 API 결과에 대입해 보면

공식 API 계약과 Moby 구현, in-toto·SLSA 모델을 세 상황에 적용하면 배포 판단선이 또렷해집니다.

입력 상황 API에서 보일 수 있는 결과 API만으로 할 수 있는 판단 다음 조치 배포 판단
정상 후보 SBOM·provenance의 descriptor/type, statement=true일 때 JSON 본문 존재, schema, subject digest와 predicate 내용 검사 승인 attester의 서명과 신뢰 정책 검증 모든 조건 통과 후 허용
누락 구현상 빈 결과 가능. 필터 불일치도 같은 모양일 수 있음 요구 attestation이 조회되지 않았음 무필터·명시적 플랫폼 재조회, 버전·store·exporter 점검 원인을 몰라도 차단
위조 후보 그럴듯한 descriptor/type/body가 정상처럼 반환될 수 있음 schema·subject·정책 값의 모순 탐지 envelope/signature, signer identity, trust root와 투명성 증거 검증 조회 성공만으로 허용 금지
잘못된 플랫폼 manifest가 없으면 Not Found, 생략하면 host 플랫폼 결과 대상 불일치 또는 host 변형 검사 여부 확인 배포 플랫폼별 명시 요청 전체 플랫폼 검증 전 보류
blob 읽기 실패 descriptor는 보이지만 statement=true에서 오류 가능 metadata 존재와 body 가용성 차이 확인 최종 gate에서 본문 읽기 필수화 읽기 실패 시 차단

빈 결과를 보고 “빌드가 SBOM을 만들지 않았네”라고 바로 결론 내리면 조사 방향이 꼬입니다. type 필터가 틀렸는지, host와 배포 플랫폼이 다른지, 이미지가 OCI index인지, attestation을 보존하지 않는 exporter나 store를 거쳤는지부터 나눠 봐야 해요. 조사 환경에서 실제 HTTP JSON 직렬화와 status를 재현하지 못했으므로, 여기서는 Moby 구현상 여러 누락 조건이 빈 결과로 귀결될 수 있다는 범위로만 표현합니다.

위조 후보는 더 조심해야 합니다. OCI descriptor digest는 가져온 blob의 content integrity 확인에 도움을 주지만, 공격자가 새 statement와 그에 맞는 새 digest를 함께 게시할 수 있어요. digest가 맞는다는 사실은 “내용이 전송 중 바뀌지 않았다”에 가깝지 “승인한 주체가 작성했다”는 인증이 아닙니다. content integrity와 attester authenticity, 둘을 한 칸에 넣으면 안 됨. OCI Descriptor 사양SLSA attestation model을 대조하면 이 경계가 분명합니다.

정상·누락·위조 후보별 API 관찰 결과와 추가 검증 및 배포 결정을 연결한 판정표

배포 게이트는 발견·내용 검사와 진위 검증으로 나눈다

자동화 정책의 최소선은 아래처럼 잡을 수 있습니다.

1. Engine API v1.55 discovery/content gate
   - 정확한 image digest와 platform 지정
   - 요구 predicate type 모두 존재
   - statement=true로 본문 읽기 성공
   - schema 유효, subject digest 일치
   - SBOM/provenance 조직 정책 충족

2. authenticity/policy gate
   - 인증 envelope와 signature 검증
   - signer identity와 trust root 허용
   - source repository, revision, builder 기대값 충족
   - 실패하거나 판단할 수 없으면 배포 차단

첫 gate는 endpoint가 잘하는 일이고, 두 번째는 이 endpoint 응답에 없는 서명자·서명 검증 결과·신뢰 루트를 다루는 일입니다. Descriptor.Digest와 in-toto subject.digest 모두 중요하지만 승인한 주체가 작성했다는 증거를 대신하지 않아요. in-toto Statement v1은 subject를 artifact에 digest로 연결하고, SLSA 모델은 envelope와 signature를 통해 메시지와 attester를 인증하는 계층을 따로 둡니다.

이미지 검증과 함께 패키지 의존성 경보의 다음 단계를 정리하려면 Dependabot malware alerts 대응 흐름도 이어서 볼 수 있습니다.

완벽한 원클릭 검증 API는 아니지만, 존재 여부와 본문 검사를 배포 gate로 가져오기에는 꽤 명확한 재료입니다. 대신 빈 결과는 원인을 분해하고, 조회 성공은 진위 검증으로 넘기는 선을 지켜야 해요. 운영 중인 이미지 검증 파이프라인에서도 attestation 존재 검사서명·정책 검증이 별도 단계로 나뉘어 있는지 한 번 점검해볼 만함.

FAQ

Docker 29.0부터 image attestations API를 쓸 수 있나요?

아니요. 공식 릴리스 기록상 endpoint는 Docker Engine 29.6.0에서 추가됐고 대응 API는 v1.55입니다. Server API가 v1.55 이상인지 확인해야 하며, 구버전 API로 협상하면 이 기능을 사용할 수 없습니다.

statement=true를 생략하면 무엇이 반환되나요?

descriptor와 predicate type을 확인할 수 있지만 statement blob은 읽지 않고 원문도 포함하지 않습니다. 빠른 목록 확인에는 쓸 수 있어도 subject digest, SBOM package, provenance materials를 검사하는 최종 gate에는 부족해요.

SBOM과 SLSA provenance를 한 요청에서 함께 받을 수 있나요?

요구 predicate를 함께 조회할 수 있습니다. type은 반복 지정할 수 있지만 sbom 같은 별칭이 아니라 실제 predicate type URI를 사용해야 해요. 먼저 무필터 조회로 이미지의 PredicateType 값을 확인한 뒤 허용 목록과 비교하는 편이 안전합니다.

빈 결과는 SBOM이 생성되지 않았다는 뜻인가요?

그렇게 단정할 수 없습니다. attestation 자체의 누락뿐 아니라 선택 플랫폼, type 필터 불일치, OCI index 구조, attestation을 보존하지 않는 image store나 exporter가 원인일 수 있습니다. 무필터와 명시적 플랫폼으로 다시 조회하고 저장 경로를 확인해야 함.

이 API만으로 위조된 SLSA provenance를 잡을 수 있나요?

아니요. schema나 subject digest, 정책 값의 모순은 검사할 수 있지만 작성자 진위는 판정하지 못합니다. 인증 envelope와 signature, signer identity, trust root, 필요한 경우 투명성 증거를 별도 검증해야 합니다.

비슷한 글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다