【テクニカル・上級編】PulumiのSecrets管理完全ガイド:AWS/GCPの暗号化プロバイダ活用術 – インフラ構成管理(IaC)活用バイブル

PulumiのSecrets管理完全ガイド:AWS/GCPの暗号化プロバイダ極限活用術

インフラストラクチャ・アイズ・コード(IaC)の成熟に伴い、我々は「インフラをコードで定義する」というフェーズを通り越し、「インフラのライフサイクル全体をソフトウェア工学の範疇に収める」という極限の領域に到達している。

その中で、最も妥協が許されない領域が「機密情報(Secrets)のライフサイクル管理」だ。

平文のデータベースパスワード、APIトークン、TLSプライベートキー。これらがGitリポジトリにコミットされた瞬間、インフラストラクチャの信頼性は崩壊する。TerraformのStateファイル(S3バックエンド等)の暗号化に苦悶した時代は終わった。現代の我々には、Pulumiという強力なプログラマティックIaCエンジンがある。

本稿では、PulumiのSecrets管理アーキテクチャの深淵に潜り込み、標準機能のメカニズムから、AWS KMS / GCP Cloud KMSを用いたエンタープライズグレードの暗号化プロバイダ統合、そしてコードレビュー時の漏洩を物理的に阻止するパイプライン設計まで、現場の最前線で培った知見を余すところなく解説する。

—

1. IaCにおける機密情報管理のパラダイムシフト

従来の宣言型IaC(例えばHCLベースのツール)では、機密情報の扱いは常に「後付けのパッチ」のような側面を持っていた。変数に `sensitive = true` を付与しても、Stateファイル内では平文(Base64エンコードされただけの状態)でJSONとして永続化されるリスクがつきまとう。

Pulumiの設計思想は根本的に異なる。「最初からすべての値に暗号化のコンテキストを付与する」というアプローチだ。

Pulumi Secretsの内部アーキテクチャ

Pulumiにおけるシークレットは、単なる文字列ではない。それは `Output` 型のラップされたオブジェクトであり、明示的にデシリアライズ(復号)されない限り、メモリ上でも暗号化されたトークン(例: `v1:U2FsdGVkX1…`)として保持される。

[コード上の記述] -> pulumi.secret(“my-password”)
│
▼
[暗号化プロバイダ (KMS等)] -> 暗号化されたCiphertextを生成
│
▼
[Stateファイル] -> “secret:v1:…” として安全に永続化
│
▼
[プロバイダへの送信直前] -> オンデマンドで復号されAPIリクエストへ

このアーキテクチャにより、CI/CDパイプラインのログ出力(`pulumi up` の標準出力など)においても、Pulumiエンジンが自動的にシークレット値を検知し、アスタリスク(`[secret]`)にマスクして漏洩を防ぐ。

—

2. Pulumi標準Secrets機能の深層と落とし穴

Pulumiを初期化すると生成される `Pulumi..yaml` には、スタック固有の設定値が保存される。ここに平文でパスワードを書く愚を犯す者はいないはずだ。

正しいシークレットの登録方法(暗号化されてYAMLに保存される)
pulumi config set –secret dbPassword “SuperSecret123!”

デフォルトの暗号化プロバイダ(Passphrase)の限界

明示的な設定を行わない場合、Pulumiはローカルの passphrase(パスフレーズ)を用いてAES-256でシークレットを暗号化する。このパスフレーズは環境変数 `PULUMI_CONFIG_PASSPHRASE` またはローカルの `~/.pulumi` に保存される。

【SREの現場における致命的な課題】
チーム開発において、このパスフレーズをメンバー間で共有する必要が生じる。Slackや1Passwordで共有されたパスフレーズが漏洩した場合、すべてのスタックのシークレットが容易に復号されるリスクを抱える。また、CI/CD環境でこのパスフレーズを安全に注入し続ける運用の複雑さは、スケーラブルではない。

したがって、プロダクション環境においては、クラウドネイティブなKMS(Key Management Service)をバックエンドとした暗号化プロバイダへ移行することが絶対条件となる。

—

3. 外部シークレットマネージャーの統合(AWS KMS / GCP Cloud KMS)

プログレッシブなインフラストラクチャでは、クラウドプロバイダが提供するハードウェアセキュリティモジュール(HSM)に裏打ちされたKMSをPulumiの暗号化プロバイダとして直結させる。

AWS KMSを使用したバックエンド設定

AWS環境でPulumiを運用する場合、次のようにKMSをバックエンドに指定する。

1. AWS側でのKMSキー作成

aws kms create-key –description “Pulumi Secrets Provider Key”
# 返却された KeyId または ARN を控えておく (例: arn:aws:kms:us-east-1:123456789012:key/xxxx-xxxx)

2. Pulumiスタックの暗号化プロバイダの変更
既存のスタック、または新規スタック作成時にKMSを指定する。

pulumi stack init production –secrets-provider=”awskms://arn:aws:kms:us-east-1:123456789012:key/xxxx-xxxx?region=us-east-1″

すでにPassphrase方式で作成しているスタックであっても、以下のコマンドでKMSへ移行(リプロテキスト)できる。

pulumi stack change-secrets-provider “awskms://arn:aws:kms:us-east-1:123456789012:key/xxxx-xxxx?region=us-east-1”

GCP Cloud KMSを使用したバックエンド設定

GCPエコシステムであれば、Cloud KMSのCryptoKeyを指定する。

1. GCP側でのKesleyリソース作成

gcloud kms keyrings create pulumi-keyring –location=global
gcloud kms keys create pulumi-secret-key –location=global \
–keyring=pulumi-keyring \
–purpose=encryption

2. Pulumiスタックの暗号化プロバイダの変更

pulumi stack change-secrets-provider “gcpkms://projects/my-gcp-project/locations/global/keyRings/pulumi-keyring/cryptoKeys/pulumi-secret-key”

この構成により、Pulumi CLIは暗号化/復号の権限をIAMロール経由でAWS/GCPのKMSに完全に委譲する。シークレットの平文データがPulumiのSaaSバックエンド(Pulumi Service)に保存されることもない(Pulumi Serviceは暗号化されたバイト列のみを保持する)。

—

4. コード&パイプラインにおける極限の安全対策(コードレビュー・静的解析)

どれほど強力なKMSを使おうとも、開発者がうっかりコード内にハードコードしてしまっては意味がない。
ここでは、TypeScript / Python / Go等のコードベースおよびCI/CDパイプラインにおいて、シークレットの漏洩を機械的に阻止するための実践的アプローチを示す。

① 独自カスタムバリデーターによる静的解析(TypeScript例)

コードレビューの目視に頼るな。AST(抽象構文木)解析やカスタムESLintルールを導入し、`pulumi.secret()` を介さずに生の機密情報をリソースプロパティに渡しているコードをビルド時に検知・排除する。

以下は、TypeScript環境におけるカスタムチェックの概念を応用した、安全なシークレット運用のラッパーコードの設計例である。

import as pulumi from “@pulumi/pulumi”;
import as aws from “@pulumi/aws”;

interface SecureDatabaseConfig {
username: string;
// 型レベルで通常のstringではなく、Output(つまりSecretsプロパティ)を強制する
password: pulumi.Output;
}

function createSecureDatabase(name: string, config: SecureDatabaseConfig) {
return new aws.rds.Instance(name, {
engine: “postgres”,
instanceClass: “db.t3.micro”,
username: config.username,
password: config.password, // ここに平文を渡すとTypeScriptのコンパイルエラーまたは型不一致を起こす設計にする
skipFinalSnapshot: true,
});
}

// ————————————————————————-
// 呼び出し側の実装
// ————————————————————————-
// Configから安全にシークレットとしてロード
const config = new pulumi.Config();
const dbPassword = config.requireSecret(“dbPassword”); // 必ず –secret で登録された値であることを強制

const db = createSecureDatabase(“app-db”, {
username: “dbadmin”,
password: dbPassword, // 正しくOutputとして渡される
});

export const dbEndpoint = db.endpoint;

② CI/CDパイプラインでの自動スキャン(Checkov / Trivy)

GitHub ActionsやGitLab CIのパイプラインにおいて、Pulumiが生成するプラン(Plan JSON)やソースコードをスキャンし、ハードコードされた機密情報やセキュアでない設定を検出する。

以下は、GitHub Actionsでの `Checkov` 統合パイプラインの極限まで最適化されたワークフロー定義だ。

name: Pulumi Production Deploy

on:
push:
branches:

  • main

jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # AWS OIDC連携用
contents: read
steps:

  • name: Checkout Repository

uses: actions/checkout@v4

  • name: Authenticate to AWS (OIDC)

uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsPulumiDeployRole
aws-region: us-east-1

  • name: Setup Node.js

uses: actions/setup-node@v4
with:
node-version: ’20’
cache: ‘npm’

  • name: Install Dependencies

run: npm ci

  • name: Run Checkov Static Analysis (IaC Security)

uses: bridgecrewio/checkov-action@master
with:
framework: pulumi
output_format: cli
soft_fail: false # 脆弱性やシークレット検出時は即座にパイプラインを落とす

  • name: Pulumi Preview & Up

uses: pulumi/actions@v5
with:
command: up
stack: production
cloud-url: s3://my-pulumi-state-bucket-production
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}

—

5. エキスパートのためのメモリ最適化とシークレットハンドリングのハック

大規模なマイクロサービスアーキテクチャでは、数百に及ぶシークレット(DBパスワード、JWTシークレット、外部APIキー)をPulumiで管理することになる。ここで問題になるのがメモリ消費量とNode.jsプロセスのヒープサイズだ。

非同期ロードとメモリ効率の極限追求

TypeScript/Node.jsランタイムで動作するPulumiプログラムにおいて、全てのシークレットを起動時に一括で同期ロードすると、V8エンジンのガベージコレクションに負荷がかかり、メモリリークやOOM(Out Of Memory)Killerの発動を誘発する。

これを回避するため、シークレットの参照は可能な限り遅延評価(Lazy Evaluation)を活用し、必要なリソースのスコープ内でのみ `pulumi.secret()` および `config.requireSecret()` を呼び出すべきである。

また、シークレットの値をデバッグ目的で `console.log()` や `pulumi.log.info()` に出力する行為は厳禁だ。Pulumiはマスカレーディング機能によって自動マスクを試みるが、文字列結合(テンプレートリテラル)などを行った場合、マスク機構がバイパスされ、CI/CDのコンソールログに平文が露見するリスクがある。

【アンチパターン】

// ❌ 危険:文字列テンプレートリテラルを使用すると、マスクが機能せず平文がログに出力されるリスクがある
const pwd = config.requireSecret(“dbPassword”);
pwd.apply(p => {
console.log(`Connecting with password: ${p}`); // ログに平文が出る!
});

【ベストプラクティス】

// 〇 安全:Outputのスコープ内で完結させ、外部に出力を一切漏らさない
const pwd = config.requireSecret(“dbPassword”);
const connectionString = pulumi.interpolate`postgres://admin:${pwd}@db.internal:5432/app`;
// connectionString 自体も自動的に Secret Output として伝搬される

—

結び:インフラストラクチャの信頼性はシークレット管理の厳格さに比例する

PulumiにおけるSecrets管理の本質は、「暗号化アルゴリズムの選択」にとどまらない。それは、コードの型システム、クラウドプロバイダのIAM/KMS、CI/CDのパイプライン、そしてエンジニアのコーディング規律のすべてを貫く「セキュリティの垂直統合」である。

Passphraseによる手動運用を捨て、AWS KMS / GCP Cloud KMSによるハードウェアレベルの暗号化を導入し、静的解析による自動ガードレールを張り巡らせる。この境地に達したとき、あなたのインフラストラクチャは真の意味で「コードとして安全に、かつ自律的にスケールする」要塞となる。

妥協のない設計で、次のステージのDevOpsを切り拓いてほしい。

タイトルとURLをコピーしました