LabHub
배우기 러닝패스 코스

The Build Was Green — So Who Put That Library In?

Count all twenty-five by hand and write them as an SBOM

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

잠금 파일 하나에서 '무엇이 들어 있는가' 를 직접 세어 CycloneDX 형식의 SBOM 으로 적고, 아무도 고르지 않은 패키지가 어느 경로로 들어왔는지 추적합니다.

왜 중요한가

사고가 난 뒤 가장 먼저 오는 질문은 "우리 그거 쓰나요" 입니다. 이 질문에 몇 시간 안에 답하지 못하는 조직은, 답이 '아니오' 여도 며칠을 태웁니다. 답을 빨리 내는 방법은 사고 뒤에 찾는 것이 아니라 릴리스마다 목록을 함께 만들어 두는 것입니다. 그런데 목록의 대부분은 우리가 고른 것이 아닙니다 — 직접 적은 의존 일곱 개가 스물다섯 개를 끌고 옵니다. 그 스물다섯 개가 실제로 배포에 실리는지, 빌드에만 쓰이는지도 갈라야 합니다. 고칠 목록과 급한 목록은 다른 목록이기 때문입니다.

단계

  1. /opt/fixtures/sbom/release/package.json 에서 직접 의존을 뽑아 /root/sbom/01-direct.json 에 씁니다.
  2. /opt/fixtures/sbom/release/package-lock.json 에서 설치된 전부를 세어 /root/sbom/02-installed.json 에 씁니다.
  3. 운영과 개발 전용을 갈라 /root/sbom/03-scope.json 에 씁니다.
  4. sock-ttl 이 어떤 경로로 들어왔는지 추적해 /root/sbom/04-why.json 에 씁니다.
  5. CycloneDX 1.6 형식으로 /root/sbom/05-bom.json 을 만듭니다.
  6. 산출물의 다이제스트를 /root/sbom/06-subject.json 에 못박습니다.
  7. 배포에 실리는 것만 남겨 /root/sbom/07-runtime-bom.json 을 만듭니다.
  8. 지난 릴리스와 견주어 /root/sbom/08-diff.json 을 만듭니다.

참고

우리가 직접 고른 것부터 센다

/opt/fixtures/sbom/release/package.json 을 읽어 /root/sbom/01-direct.json 에 저장하세요. runtime 은 dependencies 의 이름을 사전순으로, dev 는 devDependencies 의 이름을 사전순으로 담은 배열입니다.

package.json 에 적힌 것은 '우리가 고른 것' 뿐입니다. 버전 범위(^)가 붙어 있으니 이름만 뽑으면 됩니다. 설치된 전부는 여기에 없습니다.

실제로 설치된 것을 센다

/opt/fixtures/sbom/release/package-lock.json 을 읽어 /root/sbom/02-installed.json 에 lockfile_version·total·direct·transitive 를 저장하세요. total 은 packages 맵에서 루트 항목(키가 빈 문자열)을 뺀 개수, direct 는 1단계에서 센 직접 의존의 수, transitive 는 그 나머지입니다.

lockfileVersion 3 의 packages 는 설치 위치를 키로 하는 맵이고 루트 프로젝트는 빈 문자열 키로 들어갑니다. 루트를 빼고 세지 않으면 하나가 더 나옵니다.

배포에 실리는 것과 빌드에만 쓰는 것을 가른다

/root/sbom/03-scope.json 에 runtime·dev·dev_names 를 저장하세요. 잠금 파일의 항목에 dev 가 true 이면 개발 전용입니다. runtime 과 dev 는 개수, dev_names 는 개발 전용 패키지 이름을 사전순으로 담은 배열입니다.

npm 문서는 'strictly part of the devDependencies tree' 일 때만 dev 가 true 라고 적습니다. 플래그가 아예 없는 항목이 운영 쪽이라는 뜻입니다.

아무도 고르지 않은 패키지가 어떻게 들어왔는지 추적한다

sock-ttl 이 왜 설치됐는지 잠금 파일의 dependencies 를 따라가 /root/sbom/04-why.json 에 package·version·direct·depth·paths 를 저장하세요. paths 는 루트에서 시작하는 이름 배열들의 배열이고 첫 칸은 paygate 입니다. depth 는 그 경로의 간선 수입니다.

잠금 파일의 각 항목에 있는 dependencies 가 간선입니다. 루트에서 너비 우선으로 내려가면서 어디서 그 이름이 처음 나오는지 보세요.

CycloneDX 형식으로 목록을 적는다

/root/sbom/05-bom.json 에 SBOM 을 저장하세요. bomFormat 은 "CycloneDX", specVersion 은 "1.6", version 은 1, metadata.component 는 paygate 1.4.2(type application, purl pkg:npm/paygate@1.4.2), components 는 설치된 25개를 이름 사전순으로 담습니다. 각 항목은 type "library"·name·version·purl(pkg:npm/이름@버전)·scope 를 갖고, scope 는 개발 전용이면 "excluded", 아니면 "required" 입니다.

bomFormat 값이 "CycloneDX" 로 못박혀 있는 이유는 BOM 파일에 이름 규칙이 없기 때문입니다. 파일만 보고도 이게 무슨 형식인지 알아야 하니까요.

목록이 어느 산출물의 것인지 못박는다

/opt/fixtures/sbom/release/paygate-1.4.2.js 의 SHA-256 을 계산해 /root/sbom/06-subject.json 에 name(파일 이름만)·version·bytes·sha256 을 저장하세요. sha256 은 소문자 16진 64자입니다.

목록에 대상이 적혀 있지 않으면 '무엇의 목록인지' 를 아무도 증명할 수 없습니다. hashlib.sha256 으로 파일 바이트를 해시하세요.

배포 산출물에 실리는 것만 남긴다

/root/sbom/05-bom.json 에서 scope 가 excluded 인 항목을 빼고 /root/sbom/07-runtime-bom.json 에 저장하세요. 형식은 05-bom.json 과 같고 components 만 줄어듭니다.

개발 전용 의존의 취약점은 사용자에게 닿지 않습니다. 둘을 섞어 세면 고쳐야 할 숫자가 부풀어 정작 급한 것이 묻힙니다.

지난 릴리스와 무엇이 달라졌는지 센다

/opt/fixtures/sbom/sbom/paygate-1.4.1.cdx.json 과 /root/sbom/05-bom.json 을 견주어 /root/sbom/08-diff.json 에 added·removed·changed·unchanged 를 저장하세요. added 와 removed 는 name·version 을 담은 객체 배열(이름 사전순), changed 는 name·from·to 를 담은 객체 배열(이름 사전순), unchanged 는 양쪽에 있고 버전이 같은 것의 개수입니다.

릴리스 노트가 답해 주지 않는 질문이 이것입니다 — 아무도 고치지 않았는데 늘어난 것이 있는가. 이름 집합의 차와 교집합을 각각 따로 보세요.