リポジトリやランナーにシークレットを残さず、短寿命トークンと動的シークレットで使い切るのが基本です。Vault は CI/CD と相性がよく、認証・取得・監査まで一貫して扱えます。
本稿は、公式ドキュメントの動作に基づく安定パターンだけを扱います。プロバイダやバージョン差異に左右されにくい設計と、運用/試験で問われやすい論点を優先します。
CI ランナーは Vault に短寿命で認証し、必要最小限のシークレットだけをその場で取得して消費します。リポジトリ・アーティファクト・ログにはシークレットを保存しません。ステージ間の受け渡しが必要な場合はレスポンスラッピングを使い、ラップトークンの TTL を短くします。
ビルド(依存取得、コンテナビルド)とデプロイ(Kubernetes/Helm 等)で取得経路を分け、権限を分離します。ビルドは読み取り中心、デプロイは環境への投入を担い、両者のポリシーを分けて最小権限を徹底します。
CI→Vault→Secret Engine→実行環境のデータフロー
レスポンスラッピングでステージ間受け渡し(例: kv データを 5 分ラップ)
vault write -field=wrapping_token -wrap-ttl=5m sys/wrapping/wrap \
secret=@<(vault kv get -format=json kv/build/npmrc | jq -c '.data.data')
# 受け取り側ステージ(ラップ解除は 1 回のみ有効)
vault write sys/wrapping/unwrap wrapping_token=$WRAPPING_TOKEN > unwrapped.json
cat unwrapped.json | jq '.'CI から Vault への入口は運用性と信頼境界で選びます。SaaS CI(GitHub Actions 等)では OIDC(JWT) が第一候補。自己管理ランナーでは AppRole、Kubernetes 内の Pod からは Kubernetes 認証が自然です。
いずれもポリシーで最小権限を定義し、トークンの TTL と max_ttl を短く保ちます。OIDC はフェデレーションで長期共有秘密を不要にでき、監査面でもメリットがあります。
| パターン | 主な適用先 | 強み | 注意点 |
|---|---|---|---|
| OIDC (JWT) | SaaS CI(GitHub Actions、GitLab) | 長期共有鍵不要・監査しやすい | IdP クレーム設計とロール制約が肝 |
| AppRole | 自己管理ランナー/オンプレ | シンプル・依存少ない | SecretID の保護と配布、ラッピング必須 |
| Kubernetes | Pod(Injector/CSI/Agent) | Pod 単位で最小権限・自動ローテ | SA/JWT のスコープ管理 |
GitHub Actions OIDC で Vault にログインして KV を取得
jobs:
build:
permissions:
id-token: write # OIDC 発行に必要
contents: read
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get OIDC token
id: oidc
run: echo "token=$(curl -H 'Authorization: bearer $ACTIONS_ID_TOKEN' 2>/dev/null)" >> $GITHUB_OUTPUT
env:
ACTIONS_ID_TOKEN: ${{ steps.generate_token.outputs.token }}
- name: Vault Login (JWT)
env:
VAULT_ADDR: https://vault.example.com
JWT: ${{ steps.oidc.outputs.token }}
run: |
vault write -format=json auth/jwt/login role=gha-build jwt="$JWT" > login.json
export VAULT_TOKEN=$(jq -r .auth.client_token login.json)
vault kv get -format=json kv/build/npmrc | jq -r .data.data.authToken >> $GITHUB_ENVnpm/pip/maven の資格情報はファイルに一時的に出力し、ジョブ終了時に削除します。コンテナビルドでは Docker BuildKit の secrets 機能を使い、Dockerfile に平文で残さないのが安全です。
CI トークンは非更新・短 TTL が基本。リトライを考慮しつつも max_ttl を短く設定して、ビルドキャッシュや再実行で漏れにくい状態を保ちます。
BuildKit で Vault 取得シークレットを安全に渡す例
# 1) CI で Vault から取得しファイル化(短命)
vault kv get -format=json kv/build/npmrc | jq -r .data.data.npmrc > .npmrc.tmp
# 2) BuildKit 経由で Dockerfile に渡す(イメージには残らない)
DOCKER_BUILDKIT=1 docker build \
--secret id=npmrc,src=.npmrc.tmp \
-t app:build .
# Dockerfile (抜粋)
# syntax=docker/dockerfile:1.6
RUN --mount=type=secret,id=npmrc,dst=/root/.npmrc \
npm ci && rm -f /root/.npmrc
# 3) 後始末
shred -u .npmrc.tmpKubernetes では Vault Agent Injector(サイドカー)や CSI Driver を使い、Pod 内のファイルとしてシークレットを供給します。etcd に平文を置かないため、Kubernetes Secret 単体より安全です。
Kubernetes 認証メソッドで ServiceAccount をロールにひも付け、Pod 単位の最小権限を実現します。ローテーションは Agent が自動更新し、アプリはファイル監視で再読込する設計が実務的です。
Vault Agent Injector を用いた Deployment 例(テンプレート生成)
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "k8s-web"
vault.hashicorp.com/agent-inject-secret-config.yaml: "kv/app/config"
vault.hashicorp.com/agent-inject-template-config.yaml: |
{{- with secret "kv/app/config" -}}
DB_URL={{ .Data.data.DB_URL }}
API_KEY={{ .Data.data.API_KEY }}
{{- end -}}
spec:
replicas: 2
selector:
matchLabels: { app: web }
template:
metadata:
labels: { app: web }
spec:
serviceAccountName: web-sa
containers:
- name: app
image: ghcr.io/acme/web:1.0
envFrom:
- secretRef: { name: dummy } # 実際の値はファイルから読み込む
volumeMounts:
- name: config
mountPath: /run/secrets
volumes:
- name: config
emptyDir: {}Vault にはサービストークン(更新可能)とバッチトークン(軽量・非更新)があり、CI の使い切りにはバッチトークンが適します。TTL と max_ttl を短く設定し、必要なら periodic で計画更新します。
動的シークレットはリースで管理され、ジョブ終了時に明示的に revoke するか、TTL で自動失効させます。監査デバイスは必ず有効化して、誰がどのポリシーでどのパスにアクセスしたかを記録します。
代表的な運用コマンド(監査・トークン・リース)
# 監査ログを有効化(ファイル例)
vault audit enable file file_path=/var/log/vault_audit.log
# バッチトークン(軽量・非更新)を短 TTL で発行
vault token create -type=batch -policy=ci-readonly -ttl=15m -explicit-max-ttl=30m
# 動的シークレットの強制失効(接頭辞でまとめて)
vault lease revoke -prefix database/creds/ci-test
# レスポンスラッピング(5 分)で安全に受け渡し
vault write -wrap-ttl=5m sys/wrapping/wrap [email protected]CI の同時実行が多いと Vault にスパイクがかかります。レート制御とコネクション再利用、Agent キャッシュを活用します。可用性要件に応じて HA(ストレージは公式推奨のバックエンド)と自動アンシールを設計します。
Vault 障害時の挙動も定義します。Injector/Agent ではキャッシュが効く範囲がありますが、強い一貫性が必要なケースではフェイルクローズ(デプロイ停止)を選ぶほうが安全です。
Vault Agent(auto_auth + cache)の最小構成例
auto_auth {
method {
type = "kubernetes"
config = {
role = "k8s-web"
}
}
sink {
type = "file"
config = {
path = "/home/vault/.token"
}
}
}
cache {
use_auto_auth_token = true
}
template {
source = "/vault/templates/config.tpl"
destination = "/run/secrets/config.env"
# 更新間隔の下限を指定して過剰な再読込を抑制
min_refresh_interval = "15s"
}Ops
問題 1
CI ビルド中にユニットテスト用のデータベース資格情報が必要です。長期共有鍵は使わず、ジョブ終了後は自動的に無効化されること、ステージ間の受け渡しも安全であることが要件です。最も適切なアプローチはどれですか?
正解: A
Vault の動的シークレットは短 TTL と自動失効が特長で、CI の使い切りに最適です。ステージ間は sys/wrapping を用いたレスポンスラッピングで安全に受け渡しできます。他の選択肢は長期鍵やイメージ埋め込みなど、露出リスクと監査性の面で不適切です.
GitHub Actions では OIDC と AppRole のどちらを使うべきですか?
原則 OIDC を推奨します。IdP からの署名付き JWT を Vault が検証するため、長期共有秘密が不要で監査もしやすいからです。自己管理ランナーや OIDC 非対応環境では AppRole を用い、SecretID はレスポンスラッピングで配布してください。
シークレットを環境変数とファイルのどちらで渡すべきですか?
ビルド・実行時ともにファイル渡しが望ましいです。環境変数はプロセスツリーやクラッシュダンプ、ログに残る可能性があります。Vault Agent/Injector のテンプレートでファイル化し、アプリは再読込可能な設計にします。
Vault 障害時にデプロイをどう扱えばよいですか?
機密性重視ならフェイルクローズ(Vault から取得できなければデプロイ中止)を選びます。可用性重視で、かつ短時間の断を許容できる場合は Agent/Injector のキャッシュを利用し、後で正規のローテに追従する方針を定めます。いずれも監査ログで痕跡を残してください。
NicheeLab編集部
データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。
Vault のコア概念を最短距離で理解する:Secret / Auth / Policy / Token
HashiCorp Vault Associate レベルで押さえるべきコア概念(Secret Engine、Auth ...
Vault Operations Professional: 上位資格としての範囲を実務目線で押さえる
HashiCorp Vault Operations Professional(Ops Pro)の出題範囲を、Assoc...
Vaultにおけるパスベースのルーティング: マウントとAPI構造を読み解く
HashiCorp Vaultのパスベースのルーティングを、マウント設計とAPIパスの観点から整理。Associateレ...
Vault Tokens の基礎: 認証の起点となる概念
HashiCorp Vault におけるトークンの役割、種類、ライフサイクル、ポリシー連携、設計パターンをAssocia...
Vault のトークン種類を正しく使い分ける: service / batch 実践
HashiCorp Vault Associate 向けの試験対策と実務運用を両立させた、service トークンと b...