LESSON 30分

ストーリー

田中VPoE
シークレット管理の全体像を把握した。ここからはHashiCorp Vaultを中心に、具体的な設計パターンを学ぶ
あなた
Vaultは「シークレットのデータベース」のようなものですか?
田中VPoE
それ以上だ。Vaultは3つの役割を持つ。第1にシークレットの安全な保存。第2に動的シークレットの生成。第3に暗号化サービス(Encryption as a Service)。特に動的シークレットは画期的だ。必要な時にその場でDB認証情報を生成し、使い終わったら自動で失効させる
あなた
つまり、長期間有効なパスワードそのものが存在しなくなる
田中VPoE
そうだ。漏洩するシークレットが存在しなければ、漏洩のリスクもゼロだ。これがVaultの真の価値だ

HashiCorp Vault のアーキテクチャ

コンポーネント

Vault アーキテクチャ:

┌─────────────────────────────────────────┐
│              Vault Server                │
│                                          │
│  ┌─────────────┐  ┌──────────────────┐  │
│  │ Secret       │  │ Auth Methods     │  │
│  │ Engines      │  │                  │  │
│  │ ├── KV v2    │  │ ├── Token        │  │
│  │ ├── Database │  │ ├── AppRole      │  │
│  │ ├── AWS      │  │ ├── Kubernetes   │  │
│  │ ├── PKI      │  │ ├── OIDC/JWT     │  │
│  │ └── Transit  │  │ └── AWS IAM      │  │
│  └─────────────┘  └──────────────────┘  │
│                                          │
│  ┌─────────────┐  ┌──────────────────┐  │
│  │ Policies     │  │ Audit Devices    │  │
│  │ (HCL/JSON)  │  │ ├── File         │  │
│  │              │  │ ├── Syslog       │  │
│  │              │  │ └── Socket       │  │
│  └─────────────┘  └──────────────────┘  │
│                                          │
│  ┌──────────────────────────────────┐   │
│  │ Storage Backend                    │   │
│  │ ├── Raft (推奨、内蔵)              │   │
│  │ ├── Consul                         │   │
│  │ └── DynamoDB                       │   │
│  └──────────────────────────────────┘   │
└─────────────────────────────────────────┘

Secret Engines の種類

Secret Engine用途出力例
KV v2静的シークレットの保存(バージョン管理付き)APIキー、設定値
Database動的DBユーザー/パスワードの生成一時的なPostgreSQL認証情報
AWS動的AWSアクセスキーの生成一時的なIAMアクセスキー
PKIX.509証明書の発行TLS証明書 + 秘密鍵
Transit暗号化/復号化(Encryption as a Service)暗号化されたデータ
SSH動的SSH認証情報一時的なSSH証明書
TOTPワンタイムパスワード生成TOTP コード

動的シークレットの設計

PostgreSQL動的シークレットの例

# Vault設定: PostgreSQLバックエンドの設定
resource "vault_database_secret_backend_connection" "postgres" {
  backend       = "database"
  name          = "payconnect-db"
  allowed_roles = ["readonly", "readwrite"]

  postgresql {
    connection_url = "postgres://vault_admin:{{password}}@db.example.com:5432/payconnect"
  }
}

# 読み取り専用ロール
resource "vault_database_secret_backend_role" "readonly" {
  backend     = "database"
  name        = "readonly"
  db_name     = vault_database_secret_backend_connection.postgres.name
  default_ttl = 3600    # 1時間
  max_ttl     = 86400   # 24時間

  creation_statements = [
    "CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';",
    "GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";"
  ]

  revocation_statements = [
    "REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM \"{{name}}\";",
    "DROP ROLE IF EXISTS \"{{name}}\";"
  ]
}
// アプリケーションからの動的シークレット取得(TypeScript)
import Vault from "node-vault";

const vault = Vault({
  endpoint: process.env.VAULT_ADDR,
  token: process.env.VAULT_TOKEN, // AppRole等で取得
});

async function getDatabaseCredentials(): Promise<{
  username: string;
  password: string;
  leaseId: string;
}> {
  const result = await vault.read("database/creds/readonly");

  return {
    username: result.data.username,
    password: result.data.password,
    leaseId: result.lease_id,
  };
  // TTL(1時間)後に自動失効
  // アプリケーションは定期的に新しい認証情報を取得
}

動的シークレットのライフサイクル

動的シークレットのライフサイクル:

[アプリ] ──→ Vault: "database/creds/readonly" をリクエスト

Vault ──→ PostgreSQL: 一時ユーザーを作成(TTL: 1h)

Vault ──→ [アプリ]: username + password + lease_id を返却

[アプリ] ──→ PostgreSQL: 一時ユーザーでクエリ実行

        ... 1時間後 ...

Vault ──→ PostgreSQL: 一時ユーザーを自動削除(Revoke)

[アプリ] ──→ Vault: 新しい認証情報をリクエスト(繰り返し)

Vaultのポリシー設計

パス別のアクセス制御

# PayConnect API サービス用のポリシー
path "secret/data/payconnect/*" {
  capabilities = ["read", "list"]
}

# データベース動的シークレット(読み取り専用ロール)
path "database/creds/readonly" {
  capabilities = ["read"]
}

# Transit暗号化(PII暗号化用)
path "transit/encrypt/payconnect-pii" {
  capabilities = ["update"]
}

path "transit/decrypt/payconnect-pii" {
  capabilities = ["update"]
}

# 自分のトークン情報の確認のみ許可
path "auth/token/lookup-self" {
  capabilities = ["read"]
}

環境別のパス設計

Vault パス構造:

secret/
├── data/
│   ├── production/
│   │   ├── payconnect/
│   │   │   ├── stripe-api-key
│   │   │   ├── jwt-signing-key
│   │   │   └── s3-encryption-key
│   │   └── other-service/
│   ├── staging/
│   │   └── payconnect/
│   │       ├── stripe-api-key(テスト用)
│   │       └── ...
│   └── development/
│       └── payconnect/
│           └── ...

database/
├── creds/
│   ├── payconnect-readonly
│   └── payconnect-readwrite

transit/
├── keys/
│   ├── payconnect-pii(PII暗号化用)
│   └── payconnect-audit(監査ログ暗号化用)

Kubernetes統合パターン

Vault Agent Injectorパターン

# Kubernetes Deployment with Vault Agent Injector
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payconnect-api
spec:
  template:
    metadata:
      annotations:
        # Vault Agent Injector のアノテーション
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "payconnect-api"
        vault.hashicorp.com/agent-inject-secret-db-creds: "database/creds/readonly"
        vault.hashicorp.com/agent-inject-template-db-creds: |
          {{- with secret "database/creds/readonly" -}}
          postgresql://{{ .Data.username }}:{{ .Data.password }}@db.example.com:5432/payconnect
          {{- end }}
    spec:
      serviceAccountName: payconnect-api
      containers:
        - name: payconnect-api
          image: payconnect-api:latest
          env:
            - name: DATABASE_URL_FILE
              value: /vault/secrets/db-creds

「Vault Agent Injectorを使えば、アプリケーションコードをVaultに依存させずにシークレットを注入できる。既存のアプリケーションへの導入が容易だ」 — 田中VPoE


まとめ

ポイント内容
Vaultの3つの役割シークレット保存、動的シークレット生成、暗号化サービス
動的シークレット一時的な認証情報を生成し、TTL後に自動失効。漏洩リスクを根本的に低減
ポリシー設計パス別のアクセス制御で最小権限原則を実現
Kubernetes統合Vault Agent Injectorでアプリケーション非依存のシークレット注入

チェックリスト

  • VaultのSecret Engineの種類と用途を説明できる
  • 動的シークレットのライフサイクルを説明できる
  • Vaultのポリシー設計パターンを理解した
  • Kubernetes統合のパターンを理解した

次のステップへ

次は「ローテーション戦略」を学びます。シークレットの自動ローテーションを実現する設計パターンを身につけましょう。


推定読了時間: 30分