본문으로 건너뛰기

GitLab CI

포털은 GitHub Action을 미러링하는 include 가능한 GitLab CI 템플릿을 제공합니다 — 스캔을 트리거하고 최종 상태까지 폴링한 다음 빌드 게이트를 평가합니다. 템플릿은 단일 잡이며, 어떤 필드든 확장하거나 오버라이드할 수 있습니다.

MR 코멘트에는 설정 둘이 필요합니다

템플릿이 SCA 보고서를 머지 요청 노트로 답니다. 그러려면 포털에 두 가지를 설정해야 합니다.

  • GITLAB_TOKEN 입니다. 스캔 대상 프로젝트에 노트를 쓸 수 있는 토큰이어야 합니다. 배포 단위라서 포털은 어디서나 하나의 신원으로 코멘트합니다.
  • 자체 호스팅이라면 GITLAB_API_BASE 입니다. 기본값이 https://gitlab.com/api/v4 이고, 자체 호스팅은 https://gitlab.example.com/api/v4 형태입니다.

그 인스턴스가 사내 인증기관의 인증서를 쓴다면 이 호출도 다른 모든 외부 호출과 같은 방식으로 검증합니다. 사설 인증기관을 봅니다.

대상 독자

GitLab CI/CD를 사용하는 GitLab 프로젝트를 운영하는 엔지니어. 포털용 API Key가 필요합니다 — API keys 참고.

빠른 시작​

# .gitlab-ci.yml
include:
- remote: 'https://raw.githubusercontent.com/trustedoss/trusca/v0.23.0/templates/gitlab-ci.yml'

variables:
TRUSTEDOSS_API_URL: 'https://trustedoss.example.com'
TRUSTEDOSS_PROJECT_ID: '01H7XYZ…'
# TRUSTEDOSS_API_KEY는 masked CI/CD 변수입니다 — 여기에 절대 적지 마세요.

베이스 템플릿은 hidden입니다 — 직접 만든 잡에서 extend해 materialize해야 하며, 베이스를 extend하지 않는 파이프라인은 SCA를 자동 트리거하지 않습니다. 다음과 같은 잡을 추가하세요.

sca:
extends: .trustedoss-sca

셋업​

1. API Key 생성​

포털에서 /integrations → API keys → Create API key. 이 파이프라인이 스캔할 프로젝트에 project scope로 묶거나, 팀이 소유한 모든 프로젝트를 덮으려면 team을 고르세요. "이 키가 할 수 있는 일"은 읽기 및 쓰기로 설정하세요. 스캔을 실행하는 동작이라 기본값인 읽기 전용 키는 거부됩니다. 그 외에는 발급자의 역할을 그대로 물려받습니다. API keys 참고.

2. masked CI/CD 변수로 Key 저장​

GitLab 프로젝트에서 Settings → CI/CD → Variables → Add variable.

  • Key — TRUSTEDOSS_API_KEY
  • Value — 전체 Key(tos_<prefix>_<secret>)
  • Type — Variable
  • Flags — Masked(yes), Protected(main 한정 권장)

masked 플래그는 잡 로그에 Key가 그대로 노출되는 것을 막습니다.

3. URL과 프로젝트 ID 설정​

TRUSTEDOSS_API_URL과 TRUSTEDOSS_PROJECT_ID는 다음 중 하나에 둘 수 있습니다.

  • .gitlab-ci.yml의 variables:(읽기 권한자에게 보임).
  • 또는 CI/CD 변수(여러 환경을 운영한다면 더 나은 선택).

어느 쪽이든 TRUSTEDOSS_API_KEY만 masked여야 합니다.

변수​

변수필수기본값설명
TRUSTEDOSS_API_URLyes—포털 base URL.
TRUSTEDOSS_API_KEYyes—API Key(masked CI/CD 변수).
TRUSTEDOSS_PROJECT_IDyes—프로젝트 UUID.
TRUSTEDOSS_SCAN_KINDnosourcesource 또는 container.
TRUSTEDOSS_FAIL_ON_GATEnotruetrue이면 게이트 실패 시 잡이 1로 종료.
TRUSTEDOSS_POLL_TIMEOUTno1800최종 상태까지 기다리는 최대 초.
TRUSTEDOSS_POLL_INTERVALno30폴링 간격(초).
TRUSTEDOSS_POST_MR_COMMENTnotrue향후 GitLab Notes API 통합용 예약. 현재 릴리스에서는 요청이 스테이징되지만 백엔드가 전송할 수 없습니다 — 위 경고 참고.

게이트 결과의 gate 값은 pass 또는 fail입니다. 그 밖의 값이 오면 판정을 기록하기 전에 FATAL: 줄을 남기고 종료 코드 1로 작업을 멈춥니다. 게이트 엔드포인트가 예상하지 못한 HTTP 상태를 돌려줄 때와 같은 처리이고, TRUSTEDOSS_FAIL_ON_GATE가 false여도 마찬가지입니다. 템플릿이 읽지 못한 판정을 통과로 다루지 않기 위해서입니다.

레시피​

Advisory 모드​

include:
- remote: 'https://raw.githubusercontent.com/trustedoss/trusca/v0.23.0/templates/gitlab-ci.yml'

variables:
TRUSTEDOSS_API_URL: 'https://trustedoss.example.com'
TRUSTEDOSS_PROJECT_ID: '01H7XYZ…'
TRUSTEDOSS_FAIL_ON_GATE: 'false'

잡은 green을 유지하고 MR 노트는 그대로 게시됩니다.

보호된 브랜치에서만 실행​

include한 잡의 rules를 오버라이드:

include:
- remote: 'https://raw.githubusercontent.com/trustedoss/trusca/v0.23.0/templates/gitlab-ci.yml'

.trustedoss-sca:
rules:
- if: '$CI_COMMIT_REF_PROTECTED == "true"'
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

컨테이너 스캔을 별도 잡으로​

include:
- remote: 'https://raw.githubusercontent.com/trustedoss/trusca/v0.23.0/templates/gitlab-ci.yml'

trustedoss:scan-container:
extends: .trustedoss-sca
variables:
TRUSTEDOSS_SCAN_KIND: 'container'

태그 핀​

재현 가능한 파이프라인을 위해 include URL을 main이 아닌 릴리스 태그(v0.23.0)에 고정하세요.

템플릿 해부 (고급)​

러너가 include를 위해 GitHub에 도달하지 못하는 등의 이유로 잡을 복사·인라인해야 한다면 표준 형태는 다음과 같습니다.

.trustedoss-sca:
image: alpine:3.20
stage: test
before_script:
- apk add --no-cache curl jq bash ca-certificates
script:
- bash -c '
set -euo pipefail;
SCAN_ID=$(curl -fsS -X POST
-H "Authorization: Bearer ${TRUSTEDOSS_API_KEY}"
-H "Content-Type: application/json"
-d "{\"kind\": \"${TRUSTEDOSS_SCAN_KIND:-source}\"}"
"${TRUSTEDOSS_API_URL}/v1/projects/${TRUSTEDOSS_PROJECT_ID}/scans"
| jq -r .id);
echo "scan_id=$SCAN_ID";
# 최종 상태까지 폴링 …
# 게이트 평가, MR 노트 게시, 0/1 종료
'
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

전체 표준 버전은 templates/gitlab-ci.yml에 있습니다. fork 전에 읽어 보세요 — 다시 구현하고 싶지 않은 엣지 케이스(폴링 중 네트워크 단절, masked-token 회전)를 다룹니다.

ref가 보존 키가 되는 방식​

템플릿은 파이프라인의 ref를 스캔 metadata로 전달합니다. CI_COMMIT_REF_NAME이며, 브랜치 파이프라인에서는 브랜치 이름이고 merge_request_event에서는 소스 브랜치 이름입니다. 포털은 그 ref를 정규화하고(refs/heads/main → main, refs/merge-requests/7/head → mr-7, 이름만 온 값은 그대로 통과) (project, 정규화된 ref)를 보존 키로 사용합니다. 키별로 가장 최근 성공 스캔이 살아 있고 이전 것을 대체합니다.

워커가 체크아웃하는 것도 이 ref입니다. 그래서 스캔은 저장소의 기본 HEAD가 아니라 그 브랜치의 코드를 봅니다. 워커가 시작할 시점에 그 ref가 사라졌다면(머지 후 브랜치가 삭제된 경우 등) 기본 브랜치로 넘어가고 원하던 ref를 metadata.ref_fallback에 기록합니다.

템플릿은 MR IID도 metadata.merge_request_iid로 함께 보내지만 이 값은 참고용으로 기록될 뿐 보존 키가 되지 않습니다. 그래서 MR의 스캔은 mr-<iid>가 아니라 소스 브랜치 이름으로 묶입니다. MR 단위로 묶고 싶다면 트리거와 게이트 조회 양쪽에서 ref로 refs/merge-requests/$CI_MERGE_REQUEST_IID/head를 보내세요. 이 형태로 보내면 워커가 소스 브랜치 끝이 아니라 머지 결과를 체크아웃합니다.

같은 ref가 게이트 조회에도 쓰이고, 이것이 MR의 판정을 그 MR의 것으로 지켜 줍니다. ref가 없으면 포털은 프로젝트 메인 라인에서 가장 최근에 성공한 스캔으로 답하므로, 자기 스캔이 끝나기를 기다린 MR 파이프라인이 정작 main 기준으로 판정됩니다.

설정할 것은 없습니다 — 브랜치·MR에서 템플릿을 실행하면 브랜치별·MR별 그룹화가 올바르게 동작합니다. 스캔을 영구 보존하려면(태그 릴리스용) metadata.release 라벨과 함께 트리거하십시오. 전체 모델과 release 면제는 스캔 보존 페이지에서 다룹니다.

스캔이 컴포넌트를 찾지 못했을 때​

게이트 결과에는 component_outcome이 들어 있고, 템플릿은 판정 줄에 이 값을 components=<값>으로 출력합니다. 다음 두 값은 스캔 결과에 컴포넌트가 하나도 없다는 뜻입니다. 이때는 모든 수치가 그 때문에 0이고, pass는 판단할 대상이 없었다는 뜻일 뿐입니다.

  • empty_with_manifests: 소스가 의존성을 선언했는데(의존성 매니페스트가 있는데) 스캔 결과에 컴포넌트가 없습니다. 의존성 해석 같은 스캔 단계에서 문제가 생긴 것입니다. 스캔 로그를 확인하세요.
  • empty_no_manifests: 소스에 TRUSCA가 읽는 의존성 매니페스트가 없습니다. TRUSCA가 그 빌드 시스템을 지원하는지 확인하세요.

두 값 모두 템플릿은 판정 다음 줄에 WARNING: 줄을 출력하고 종료 코드는 그대로 두므로 작업은 통과합니다. 이 값은 뒤따르는 작업이 쓸 수 있도록 trustedoss.env에 TRUSTEDOSS_COMPONENT_OUTCOME으로도 기록됩니다.

empty_with_manifests일 때 빌드를 실패시키려면 관리자가 포털에 GATE_INCOMPLETE_SCAN_ENABLED=true를 설정합니다(선택 게이트 축 참고). 그러면 게이트가 fail을 반환하고 incomplete_scan_basis는 empty_with_manifests가 되며, 템플릿은 다른 게이트 실패와 똑같이 1로 종료합니다. 각 경우의 뜻은 스캔이 아무것도 찾지 못했을 때를 보세요.

브랜치 / 머지 보호​

모든 MR에 SCA를 강제하려면:

  1. Settings → Repository → Protected branches — main을 보호.
  2. Settings → Merge requests → Merge checks — "Pipelines must succeed"를 켜기.

SCA 잡(.trustedoss-sca를 extend한 잡)이 실패하는 MR은 머지할 수 없습니다.

트러블슈팅​

include된 잡에 Authorization 헤더가 빠짐​

GitLab은 빈 변수를 제거합니다. 관련 환경 / 브랜치에 TRUSTEDOSS_API_KEY가 정의되어 있는지 확인하세요. 변수의 "Protected" 플래그는 보호된 ref에만 주입됨을 의미하므로 — 일반 MR에도 필요하면 조정.

MR 노트가 게시되지 않음​

현재 릴리스에서는 정상 동작입니다 — GitLab Notes API 클라이언트가 아직 출하되지 않았습니다(페이지 상단 경고 참고). verdict를 표면화하려면 GitLab 측에서 빌드 게이트 종료 코드(TRUSTEDOSS_FAIL_ON_GATE=true)를 사용하세요.

폴링 단계에서 잡이 시간 초과​

TRUSTEDOSS_POLL_TIMEOUT은 기본 30분 — 큰 레포에서는 초과될 수 있습니다. 3600(1시간)으로 올리고 재실행.

POST /scans에서 "Forbidden"​

Key에는 동작별 권한이 없으므로 scan:trigger 누락이 원인일 수 없습니다. 둘 중 하나입니다. 발급한 사용자가 그 프로젝트의 팀 구성원이 아니거나, Key가 TRUSTEDOSS_PROJECT_ID와 다른 프로젝트에 묶여 있습니다. /integrations → API keys에서 Key의 scope를 확인하세요.

참고로 GET /gate-result는 다른 프로젝트를 겨냥한 요청에 403이 아니라 404로 답합니다. 프로젝트의 존재 자체를 감추기 때문이고, 두 엔드포인트의 차이는 의도된 것입니다.

함께 보기​