튜토리얼: HashiCorp Vault로 시크릿 인증 및 읽기
Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
이 튜토리얼은 GitLab CI/CD에서 HashiCorp의 Vault로 인증하고, 구성하고, 시크릿을 읽는 방법을 보여줍니다. 이 튜토리얼은 GitLab CI/CD와 Vault에 익숙하다고 가정합니다. 다음 예시의 vault.example.com URL을 Vault 서버의 URL로 교체하고, gitlab.example.com을 GitLab 인스턴스의 URL로 교체해야 합니다.
이 튜토리얼은 GitLab CI/CD에서 HashiCorp의 Vault로 인증하고, 구성하고, 시크릿을 읽는 방법을 보여줍니다.
사전 요구 사항#
이 튜토리얼은 GitLab CI/CD와 Vault에 익숙하다고 가정합니다.
따라하려면 다음이 필요합니다:
- GitLab 계정.
- 인증 구성, 역할 및 정책 생성을 위한 실행 중인 Vault 서버(최소 v1.2.0) 접근 권한. HashiCorp Vault의 경우 오픈 소스 또는 엔터프라이즈 버전일 수 있습니다.
다음 예시의 vault.example.com URL을 Vault 서버의 URL로 교체하고,
gitlab.example.com을 GitLab 인스턴스의 URL로 교체해야 합니다.
Vault 구성#
JWT는 자격 증명으로, 리소스에 대한 접근 권한을 부여할 수 있습니다. 붙여넣는 위치에 주의하세요!
스테이징 및 프로덕션 데이터베이스 비밀번호를 Vault 서버에 저장하는 시나리오를 생각해 보세요.
이 시나리오는 KV v2 시크릿 엔진을 사용한다고 가정합니다.
KV v1을 사용하는 경우,
다음 정책 경로에서 /data/를 제거하고 CI/CD 작업 구성 방법을 참조하세요.
vault kv get 명령으로 비밀번호를 검색할 수 있습니다.
$ vault kv get -field=password secret/myproject/staging/db
pa$$w0rd
$ vault kv get -field=password secret/myproject/production/db
real-pa$$w0rd
스테이징 비밀번호는 pa$$w0rd이고,
프로덕션 비밀번호는 real-pa$$w0rd입니다.
Vault 서버를 구성하려면 먼저 JWT Auth 방법을 활성화합니다:
$ vault auth enable jwt
Success! Enabled jwt auth method at: jwt/
그런 다음 이러한 시크릿을 읽을 수 있는 정책을 생성합니다(각 시크릿별로 하나씩):
$ vault policy write myproject-staging - <<EOF
# Policy name: myproject-staging
#
# Read-only permission on 'secret/data/myproject/staging/*' path
path "secret/data/myproject/staging/*" {
capabilities = [ "read" ]
}
EOF
Success! Uploaded policy: myproject-staging
$ vault policy write myproject-production - <
# Policy name: myproject-production
#
# Read-only permission on 'secret/data/myproject/production/*' path
path "secret/data/myproject/production/*" {
capabilities = [ "read" ]
}
EOF
Success! Uploaded policy: myproject-production
JWT를 이러한 정책과 연결하는 역할도 필요합니다.
예를 들어, myproject-staging이라는 스테이징용 역할 하나. bound claims는 ID가 22인 프로젝트의 main 브랜치에서만 정책을 사용할 수 있도록 구성됩니다:
$ vault write auth/jwt/role/myproject-staging - <{
"role_type": "jwt",
"policies": ["myproject-staging"],
"token_explicit_max_ttl": 60,
"user_claim": "user_email",
"bound_audiences": "https://vault.example.com",
"bound_claims": {
"project_id": "22",
"ref": "main",
"ref_type": "branch"
}
}
EOF
그리고 myproject-production이라는 프로덕션용 역할 하나. 이 역할의 bound_claims 섹션은 auto-deploy-* 패턴과 일치하는 보호된 브랜치만 시크릿에 접근할 수 있도록 허용합니다.
$ vault write auth/jwt/role/myproject-production - <{
"role_type": "jwt",
"policies": ["myproject-production"],
"token_explicit_max_ttl": 60,
"user_claim": "user_email",
"bound_audiences": "https://vault.example.com",
"bound_claims_type": "glob",
"bound_claims": {
"project_id": "22",
"ref_protected": "true",
"ref_type": "branch",
"ref": "auto-deploy-*"
}
}
EOF
보호된 브랜치와 결합하여 인증하고 시크릿을 읽을 수 있는 사람을 제한할 수 있습니다.
JWT에 포함된 클레임 중 어느 것이든 bound claims의 값 목록과 대조할 수 있습니다. 예를 들어:
"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만 사용하면 네임스페이스의 모든 프로젝트가 허용됩니다. 중첩된 프로젝트는 포함되지 않으므로 필요한 경우 해당 네임스페이스 ID도 목록에 추가해야 합니다.namespace_id와project_id가 모두 사용되면 Vault는 먼저 프로젝트의 네임스페이스가namespace_id에 있는지 확인한 다음 프로젝트가project_id에 있는지 확인합니다.
token_explicit_max_ttl은
성공적인 인증 시 Vault에서 발급된 토큰이 60초의 하드 수명 제한이 있음을 지정합니다.
user_claim은
성공적인 로그인 시 Vault가 생성하는 Identity 별칭의 이름을 지정합니다.
bound_claims_type은
bound_claims 값의 해석을 구성합니다. glob으로 설정하면 값이 글롭으로 해석되어
*가 임의 개수의 문자와 일치합니다.
클레임 필드는 Vault의 JWT auth 마운트 액세서 이름을 사용하여
Vault의 정책 경로 템플릿
목적으로도 접근할 수 있습니다.
마운트 액세서 이름
(다음 예시에서 ACCESSOR_NAME)은 vault auth list를 실행하여 검색할 수 있습니다.
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의 역할 생성 문서를 참조하세요.
제공된 클레임(예: project_id 또는 namespace_id) 중 하나를 사용하여 항상 역할을 프로젝트 또는 네임스페이스로 제한하세요.
그렇지 않으면 이 인스턴스에서 생성된 JWT가 이 역할을 사용하여 인증할 수 있습니다.
이제 JWT 인증 방법을 구성합니다:
$ vault write auth/jwt/config \
oidc_discovery_url="https://gitlab.example.com" \
bound_issuer="https://gitlab.example.com"
bound_issuer는
발급자(즉, iss 클레임)가 gitlab.example.com으로 설정된 JWT만 이 방법을 사용하여 인증할 수 있으며,
oidc_discovery_url (https://gitlab.example.com)을 사용하여 토큰을 검증해야 함을 지정합니다.
사용 가능한 구성 옵션의 전체 목록은 Vault의 API 문서를 참조하세요.
GitLab에서 Vault 서버에 대한 세부 정보를 제공하기 위해 다음 CI/CD 변수를 생성합니다:
VAULT_SERVER_URL: Vault 서버의 URL, 예:https://vault.example.com:8200.VAULT_AUTH_ROLE: 선택 사항. 인증 시도 시 사용할 Vault JWT Auth 역할의 이름. 이 튜토리얼에서 이미myproject-staging과myproject-production이라는 이름으로 두 역할을 생성했습니다. 역할을 지정하지 않으면 Vault는 인증 방법을 구성할 때 지정된 기본 역할을 사용합니다.VAULT_AUTH_PATH: 선택 사항. 인증 방법이 마운트된 경로. 기본값은jwt입니다.VAULT_NAMESPACE: 선택 사항. 시크릿 읽기 및 인증에 사용할 Vault 엔터프라이즈 네임스페이스. 네임스페이스를 지정하지 않으면 Vault는 루트(/) 네임스페이스를 사용합니다. 이 설정은 Vault 오픈 소스에서 무시됩니다.
자동 ID 토큰 인증#
다음 작업은 기본 브랜치에서 실행될 때 secret/myproject/staging/ 아래의 시크릿을 읽을 수 있지만,
secret/myproject/production/ 아래의 시크릿은 읽을 수 없습니다:
job_with_secrets:
id_tokens:
VAULT_ID_TOKEN:
aud: https://vault.example.com
secrets:
STAGING_DB_PASSWORD:
vault: myproject/staging/db/password@secret # translates to a path of 'secret/myproject/staging/db' and field 'password'. Authenticates using $VAULT_ID_TOKEN.
script:
- access-staging-db.sh --token $STAGING_DB_PASSWORD
이 예시에서:
id_tokens- OIDC 인증에 사용되는 JSON 웹 토큰(JWT).aud클레임은 Vault JWT 인증 방법에 사용되는role의bound_audiences매개변수와 일치하도록 설정됩니다.@secret- 시크릿 엔진이 활성화된 vault 이름.myproject/staging/db- Vault에서 시크릿의 경로 위치.password참조된 시크릿에서 가져올 필드.
둘 이상의 ID 토큰이 정의된 경우 token 키워드를 사용하여 어떤 토큰을 사용할지 지정합니다. 예를 들어:
job_with_secrets:
id_tokens:
FIRST_ID_TOKEN:
aud: https://first.service.com
SECOND_ID_TOKEN:
aud: https://second.service.com
secrets:
FIRST_DB_PASSWORD:
vault: first/db/password
token: $FIRST_ID_TOKEN
SECOND_DB_PASSWORD:
vault: second/db/password
token: $SECOND_ID_TOKEN
script:
- access-first-db.sh --token $FIRST_DB_PASSWORD
- access-second-db.sh --token $SECOND_DB_PASSWORD
Vault 1.17부터 JWT에 aud 클레임이 포함된 경우 JWT auth 로그인은 역할에 bound audiences가 필요합니다.
aud 클레임은 단일 문자열이거나 문자열 목록일 수 있습니다.
수동 인증#
ID 토큰을 사용하여 HashiCorp Vault로 수동으로 인증할 수 있습니다. 예를 들어:
manual_authentication:
variables:
VAULT_ADDR: http://vault.example.com:8200
image: vault:latest
id_tokens:
VAULT_ID_TOKEN:
aud: http://vault.example.com
script:
- export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=myproject-example jwt=$VAULT_ID_TOKEN)"
- export PASSWORD="$(vault kv get -field=password secret/myproject/example/db)"
- my-authentication-script.sh $VAULT_TOKEN $PASSWORD
Vault 시크릿에 대한 토큰 접근 제한#
Vault 보호 기능과 GitLab 기능을 사용하여 Vault 시크릿에 대한 ID 토큰 접근을 제어할 수 있습니다. 예를 들어 다음을 통해 토큰을 제한합니다:
- 특정 ID 토큰
aud클레임에 대해 Vault bound audiences 사용. group_claim을 사용하여 특정 그룹에 대해 Vault bound claims 사용.- 특정 사용자의
user_login및user_email을 기반으로 Vault bound claims에 값을 하드코딩. token_explicit_max_ttl에 지정된 대로 토큰의 TTL에 대한 Vault 시간 제한 설정. 토큰은 인증 후 만료됩니다.- 프로젝트 사용자의 하위 집합으로 제한되는 GitLab 보호된 브랜치로 JWT 범위 지정.
- 프로젝트 사용자의 하위 집합으로 제한되는 GitLab 보호된 태그로 JWT 범위 지정.
문제 해결#
The secrets provider can not be found. Check your CI/CD variables and try again. 메시지#
HashiCorp Vault에 접근하도록 구성된 작업을 시작하려고 할 때 이 오류가 발생할 수 있습니다:
The secrets provider can not be found. Check your CI/CD variables and try again.
필요한 변수가 정의되지 않아 작업을 생성할 수 없습니다:
VAULT_SERVER_URL
api error: status code 400: missing role 오류#
HashiCorp Vault에 접근하도록 구성된 작업을 시작하려고 할 때 missing role 오류가 발생할 수 있습니다.
VAULT_AUTH_ROLE 변수가 정의되지 않아 작업이 vault 서버로 인증할 수 없기 때문일 수 있습니다.
audience claim does not match any expected audience 오류#
YAML 파일에 지정된 ID 토큰의 aud: 클레임 값과 JWT 인증에 사용되는 role의 bound_audiences 매개변수 간에 불일치가 있으면 이 오류가 발생할 수 있습니다:
invalid audience (aud) claim: audience claim does not match any expected audience
이 값들이 동일한지 확인합니다.
