본문 바로가기
배포·CI,CD·클라우드

Harbor 레지스트리 + Cosign 서명 + Trivy 스캔 + 배포 차단 정책(OPA/Kyverno)으로 컨테이너 공급망 보안 완성

by yamoojin83 2025. 11. 3.

Harbor 레지스트리 + Cosign 서명 + Trivy 스캔 + 배포 차단 정책(OPA/Kyverno)으로 컨테이너 공급망 보안 완성

이미지 한 장이 프로덕션 전부를 흔듭니다. 오늘은 사설 레지스트리(Harbor)취약점 스캔(Trivy)이미지 서명(Cosign)을 붙이고, 배포 차단 정책(OPA Gatekeeper 또는 Kyverno)으로 “서명+무중대취약”일 때만 배포되게 만드는 실전 구성을 단계별로 정리합니다. Ubuntu 24.04 + Kubernetes 1.28+ 기준입니다.


1) 아키텍처 한 장 요약

[빌드 파이프라인(GH Actions/GitLab CI)]
      └─(푸시)─> [Harbor(Trivy 스캔)]
                   └─(서명 참조/저장)─> [Cosign(OCI 레퍼런스: <image>.sig)]
K8s 배포
  └─ Admission(OPA/Kyverno): "이 이미지, 서명 OK? 중대취약 0?"
       └─ 조건 만족 = 허용 / 불만족 = 차단

핵심은 이미지 무결성(서명)보안 기준(취약점 임계)클러스터 입장 시 검사한다는 점입니다. 레지스트리/CI가 아닌 Kubernetes 게이트에서 “막아내야” 안전합니다.


2) Harbor 설치(도커 컴포즈, Trivy 내장)

Harbor는 docker-compose 번들로 간단히 시작할 수 있습니다.

# 2-1) 바이너리 다운로드(버전은 환경에 맞게)
curl -LO https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-online-installer-v2.10.0.tgz
tar xzf harbor-online-installer-v2.10.0.tgz && cd harbor

# 2-2) 설정 템플릿 생성
cp harbor.yml.tmpl harbor.yml
# harbor.yml에서 hostname, https(선택), 관리자 비번, data volume 경로 등 수정
# Trivy 스캐너는 기본 내장(enabled=true)

# 2-3) 설치
./prepare
sudo ./install.sh

# 2-4) 접속
# http(s)://<hostname>/ 로 로그인(admin / 비번)

Harbor > Projects에서 private 프로젝트를 만들고, Configuration → Vulnerability에서 “scan on push” 옵션을 활성화하면 푸시 즉시 Trivy가 스캔합니다.


3) 개발자가 이미지 푸시하기(로그인/태깅/푸시)

# 로컬에서 Harbor 로그인
docker login harbor.example.com

# 이미지 태깅 후 푸시
docker build -t order-api:1.2.3 .
docker tag order-api:1.2.3 harbor.example.com/private/order-api:1.2.3
docker push harbor.example.com/private/order-api:1.2.3

푸시 후 Harbor UI에서 Vulnerabilities 탭을 열면 Trivy 결과가 보입니다. “Critical/High 0” 또는 기준 이하일 때만 프로덕션으로 승격되도록 정책을 잡겠습니다.


4) Cosign으로 이미지 서명(키 페어 / 키리스)

4-1) 설치 & 키 생성

curl -sSL https://raw.githubusercontent.com/sigstore/cosign/main/install.sh | sudo bash
cosign version

# 키 페어(패스워드 프롬프트 발생)
cosign generate-key-pair
# cosign.key / cosign.pub 생성

4-2) 서명 & 검증

# 서명
cosign sign --key cosign.key harbor.example.com/private/order-api:1.2.3

# 검증
cosign verify --key cosign.pub harbor.example.com/private/order-api:1.2.3
# 결과에 “Verified OK” 및 주체(subject) 메타가 출력

Cosign은 OCI 레이아웃을 이용해 레지스트리에 .sig 아티팩트를 같이 저장합니다. 따라서 별도 스토리지 없이 “이미지 옆”에 서명이 보관됩니다.


5) CI 파이프라인에 서명·메타데이터 첨부

GitHub Actions 예시(요지):

name: build-sign-push
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: harbor.example.com
          username: ${{ secrets.HARBOR_USER }}
          password: ${{ secrets.HARBOR_PASS }}
      - name: Build & Push
        run: |
          TAG=harbor.example.com/private/order-api:${GITHUB_SHA::7}
          docker build -t $TAG .
          docker push $TAG

      - name: Cosign Sign
        env:
          COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
        run: |
          printf '%s' "${{ secrets.COSIGN_KEY }}" > cosign.key
          printf '%s' "${{ secrets.COSIGN_PUB }}" > cosign.pub
          cosign sign --key cosign.key \
            --annotations git_sha=${GITHUB_SHA} \
            --annotations built_at=$(date -u +%FT%TZ) \
            $TAG

빌드 정보가 annotations로 서명에 포함되어 감사 추적성이 좋아집니다(누가/언제/무엇을 배포했는지).


6) Kubernetes에서 “서명 필수 + 중대취약 0” 정책 강제

두 가지 길이 있습니다. Kyverno(표현 간단) 또는 OPA Gatekeeper(Rego 기반). 여기서는 Kyverno를 예시로 듭니다.

6-1) Kyverno 설치

kubectl create -f https://raw.githubusercontent.com/kyverno/kyverno/release-1.12/config/install.yaml
kubectl -n kyverno get pods

6-2) Cosign 서명 요구 정책(이미지 당 1개 이상 서명)

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-cosign-signature
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-image-signature
      match:
        any:
          - resources:
              kinds: ["Pod","Deployment","StatefulSet","DaemonSet","Job","CronJob"]
      verifyImages:
        - image: "harbor.example.com/private/*"
          key: |
            -----BEGIN PUBLIC KEY-----
            (여기에 cosign.pub 내용)
            -----END PUBLIC KEY-----
          attestations: []

이제 harbor.example.com/private/... 이미지는 cosign.pub로 검증 가능한 서명이 없으면 Admission에서 거절됩니다.

6-3) Trivy 결과 기반 “중대취약 0” 정책

Harbor는 스캔 결과를 Harbor API로 제공합니다. 간단한 방식은 이미지 태그 규칙 또는 레이블(Annotation)로 “검증 완료 이미지만 배포”하는 것입니다. 더 엄격하게 하려면 policy-controller(Cosign의 정책 엔진)나 Kyverno의 externalData를 이용해 API 응답을 평가합니다. 여기서는 실무에서 손쉬운 “태그 규약” + “서명에 스캔 요약을 담기” 조합을 보여줍니다.

# 빌드 파이프라인에서 Trivy CLI로 사전 스캔 후,
# "nohigh" 같은 태그를 추가 푸시(고위험/치명 0일 때만)
trivy image --exit-code 1 --severity CRITICAL,HIGH $TAG || exit 1
docker tag $TAG ${TAG}-nohigh
docker push ${TAG}-nohigh

# 그리고 서명도 nohigh 태그에 대해 진행
cosign sign --key cosign.key ${TAG}-nohigh

Kyverno로 “-nohigh 태그만 허용”하면 손쉽게 “중대취약 0”을 강제할 수 있습니다.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: allow-only-nohigh-tag
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: require-nohigh-tag
      match:
        any:
          - resources:
              kinds: ["Pod","Deployment","StatefulSet","DaemonSet","Job","CronJob"]
      validate:
        message: "이미지는 반드시 -nohigh 태그여야 합니다(High/Critical 0)."
        pattern:
          spec:
            template:
              spec:
                containers:
                  - image: "harbor.example.com/private/*-nohigh"

엄격한 API 연동까지 하려면 Kyverno context + HTTPReference로 Harbor API를 조회하거나, policy-controller를 도입해 Cosign attestation에 Trivy SBOM/결과를 싣고 정책으로 평가하세요.


7) 배포 예시(허용/거부 확인)

# (허용) 서명 + -nohigh 태그
kubectl -n prod set image deploy/order-api app=harbor.example.com/private/order-api:1a2b3c4-nohigh

# (거부) 서명 없음 or -nohigh 아님
kubectl -n prod set image deploy/order-api app=harbor.example.com/private/order-api:latest
# Admission 웹훅에서 "require-cosign-signature / require-nohigh-tag" 위반으로 실패

8) 운영 체크리스트

  • 레지스트리: Harbor “scan on push” 활성화, 프로젝트 별 권한 최소화
  • 서명 키: cosign.key는 External Secrets 등으로 K8s에 안전 보관(또는 키리스 OIDC 활용)
  • CI: “빌드 → 스캔 → 태깅(-nohigh) → 서명 → 푸시” 순서 고정
  • 정책: Kyverno/Gatekeeper 정책의 Enforce 모드 적용 전 Audit로 시뮬레이션
  • 가시성: Harbor 웹훅/알림, Kyverno 정책 위반 이벤트를 Slack/Alertmanager로 통지

9) 자주 겪는 문제와 해결

  • 서명 검증 실패: 이미지 도메인/태그 불일치, cosign.pub 오타, 프록시로 인한 레지스트리 접근 실패 점검
  • Trivy 스캔 누락: 프로젝트 설정에서 “scan on push” 켜기, 오프라인 DB 동기화 실패 시 네트워크/프록시 확인
  • 정책 오탐: Init/Sidecar 이미지까지 규칙이 적용되는지 구조 확인, match.exclude로 예외 처리
  • 퍼포먼스: Admission 병목 시 Kyverno 리소스 상향, 정책 단순화(정규식 최소화)

10) 마무리

공급망 보안은 레지스트리-빌드-클러스터를 한 줄로 묶는 일입니다. Harbor(스캔) + Cosign(서명) + Kyverno/OPA(정책) 삼박자를 맞추면 “검증된 이미지”만 프로덕션에 들어옵니다. 오늘은 한 서비스부터 시작해 보세요. 푸시 즉시 스캔, -nohigh 태그, 서명 필수 정책만으로도 체감 효과가 큽니다.