HashiCorp Vault를 사용하여 인증 및 비밀 정보 읽기

Tier: Premium, Ultimate
제공: GitLab.com, Self-managed, GitLab Dedicated
caution

CI_JOB_JWT로의 인증은 GitLab 15.9에서 사용 중단되었으며 해당 토큰은 GitLab 17.0에서 삭제 예정입니다. 대신 HashiCorp Vault로 인증하기 위해 ID 토큰을 사용하십시오. 이 페이지에서 설명된 대로.

이 튜토리얼에서는 GitLab CI/CD에서 HashiCorp의 Vault로의 인증, 구성, 및 비밀 정보 읽기를 보여줍니다.

전제 조건

이 튜토리얼은 GitLab CI/CD와 Vault에 익숙하다고 가정합니다.

따라오기 위해서는 다음이 있어야 합니다:

  • GitLab에 계정.
  • 인증 구성 및 역할 및 정책 생성을 위한 실행 중인 Vault 서버에 액세스 (최소 v1.2.0). HashiCorp Vaults의 경우 Open Source 또는 Enterprise 버전이 될 수 있습니다.
note

아래의 vault.example.com URL을 귀하의 Vault 서버 URL로, gitlab.example.com을 귀하의 GitLab 인스턴스 URL로 교체해야 합니다.

작동 방식

ID 토큰은 제3자 서비스와의 OIDC 인증을 위해 사용되는 JSON Web Token (JWT)입니다. 작업에 ID 토큰이 하나 이상 정의된 경우, secrets 키워드는 자동으로 해당 토큰을 사용하여 Vault로 인증합니다.

다음 필드가 JWT에 포함됩니다:

필드 시점 설명
jti 항상 이 토큰에 대한 고유 식별자
iss 항상 발행자, 귀하의 GitLab 인스턴스 도메인
iat 항상 발급 시간
nbf 항상 유효하지 않은 시간
exp 항상 만료 시간
sub 항상 주제 (작업 ID)
namespace_id 항상 ID를 사용하여 그룹 또는 사용자 수준의 네임스페이스에 범위를 지정할 수 있음
namespace_path 항상 경로를 사용하여 그룹 또는 사용자 수준의 네임스페이스에 범위를 지정할 수 있음
project_id 항상 ID를 사용하여 프로젝트에 범위를 지정할 수 있음
project_path 항상 경로를 사용하여 프로젝트에 범위를 지정할 수 있음
user_id 항상 작업을 실행하는 사용자의 ID
user_login 항상 작업을 실행하는 사용자의 사용자명
user_email 항상 작업을 실행하는 사용자의 이메일
pipeline_id 항상 이 파이프라인의 ID
pipeline_source 항상 파이프라인 소스
job_id 항상 이 작업의 ID
ref 항상 이 작업의 Git 참조
ref_type 항상 Git 참조 유형, branch 또는 tag 중 하나
ref_path 항상 작업의 완전히 지정된 참조. 예: refs/heads/main. GitLab 16.0에서 도입
ref_protected 항상 이 Git 참조가 보호되어 있는 경우 true, 그렇지 않으면 false
environment 작업이 환경을 지정 이 작업이 지정하는 환경 (GitLab 13.9에 도입)
environment_protected 작업이 환경을 지정 지정된 환경이 보호되어 있는 경우 true, 그렇지 않으면 false (GitLab 13.9에 도입)
deployment_tier 작업이 환경을 지정 이 작업이 지정하는 환경의 배포 티어 (GitLab 15.2에 도입)
environment_action 작업이 환경을 지정 작업에서 지정된 환경 동작(environment:action) (GitLab 16.5에 도입)

예시 JWT 페이로드:

{
  "jti": "c82eeb0c-5c6f-4a33-abf5-4c474b92b558",
  "iss": "gitlab.example.com",
  "iat": 1585710286,
  "nbf": 1585798372,
  "exp": 1585713886,
  "sub": "job_1212",
  "namespace_id": "1",
  "namespace_path": "mygroup",
  "project_id": "22",
  "project_path": "mygroup/myproject",
  "user_id": "42",
  "user_login": "myuser",
  "user_email": "myuser@example.com",
  "pipeline_id": "1212",
  "pipeline_source": "web",
  "job_id": "1212",
  "ref": "auto-deploy-2020-04-01",
  "ref_type": "branch",
  "ref_path": "refs/heads/auto-deploy-2020-04-01",
  "ref_protected": "true",
  "environment": "production",
  "environment_protected": "true",
  "environment_action": "start"
}

이 JWT는 RS256으로 인코딩되고 별도의 개인 키로 서명됩니다. 해당 토큰의 만료 시간은 작업의 타임아웃으로 설정되거나 그렇지 않은 경우 5분으로 설정됩니다. 이 토큰 서명에 사용되는 키는 언제든지 변경될 수 있습니다. 이 경우 작업을 재시도하면 최신 서명 키를 사용하여 새 JWT를 생성합니다.

귀하의 Vault 서버가 JWT 인증 방법을 허용하도록 구성된 경우, 이 JWT를 Vault 서버와의 인증에 사용할 수 있습니다. 귀하의 GitLab 인스턴스의 기본 URL을 Vault 서버의 oidc_discovery_url로 제공할 수 있습니다. 서버는 그런 다음 귀하의 인스턴스로부터 토큰을 유효성 검사를 위한 키를 검색할 수 있습니다.

Vault에서 역할을 구성하는 경우 bound claims를 사용하여 JWT 클레임과 일치시켜 각 CI/CD 작업이 액세스할 수 있는 비밀 정보를 제한할 수 있습니다.

Vault와 통신하기 위해서는 CLI 클라이언트를 사용하거나 (curl 또는 다른 클라이언트를 사용하여) API 요청을 수행할 수 있습니다.

예시

caution
JWT는 리소스에 액세스 할 수 있는 자격 증명으로, 그대로 붙여넣으면 조심하세요!

예를 들어, 스테이징과 프로덕션 데이터베이스의 비밀번호가 http://vault.example.com:8200에서 실행 중인 보르트 서버에 저장되어 있다고 가정해봅시다. 스테이징 비밀번호는 pa$$w0rd이고, 프로덕션 비밀번호는 real-pa$$w0rd입니다.

$ vault kv get -field=password secret/myproject/staging/db
pa$$w0rd

$ vault kv get -field=password secret/myproject/production/db
real-pa$$w0rd

보르트 서버를 구성하려면 먼저 JWT 인증 방법을 활성화하세요.

$ vault auth enable jwt
Success! Enabled jwt auth method at: jwt/

그런 다음 이러한 비밀을 읽을 수 있는 정책을 생성하세요 (각 비밀에 대해 하나씩):

$ vault policy write myproject-staging - <<EOF
# 정책 이름: myproject-staging
#
# 'secret/myproject/staging/*' 경로에 대한 읽기 전용 권한
path "secret/myproject/staging/*" {
  capabilities = [ "read" ]
}
EOF
Success! Uploaded policy: myproject-staging

$ vault policy write myproject-production - <<EOF
# 정책 이름: myproject-production
#
# 'secret/myproject/production/*' 경로에 대한 읽기 전용 권한
path "secret/myproject/production/*" {
  capabilities = [ "read" ]
}
EOF
Success! Uploaded policy: myproject-production

또한 JWT를 이러한 정책과 연결하는 역할도 필요합니다.

스테이징용으로 myproject-staging이라는 이름의 역할하나:

$ vault write auth/jwt/role/myproject-staging - <<EOF
{
  "role_type": "jwt",
  "policies": ["myproject-staging"],
  "token_explicit_max_ttl": 60,
  "user_claim": "user_email",
  "bound_claims": {
    "project_id": "22",
    "ref": "master",
    "ref_type": "branch"
  }
}
EOF

그리고 프로덕션용으로 myproject-production이라는 이름의 역할하나:

$ vault write auth/jwt/role/myproject-production - <<EOF
{
  "role_type": "jwt",
  "policies": ["myproject-production"],
  "token_explicit_max_ttl": 60,
  "user_claim": "user_email",
  "bound_claims_type": "glob",
  "bound_claims": {
    "project_id": "22",
    "ref_protected": "true",
    "ref_type": "branch",
    "ref": "auto-deploy-*"
  }
}
EOF

이 예시에서는 bound claims를 사용하여 지정된 클레임 값과 일치하는 JWT만 인증하는 방법을 지정합니다.

이를 보호된 브랜치와 함께 사용하면 누가 인증하고 비밀을 읽을 수 있는지 제한할 수 있습니다.

JWT에 포함된 클레임 중 어떤 것이든 이곳에서 나열된 클레임 값 디렉터리과 일치하게 할 수 있습니다. 예를 들어:

"bound_claims": {
  "user_login": ["alice", "bob", "mallory"]
}

"bound_claims": {
  "ref": ["main", "develop", "test"]
}

"bound_claims": {
  "namespace_id": ["10", "20", "30"]
}

"bound_claims": {
  "project_id": ["12", "22", "37"]
}
  • namespace_id만 사용되면 해당 네임스페이스의 모든 프로젝트가 허용됩니다.
  • namespace_idproject_id 둘 다 사용되면, Vault는 먼저 프로젝트의 네임스페이스가 namespace_id에 있는지 확인합니다. 그렇지 않으면 프로젝트가 project_id에 있는지 확인합니다.

token_explicit_max_ttl은 Vault에서 성공적으로 인증한 토큰의 하드 수명을 60초로 설정함을 나타냅니다.

user_claim은 Vault에서 성공적인 로그인 후 생성된 Identity alias의 이름을 지정합니다.

bound_claims_typebound_claims 값의 해석을 구성합니다. glob로 설정할 경우, 값은 glob으로 해석되며, *는 모든 문자와 일치합니다.

위의 테이블에서 나열된 클레임 필드들은 Vault의 정책 경로 템플릿화를 위해 JWT 인증의 엑세서 이름을 사용하여 접근할 수도 있습니다(여기 참조).

메타데이터 필드인 project_path를 사용하는 정책 템플릿 예시:

path "secret/data/{{identity.entity.aliases.ACCESSOR_NAME.metadata.project_path}}/staging/*" {
  capabilities = [ "read" ]
}

위 템플릿에 따른 역할 예시로, claim_mappings 구성을 통해 클레임 필드 project_path를 메타데이터 필드로 매핑:

{
  "role_type": "jwt",
  ...
  "claim_mappings": {
    "project_path": "project_path"
  }
}

모든 옵션의 전체 디렉터리은 Vault의 역할 생성 문서를 참조하세요.

caution
제공된 클레임 중 하나를 사용하여 역할을 프로젝트 또는 네임스페이스에 제한하세요. 그렇지 않으면 이 인스턴스에서 생성된 JWT는 이 역할을 사용하여 인증할 수 있습니다.

이제 JWT 인증 방법을 구성하세요:

$ vault write auth/jwt/config \
    oidc_discovery_url="https://gitlab.example.com" \
    bound_issuer="https://gitlab.example.com"

bound_issueriss 클레임이 gitlab.example.com로 설정된 JWT만이 이 방법을 사용하여 인증하고, 토큰을 유효성 검사하는 데 oidc_discovery_url(https://gitlab.example.com)을 사용해야 함을 지정합니다.

사용 가능한 모든 구성 옵션의 전체 디렉터리은 Vault의 API 문서를 참조하세요.

GitLab에서 프로젝트용 다음과 같은 CI/CD 변수를 생성하여 보르트 서버에 대한 자세한 정보를 제공하세요:

  • VAULT_SERVER_URL - 보르트 서버의 URL, 예: https://vault.example.com:8200.
  • VAULT_AUTH_ROLE - 선택 사항. 인증 시 사용할 역할. 역할이 지정되지 않으면 Vault는 인증 방법이 구성될 때 지정한 기본 역할을 사용합니다.
  • VAULT_AUTH_PATH - 선택 사항. 인증 방법이 마운트된 경로. 기본값은 jwt입니다.
  • VAULT_NAMESPACE - 선택 사항. 비밀번호 읽기 및 인증에 사용할 Vault 엔터프라이즈 네임스페이스. 네임스페이스가 지정되지 않으면 Vault는 루트(/) 네임스페이스를 사용합니다. 이 설정은 Vault 오픈 소스에서 무시됩니다.

기본 브랜치에서 실행될 경우 다음 작업은 secret/myproject/staging/ 아래의 비밀을 읽지만, secret/myproject/production/ 아래의 비밀은 읽을 수 없습니다:

job_with_secrets:
  id_tokens:
    VAULT_ID_TOKEN:
      aud: https://example.vault.com
  secrets:
    STAGING_DB_PASSWORD:
      vault: secret/myproject/staging/db/password@secrets # $VAULT_ID_TOKEN을 사용하여 인증
  script:
    - access-staging-db.sh --token $STAGING_DB_PASSWORD

이 예시에서:

  • @secrets - 활성화된 Secrets Engine이 있는 보르트 이름.
  • secret/myproject/staging/db - Vault의 중요 정보가 위치한 경로.
  • password - 참조되는 비밀번호 필드입니다.

Vault 시크릿의 토큰 접근 제한

Vault 보호 및 GitLab 기능을 사용하여 Vault 시크릿의 ID 토큰 접근을 제어할 수 있습니다. 예를 들어, 다음과 같이 토큰을 제한할 수 있습니다:

  • 특정 그룹을 위해 Vault bound claimsgroup_claim을 사용합니다.
  • 특정 사용자의 user_loginuser_email을 기반으로 Vault bound claims의 값을 하드 코딩합니다.
  • 인증 후 토큰이 만료되도록 token_explicit_max_ttl에서 토큰의 TTL에 대한 Vault 시간 제한을 설정합니다.
  • GitLab 보호 브랜치에 JWT를 스코핑하여 프로젝트 사용자의 하위 집합에 제한된 브랜치에 적용합니다.
  • 프로젝트 사용자의 하위 집합에 제한된 GitLab 보호 태그에 JWT를 스코핑합니다.