2.18 의존성과 공급망 관리
개요와 동기
프로젝트의 락 파일을 열어 패키지를 세어 보십시오. 대부분의 팀과 같다면, 여러분이 쓴 코드는 쓰지도 않았고, 완전히 이해하지도 못하고, 쉽게 감사할 수도 없는 수백, 수천 개의 의존성 위에 얹은 얇은 층입니다. 현대적인 웹 서비스는 프레임워크, 데이터베이스 드라이버, 로깅 라이브러리, 직렬화 형식을 끌어오고, 그 각각이 더 많은 것을 끌어옵니다. 그 결과 실행 중인 소프트웨어의 대부분, 흔히 압도적 다수가 인터넷의 낯선 사람들에게서 왔습니다. 그것은 실패가 아닙니다. 작은 팀이 예전에는 몇 년 걸렸을 것을 몇 주 만에 출시하게 해 주는 거래입니다. 요점은 눈을 뜨고 그 거래를 하는 것입니다.
그렇게 빌려 온 코드를 잘 관리하는 것은 그 자체로 하나의 엔지니어링 규율이며, 이 장은 그 기술을 다룹니다. 의존성을 어떻게 고르고, 고정하고, 갱신하고, 빌드를 재현하고, 커지는 동안 전체 그래프를 읽을 수 있게 유지하는가. 공격자가 그 그래프를 의도적으로 오염시키는 보안 위협 측면은 애플리케이션 보안에 관한 4.2장에서 충분히 다룹니다. 여기서의 관심은 일상의 엔지니어링입니다. 버전 제약, 락 파일, 전이 충돌, 갱신 주기, 소프트웨어에 무엇이 들어 있는지 아는 것입니다. 이를 제대로 하면 보안이 훨씬 쉬워집니다. 볼 수 없는 공급망은 지킬 수 없기 때문입니다.
큰 팀, 특히 기업과 정부에서는 규모에 따라 이해관계가 오릅니다. 오백 개의 저장소가 각자 라이브러리를 고르면, 같은 로깅 프레임워크의 오백 가지 약간씩 다른 버전, 아무도 승인하지 않은 라이선스, 심각한 취약점이 터졌을 때 “우리가 영향받는가?”에 답할 방법이 없는 상태가 됩니다. 기업은 승인된 라이브러리와 공유 레지스트리로 이에 답합니다. 정부는 점점 의무로 답합니다. 미국 행정명령 14028은 구매하는 소프트웨어의 기준선에 소프트웨어 자재 명세서(SBOM)와 빌드 출처를 밀어 넣었습니다. 다음 의존성 위기에서 침착함을 유지하는 조직은 필요해지기 전에 이 일을 해 둔 조직입니다.
핵심 원칙
- 소프트웨어의 대부분은 여러분이 쓰지 않은 코드입니다. 쓰지 않았어도 책임은 소유하십시오.
- 모든 의존성은 자산인 만큼 영구적인 부채입니다. 반사적이 아니라 의도적으로 추가하십시오.
- 빌드가 “그날의 최신”이 아니라 재현 가능하고 결정적이도록 락 파일로 버전을 고정하십시오.
- 드물고 끔찍한 도약이 아니라 꾸준한 주기로 작은 자동화된 증분으로 갱신하십시오.
- 소프트웨어에 정확히 무엇이 있는지 아십시오. 열거할 수 없는 것은 보호하거나 라이선스할 수 없습니다.
- 편리한 많은 것보다 잘 유지되는 적은 의존성을 선호하십시오.
- 패키지가 어디서 오는지 통제하십시오. 검증되지 않은 레지스트리는 열린 문입니다.
권장 사항
버전 관리를 이해하고 의도적으로 제약한다
생태계가 버전을 어떻게 표현하는지 배우십시오. 갱신 동작이 전적으로 거기에 실려 있기 때문입니다. 대부분의 패키지 관리자는 어떤 형태의 유의적 버전(SemVer)을 씁니다. 버전은 MAJOR.MINOR.PATCH로 읽히며, 패치 증가는 버그 수정만 약속하고, 마이너 증가는 하위 호환 기능을 더하고, 메이저 증가는 호환되지 않는 변경을 알립니다. 의존성 선언은 “4.x와 호환” 또는 “최소 2.3.0” 같은 제약을 정해, 리졸버가 버전을 고를 때 얼마나 멀리 돌아다닐 수 있는지 알려 줍니다.
그 제약을 얼마나 느슨하게 또는 촘촘하게 할지 의도적으로 정하십시오. 느슨한 범위는 수정을 자동으로 가져오지만 검토한 적 없는 마이너 릴리스가 프로덕션에 스며들 위험이 있고, 촘촘한 고정은 수동 노력의 대가로 통제를 줍니다. 대부분의 팀에 실용적인 답은 매니페스트에 합리적으로 허용적인 범위를 선언한 뒤, 해석된 정확한 버전을 락 파일에 얼려 범위가 의도적으로 갱신할 때만 다시 평가되게 하는 것입니다. SemVer를 유지보수자가 지키려는 약속으로 다루고 항상 지키는 보장으로 다루지 마십시오. “패치” 릴리스도 여러분을 깨뜨릴 수 있으며, 그래서 갱신을 신뢰하지 않고 테스트합니다.
락 파일을 커밋하고 재현 가능한 빌드를 요구한다
락 파일은 직접이든 전이든 의존성 그래프의 모든 패키지의 정확한 버전과 암호 해시를 기록합니다. 버전 관리(2.6장)에 커밋하고 소스의 일급 부분으로 다루십시오. 그 일은 빌드를 함수로 만드는 것입니다. 같은 입력이 모든 머신에서 올해도 내년도 매번 같은 출력을 냅니다. 그것이 없으면 일주일 간격으로 “install”을 실행한 두 엔지니어가 다른 코드를 얻을 수 있고, 프로덕션에 나타난 버그를 빌드한 노트북에서 재현하는 것이 불가능할 수 있습니다.
주어진 커밋이 항상 동작이 동일한 산출물을 내는 진정한 재현 가능한 빌드를 목표로 하십시오. 지속적 통합(8.1장)에서 락 파일에서만 엄격하게 설치하고, 새 버전을 조용히 해석하는 대신 락 파일과 매니페스트가 불일치하면 빌드를 실패시키십시오. 락 파일의 해시는 두 가지 일을 합니다. 동작을 고정하고, 변조를 탐지합니다. 내용이 더는 기록된 해시와 맞지 않는 패키지는 설치되지 않기 때문입니다. 재현 가능성은 이 장의 나머지 모든 것이 서 있는 토대입니다.
전이 의존성과 다이아몬드 충돌을 의도적으로 관리한다
직접 의존성은 이름을 붙인 것뿐입니다. 그 밑에는 훨씬 큰 전이 의존성 그래프, 즉 패키지가 의존하는 패키지가 있으며, 위험과 놀라움의 대부분이 사는 곳입니다. 고전적인 실패는 다이아몬드 의존성입니다. 라이브러리 A는 공유 유틸리티의 버전 1을 필요로 하고 라이브러리 B는 버전 2를 필요로 해서, 리졸버가 불가능한 요청을 조정해야 합니다. 일부 생태계는 여러 버전의 공존을 허용해 디스크와 메모리를 평화와 맞바꾸고, 다른 생태계는 단일 버전을 강제하고 충돌을 중재하는 일을 여러분에게 맡깁니다.
이런 충돌이 곪게 두지 말고 눈에 보이게 하십시오. 도구로 전체 의존성 트리를 출력하고, 특정 패키지가 왜 있고 누가 끌어왔는지 설명하게 하십시오. 충돌이 나타나면 의도적으로 해결하십시오. 뒤처진 쪽을 업그레이드하거나, 오버라이드를 고정하거나, 요구를 충족할 수 없는 의존성을 버립니다. 시간에 따른 그래프의 성장을 지켜보십시오. 전이 의존성의 억제되지 않은 난립은 결국 풀 수 없는 업그레이드나 재작성 없이는 패치할 수 없는 취약점으로 나타나는 기술 부채의 느린 축적이기 때문입니다.
자동화된 풀 리퀘스트로 꾸준한 주기로 갱신한다
가장 위험한 갱신 전략은 대부분의 팀이 우연히 빠져드는 것입니다. 절대 갱신하지 않다가 치명적 취약점이 손을 강제할 때 비상 압박 아래 한꺼번에 모두 갱신합니다. 그때쯤이면 여러 해 뒤처져 있고, 변경 로그는 벽이며, 업그레이드는 일상적 잡무가 아니라 몇 주짜리 프로젝트입니다. 해법은 주기입니다. 의존성에 새 버전이 나올 때마다 변경 로그와 테스트 결과를 첨부한 풀 리퀘스트를 여는 자동 의존성 갱신기(Dependabot과 Renovate가 흔한 예)를 채택하십시오.
그다음 흐름을 압도하지 않고 돕도록 조정하십시오. 매일 아침 개별 풀 리퀘스트가 쏟아지면 사람들이 무시하도록 훈련되며, 이는 자동화가 없는 것보다 나쁩니다. 패치 릴리스 같은 위험이 낮은 갱신은 묶고, 테스트가 통과하면 자동으로 병합되게 하고, 사람의 주의는 메이저 버전 증가와 민감한 라이브러리를 건드리는 것에 남겨 두십시오. 팀이 지속할 수 있는 리듬, 예컨대 주간 리뷰를 정해 갱신이 드물고 고통스러운 청구서가 아니라 작고 꾸준한 세금으로 남게 하십시오. 여기가 강한 테스트 전략(2.4장)이 본전을 뽑는 곳입니다. 자동 갱신은 테스트가 그것이 깨뜨리는 것을 잡을 수 있을 때만 안전하기 때문입니다.
발자국을 최소화하고 채택 전에 평가한다
추가하는 모든 의존성은 지속적인 약속입니다. 그 버그, 취약점, 라이선스, 유지보수자의 지속적 관심, 그 자체의 커지는 하위 의존성 그래프에 대한 약속입니다. 관리하기 가장 싼 의존성은 추가하지 않은 것입니다. 패키지에 손을 뻗기 전에, 특히 사소한 기능이라면 몇십 줄의 자체 코드로 충분하지 않은지 물으십시오. 패키지 생태계의 역사는 널리 의존되는 작은 패키지가 제거되거나 탈취되어 인터넷의 절반이 깨진 교훈적 이야기로 가득합니다.
채택할 때는 후보를 장기적인 관계처럼 평가하십시오. 유지보수 건강을 확인하십시오. 최근 커밋, 응답하는 유지보수자, 진짜 릴리스 이력, 열쇠를 가진 사람이 한 명 이상인지. 라이선스를 확인하고 승인 목록(10.3장)에 있는지 확인하십시오. 보안 이력, 크기, 자체의 전이 발자국을 확인하십시오. 작은 기능이 백 개의 패키지를 끌어오는 것은 가치가 없기 때문입니다. “이걸 추가해야 하나?”가 기분이 아니라 팀 전체가 일관되게 적용하는 체크리스트가 되도록 이 기준을 적어 두십시오.
SBOM을 만들고 빌드 출처를 포착한다
이미 소프트웨어에 무엇이 들어 있는지 알지 못하면 “이 취약점의 영향을 받는가?”에 빨리 답할 수 없습니다. SBOM이 그 답입니다. SPDX나 CycloneDX 같은 표준 형식으로, 빌드의 모든 구성 요소의 버전과 라이선스를 담은 기계가 읽을 수 있는 목록입니다. 빌드 파이프라인의 일부로 자동 생성하고, 산출물 옆에 저장하고, 그 산출물이 어디서든 실행되는 동안 보관하십시오. 다음 헤드라인 취약점이 터지면 SBOM에 대한 질의가 일주일의 허둥대는 grep을 5분짜리 보고서로 바꿉니다.
한 걸음 더 나아가 출처를 포착하십시오. 산출물이 어떻게, 어느 소스 커밋에서, 어느 파이프라인으로 빌드되었는지에 대한 서명되고 변조 증거가 있는 기록입니다. 오픈 소스 소프트웨어 커뮤니티는 정확히 이를 위한 단계적 모델로 SLSA 프레임워크(Supply-chain Levels for Software Artifacts)에 수렴했으며, “빌드를 기술할 수 있다”에서 “증명할 수 있고, 그 증명이 침해된 빌드 시스템에 저항한다”까지 올라갑니다. 증명(attestation)은 소비자가 산출물이 정말 여러분의 파이프라인에서 왔음을 검증하게 합니다. 정부 업무에서 이는 점점 선택이 아닙니다. 출처와 SBOM이 조달 의무 안에 있으므로, 능력을 일찍 구축하면 입찰 자격을 유지합니다.
레지스트리, 미러, 벤더링으로 출처를 통제한다
패키지가 어디서 오는지는 어떤 패키지를 고르는지만큼 중요합니다. 모든 빌드에서 공용 인터넷에서 직접 끌어오면 그 장애, 철회된 버전, 공격자를 물려받습니다. 공용 생태계를 프록시하는 내부 패키지 레지스트리나 캐싱 미러를 세워 빌드를 빠르고 반복 가능하고 상류가 사라지는 것으로부터 격리하십시오. 레지스트리는 정책을 시행하는 자연스러운 자리가 되기도 합니다. 알려진 나쁜 버전을 차단하고, 새 릴리스를 짧은 숙성 기간 동안 격리하고, 라이선스나 보안 관문을 통과하지 못하는 패키지를 거부합니다.
두 가지 특정한 함정을 피하도록 레지스트리를 신중하게 설정하십시오. 의존성 혼동은 빌드 도구가 비공개 내부 패키지와 같은 이름의 공개 패키지를 둘 다 제공받았을 때 공격자의 공개 것을 가져오는 것입니다. 내부 이름의 범위를 정하고 내부 패키지를 내부 출처에 명시적으로 고정하여 방어합니다. 타이포스쿼팅은 악성 패키지가 인기 있는 것에서 한 타 떨어진 이름을 쓰고 서툰 손가락을 기다리는 것이며, 허용 목록이 있는 선별된 레지스트리가 문 앞에서 막습니다. 소수의 핵심적이거나 느리게 변하는 의존성에는 벤더링, 즉 실제 의존성 소스를 자기 저장소에 체크인하는 것을 고려해 빌드에 외부 의존성이 전혀 없게 하십시오. 갱신 편의를 완전한 통제와 맞바꾸며, 때로는 정확히 맞습니다.
장단점
| 접근 방식 | 장점 | 단점 |
|---|---|---|
| 느슨한 버전 범위 | 자동 수정. 낮은 수동 노력 | 검토되지 않은 코드가 프로덕션에 닿음. 락 파일 없이는 비결정적 |
| 엄격한 고정 + 락 파일 | 재현 가능하고 감사 가능한 빌드 | 의도적인 갱신 작업이 필요. 수정에 뒤처질 수 있음 |
| 적극적 갱신 주기 | 작고 안전한 단계. 항상 최신에 가까움 | 끊임없는 변동. 꾸준한 리뷰어 주의 필요 |
| 드문 묶음의 큰 업그레이드 | 일상의 방해 감소 | 강제될 때 끔찍하고, 위험하고, 비쌈 |
| 편리한 많은 의존성 | 기능 구축이 빠름 | 큰 공격 표면. 무거운 유지보수 부하 |
| 최소 발자국 + 벤더링 | 통제, 작은 표면, 상류 위험 없음 | 소유하는 코드가 늘어남. 갱신을 직접 짊어짐 |
| 공용 레지스트리 직접 사용 | 설정 없음 | 장애, 철회, 혼동 및 타이포스쿼팅 노출 |
| 내부 레지스트리와 미러 | 속도, 정책 시행, 격리 | 운영하고 유지할 인프라 |
핵심 긴장은 속도와 통제 사이에 있습니다. 위의 모든 선택은 다른 각도에서 본 같은 다이얼입니다. 빌려 온 코드의 얼마를 적극적으로 다스리고 얼마를 신뢰로 흘러들게 둘 것인가. 통제 쪽으로 너무 기울면 수동 리뷰에 빠지고, 보안 수정에 뒤처지고, 의존성이 빠르게 해 주기로 되어 있던 팀을 늦춥니다. 속도 쪽으로 너무 기울면 어느 날 감사할 수도 업그레이드할 수도 없는 그래프와 법무에 설명할 수 없는 라이선스 위반을 안고 깨어납니다. 해법은 고정된 점이 아니라 자세입니다. 모든 것을 잠그고 재현하고, 작은 단계로 지속적으로 갱신하고, 떠안는 것을 최소화하고, 통제하는 길목에서 정책을 시행하십시오. 그 조합이 규모에서 할 가치가 있는 거래인 속도와 안전 모두를 줍니다.
팀과 논의할 질문
실제 갱신 주기는 어떠하며, 강제된 비상 업그레이드는 몇 시간이 걸리겠습니까 몇 주가 걸리겠습니까? 대부분의 팀은 치명적 취약점이 문제를 강제하기 전까지 이에 정직하게 답할 수 없습니다. 이 장은 꾸준하고 자동화된 작은 단계의 갱신을 안전한 길로, 드문 빅뱅 업그레이드를 위험한 길로 다룹니다. 열어 둔 간극이 나중에 압박 아래 질주해 건너야 하는 간극이기 때문입니다. 증거를 가져오십시오. 의존성 중 한 메이저 버전 이상 뒤처진 것이 몇 개이며, 마지막 중대한 업그레이드가 실제로 얼마나 걸렸습니까? 자동 갱신기를 채택할 수 있는지, 사람들이 무시하지 않도록 위험이 낮은 변경을 어떻게 묶을지, 자동 병합이 안전하려면 어떤 테스트가 필요한지 논의하십시오. 답은 엔지니어링 시간을 예산 잡는 방식을 바꾸어, 드문 위기를 일상적인 주간 세금으로 바꿔야 합니다. 정직한 답이 “몇 주”라면, 그것은 인시던트 중에 발견할 것이 아니라 지금 이름 붙일 위험입니다.
지금 일반적인 라이브러리에 심각한 취약점이 공지된다면, 실행하는 영향받는 모든 산출물을 얼마나 빨리 나열할 수 있습니까? 이것이 SBOM이 답하려고 존재하는 질문이며, 답의 속도는 공급망 성숙도의 직접적 척도입니다. 목록이 없으면 저장소를 grep하고 팀을 인터뷰하는 처지가 되며, 시계가 흐르는 동안 없을지도 모르는 며칠이 걸립니다. 구체적 신호를 가져오십시오. 빌드마다 SBOM을 생성하는지, 어디에 저장하는지, 오늘 전부에 걸쳐 실제로 질의할 수 있는지. 직접 의존성만이 아니라 전이 그래프를 아는지 논의하십시오. 취약한 패키지는 대개 이름을 붙인 적 없는 것이기 때문입니다. 답이 다음 인시던트가 질의일지 소방 훈련일지를 결정하며, 필요해지기 전에 능력을 구축할 가치가 있습니다. 정부는 바로 그 이유로 이를 의무화합니다.
새 의존성을 채택할 가치가 있는지 어떻게 결정하며, 모두가 같은 기준을 적용합니까? 이 장은 모든 의존성이 편의만큼 영구적인 부채이며, 관리하기 가장 싼 것은 추가하지 않은 것이라고 주장합니다. 그러나 대부분의 팀에서 그 결정은 보이지 않습니다. 엔지니어가 기능이 필요하고, 패키지를 찾고, 유지보수, 라이선스, 보안 이력, 발자국 검토 없이 점심 전에 락 파일에 들어갑니다. 자기 그래프에서 아무도 채택한 기억이 없고 오늘은 변호할 수 없는 패키지의 예를 가져오십시오. 서면 평가 체크리스트와 승인된 라이브러리 목록(10.3장)이 도움이 될지 마찰만 더할지, 직접 써야 할 사소한 헬퍼와 의존할 가치가 있는 진짜 인프라 사이의 선이 어디인지 논의하십시오. 답은 작은 결정 하나하나로 팀이 지는 장기적 무게를 형성합니다.
전이 의존성을 실제로 다스립니까, 아니면 이름 붙인 것만 다스립니까? 위험의 대부분은 한 층 아래, 패키지가 끌어온 패키지에 있으며, 두 라이브러리가 공유 유틸리티의 호환되지 않는 버전을 요구하는 다이아몬드 충돌은 최악의 순간에 업그레이드를 막을 수 있습니다. 규모에서 이것이 중요한 이유는 고칠 수 없는 전이 패키지 하나가 수백 개 저장소에 걸친 보안 수정을 얼릴 수 있기 때문이며, 상충하는 끌림은 실제입니다. 전체 그래프를 드러내고 고정하는 데는 지속적인 노력이 들고, 무시하면 그 노력을 풀 수 없는 업그레이드로 나타나는 부채의 느린 축적과 맞바꿉니다. 증거를 가져오십시오. 도구가 전체 트리를 출력하고 특정 패키지가 왜 있고 누가 끌어왔는지 설명할 수 있습니까, 가장 흔한 라이브러리의 서로 다른 버전이 오늘 몇 개 공존합니까? 기업과 정부에서는 목록과 정책이 전이 구성 요소에 닿기나 하는지를 더하십시오. 그래프의 절반이 보이지 않는다면 소프트웨어에 무엇이 있는지 알라는 의무는 의미가 없기 때문입니다. 답은 다음 강제 업그레이드가 일상적 병합일지 여러 팀의 발굴 작업일지를 알려 줍니다.
패키지는 실제로 어디서 오며, 공격자가 하나를 끼워 넣지 못하게 막는 것은 무엇입니까? 공용 인터넷에서 직접 끌어오는 모든 빌드는 그 장애, 철회된 버전, 두 가지 특정 공격을 물려받습니다. 빌드 도구가 비공개 패키지를 가리는 공개 패키지를 가져오는 의존성 혼동, 악성 패키지가 인기 있는 이름에서 한 타 떨어져 있는 타이포스쿼팅입니다. 큰 팀에서 이것이 중요한 이유는 오염된 가져오기 하나가 아무도 알아채기 전에 전체 자산으로 전파될 수 있기 때문이고, 트레이드오프는 진짜입니다. 내부 레지스트리나 캐싱 미러는 정책 길목과 상류로부터의 격리를 주지만 누군가 운영하고 최신으로 유지해야 하는 인프라입니다. 구체적 신호를 가져오십시오. 내부 패키지 이름이 내부 출처에 범위가 정해지고 명시적으로 고정되는지, 허용 목록이 있는지, 새 릴리스가 사용되기 전에 짧은 격리를 받는지. 정부와 규제 구매자에게는 이를 조달이 점점 요구하는 승인된 소프트웨어 목록과 직접 인터넷 접근 금지 자세에 연결하고, 현재 설정이 오늘 그 기준을 통과할지 솔직해지십시오.
산출물이 어떻게 빌드되었는지 실제로 재현하고 증명할 수 있습니까? 암호 해시가 있는 커밋된 락 파일은 빌드를 함수로, 즉 같은 입력이 올해도 내년도 모든 머신에서 같은 출력을 내게 해야 하고, 출처는 누구든 산출물이 정말 여러분의 파이프라인과 소스 커밋에서 왔음을 검증하게 해야 합니다. 이것이 중요한 이유는 재현 불가능한 빌드가 프로덕션 버그를 풀 수 없는 수수께끼로 바꾸고 변조가 없었음을 증명할 수 없게 만들기 때문이며, 상충하는 고려는 노력 대 보증입니다. 엄격한 락 파일 설치, 서명된 증명, SLSA에 맞춘 출처는 “최신을 설치” 흐름이 피하는 설정과 규율의 비용이 듭니다. 증거를 가져오십시오. 지속적 통합이 락 파일과 매니페스트가 불일치하면 실패합니까, 빌드마다 SBOM과 서명된 출처 기록을 생성하고 저장합니까, 누군가 하나라도 검증한 적이 있습니까? 기업과 특히 정부 업무에서 출처와 SBOM은 점점 조달 의무 안에 있으므로, 여기서의 정직한 답이 입찰 자격을 유지할지 배제될지를 결정합니다.
분야별 관점
스타트업. 작은 팀과 플랫폼 그룹이 없다면 프로세스 대신 기본값과 자동화에 기대십시오. 첫날부터 락 파일을 커밋하고, 패치 릴리스를 묶어 초록 테스트에서 자동 병합하는 자동 갱신기를 켜고, 패키지 추가에는 하나의 가벼운 규칙을 두십시오. 지루하고 잘 유지되는 라이브러리를 선호하고 작은 것은 두 번 생각하십시오. 아직 내부 레지스트리를 세우지 않아도 괜찮지만, 커밋된 해시만으로 이미 보호받습니다. 오염된 버전은 그냥 설치되지 않기 때문입니다.
소기업. 의존성 전문가도 빠듯한 예산도 없으니 규율을 만들지 말고 사십시오. 호스팅 및 코드 플랫폼이 이미 제공하는 갱신 자동화에 의지하고, 업그레이드가 싸게 유지되도록 소수의 성숙한 라이브러리를 선호하고, 인력을 두지 않고도 “영향받는가?”에 답할 수 있도록 파이프라인에 무료 SBOM 생성기를 쓰십시오. 희소한 주의를 라이선스 확인과, 열두 줄로 쓸 수 있는 사소한 패키지를 채택하지 않는 데 쓰십시오.
대기업. 문제는 많은 팀에 걸친 일관성입니다. 공용 생태계를 미러링하고 한 길목에서 라이선스, 출처, 버전 정책을 시행하는 공유 내부 레지스트리, 기본값으로서의 선별된 골든 라이브러리 집합과 그 밖의 것에 대한 문서화된 예외 경로입니다. 빌드마다 중앙 저장소로 SBOM을 내보내 하나의 질의가 전체 자산에 걸친 노출에 답하게 하고, 자동화된 풀 리퀘스트로 조율된 업그레이드를 굴리고, 의존성 건강을 저장소별 우연이 아니라 측정되고 다스려지는 포트폴리오로 다루십시오.
정부. 조달 규칙과 공적 책임성이 모든 것을 형성합니다. 벤더에게 모든 릴리스와 함께 기계가 읽을 수 있는 SBOM과 SLSA에 맞춘 빌드 출처를 전달하도록 요구하고, 공용 인터넷으로의 직접 경로가 없는 미러가 제공하는 승인된 소프트웨어 목록에서만 내부 설치하게 하고, 시스템이 15년 운영되고 그 기간 내내 패치 가능해야 하므로 유지보수가 안정적이고 라이선스가 분명한 의존성을 선호하십시오. 수명 종료 이전은 비상 상황이 아니라 의도적으로 계획하고, 감사자가 출하된 어떤 산출물이든 소스까지 추적할 수 있는 기록을 유지하십시오.
사례
스타트업. 여섯 명 규모의 스타트업이 프레임워크, 결제 라이브러리, 검사한 적 없는 약 900개의 전이 패키지 위에 지은 웹 애플리케이션을 출시합니다. 플랫폼 팀을 둘 여유가 없어서 자동화에 기댑니다. 첫날부터 커밋된 락 파일, 패치 릴리스를 묶어 초록 테스트에서 병합하는 자동 갱신기, 쌓인 메이저 버전 증가를 검토하는 월 한 시간입니다. 의존성 추가의 한 문단 규칙은 대부분 “지루하고 잘 유지되는 라이브러리를 선호하고, 작은 것은 두 번 생각하라”입니다. 인기 패키지가 침해되었을 때, 커밋된 락 파일 해시 덕에 오염된 버전은 설치되지 않았고, 그들은 사건을 겪는 대신 읽었습니다.
대기업. 400개 저장소를 가진 은행이 공용 생태계를 미러링하고 그 길목에서 정책을 시행하는 내부 패키지 레지스트리를 운영합니다. 하나의 승인된 로깅 프레임워크, 하나의 HTTP 클라이언트, 하나의 JSON 파서라는 선별된 골든 라이브러리가 기본이며, 그 밖의 것은 문서화된 예외가 필요합니다. 이너소스 모델로 어느 팀이든 그 공유 라이브러리에 기여할 수 있고 작은 플랫폼 그룹이 건강을 소유합니다. 조율된 업그레이드가 자동화된 풀 리퀘스트로 400개 저장소 전체에 보안 패치를 며칠 안에 굴리고, 모든 빌드가 중앙 저장소로 SBOM을 내보냅니다. 치명적 취약점이 공지되면 하나의 질의를 실행해 뉴스 주기가 끝나기 전에 노출을 압니다.
정부. 한 연방 기관이 행정명령 14028로 거슬러 올라가는 출처 및 SBOM 요건 아래 소프트웨어를 조달합니다. 벤더는 모든 릴리스와 함께 기계가 읽을 수 있는 SBOM을 전달하고 SLSA 프레임워크에 맞춘 빌드 출처를 입증해야 하므로, 기관은 각 산출물이 주장된 소스에서 왔음을 검증할 수 있습니다. 내부적으로 개발자는 공용 인터넷으로의 직접 경로가 없는 내부 미러가 제공하는 승인된 소프트웨어 목록에서만 설치할 수 있습니다. 장기 지원 가능성이 선택을 이끕니다. 시스템이 15년 운영되고 그 기간 내내 패치 가능해야 하므로 유지보수가 안정적이고 라이선스가 분명한 의존성을 선호합니다. 구성 요소가 수명 종료에 이르면 비상 상황이 아니라 계획된 이전이 그것을 대체합니다.
비즈니스 사례: 동기, ROI, TCO
의존성 규율의 수익은 대부분 일어나지 않은 재앙으로 측정됩니다. 커밋된 락 파일과 재현 가능한 빌드는 도입 비용이 거의 없고 “내 머신에서는 동작한다” 결함과 재현할 수 없는 프로덕션 버그의 부류 전체를 없애며, 각각이 시니어 엔지니어링 시간의 며칠을 태울 수 있습니다. 자동 갱신 주기는 로드맵을 멈추고 팀을 지치게 하는 가끔의 몇 주짜리 비상 업그레이드를 작은 병합된 변경의 꾸준한 웅성거림으로 바꿉니다. 많은 저장소의 포트폴리오 전반에서 드물고 거대한 것에서 빈번하고 아주 작은 것으로의 전환은 엔지니어링 조직이 쓸 수 있는 지렛대 효과가 가장 큰 프로세스 변화에 속합니다.
총소유비용 논거는 이번 스프린트에 쓰는 것이 아니라 여러 해에 걸쳐 짊어지는 것에 관한 것입니다. 관리되지 않는 의존성은 조용히 쌓입니다. 재작성 없이는 더 업그레이드할 수 없는 오래된 버전, 아무도 가격을 매기지 않은 법적 노출을 만드는 라이선스, 필요한 패치 하나가 호환되지 않는 변경의 연쇄를 촉발할 만큼 뒤엉킨 그래프입니다. 하지 않는 비용은 한꺼번에, 가장 나쁜 때에 옵니다. 보안 인시던트, 감사, 강제 이전 중에, 여러 해 미뤄진 유지보수의 청구서가 이자와 함께 돌아옵니다. 리더십에게 그들의 언어로 논거를 세우십시오. 재현 가능한 빌드는 인시던트 비용을 줄이고, SBOM은 취약점 대응 시간을 며칠에서 몇 분으로 줄이고, 승인된 라이브러리와 출처는 그렇지 않았다면 배제되었을 규제 및 정부 계약의 자격을 유지해 줍니다.
안티패턴과 함정
- 락 파일이 없거나 커밋되지 않음: 빌드가 매번 새로 해석되어, 아무도 무엇이 출시되었고 무엇이 깨졌는지 믿을 만하게 재현할 수 없는 것.
- 프로덕션의 부유하는 “latest”: 그 순간 레지스트리가 내준 것이 검토되지도 추적되지도 않은 릴리스가 되는 것.
- 강제될 때까지 갱신하지 않음: 여러 해의 표류가 취약점 압박 아래 하나의 끔찍하고 위험한 비상 업그레이드로 붕괴하는 것.
- 갱신 봇 피로: 묶이지 않은 풀 리퀘스트의 폭포가 팀에게 긴급한 것을 포함한 모두를 무시하도록 훈련시키는 것.
- 의존성 난립: 사소한 기능을 위해 반사적으로 패키지를 추가해 유지할 수 없는 그래프와 넓은 공격 표면을 키우는 것.
- 목록 없음: SBOM이 없으면 “영향받는가?”에 답하는 것이 저장소 전반의 며칠간 수동 고고학을 뜻합니다.
- 공용 레지스트리를 맹신: 직접 가져오기는 장애, 철회된 버전, 의존성 혼동, 타이포스쿼팅에 노출시킵니다.
- 전이 의존성 무시: 이름 붙인 것만 다스리는 동안 위험의 대부분이 한 층 아래 숨어 있는 것.
- 검증되지 않은 라이선스: 출시 방식과 충돌하는 라이선스의 코드를 끌어와 감사나 인수 때에야 발견하는 것.
성숙도 모델
- 1단계, 시작: 의존성이 평가 없이 자유롭게 추가됩니다. 커밋된 락 파일이 없고, 빌드는 재현 가능하지 않으며, 갱신은 강제된 비상 상황에서만 일어나고, 아무도 소프트웨어에 무엇이 있는지 열거할 수 없습니다.
- 2단계, 발전: 일부 팀이 락 파일을 커밋하고 대체로 재현 가능한 빌드를 얻으며, 약간의 자동화가 갱신 풀 리퀘스트를 열지만, 관행은 저장소마다 일관되지 않습니다. 라이선스와 전이 위험에 대한 인식은 비공식적이고, 공유 정책도, 목록도, 패키지가 어디서 오는지에 대한 통제도 없습니다.
- 3단계, 표준화: 관행이 문서화되어 조직 전체에서 시행됩니다. 자동 갱신기가 합리적인 묶음과 함께 꾸준한 주기로 돌고, 빌드는 락 파일에서만 엄격히 설치하고 락 파일과 매니페스트가 불일치하면 실패하며, 빌드마다 SBOM이 생성되고, 내부 레지스트리가 출처와 라이선스 정책을 시행하고, 새 의존성은 모든 팀이 적용하는 서면 체크리스트에 대해 평가됩니다.
- 4단계, 관리: 의존성 자산이 기준선에 대한 데이터로 측정되고 통제됩니다. 버전 지연(한 메이저 버전 이상 뒤처진 의존성의 수), 모든 산출물에 걸쳐 치명적 취약점을 패치하는 평균 시간, 자동 갱신 병합률, 출하된 빌드 대비 SBOM 커버리지 비율, 해결되지 않은 다이아몬드 충돌과 정책 예외의 수를 추적합니다. 이 지표가 릴리스를 관문으로 통제하고 노력을 쓸 곳을 이끌어, 업그레이드와 시정은 가장 크게 외치는 사람이 아니라 증거로 관리됩니다.
- 5단계, 오케스트레이션: 의존성 관리가 조직 전체에서 지속적으로 개선되고 통합됩니다. 빌드 출처와 증명이 포착되고 검증되며, SBOM은 즉각적인 취약점 대응을 위해 포트폴리오 전체에 걸쳐 질의 가능하고, 조율된 업그레이드가 많은 저장소에 자동으로 굴러가고, 골든 라이브러리는 선별되고 이너소스되며, 전체 시스템이 생태계, 위협, 조달 의무가 이동함에 따라 적응합니다.
논의를 위한 아이디어
- 팀에게 작은 유틸리티를 직접 쓸 것인지 그것을 위해 의존성을 떠안을 것인지 사이의 알맞은 선은 어디입니까?
- 버전 제약은 얼마나 느슨하거나 촘촘해야 하며, 애플리케이션과 공개 라이브러리에서 그 답이 다릅니까?
- 위험이 낮은 패치 갱신이 초록 테스트에서 자동 병합되어야 합니까, 그렇다면 안전하려면 테스트 스위트에 무엇이 필요합니까?
- 조직의 규모와 위험 프로필에 내부 레지스트리나 미러가 운영 비용을 감수할 가치가 있습니까?
- 최대한의 통제를 위해 어떤 의존성을 벤더링하고, 어떤 것을 공용 레지스트리에 남길지 어떻게 우선순위를 정하겠습니까?
- 이번 분기부터 출하하는 모든 산출물에 SBOM을 생성하고 실제로 활용하려면 무엇이 필요합니까?
핵심 요점
- 소프트웨어의 대부분은 빌려 온 코드입니다. 그것을 잘 관리하는 것은 사후 생각이 아니라 핵심 엔지니어링 규율입니다.
- 락 파일을 커밋하고 재현 가능하고 결정적인 빌드를 요구해 같은 입력이 항상 같은 출력을 내게 하십시오.
- 드물고 강제되는 끔찍한 도약이 아니라 작은 자동화된 단계로 지속적으로 갱신하십시오.
- 서면 기준에 맞춰 의존성을 의도적으로 추가하십시오. 관리하기 가장 싼 것은 떠안지 않은 것입니다.
- 소프트웨어에 무엇이 있고 어디서 왔는지 항상 알 수 있도록 SBOM을 생성하고 출처를 포착하십시오.
- 혼동, 타이포스쿼팅, 상류 장애를 막도록 내부 레지스트리로 출처를 통제하십시오.
참고 문헌과 더 읽을거리
- U.S. Executive Order 14028, Improving the Nation’s Cybersecurity (2021)
- National Institute of Standards and Technology (NIST), Secure Software Development Framework (SP 800-218)
- SLSA (Supply-chain Levels for Software Artifacts) framework specification, Open Source Security Foundation
- OWASP CycloneDX specification and the SPDX specification, for SBOM formats
- Tom Preston-Werner, Semantic Versioning Specification (SemVer)
- The Reproducible Builds project documentation
- Nicole Forsgren, Jez Humble, Gene Kim, Accelerate: The Science of Lean Software and DevOps