ストーリー
田
田中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_ttl | Critical |
| 証明書の有効期限 | < 30日 | Warning、< 7日: Critical |
まとめ
| ポイント | 内容 |
|---|
| ローテーションの必要性 | 漏洩影響の限定、コンプライアンス、内部脅威対策 |
| ゼロダウンタイム | デュアルアクティブパターンで新旧両方を一時的に有効化 |
| Envelope暗号化 | KEKのローテーションのみでデータ再暗号化が不要 |
| 自動化 | Vault動的シークレット or AWS Secrets Managerで自動ローテーション |
チェックリスト
次のステップへ
次は「ゼロスタンディング権限」を学びます。常時アクセス権を持たない、Just-In-Timeのアクセス制御を設計しましょう。
推定読了時間: 30分