HashiCorp Vault를 사용하여 인증 및 시크릿 읽기

Tier: Premium, Ultimate Offering: 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의 경우 오픈 소스 또는 엔터프라이즈 버전을 사용할 수 있습니다.
note
아래의 vault.example.com URL은 귀하의 Vault 서버 URL로 교체하고, gitlab.example.com은 귀하의 GitLab 인스턴스 URL로 교체해야 합니다.

동작 방식

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

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

필드 조건 설명
jti 항상 이 토큰에 대한 고유 식별자
iss 항상 발행자(GitLab 인스턴스의 도메인)
iat 항상 발급 시간
… (중략) … … (중략) … … (중략) …

예시 JWT payload:

{
  "jti": "c82eeb0c-5c6f-4a33-abf5-4c474b92b558",
  "iss": "gitlab.example.com",
  "iat": 1585710286,
  ...
}

JWT는 RS256을 사용하여 인코딩되며 전용 개인 키로 서명됩니다. 토큰의 만료 시간은 작업 제한 시간 또는 5분으로 설정됩니다. 이 토큰에 사용된 키는 사전에 공지 없이 변경될 수 있습니다. 이 경우 작업을 다시 시도하면 현재 서명 키를 사용하여 새 JWT를 생성합니다.

이 JWT를 사용하여 정의된 Vault 서버와 인증할 수 있으며 작업 중에만 해당 토큰을 제공합니다. 서버는 인스턴스에서 토큰을 유효성 검사하기 위해 키를 검색할 수 있습니다.

Vault에서 역할을 구성할 때 bound claims를 사용하여 JWT 클레임과 일치시키고 각 CI/CD 작업이 액세스할 수 있는 시크릿을 제한할 수 있습니다.

Vault와 통신하기 위해 CLI 클라이언트를 사용하거나 curl 또는 다른 클라이언트를 사용할 수 있습니다.

예시

caution
JWT는 리소스에 액세스 권한을 부여할 수 있는 자격 증명이므로 이를 붙여 넣는 위치를 신중히 선택하세요!

예를 들어 http://vault.example.com:8200에서 실행 중인 Vault 서버에 스테이징 및 프로덕션 데이터베이스의 암호가 저장되어 있다고 가정해보겠습니다. 스테이징 암호는 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

Vault 서버를 구성하려면 먼저 JWT Auth 메서드를 활성화하세요:

$ 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"]
}
  • namespace_id만 사용하는 경우 해당 네임스페이스의 모든 프로젝트가 허용됩니다.
  • namespace_idproject_id를 모두 사용하는 경우 Vault는 프로젝트의 네임스페이스가 namespace_id에 있는지 먼저 확인합니다. 그렇지 않으면 프로젝트가 project_id에 있는지 확인합니다.

token_explicit_max_ttl은 Vault가 성공적으로 인증한 경우에 발급하는 토큰의 최대 수명을 60초로 지정합니다.

user_claim은 Vault에서 성공적인 로그인 시 생성되는 Identity 별칭의 이름을 지정합니다.

bound_claims_typebound_claims 값을 해석하는 구형의 해석을 구성합니다. glob로 설정하면 *가 임의의 문자와 일치하도록 기본 설정된 globs로 값을 해석합니다.

테이블에서 나열된 클레임 필드는 또한 Vault의 정책 경로 템플릿을 위한 접근자 이름으로 액세스할 수 있습니다. 엔터프라이즈 네임스페이스를 사용하려면 claim_mappings 구성을 사용하여 클레임 필드를 메타데이터 필드로 매핑하는 역할 예를 들어:

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

같은 종류의 옵션에 대한 전체 디렉터리은 Vault의 역할 생성 문서를 참고하세요.

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

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

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

bound_issueriss 클레임으로 설정된 발급자의(JWT의 iss 클레임)만이 이 방법을 사용하여 인증하고 인스턴스에서 토큰을 검증하기 위해 oidc_discovery_url(https://gitlab.example.com)이 사용되어야 함을 지정합니다.

사용하려면 GitLab에서 다음을 CI/CD 변수로 만들어 Vault 서버에 대한 세부 정보를 제공하세요:

  • VAULT_SERVER_URL - Vault 서버의 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 - 시크릿 엔진이 활성화된 Vault 이름
  • secret/myproject/staging/db - Vault의 시크릿 위치 경로
  • password - 참조된 시크릿 내에서 가져올 필드입니다.

Vault 시크릿에 대한 토큰 액세스 제한

Vault 보호 및 GitLab 기능을 사용하여 Vault 시크릿에 대한 ID 토큰 액세스를 제어할 수 있습니다. 예를 들어 다음과 같이 토큰을 제한할 수 있습니다.

  • 특정 그룹에 대한 Vault 바운드 클레임을 사용하여 group_claim을 사용합니다.
  • 특정 사용자의 user_loginuser_email에 기반하여 Vault 바운드 클레임에 대한 값을 하드 코딩합니다.
  • 인증 후 토큰의 만료 시점으로 지정된 토큰의 TTL에 대한 Vault 시간 제한을 설정합니다. token_explicit_max_ttl에서 토큰은 만료됩니다.
  • 일부 프로젝트 사용자 집합으로 제한된 GitLab 보호 브랜치에 JWT를 스코프 지정합니다.
  • 일부 프로젝트 사용자 집합으로 제한된 GitLab 보호된 태그에 JWT를 스코프 지정합니다.