ユースケーススタディ: CI ジョブでのシークレット利用
目的
- ユーザーが CI ジョブでネイティブ GitLab シークレットを使用する方法をまとめます。
- OpenBao は HashiCorp Vault のフォークであるため、Runner における Vault インテグレーションとの互換性を確認します。
- OpenBao のポリシーとJWT ロールを、プロジェクトごとに異なる GitLab ユーザーロールのパーミッションと互換性を持つ形で構成する方法を高レベルで理解します。
前提条件
このワークフローでは、capabilities(例: read+update、read+update+create)のすべての組み合わせに対するテンプレート化されたポリシーが事前に定義されている必要があります。たとえば、プロジェクトのシークレットへの完全なアクセスを許可する次のテンプレート化されたポリシーを考えます。
bao policy write project_full_access - <<EOF
path "kv-v2/data/projects/{{identity.entity.aliases.auth_jwt_02163755.metadata.project_id}}/*" {
capabilities = [ "read", "create", "update", "delete", "list" ]
}
EOF
ポリシーは認可時に JWT ロールと関連付けられます。project_full_access ポリシーは特に初期プロジェクトオーナーロールに対して重要です。
bao write auth/jwt/role/project_owner - <<EOF
{
"role_type": "jwt",
"policies": ["project_full_access"],
"token_explicit_max_ttl": 60,
"user_claim": "user_id",
"claim_mappings": {
"project_id": "project_id"
},
"bound_audiences": "secrets.gitlab.com",
"bound_claims_type": "glob",
"bound_claims": {
"user_access_level": "owner"
}
}
EOF
OpenBao のポリシーはデフォルトで拒否されるため、プロジェクトオーナーにシークレットの読み書き完全アクセス権を付与するためにこの初期 JWT ロールが必要です。
初期セットアップワークフロー
プロジェクトのネイティブシークレットが初めて設定される際の手順と技術情報を詳述します。
- プロジェクトオーナーが GitLab UI から GitLab Secrets Manager を有効化します。
- プロジェクトオーナーが GitLab UI から、どの GitLab ユーザーロールがシークレットを読み取り・書き込み・作成できるかについて追加パーミッションを定義します。
デフォルトでは、プロジェクトオーナーは完全アクセス権を持ち、他のロールは拒否されます。
たとえば、オーナーが
developerロールに読み取り専用アクセスを許可する場合、OpenBao API を通じて Rails バックエンドがproject_88_developerを定義します。# ロール名のフォーマットは `project_<project-id>_<user-role>` bao write auth/jwt/role/project_88_developer - <<EOF { "role_type": "jwt", "policies": ["project_read_only"], "token_explicit_max_ttl": 60, "user_claim": "user_id", "claim_mappings": { "project_id": "project_id" }, "bound_audiences": "secrets.gitlab.com", "bound_claims_type": "glob", "bound_claims": { "user_access_level": "developer" } } EOFproject_ownerの汎用ロールとは異なり、プロジェクトごとにユーザーロールのパーミッションの組み合わせが異なる可能性があるため、オーナー以外のロールをプロジェクトに紐付けて定義する必要があります。
- プロジェクトオーナーが GitLab UI からシークレットを定義します。
ユーザーは名前・キー・値などの詳細を定義します。入力例:
- name:
Production Database Password - key:
DB_PASS - value:
mydbpass
- name:
シークレットは
kv-v2/data/projects/88/ci/DB_PASSの下に以下の JSON データで OpenBao に保存されます。{ "data": "mydbpass" }ユーザーはシークレットの値を JSON 形式で入力する必要はありません。Rails バックエンドが入力を OpenBao へ送信する前に
dataキーを持つ JSON オブジェクトに変換します。
- 開発者が
.gitlab-ci.ymlでsecretsキーワードを使用します。設定例:
job-with-secrets: secrets: MY_SECRET_ON_OPENBAO: key: DB_PASS # kv-v2/data/projects/88/DB_PASS(フィールド `data`)に対応audのデフォルトが OpenBao サービスのあるhttps://secrets.gitlab.comになるため、id_tokens:VAULT_ID_TOKENを指定する必要はありません。HashiCorp Vault とは異なり、CI/CD 変数を定義する必要はありません。
VAULT_SERVER_URLは OpenBao サービスのあるhttps://secrets.gitlab.comがデフォルトです。VAULT_AUTH_ROLEは OpenBao の JWT ロールに一致するようproject_<project_id>_<job_user_role>がデフォルトです。
- CI ジョブが実行され、
MY_SECRET_ON_OPENBAOが環境変数として利用可能になります。- OpenBao は ID トークンの整合性を検証し、
bound_claimsがカスタムクレーム(特に GitLab ユーザーロールを含むuser_access_level)と一致するかを検証します。 - HashiCorp Vault のシークレットと同様に、これは
file変数です。
- OpenBao は ID トークンの整合性を検証し、
技術実装の知見
ワークフローをサポートするための OpenBao と Rails に関する高レベルの技術実装詳細です。
- ワークフローと互換性を持たせるために、OpenBao サービスを適切に設定する必要があります。
- ID トークン認証と連携させるために JWT 認証を設定します。
- ドキュメントには
vaultCLI を使った手順が示されていますが、baoでも同様に機能します。 - OpenBao API は
https://secrets.gitlab.comから到達可能です。 - テンプレート化されたポリシーで
project_idを参照するには、JWT auth マウントアクセサの値(bao auth listの結果からauth_jwt_02163755)を取得する必要がありました。テンプレート化されたポリシーが正しいアクセサで最新の状態を保つよう、デプロイ時に自動化する必要があります。マウントアクセサの値はストレージに永続化され、OpenBao サーバーが再起動・シールされても値が保持されます。
- ワークフローをサポートするために、Rails バックエンドに付随する実装が必要です。
- シークレットの ActiveRecord モデル。UI でのシークレット一覧表示や詳細表示では OpenBao へのリクエストを行わないようにします。
- パーミッションの ActiveRecord モデル。UI でのパーミッション一覧表示では OpenBao へのリクエストを行わないようにします。
- CI 設定で
id_tokensを定義せずに ID トークンを使用できるよう、ID トークン関連の実装を更新します。 VAULT_SERVER_URLとVAULT_AUTH_ROLEのデフォルト値の適切なマッピング。
ローカルでのテスト方法
ここで紹介するポリシーとロールの構成は、まず GDK セットアップと dev モードで動作する OpenBao サーバーを使ってローカルでテストしました。
以下はローカルでテストするためのステップバイステップガイドです。
runner を使って GDK を適切にセットアップしていることを確認します。
- Docker executor を使った GDKでテストし、
gdk.testを172.16.123.1に向けましたが、shell executor でも動作するはずです。 - テストプロジェクトで CI パイプラインを正常に実行できることを確認します。
- Docker executor を使った GDKでテストし、
後で OpenBao からシークレットを取得するためのテストプロジェクトを作成します。
- プロジェクト ID をメモしておきます。この例ではプロジェクト ID は
53でした。
- プロジェクト ID をメモしておきます。この例ではプロジェクト ID は
devモードで OpenBao を起動します。bao server -dev -dev-root-token-id="dev-only-token"- これにより OpenBao が
https://127.0.0.1:8200で到達可能になります。 - 以下の
baoCLI コマンドを動作させるにはexport BAO_ADDR='https://127.0.0.1:8200'を実行する必要があるかもしれません。
- これにより OpenBao が
kv-v2 シークレットエンジンを有効化します。
bao secrets enable kv-v2 # デフォルトで `kv-v2/data` にマウントされますJWT 認証を有効化します。
bao auth enable jwtOpenBao の JWT 認証を設定します。
bao write auth/jwt/config \ oidc_discovery_url="https://gdk.test:3000" \ bound_issuer="https://gdk.test:3000"GitLab ユーザーロール
ownerを持つプロジェクトオーナーに対して生成されるポリシーとロールをテストするために、テンプレート化されたポリシーと特定のownerロール用の JWT ロールを作成します。JWT ロールは GitLab Vault サンプルサーバーロールに基づいています。bao auth list実行時の JWT auth マウントアクセサの値をメモします。Path Type Accessor Description Version ---- ---- -------- ----------- ------- jwt/ jwt auth_jwt_02163755 n/a n/a token/ token auth_token_90d6d0c1 token based credentials n/aテンプレート化されたポリシーを定義し、マウントされた JWT auth プラグインのメタデータを通じて
project_idを参照します。bao policy write project_full_access - <<EOF # オーナーはプロジェクトのシークレットへの完全な読み書きアクセス権を持ちます # `auth_jwt_02163755` マウントアクセサの値をコピーしてください path "kv-v2/data/projects/{{identity.entity.aliases.auth_jwt_02163755.metadata.project_id}}/*" { capabilities = [ "read", "create", "update", "delete", "list" ] } EOFJWT ロールを定義し、
project_full_accessポリシーを関連付けます。bao write auth/jwt/role/project_owner - <<EOF { "role_type": "jwt", "policies": ["project_full_access"], "token_explicit_max_ttl": 60, "user_claim": "user_id", "claim_mappings": { "project_id": "project_id" }, "bound_audiences": "secrets.gitlab.com", "bound_claims_type": "glob", "bound_claims": { "user_access_level": "owner" } } EOF
CI ジョブで取得したいサンプルシークレットを作成します。
bao kv put -mount=kv-v2 projects/53/foo val=my-long-passcodeテストプロジェクトで、既存の Vault インテグレーションを使って OpenBao からシークレットを取得するように
.gitlab-ci.ymlを設定します。test_openbao: variables: VAULT_SERVER_URL: https://127.0.0.1:8200 VAULT_AUTH_ROLE: project_owner id_tokens: VAULT_ID_TOKEN: aud: secrets.gitlab.com secrets: SECRET: vault: projects/53/foo/val # シークレット `kv-v2/data/projects/53/foo`(フィールド `val`)に対応 token: $VAULT_ID_TOKEN script: - echo "testing..." - cat $SECRET - echo "done."VAULT_AUTH_ROLEは先ほど作成した JWT ロールと一致します。audはロールのbound_audiencesと一致します。- このジョブで生成された ID トークンは、
bound_claims(特に ID トークンのカスタムクレームに含まれる GitLab ユーザーロールを示すuser_access_level)を使って OpenBao によって照合されます。
パイプラインを実行し、ジョブトレースに OpenBao から取得したシークレットのマスクされた出力が表示されることを確認します。
