チュートリアル: HashiCorp Vaultを使用して認証し、シークレットを読み取ります。
- プラン: Premium、Ultimate
- 提供形態: GitLab.com、GitLab Self-Managed、GitLab Dedicated
このチュートリアルでは、GitLab CI/CDからHashiCorp Vaultを使用して、認証、設定、およびシークレットを読み取る方法を説明します。
前提要件
このチュートリアルでは、GitLab CI/CDとVaultについて理解していることを前提としています。
次に進むには、以下が必要です:
- GitLabのアカウントを持っている。
- 実行中のVaultサーバー(少なくともv1.2.0)へのアクセス権があり、認証を設定し、ロールとポリシーを作成できる。HashiCorp Vaultの場合、これはオープンソースまたはEnterpriseバージョンにすることができます。
以下の例の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認証方法を有効にします:
$ vault auth enable jwt
Success! Enabled jwt auth method at: jwt/次に、これらのシークレットの読み取りを許可するポリシーを作成します(シークレットごとに1つ):
$ 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 - <<EOF
# 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-productionJWTをこれらのポリシーにリンクするロールも必要です。
たとえば、myproject-stagingという名前のステージング用のロールが1つあります。bound_claimsは、ID 22のプロジェクトのmainブランチでのみポリシーを使用できるように設定されています:
$ vault write auth/jwt/role/myproject-staging - <<EOF
{
"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という名前の本番環境用のロールが1つあります。このロールのbound_claimsセクションでは、auto-deploy-*パターンに一致する保護ブランチのみがシークレットにアクセスできます。
$ vault write auth/jwt/role/myproject-production - <<EOF
{
"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によって作成されたアイデンティティエイリアスの名前を指定します。
bound_claims_typeは、bound_claims値の解釈を設定します。globに設定すると、値はglobとして解釈され、*は任意の数の文字に一致します。
上記の表にリストされているクレームフィールドには、VaultのJWT認証のアクセサー名を使用することにより、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は、gitlab.example.comに設定された発行者(つまり、issクレーム)を持つ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認証ロールの名前。このチュートリアルでは、myproject-stagingおよびmyproject-productionという名前の2つのロールをすでに作成しました。ロールが指定されていない場合、Vaultは、認証方法の設定時に指定されたデフォルトロールを使用します。VAULT_AUTH_PATH: オプション。認証方法がマウントされているパス。デフォルトはjwtです。VAULT_NAMESPACE: オプション。シークレットの読み取りと認証に使用するVault Enterpriseネームスペース。ネームスペースが指定されていない場合、Vaultはルート(/)ネームスペースを使用します。この設定はVault Open Sourceでは無視されます。
自動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 Webトークン(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_PASSWORDVault 1.17以降、JWTにaudクレームが含まれている場合、JWT認証ログインにはロールに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 $PASSWORDVaultシークレットへのトークンアクセスを制限する
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
これらの値が同じであることを確認してください。