IAMの地獄から抜け出せ:Pulumi CrossGuardと静的解析で築く「ゼロ・トラスト」IaCパイプライン
クラウドインフラストラクチャの規模が拡大するにつれ、IAM(Identity and Access Management)の管理はSREにとって終わりのない悪夢と化す。開発速度のプレッシャーに押され、誰かが書いた`”Resource”: “”`と`”Action”: “s3:”`を含むポリシードキュメント。それがプルリクエストをすり抜け、本番環境にデプロイされた瞬間、あなたのクラウド環境は外敵に対して無防備な要塞となる。
「コードレビューで防ぐ」という属人化したアプローチは、複雑化した現代のインフラストラクチャの前では完全に破綻している。我々は人間を信じるのをやめ、コードの実行フェーズとCI/CDパイプラインの二段構えで、過剰な権限の生成を物理的に阻止するメカニズムを構築しなければならない。
本稿では、Pulumiのプログラム実行時、プレビュー時、そしてCI/CDパイプラインの深部において、IAMポリシーの安全性を限界まで高めるセキュリティ自動化パイプラインの構築手法を、骨の髄まで解説する。
—
1. アーキテクチャ概要:多層防御によるIAMセーフティネット
安全性を限界まで高めるためには、単一のツールに依存してはならない。以下の3つのレイヤーでポリシーを検閲・強制する。
1. レイヤー1(開発時 / ランタイム): Pulumiプログラムの動的評価による即時検知。
2. レイヤー2(プレビュー時 / Policy as Code): Pulumi CrossGuard(Policy Packs)による、リソースグラフ全体を俯瞰したポリシー強制。
3. レイヤー3(CI/CDパイプライン / 静的解析): `Checkov`や`Conftest (Rego)`を用いた、生成されたCloudFormation/JSONテンプレートレベルでの構文・セキュリティ監査。
この鉄壁のパイプラインにより、脆弱なIAMポリシーが`pulumi up`の実行に到達することは物理的に不可能になる。
—
2. Pulumiカスタムポリシー(CrossGuard)によるポリシーの強制
Pulumi CrossGuardは、インフラストラクチャのデプロイ前にポリシーを適用するためのフレームワークである。ここでは、TypeScriptを用いて「IAMポリシーにおけるワイルドカード(“)の乱用」と「管理者権限(`AdministratorAccess`等)の付与」を検出し、デプロイを強制停止するPolicy Packを実装する。
2.1 Policy Packのプロジェクト構成
iam-guard-policy/
├── package.json
├── tsconfig.json
└── index.ts
2.2 厳格なポリシー定義コード (`index.ts`)
import as policy from “@pulumi/policy”;
import { parse } from “node-html-parser”; // 必要に応じてパース用
/
- IAMポリシーのアクションおよびリソースに危険なワイルドカードが含まれていないかを検証する
/
const disallowWildcardPermissions = new policy.Policy({
name: “disallow-wildcard-iam-permissions”,
description: “IAMポリシーにおいて、危険なアクションやリソースに対するワイルドカードの使用を禁止します。”,
severity: “critical”, // 違反時はデプロイを即座に失敗させる
enforcement: “mandatory”, // advisory(警告のみ)ではなく mandatory(強制阻止)
resourceTypes: [“aws:iam/policy:Policy”, “aws:iam/rolePolicy:RolePolicy”],
validate: (args, reportViolation) => {
const policyDocument = args.props.policy;
// ポリシーが文字列(JSON)として渡される場合をパース
let doc: any;
try {
doc = typeof policyDocument === “string” ? JSON.parse(policyDocument) : policyDocument;
} catch (e) {
reportViolation(“IAMポリシーのJSONパースに失敗しました。構文を確認してください。”);
return;
}
if (!doc.Statement) {
return;
}
const statements = Array.isArray(doc.Statement) ? doc.Statement : [doc.Statement];
for (const statement of statements) {
// EffectがAllowの場合のみ検証
if (statement.Effect !== “Allow”) {
continue;
}
const actions = Array.isArray(statement.Action) ? statement.Action : [statement.Action];
const resources = Array.isArray(statement.Resource) ? statement.Resource : [statement.Resource];
// 1. 全権限(:)の検知
const hasFullAdmin = actions.includes(“”) && resources.includes(“”);
if (hasFullAdmin) {
reportViolation(
`[SECURITY CRITICAL] 検出: 完全な管理者権限 (Action: , Resource: ) が付与されています。` +
`ゼロ・トラスト原則に基づき、最小権限の原則(PoLP)を適用してください。`
);
}
// 2. 危険なサービスに対するワイルドカードアクションの検知(例: iam:, s3:)
for (const action of actions) {
if (action.endsWith(“:”) || action === “”) {
reportViolation(
`[SECURITY HIGH] 検出: ワイルドカードを含む広範な権限アクション ‘${action}’ が許可されています。` +
`具体的なアクションを指定してください。`
);
}
}
}
},
});
// ポリシーパックのエクスポート
// Pulumiエンジンはこのエクスポートを読み取り、評価エンジンに組み込む
export const awsSecurityPolicies: policy.PolicyPack = {
name: “aws-iam-security-guardrails”,
policies: [
disallowWildcardPermissions,
],
};
3. CI/CDパイプライン統合:完全自動化スクリプト
ローカル開発環境だけでなく、GitHub ActionsやGitLab CIなどのパイプライン上でCrossGuardと静的解析ツール(Checkov)を統合する。ここではGitHub Actionsのワークフローを例に、妥協のない自動化パイプラインを示す。
`.github/workflows/pulumi-security-pipeline.yml`
name: “Pulumi Security & Policy Pipeline”
on:
pull_request:
branches:
- main
push:
branches:
- main
jobs:
security-and-deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-requests: write
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ’20’
cache: ‘npm’
- name: Setup Python (for Checkov)
uses: actions/setup-python@v5
with:
python-version: ‘3.11’
- name: Install Checkov (Static Analysis)
run: |
pip install –no-cache-dir checkov
- name: Install Pulumi CLI
uses: pulumi/action-install-pulumi@v3
- name: Install Dependencies
run: |
npm ci
cd iam-guard-policy && npm ci && cd ..
- name: AWS Assume Role (OIDC Authentication)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_IAM_ROLE_ARN }}
aws-region: ap-northeast-1
- name: Run Pulumi Preview with Policy Pack (CrossGuard)
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
run: |
# CrossGuardのPolicy Packを指定してプレビューを実行
# ポリシー違反(mandatory)がある場合、このコマンドは非ゼロで終了しパイプラインを破壊する
pulumi preview \
–stack production \
–policy-pack ./iam-guard-policy \
–suppress-outputs
- name: Run Checkov on Pulumi/CloudFormation Artifacts
run: |
# Pulumiが生成する中間表現またはインフラコードに対し、Checkovで静的解析を実行
# CKV_AWS_111 (IAM policy should not allow access) などを厳格にチェック
checkov -d . –framework terraform,cloudformation,arm –soft-fail: false
—
4. 低レイヤ&エキスパート知見:高度なカスタマイズとパフォーマンス最適化
大規模なインフラストラクチャ(数万リソース規模)をPulumiで管理する場合、ポリシーの検証プロセスがボトルネックになることがある。メモリ消費と実行速度を極限まで最適化するための知見を共有する。
4.1 ポリシー評価におけるメモリーリークの防止
TypeScriptによるPolicy Packの実装時、大規模なアレイ操作や巨大なJSONオブジェクトのディープコピー(`JSON.parse(JSON.stringify(…))`など)をループ内で多用すると、V8エンジンのガベージコレクションが追いつかず、メモリ消費量が急増する。
対策:
- AST(抽象構文木)やポリシードキュメントのパース結果は、モジュールスコープでのキャッシュ機構を自作するか、必要なプロパティのみをO(N)のアルゴリズムで走査すること。
- 不要な正規表現(RegEx)のループ内での生成を避け、コンパイル時にグローバルスコープでインスタンス化する。
4.2 動的リソースグラフ解析による「暗黙的エスカレーション」の検知
単純なIAMポリシー文字列のチェックだけでは、セキュリティ・ホールを見落とすことがある。例えば、`iam:PassRole`権限と`lambda:CreateFunction`権限が別々のステートメントに分散して存在する場合、攻撃者は特権ロールをアタッチしたLambda関数を作成し、実質的な管理者昇格(Privilege Escalation)を達成できる。
これを防ぐため、CrossGuardの`validateStack`フックを使用し、スタック内の全リソースの関連性(リソースグラフ)を横断的に解析する。
import as policy from “@pulumi/policy”;
/
- スタック全体のリソースグラフを解析し、危険な権限の組み合わせを検出する
/
const detectPrivilegeEscalation = new policy.StackPolicy({
name: “detect-privilege-escalation”,
description: “IAM PassRole とリソース作成権限の組み合わせによる昇格パスを検知します。”,
enforcement: “mandatory”,
validate: (args, reportViolation) => {
// スタック内のすべてのIAMポリシーおよびロールを取得
const iamPolicies = args.resources.filter(r => r.isType(“aws:iam/policy:Policy”) || r.isType(“aws:iam/role:Role”));
let hasPassRole = false;
let hasCreateResource = false;
for (const resource of iamPolicies) {
const doc = resource.props.policy || resource.props.assumeRolePolicyDocument;
if (!doc) continue;
const parsed = typeof doc === “string” ? JSON.parse(doc) : doc;
const statements = Array.isArray(parsed.Statement) ? parsed.Statement : [parsed.Statement];
for (const stmt of statements) {
if (stmt.Effect !== “Allow”) continue;
const actions = Array.isArray(stmt.Action) ? stmt.Action : [stmt.Action];
if (actions.some((a: string) => a === “iam:PassRole” || a === “iam:”)) {
hasPassRole = true;
}
if (actions.some((a: string) => a.startsWith(“lambda:Create”) || a.startsWith(“ec2:RunInstances”))) {
hasCreateResource = true;
}
}
}
if (hasPassRole && hasCreateResource) {
reportViolation(
“[SECURITY ARCHITECTURE VIOLATION] 危険な権限の組み合わせが検知されました: ” +
“iam:PassRole とリソース作成系権限が同一スタック内に混在しています。” +
“意図しない権限昇格経路につながるおそれがあります。”
);
}
},
});
—
5. 結び:セキュリティは「文化」ではなく「強制力」である
「開発者にはセキュリティ意識を持ってもらうべきだ」という綺麗事は、深夜の障害対応や切迫したデリバリーの前では無力である。人的ミスを前提としたシステム設計こそが、真に成熟したSRE組織の証左である。
Pulumiのプログラム言語表現力、CrossGuardによるPolicy as Code、そして静的解析パイプラインの三位一体化により、インフラストラクチャの安全性は「祈るもの」から「数学的に保証されるもの」へと進化する。
今すぐ既存のIaCリポジトリにこの仕組みを組み込み、脆弱なIAMポリシーがデプロイされる余地を完全に断ち切れ。