v0.22.0 - 설정 가능한 거버넌스, 워커 확장, bcrypt를 벗 어난 API Key
기능 릴리스입니다. 함께 진행된 두 트랙에서 나온 결과를 담았습니다. 설정 가능성 트랙은 포털이 일방적으로 정하던 것들을 조직이 직접 쓰는 설정으로 바꿉니다. 빌드 게이트가 무엇을 근거로 판단할지, 리스크를 수용하기 전에 누구의 동의가 필요할지, 발견 사항을 누가 더 전달받을지, 컴포넌트가 도입되기 전에 승인이 필요한지 같은 것들입니다. 동시성·확장 트랙은 워커 플릿과 데이터베이스, 검색 경로가 데모 시드가 아니라 실제 부하에서도 버티도록 손봤습니다. 두 트랙과 함께 진행한 보안 검토에서는 OAuth 콜백의 오픈 리다이렉트를 막았고, API Key 해시를 bcrypt에서 HMAC-SHA256으로 옮겼습니다.
기계가 읽는 전체 변경 목록은
CHANGELOG.md에
있습니다.
업그레이드 전에 읽어 주세요
Helm 설치는 이제 env.secret.apiKeyHmacSecret를 명시적으로 지정해야
합니다. 값을 비워 두면 차트가 secretKey에서 이 비밀 값을 조용히
파생시키던 결함이 있었습니다. 이는 아래 HMAC 전환이 막으려던 위험을
그대로 되살립니다. SECRET_KEY가 유출되면 API Key 해시까지 함께
노출되는 상황입니다. 이제 차트는 값을 파생시키는 대신 렌더링 자체를
실패시킵니다. 업그레이드하기 전에 openssl rand -hex 32로 값을 생성해
지정하세요. 지정하지 않으면 Helm 업그레이드가 실패합니다.
업그레이드를 참고하세요.
검색 동작이 두 가지 바뀌었습니다. 최소 검색어 길이가 2자에서 3자로 늘었습니다. 2자짜리 검색어는 검색이 의존하는 트라이그램 인덱스를 건너뛰기 때문입니다. 그리고 컴포넌트 검색(커맨드 팔레트와 전체 검색 결과 페이지 모두)은 이제 프로젝트의 최신 스캔만 대상으로 합니다. 취약점 검색과 라이선스 검색이 이미 그렇게 동작하던 것과 같은 방식입니다. 더 오래된, 이미 대체된 스캔에만 나타났던 컴포넌트는 더 이상 검색되지 않습니다.
PostgreSQL 커넥션 풀 기본값이 모든 배포 형태에서 낮아졌습니다.
기존 Helm 기본값만으로도 백엔드 계층이 PostgreSQL의 기본
max_connections(100)를 훌쩍 넘는 240개 커넥션을 요구할 수 있었습니다.
배포가 자체 풀 값을 설정하지 않았다면 새 기본값이 그대로 적용되며,
지정한 플릿 규모가 데이터베이스의 실제 max_connections를 여전히
초과하면 부팅 시점에 경고가 뜹니다.
api_keys.last_used_at은 더 이상 정확한 시각을 뜻하지 않습니다.
이제 키마다 최대 15분에 한 번만 기록되므로, "정확히 이 시각에
사용됨"이 아니라 "이 구간 안에 사용됨"을 뜻합니다. 오늘 기준으로는
이 값을 더 세밀하게 읽는 곳이 없지만, 정확한 타임스탬프를 전제로
만든 모니터링 연동이 있다면 이 구간을 감안해야 합니다.
주요 변경
거버넌스가 환경변수에서 조직이 쓰는 정책으로 옮겨갑니다
빌드 게이트는 원래도 조정할 수 있었지만 환경변수로만
가능했습니다. 바꾸려면 배포 환경에 접근해야 했고 누가 왜 그렇게
결정했는지 기록도 남지 않았습니다. gate_policies는 라이선스 정책과
같은 방식으로 범위를 나눕니다. 조직 기본값 하나에 팀별 재정의를 얹을
수 있고, 모든 필드는 nullable이며 NULL은 "여기서는 정하지 않음"을
뜻합니다. 정책 화면에는 임계값을 바로 편집하는 컨트롤이 생겼고, 필드마다
별도의 재정의 스위치를 둬서 "빈 임계값(조직 값을 따름)"과 "임계값
0(어떤 점수든 차단)"을 구분할 수 있게 했습니다. 행이 하나도 없는 배포는
이번 릴리스 이전과 똑같이 평가됩니다.
관련 거버넌스 기능 두 가지도 함께 나왔습니다. 조직은 이제 취약점을 수용(accept)하거나 억제(suppress)하기 전에 두 번째 사람의 동의를 요구할 수 있습니다. 첫 번째 분석가가 상태를 바꾸면 요청이 열리고, 다른 팀 관리자가 승인해야 반영됩니다. 그리고 조직은 프로젝트마다 같은 패키지를 따로 검토하는 대신 컴포넌트 하나에 대해 한 번만 판정을 내릴 수 있습니다. 프로젝트가 이미 내린 결정이 있다면 그 결정이 우선합니다.
읽기 전용 viewer 등급
지금까지 developer보다 낮은 등급은 아무것도 볼 수 없었습니다.
developer 자체가 스캔 실행과 모든 쓰기 작업, 소스 트리 접근을
일반 읽기와 한데 묶고 있었기 때문입니다. 새로 생긴 viewer 등급은
프로젝트, 컴포넌트, 취약점, 라이선스, 의무사항, SBOM, 보고서, 검색,
승인 대기열, 라이선스 정책 등 47개 읽기 경로를 열어 주면서도, 소스
트리와 자격 증명, 감사 로그, 그리고 모든 쓰기 작업은 그대로 막아
둡니다.
배포 자체의 identity provider로 로그인
세 번째 OAuth 공급자 oidc가 추가됐습니다. GitHub·Google처럼 고정된
공급자가 아니라 issuer를 기준으로 설정합니다. 운영자는 issuer와
client ID, secret을 입력하고, 포털은 나머지를 issuer의 discovery
문서에서 읽어 옵니다. identity provider의 그룹으로 새로 들어오는
사용자의 등급을 정할 수도 있습니다. 운영자가 작성한 매핑으로 정하는데,
회사 전체 가 로그인하는 배포에 특히 유용합니다. 예전 동작(새로
들어오는 모든 사람을 자기 개인 팀의 관리자로 만드는 것)은 그런
배포에는 맞지 않는 기본값이었습니다.
사용자와 팀 문서를 참고하세요.
워커 플릿과 검색 경로를 실제 부하에 맞게 강화했습니다
동시성·확장 작업이 파이프라인 전체를 손봤습니다. Celery 워커 플릿은 이제 Helm 차트와 Compose 양쪽에서 스캔 작업과 그 외 작업을 별도 큐로 구독할 수 있습니다. 그래서 한 시간짜리 스캔과 1초짜리 알림이 같은 줄에서 서로를 기다리지 않습니다. Helm 차트는 CPU 대신 큐 깊이를 기준으로도 워커를 오토스케일할 수 있습니다. cdxgen·Trivy 파이프라인은 네트워크와 디스크 대기가 많아서, 큐가 쌓이는 동안에도 CPU 사용률은 한가하게 보일 수 있기 때문입니다. 오토스케일러가 없는 Compose 배포에는 설치 시점 용량 산식과 큐 적체 알림이 대신 생겼습니다.
워커 파드의 종료 유예 시간은 스캔이 실제로 걸리는 시간에 비해 지나치게 짧았습니다(Kubernetes 기본값 30초). 그래서 부하 아래에서 워커 풀을 줄이면 실행 중이던 스캔이 처음부터 다시 시작되곤 했습니다. 이제 스캔 파이프라인 자체의 최대 실행 시간에 맞춰집니다. 그리고 소스 스캔은 같은 ref의 직전 스캔 이후 의존성 구성에 변화가 없으면 cdxgen을 아예 건너뛰고 보존된 SBOM을 재사용합니다. 동일한 SBOM을 다시 만드는 데 드는 5~30분을 절약합니다.
API Key가 bcrypt를 벗어나 HMAC-SHA256으로 옮겨갑니다
API Key의 비밀 값은 사람이 고른 비밀번호가 아니라 192비트 난수이므로, bcrypt의 의도된 느림은 아무것도 방어하지 못하면서 검증 한 번에 약 213ms의 CPU 시간만 소모하고 있었습니다. 새로 발급되는 키는 이제 HMAC-SHA256으로 해시합니다. 이전에 발급된 키는 기존 bcrypt 해시로 계속 인증되고, 새 관리자 엔드포인트가 아직 레거시 형식으로 남아 있는 키가 몇 개인지 알려 줍니다. 이 전환은 타이밍 오라클을 하나 새로 열 뻔했습니다. 추측한 key prefix가 레거시 형식으로 남아 있는지가 응답 시간만으로 드러날 수 있었던 것인데, 전체 플릿이 이전을 마칠 때까지 모든 검증 분기를 공통 하한 시간으로 맞춰서 막았습니다.
모든 프로덕션 컨테이너가 이제 root가 아닌 자기 계정으로 실행됩니다
여전히 root로 실행되던 마지막 컨테이너들을 이번 릴리스에서 정리했습니다.
프런트엔드 이미지는 이제 nginx 계정으로 실행되고, 가장 까다로웠던
워커 이미지(번들된 SCA 도구 모음이 실행 계정의 HOME 아래에 캐시를
쓰기 때문입니다)는 trustedoss 계정으로 실행되며 모든 캐시 경로가 그
홈 디렉터리 아래로 고정됐습니다. Helm 차트도 모든 워크로드에 실제 pod·
컨테이너 보안 컨텍스트를 부여합니다. 고정된 non-root 사용자, 모든
capability 제거, 권한 상승 금지, 그리고 이미지가 허용하는 한 읽기 전용
루트 파일시스템까지입니다.
스캔 상세 화면이 스캔이 실제로 무엇을 봤는지 보여 줍니다
소스 스캔은 이제 가져온 트리가 담고 있던 매니페스트와 락파일을 기록합니다. 업로드된 SBOM은 그 생성기가 스스로에 대해 주장한 내용을, 업로드된 원본 바이트에서 읽어 기록합니다. 둘 다 스캔 상세 화면에서 확인할 수 있으므로, 예상보다 적은 컴포넌트를 보고하는 스캔에 답할 수 있습니다. 패키지를 못 찾은 것인지, 그 패키지를 선언한 파일 자체를 보지 못한 것인지입니다.
알아 둘 만한 수정
취약점 표가 100건에서 멈췄습니다
발견 건수가 100건을 넘는 프로젝트는 나머지가 조용히 숨겨졌고, 목록이 잘렸다는 표시조차 없었습니다. 라이선스와 의무사항 표에도 같은 결함이 있었습니다. 세 표 모두 이제 컴포넌트 표가 이미 쓰던 방식으로 페이지를 넘깁니다.
OAuth 인가 엔드포인트의 오픈 리다이렉트
공개된 OAuth 인가(authorize) 엔드포인트의 redirect_after 파라미터는
검증된 적이 없었습니다. 그래서 링크에 절대 URL을 담으면 그 값이
서명된 OAuth 상태값에 실려 콜백을 통과한 뒤 리다이렉트로 나올 수
있었습니다. 하필 refresh 토큰 쿠키를 심는 바로 그 응답 안에서,
가짜 페이지가 가장 그럴듯하게 보이는 순간에 벌어지는 일입니다. 이제
상태값을 만들 때와 읽을 때 두 번 검증하며, 프런트엔드 자체 origin
기준으로만 리다이렉트를 허용합니다.
브로커 가시성 제한 시간을 넘긴 스캔이 워커 슬롯 두 개를 차지할 수 있었습니다
Celery 브로커의 가시성 제한 시간(visibility timeout) 기본값이 이 배포의 스캔 최대 실행 시간보다 짧게 설정돼 있었습니다. 그래서 오래 걸리는 스캔은 첫 번째 워커가 아직 실행 중인데도 두 번째 워커에게 다시 배달될 수 있었습니다. 이제 이 시간은 스캔 최대 실행 시간에 여유분을 더해 계산합니다. 운영자는 이전에는 조용한 재시도로 가려졌던 스캔 실패를 이제 볼 수 있습니다.
그 밖의 자잘한 수정
- 약 32,000개를 넘는 프로젝트를 가진 조직은 목록 대신 500 오류를 받았습니다. 대시보드의 스캔 인벤토리 쿼리가 프로젝트마다 데이터베이스 파라미터를 하나씩 바인딩했는데, 드라이버가 32,767개를 넘는 바인딩을 거부하기 때문입니다.
- bcrypt 비밀번호 검증이 요청을 처리하는 이벤트 루프 위에서 실행됐습니다. 로그인 한 번마다 약 213ms 동안 그 워커의 다른 모든 요청이 막혔습니다. 이제 비밀번호 검증과 SBOM 내보내기, 대시보드 액션 큐 재집계가 모두 오프로드되거나 배치로 처리됩니다.
- 사용자별 WebSocket 연결 수 제한이 프로세스 하나에 국한된 상태로 관리됐습니다. 그래서 두 번째 탭이 첫 번째 탭의 연결을 밀어내는지 여부가 소켓이 어느 워커 프로세스나 파드에 붙었는지에 따라 달라졌습니다. 이제 배포 전체에서 정확히 동작하며, 시스템 전체가 포화 상태일 때를 대비한 전역 제한도 새로 생겼습니다.
/auth/reset-password에는 요청 빈도 제한이 없었고, 최대 256개의 후보 토큰을 동기 bcrypt 루프로 검증했습니다. 인증되지 않은 호출자가 워커 하나를 수십 초 동안 묶어 둘 수 있었습니다. 이제 로그인과 같은 빈도 제한을 적용하고, 검증 루프 전체를 오프로드된 작업 하나로 처리합니다.- 웹 폰트를 이제 서드파티 CDN 대신 이 오리진에서 직접 제공합니다. 기존 방식은 에어갭 설치에서 아예 실패했고, 페이지를 열 때마다 사용자의 브라우저 정보를 서드파티에 알리고 있었습니다.
- OFL-1.1(SIL Open Font License)이 미분류로 처리됐습니다. 실제로는 약한 카피레프트 라이선스인데도 그랬습니다. 삼중 라이선스 컴포넌트에서 금지 항목 하나만 걸렸을 때 라이선스 레지스트리에 다시 대조하지 않던 결함도 함께 고쳤습니다.
- Creative Commons NonCommercial 라이선스가 일반 CC-BY로 정규화되어 빌드 게이트를 통과했습니다. "Attribution-NonCommercial 4.0 International"이 평범한 허용 라이선스처럼 저장되고 게이트를 통과하고 화면에도 그렇게 표시됐습니다. 이제 NC는 금지로, ND는 조건부로 분류합니다.
- 공개 웹훅 수신부가 정상 거부 대신 500 오류를 낼 수 있었고, 전달의 서명을 확인하기도 전 단계에 요청 본문 크기 제한도 요청 빈도 제한도 없었습니다. 둘 다 고쳤습니다.
업그레이드
표준 업그레이드 절차를 따르세요. 이번
릴리스에는 업그레이드 스크립트가 이미 실행하는 alembic upgrade head
단계 외에 수동 개입이 필요한 마이그레이션이 없습니다.
미리 챙겨 둘 일이 둘 있습니다.
- Helm 설치: 업그레이드 전에
env.secret.apiKeyHmacSecret를 생성해 지정하세요. 지정하지 않으면 릴리스 렌더링이 실패합니다. - Helm 차트나 Compose를 큰 규모로 운영한다면 새로 생긴
worker.scan/worker.default큐 분리를 검토하세요. 두 워커 종류 모두 기본적으로 두 큐를 함께 구독하므로 업그레이드 자체는 변경 없이 깔끔하게 넘어갑니다. 각각을 자기 큐 하나로 좁히는 것은 선택 사항이며 차트 README에 설명돼 있습니다.