실용 가이드

GitHub Actions로 SBOM 만들기…내보내기만 하면 끝나지 않는다

|작성자: QUASA 편집팀|4 분 소요| 1
GitHub Actions로 SBOM 만들기…내보내기만 하면 끝나지 않는다

GitHub Actions에서 SBOM을 자동 생성하는 방법은 대상에 따라 갈린다. GitHub가 수집한 저장소 의존성이 필요하면 dependency graph를 SPDX JSON으로 내보내고, 빌드 디렉터리·단일 파일·컨테이너 이미지의 실제 구성 요소가 필요하면 Syft를 실행하는 Anchore SBOM Action으로 그 대상을 스캔한다.

그러나 파일을 만든 것만으로 작업이 끝나지는 않는다. 출력 형식을 소비 시스템에 맞추고, 결과를 검증해 워크플로 아티팩트로 보관한 뒤, 공개 릴리스 첨부와 Dependency Submission은 각각 필요한 이벤트와 쓰기 권한을 분리해야 한다.

dependency graph 내보내기와 실제 산출물 스캔은 다르다

GitHub 의존성 그래프 내보내기와 컨테이너 이미지 스캔이 서로 다른 입력에서 SBOM을 만드는 차이

GitHub 기본 내보내기의 원본은 저장소 dependency graph의 현재 상태다. 매니페스트와 lock 파일, 이전에 제출된 스냅샷 등 GitHub가 인식한 의존성을 전달하려는 목적에 맞는다. 반면 Anchore 방식은 러너에서 지정한 대상을 직접 조사하므로 배포 파일이나 이미지에 실제로 포함된 패키지를 기준으로 SBOM을 만들 수 있다.

  • dependency graph: GitHub에 등록된 저장소 의존성의 현재 상태를 내보낸다.
  • path: 체크아웃한 소스 또는 빌드 디렉터리 전체를 조사한다.
  • file: 실행 파일이나 압축 파일처럼 하나의 산출물을 조사한다.
  • image: Docker daemon 또는 컨테이너 레지스트리에서 이미지를 가져와 조사한다.

두 결과는 서로 대체재가 아니다. 소스 저장소와 최종 이미지 사이에서는 빌드 단계가 패키지를 추가하거나 제거할 수 있다. 컨테이너가 실제 배포 단위라면 이미지가 완성된 뒤 태그보다 변경되지 않는 digest를 입력하는 구성이 대상 식별과 재현성에 유리하다.

GitHub 기본 내보내기는 비동기 REST 흐름으로 구성한다

GitHub SBOM REST 문서 는 dependency graph를 SPDX JSON으로 내보내는 기존 동기 작업이 2026년 11월 13일 이후 제공되지 않으며, 보고서 생성을 요청한 뒤 완료된 파일을 가져오는 비동기 흐름으로 이전하라고 안내한다. 생성 요청은 201과 조회 URL을 반환하고, 조회 단계는 처리 중일 때 202, 완료됐을 때 임시 다운로드 URL로 향하는 302를 반환한다.

따라서 새 워크플로를 기존 GET 응답 한 번에 의존하도록 만들지 않는다. 아래 구조로 요청과 조회를 나누고, 202에는 제한된 횟수의 재시도와 대기 간격을 적용한다. 이 API에는 저장소 Contents 읽기 권한이면 충분하며 파일시스템을 스캔하지 않으므로 checkout도 필수는 아니다.

  • on: workflow_dispatch 또는 필요한 보호된 이벤트
  • permissions: contents: read
  • 생성 단계: /repos/${{ github.repository }}/dependency-graph/sbom/generate-report 요청
  • 조회 단계: 응답의 sbom_url을 202가 아닌 상태가 될 때까지 제한적으로 확인
  • 저장 단계: 302 대상에서 SPDX JSON을 sbom.spdx.json으로 내려받기
  • 검증 단계: JSON 파싱과 SPDXID, spdxVersion, packages 존재 여부 확인
  • 업로드 단계: actions/upload-artifact@<검토한-전체-커밋-SHA>로 보관

실패한 조회를 무한 반복하거나 API 응답 전체를 로그에 출력하지 않는다. 아티팩트 이름에는 저장소와 대상 커밋을 구별할 값을 넣고, 보관 기간은 조직의 감사·배포 추적 정책에 맞춰 지정한다.

Anchore Action에서는 입력 하나와 게시 동작을 명시한다

Anchore SBOM Action이 경로·파일·이미지 중 하나를 선택해 SPDX JSON 아티팩트를 생성하는 과정

Anchore SBOM Action 사용법 에 따르면 path, file, image는 상호 배타적이며 기본 대상은 현재 작업 디렉터리다. 기본 형식은 spdx-json, 워크플로 아티팩트 업로드와 릴리스 자산 업로드 옵션은 기본적으로 활성화되고, 지원 형식은 spdx, spdx-json, cyclonedx, cyclonedx-json이다.

최소 권한으로 시작하려면 Anchore Action에는 파일 생성만 맡기고 자체 업로드 기능은 끈 뒤, 검토한 actions/upload-artifact를 별도 단계로 사용한다. 이렇게 하면 일반 생성 job은 Contents 읽기 권한만 유지하면서 공개 릴리스 게시가 우연히 결합되는 일을 피할 수 있다.

  • permissions: contents: read
  • uses: actions/checkout@<검토한-전체-커밋-SHA>
  • uses: anchore/sbom-action@<검토한-전체-커밋-SHA>
  • with: path: ./dist
  • format: spdx-json
  • output-file: ./sbom/application.spdx.json
  • upload-artifact: false
  • upload-release-assets: false
  • dependency-snapshot: false
  • uses: actions/upload-artifact@<검토한-전체-커밋-SHA>
  • with: name: application-sbom, path: ./sbom/application.spdx.json, if-no-files-found: error

단일 산출물에는 path 대신 file을, 컨테이너에는 image를 지정한다. 조건부 예시로 ghcr.io/acme/api@sha256:…처럼 빌드가 확정한 digest를 넘기면 같은 태그가 다른 이미지를 가리키는 위험을 줄일 수 있다. 비공개 레지스트리 자격 증명은 GitHub Secrets로 전달하고 로그에 출력하지 않는다.

SPDX와 CycloneDX는 다음 소비자를 보고 고른다

GitHub dependency graph 내보내기와 같은 계열로 맞추거나 SPDX 소비 도구로 전달한다면 spdx-json이 자연스럽다. CycloneDX를 요구하는 취약점 분석기나 자산 관리 시스템이 다음 단계라면 cyclonedx-json을 명시한다. XML이나 tag-value를 요구하는 기존 도구가 있을 때만 cyclonedx 또는 spdx를 선택한다.

확장자만 바꿔서는 문서 형식이 변하지 않는다. format과 output-file 확장자를 일치시키고, 업로드 전에 실제 파서로 읽어 필수 식별 필드와 패키지 목록을 확인한다. matrix 빌드에서는 운영체제·아키텍처 같은 matrix 값을 파일명과 artifact-name에 넣어 중복 이름으로 업로드가 실패하거나 산출물이 뒤섞이지 않게 한다.

워크플로 아티팩트와 공개 릴리스 자산의 노출 범위도 같지 않다. SBOM에는 사설 패키지명이나 내부 구성 정보가 포함될 수 있으므로 릴리스에 첨부하기 전 공개 가능 범위를 별도로 검토해야 한다.

아티팩트·릴리스·Dependency Submission의 권한을 나눈다

검증된 SBOM을 아티팩트 보관·릴리스 첨부·커밋별 의존성 제출로 분리하는 흐름

워크플로 아티팩트는 실행 결과를 내려받거나 후속 job에 전달하는 보관 수단이고, 릴리스 자산은 배포 버전과 함께 게시하는 수단이다. 생성 job은 읽기 권한으로 파일을 만들고 보관하게 두며, 릴리스 이벤트에서만 실행되는 별도 publish job에 actions: read와 contents: write를 부여한다. 포크에서 시작된 pull request에는 릴리스 쓰기 권한이나 레지스트리 비밀을 전달하지 않는다.

Dependency Submission은 SBOM 파일을 저장하는 기능이 아니다. GitHub Dependency Submission API 설명 에서 스냅샷은 특정 commit SHA와 연결된 의존성 집합으로 정의되며, dependency graph에 빌드 과정에서 해석된 의존성을 보충한다. 제출에는 Contents 쓰기 권한이 필요하고, matrix 실행은 correlator를 서로 구분해야 한다.

Anchore Action에서는 dependency-snapshot: true로 제출을 켤 수 있지만, 다운로드용 SBOM만 필요하다면 false로 둔다. 제출이 필요한 경우에도 신뢰하는 기본 브랜치의 push나 보호된 배포 흐름으로 이벤트를 제한하고, 일반 생성 job과 권한 경계를 분리하는 편이 안전하다.

재현 가능한 워크플로인지 마지막으로 확인한다

  • dependency graph, path, file, image 가운데 실제로 필요한 대상을 골랐는가.
  • path, file, image 중 하나만 지정했고 이미지에는 가능하면 digest를 사용했는가.
  • 소비 시스템이 요구하는 SPDX 또는 CycloneDX 형식과 확장자가 일치하는가.
  • JSON 또는 XML 파싱과 필수 식별 필드 검사를 통과한 파일만 업로드하는가.
  • matrix 실행마다 output-file, artifact-name, 제출 correlator가 고유한가.
  • 생성, 릴리스 게시, Dependency Submission을 별도 권한과 이벤트로 분리했는가.
  • Action 참조를 검토한 전체 commit SHA로 고정하고 갱신 절차를 정했는가.
  • 아티팩트 보관 기간과 공개 릴리스 첨부 기준을 정했는가.

안전한 출발점은 읽기 권한으로 정확한 대상을 스캔하고, 검증된 SBOM을 워크플로 아티팩트로만 보관하는 구성이다. 실제 소비 목적이 생겼을 때 릴리스 첨부와 Dependency Submission을 독립된 권한 경계로 추가해야 ‘내보내기’가 재현 가능한 배포 기록으로 이어진다.

공유:

뉴스레터 구독

최신 Web3, AI, 암호화폐 뉴스를 이메일로 받아보세요.

0