LESSON 30分

ストーリー

田中VPoE
Vaultの設計パターンを学んだ。次はローテーション戦略だ。シークレットは定期的に更新しなければならない。PCI-DSSでは暗号鍵のローテーションが明示的に要求されている
あなた
ローテーションは理解していますが、ダウンタイムなしでローテーションする方法がわかりません。パスワードを変えたら接続が切れますよね?
田中VPoE
いい着眼点だ。ゼロダウンタイムローテーションには設計上の工夫が必要だ。「デュアルアクティブ」パターンを使う。新旧両方のシークレットを一時的に有効にし、すべてのクライアントが新しいシークレットに切り替わったら古いシークレットを無効化する
あなた
Blue/Greenデプロイのシークレット版ですね
田中VPoE
そうだ。シークレットのローテーションも、デプロイと同じように計画的に行う必要がある

ローテーションの必要性

なぜローテーションが必要か

理由説明
漏洩の影響を限定シークレットが漏洩しても、ローテーション後は無効化される
コンプライアンス要件PCI-DSS 3.6.4: 暗号鍵は定期的にローテーション
内部脅威への対策退職者が持ち出したシークレットの無効化
攻撃窓口の縮小有効期間が短いほど、攻撃者が利用できる時間が短い

シークレット種類別のローテーション頻度

シークレット種類推奨頻度PCI-DSS要件自動化難易度
データベースパスワード30-90日
APIキー90日
TLS証明書90-365日低(ACME/Let’s Encrypt)
暗号鍵(データ暗号化)年1回3.6.4中(Envelope暗号化で容易)
JWTの署名鍵90日
SSH鍵90日
サービスアカウントトークン動的(TTL: 1-24時間)自動(Vault動的シークレット)

ゼロダウンタイムローテーションの設計パターン

パターン1: デュアルアクティブパターン

デュアルアクティブパターン(APIキーのローテーション):

Phase 1: 新しいキーの生成
  [Old Key: Active] [New Key: Active]
  ← 両方のキーが有効

Phase 2: クライアントの切り替え
  [Old Key: Active] [New Key: Active]
  ← クライアントが順次 New Key に切り替え

Phase 3: 古いキーの無効化
  [Old Key: Revoked] [New Key: Active]
  ← Old Key を無効化(猶予期間後)

タイムライン:
  Day 0      Day 1-7        Day 8
  ├──────────┼──────────────┤
  新キー生成  クライアント    旧キー
             切り替え期間    無効化

パターン2: Envelope暗号化パターン

Envelope暗号化によるデータ暗号鍵(DEK)のローテーション:

構造:
  KEK(Key Encryption Key): Vaultで管理
  DEK(Data Encryption Key): KEKで暗号化して保存
  Data: DEKで暗号化

ローテーション手順:
  1. Vault Transit で新しいKEKバージョンを生成
  2. 既存のDEKを新しいKEKで再暗号化(Rewrap)
  3. データ自体の再暗号化は不要

利点:
  - データの再暗号化が不要(高速)
  - KEKのローテーションだけで暗号鍵を更新
  - PCI-DSS 3.6.4 の要件を満たせる
// Vault Transit を使ったEnvelope暗号化の例
import Vault from "node-vault";

const vault = Vault({ endpoint: "https://vault.example.com" });

// データの暗号化
async function encryptPII(plaintext: string): Promise<string> {
  const result = await vault.write("transit/encrypt/payconnect-pii", {
    plaintext: Buffer.from(plaintext).toString("base64"),
  });
  return result.data.ciphertext; // vault:v2:xxxxx
}

// データの復号化
async function decryptPII(ciphertext: string): Promise<string> {
  const result = await vault.write("transit/decrypt/payconnect-pii", {
    ciphertext,
  });
  return Buffer.from(result.data.plaintext, "base64").toString();
}

// KEKローテーション後のRewrap
async function rewrapCiphertext(ciphertext: string): Promise<string> {
  const result = await vault.write("transit/rewrap/payconnect-pii", {
    ciphertext,
  });
  return result.data.ciphertext; // 新しいKEKバージョンで再暗号化
}

パターン3: データベースパスワードのゼロダウンタイムローテーション

PostgreSQL パスワードローテーション:

Step 1: 新しいパスワードを生成
  ALTER USER app_user SET PASSWORD 'new_password';

Step 2: Vault のシークレットを更新
  vault kv put secret/payconnect/database \
    password="new_password"

Step 3: アプリケーションの接続プールを更新
  - Vault Agent が新しいシークレットを検出
  - アプリケーションが接続プールを再作成
  - 既存の接続は現在のトランザクション完了後にクローズ

Step 4: 古いパスワードの無効化
  - 猶予期間(例: 10分)後に旧パスワードを無効化

注意: Vault の動的シークレットを使えば、
このプロセス全体が自動化される

AWS Secrets Manager によるローテーション

Lambda関数を使った自動ローテーション

# AWS Secrets Manager のRDSローテーションLambda(概念)
import boto3
import json

def lambda_handler(event, context):
    """
    4ステップのローテーションプロセス:
    1. createSecret: 新しいシークレット値を生成
    2. setSecret: データベースのパスワードを更新
    3. testSecret: 新しいパスワードで接続テスト
    4. finishSecret: 新しいバージョンをカレントに昇格
    """
    step = event["Step"]
    secret_arn = event["SecretId"]

    client = boto3.client("secretsmanager")

    if step == "createSecret":
        # 新しいパスワードを生成してPENDINGバージョンに保存
        new_password = client.get_random_password(
            PasswordLength=32,
            ExcludeCharacters="/@\"'\\"
        )["RandomPassword"]

        current = json.loads(
            client.get_secret_value(SecretId=secret_arn)["SecretString"]
        )
        current["password"] = new_password

        client.put_secret_value(
            SecretId=secret_arn,
            SecretString=json.dumps(current),
            VersionStages=["AWSPENDING"]
        )

    elif step == "setSecret":
        # データベースのパスワードを更新
        pending = json.loads(
            client.get_secret_value(
                SecretId=secret_arn,
                VersionStage="AWSPENDING"
            )["SecretString"]
        )
        # ALTER USER ... で更新

    elif step == "testSecret":
        # 新しいパスワードで接続テスト
        pass

    elif step == "finishSecret":
        # PENDINGをCURRENTに昇格
        client.update_secret_version_stage(
            SecretId=secret_arn,
            VersionStage="AWSCURRENT",
            MoveToVersionId=event["ClientRequestToken"]
        )

ローテーション監視とアラート

メトリクス閾値アラート
ローテーション未実施日数> 推奨頻度Warning
ローテーション失敗1回Critical
動的シークレットのTTL超過> max_ttlCritical
証明書の有効期限< 30日Warning、< 7日: Critical

まとめ

ポイント内容
ローテーションの必要性漏洩影響の限定、コンプライアンス、内部脅威対策
ゼロダウンタイムデュアルアクティブパターンで新旧両方を一時的に有効化
Envelope暗号化KEKのローテーションのみでデータ再暗号化が不要
自動化Vault動的シークレット or AWS Secrets Managerで自動ローテーション

チェックリスト

  • シークレット種類別の推奨ローテーション頻度を把握した
  • デュアルアクティブパターンによるゼロダウンタイムローテーションを説明できる
  • Envelope暗号化のメリットを理解した
  • ローテーション監視のメトリクスを把握した

次のステップへ

次は「ゼロスタンディング権限」を学びます。常時アクセス権を持たない、Just-In-Timeのアクセス制御を設計しましょう。


推定読了時間: 30分