파이프라인 보안

파이프라인 보안 (Pipeline security)

이 문서는 Free, Premium, Ultimate 티어에서 사용할 수 있고, GitLab.com, GitLab Self-Managed, GitLab Dedicated 모두에서 활용할 수 있어요.

출처: 문서

본문

시크릿 관리 (Secrets Management)

시크릿 관리는 개발자가 민감한 데이터를 엄격한 접근 제어가 적용된 안전한 환경에 안전하게 저장하는 데 사용하는 시스템이에요. **시크릿(secret)**은 기밀로 유지해야 하는 민감한 자격 증명이에요. 시크릿의 예로는:

  • 비밀번호
  • SSH 키
  • 접근 토큰
  • 노출되면 조직에 해가 될 수 있는 기타 모든 유형의 자격 증명

시크릿이 노출되면 어떤 일이 생기는지, 왜 안전한 보관이 중요한지가 이 주제의 핵심이에요.

시크릿 저장

시크릿 관리 제공자

가장 민감하고 가장 엄격한 정책이 적용되는 시크릿은 시크릿 매니저에 저장해야 해요. 시크릿 매니저 솔루션을 사용하면 시크릿이 GitLab 인스턴스 외부에 저장돼요. 여러 제공자가 이 서비스를 제공하며, HashiCorp Vault, Azure Key Vault, Google Cloud Secret Manager 등이 있어요.

특정 외부 시크릿 관리 제공자에 대한 GitLab 네이티브 통합을 사용해 CI/CD 파이프라인에서 필요할 때 그 시크릿을 가져올 수 있어요.

CI/CD 변수

CI/CD 변수는 CI/CD 파이프라인에서 데이터를 저장하고 재사용하는 편리한 방법이지만, 시크릿 관리 제공자보다는 덜 안전해요. 변수 값은:

  • GitLab 프로젝트, 그룹 또는 인스턴스 설정에 저장돼요. 설정에 접근할 수 있는 사용자는 숨겨지지 않은 변수 값에 접근할 수 있어요.
  • 재정의될 수 있어서 어떤 값이 사용되었는지 파악하기 어려워요.
  • 우발적인 파이프라인 잘못된 구성으로 노출될 수 있어요.

변수에 저장하기 적합한 정보는 노출되어도 악용 위험이 없는(비민감한) 데이터여야 해요.

민감한 데이터는 시크릿 관리 솔루션에 저장해야 해요. 시크릿 관리 솔루션이 없어 민감한 데이터를 CI/CD 변수에 저장해야 한다면 항상:

CI/CD 파이프라인에 매개변수 전달하기

CI/CD 파이프라인에 매개변수를 전달할 때는 파이프라인 변수 대신 CI/CD inputs을 사용해요.

Inputs가 제공하는 것:

  • 파이프라인 생성 시 타입 안전 검증.
  • 명시적인 매개변수 계약.
  • 보안을 강화하는 범위 제한 가용성.

파이프라인 변수는 타입 검증이 없고, 사전 정의 변수를 재정의해 예기치 않은 동작을 일으킬 수 있으며, 민감한 시크릿과 같은 권한 범위를 공유하므로, inputs를 구현할 때 파이프라인 변수 비활성화를 고려해 보안 취약점을 방지해요.

파이프라인 무결성 (Pipeline Integrity)

파이프라인 무결성을 보장하는 핵심 보안 원칙은 다음과 같아요.

  • 공급망 보안(Supply Chain Security): 자산은 신뢰할 수 있는 소스에서 얻고 무결성을 검증해야 해요.
  • 재현성(Reproducibility): 파이프라인은 같은 입력을 사용하면 일관된 결과를 내야 해요.
  • 감사 가능성(Auditability): 모든 파이프라인 의존성은 추적 가능하고 출처(provenance)를 검증할 수 있어야 해요.
  • 버전 제어(Version Control): 파이프라인 의존성의 변경은 추적되고 제어되어야 해요.

Docker 이미지

클라이언트 측 무결성 검증을 보장하려면 Docker 이미지에 항상 SHA 다이제스트를 사용해요. 예를 들어:

  • Node:

    • 사용: image: node@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
    • 대신에: image: node:latest
  • Python:

    • 사용: image: python@sha256:9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef
    • 대신에: image: python:3.9

특정 태그가 있는 이미지의 SHA 다이제스트는 다음으로 찾을 수 있어요.

docker pull node:18.17.1
docker images --digests node:18.17.1

이미지 무결성을 보호하는 컨테이너 레지스트리에서 가져오는 것을 선호해요.

  • 보호된 컨테이너 저장소를 사용해 누가 컨테이너 저장소의 컨테이너 이미지를 변경할 수 있는지 제한해요.
  • 보호된 태그를 사용해 누가 컨테이너 태그를 푸시하고 삭제할 수 있는지 제어해요.

가능하면 컨테이너 참조에 변수를 사용하지 마세요. 변수는 악성 이미지를 가리키도록 수정될 수 있기 때문이에요. 예를 들어:

  • 선호:

    • image: my-registry.example.com/node:18.17.1
  • 대신에:

    • image: ${CUSTOM_REGISTRY}/node:latest
    • image: node:${VERSION}

패키지 의존성

잡에서 패키지 의존성을 고정(lock down)해야 해요. 잠금 파일에 정의된 정확한 버전을 사용해요.

  • npm:

    • 사용: npm ci
    • 대신에: npm install
  • yarn:

    • 사용: yarn install --frozen-lockfile
    • 대신에: yarn install
  • Python:

    • 사용:
      • pip install -r requirements.txt --require-hashes
      • pip install -r requirements.lock
    • 대신에: pip install -r requirements.txt
  • Go:

    • go.sum의 정확한 버전 사용:
      • go mod verify
      • go mod download
    • 대신에: go get ./...

예를 들어, CI/CD 잡에서:

javascript-job:
  script:
    - npm ci

셸 명령과 스크립트

잡에서 도구를 설치할 때는 항상 정확한 버전을 지정하고 검증해요. 예를 들어, Terraform 잡에서:

terraform_job:
  script:
    # Download specific version
    - |
      wget https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_linux_amd64.zip
      # IMPORTANT: Always verify checksums
      echo "c0ed7bc32ee52ae255af9982c8c88a7a4c610485cf1d55feeb037eab75fa082c terraform_1.5.7_linux_amd64.zip" | sha256sum -c
      unzip terraform_1.5.7_linux_amd64.zip
      mv terraform /usr/local/bin/
    # Use the installed version
    - terraform init
    - terraform plan

버전 관리 도구

가능하면 버전 관리자를 사용해요.

node_build:
  script:
    # Use nvm to install and use a specific Node version
    - |
      nvm install 16.15.1
      nvm use 16.15.1
    - node --version  # Verify version
    - npm ci
    - npm run build

포함된 구성

파이프라인에 구성이나 CI/CD 컴포넌트를 추가하기 위해 [include 키워드](/ci/yaml/#include)를 사용할 때는 가능하면 특정 ref를 사용해요. 예를 들어:

include:
  - project: 'my-group/my-project'
    ref: 8b0c8b318857c8211c15c6643b0894345a238c4e  # Pin to a specific commit
    file: '/templates/build.yml'
  - project: 'my-group/security'
    ref: v2.1.0                                    # Pin to a protected tag
    file: '/templates/scan.yml'
  - component: 'my-group/security-scans'           # Pin to a specific version
    version: '1.2.3'

버전 없는 include는 피해요.

include:
  - project: 'my-group/my-project'                   # Unsafe
    file: '/templates/build.yml'
  - component: 'my-group/security-scans'             # Unsafe
  - remote: 'https://example.com/security-scan.yml'  # Unsafe

원격 파일을 include하는 대신, 파일을 다운로드해 저장소에 저장해요. 그런 다음 로컬 복사본을 include하면 돼요.

include:
  - local: '/ci/security-scan.yml'  # Verified and stored in the repository

관련 주제

  1. CIS Docker Benchmarks
  2. Google Cloud: 안전한 배포 파이프라인 설계

더 알아보기

파이프라인 보안에서 가장 중요한 건 시크릿을 어디에 저장하느냐예요. GitLab 네이티브 외부 시크릿 제공자 통합은 시크릿 문서에서, OIDC 기반 인증은 ID 토큰 인증 문서에서 확인할 수 있어요.