PulumiでIAMポリシーの安全性を限界まで高める:Policy as Codeによるセキュリティ自動化パイプライン
テックリードの私たちが日々の開発で直面する最大のジレンマは、「デプロイのスピード」と「セキュリティの堅牢性」の両立だ。特にAWSのIAM(Identity and Access Management)設計において、開発スピードを優先するあまり `Action: “”` や `Resource: “”` といった危険なワイルドカードが野放しになり、セキュリティレビューで差し戻される……という不毛なやり取りに疲弊していないだろうか。
「後から人間がレビューする」という属人化したプロセスは、クラウドネイティブのスピード感においては既に破綻している。
今回は、Pulumi と Policy as Code(CrossGuard)、そして静的解析ツールを組み合わせ、「危険なIAMポリシーをコンパイル段階およびCI/CDパイプラインで物理的にデプロイさせない」ための究極のセキュリティ自動化アーキテクチャを解説する。
—
1. 現場の生産性を爆発させる開発環境の極意
セキュアなパイプラインを構築する前に、まず我々エンジニア自身の手元の開発体験(DX)を極限まで引き上げなければならない。セキュリティチェックが「開発者の邪魔をする足枷」になっては本末転倒だからだ。
必携の神プラグイン & 拡張機能
- VS Code / Cursor: `Pulumi` 公式拡張機能
TypeScriptやPythonでリソースを定義する際、補完と型推論の精度が劇的に上がる。IAMのJSON構文を直接書く苦痛から解放され、AWS型定義に基づいた安全なコーディングが可能になる。
- VS Code: `Cfn-Linter` および `IAM Linter`
インラインポリシーやJSON文字列としてIAMを記述する際のエディタ内リアルタイムバリデーション。
開発スピードを加速するキーボードショートカット(VS Code)
- `Cmd + Shift + V` (Mac) / `Ctrl + Shift + V`: Pulumi Previewのトリガー(カスタムタスク設定推奨)
コードの変更からインフラ差分の確認までを指先一動作で行う。
- `F1` -> `Pulumi: Refresh`
ステートの不整合に怯える時間をゼロにする。
—
2. Pulumi CrossGuardによるPolicy as Codeの実装
Pulumiには、デプロイ実行前(`pulumi preview` や `pulumi up` の瞬間)にリソースの構成をプログラムで検証・拒否するフレームワーク CrossGuard(ポリシーパック) が組み込まれている。
ここでは、「IAMポリシーで `Resource: “”` かつ `Effect: “Allow”` を絶対に許可しない」という組織の鉄則をコードで強制する。
ポリシーパックのプロジェクト構成
security-policies/
├── package.json
├── tsconfig.json
└── index.ts # ポリシー定義本体
実装コード: `index.ts`
TypeScriptを用いて、AWS IAMポリシーの抽象構文木(AST)またはプロパティを走査し、ワイルドカードを検知して即座にエラーを吐くポリシーを記述する。
import as policies from “@pulumi/policies”;
import as aws from “@pulumi/aws”;
// IAMポリシーにワイルドカード資源が含まれていないかを検証するポリシーパック
const disallowWildcardResourcesInIam = new policies.Policy({
name: “disallow-wildcard-resources-in-iam”,
description: “セキュアなIAM設計のため、IAMポリシーでのワイルドカードリソースの使用を禁止します。”,
severity: “mandatory”, // 違反時はデプロイを強制停止
resourceValidation: {
// 対象リソースをIAMポリシーおよびIAMロールに限定
resources: [
aws.iam.Policy,
aws.iam.Role,
aws.iam.UserPolicy,
aws.iam.RolePolicy,
],
validate: (args, reportViolation) => {
let policyDocument: any;
// リソースタイプに応じたパース処理
if (args.resourceType === “aws:iam/policy:Policy” || args.resourceType === “aws:iam/userPolicy:UserPolicy” || args.resourceType === “aws:iam/rolePolicy:RolePolicy”) {
try {
policyDocument = typeof args.props.policy === “string”
? JSON.parse(args.props.policy)
: args.props.policy;
} catch (e) {
reportViolation(“IAMポリシーのJSONパースに失敗しました。構文を確認してください。”);
return;
}
} else if (args.resourceType === “aws:iam/role:Role”) {
// assumeRolePolicyの検証
policyDocument = typeof args.props.assumeRolePolicy === “string”
? JSON.parse(args.props.assumeRolePolicy)
: args.props.assumeRolePolicy;
}
if (!policyDocument || !policyDocument.Statement) {
return;
}
// ステートメントを走査し、危険なワイルドカードを検知
for (const statement of policyDocument.Statement) {
if (statement.Effect === “Allow”) {
const resources = Array.isArray(statement.Resource) ? statement.Resource : [statement.Resource];
// リソースに “” が含まれているか、またはActionに危険な権限があるか
const hasGlobalWildcard = resources.includes(“”);
if (hasGlobalWildcard) {
// 特定の例外(例: CloudWatchLogsへの書き込み等)を除外するロジックをここに挟むことも可能
reportViolation(
`[SECURITY ERROR] 危険なワイルドカードリソース (“Resource”: “”) が検出されました。` +
`最小権限の原則(Least Privilege)に従い、特定のリソースARNを指定してください。` +
`該当Action: ${JSON.stringify(statement.Action)}`
);
}
}
}
},
},
});
// ポリシーパックとしてエクスポート
export const policyPack = new policies.PolicyPack(“aws-security-guardrails”, {
policies: [
disallowWildcardResourcesInIam,
],
});
—
3. サードパーティ製静的解析との多層防御(Defense in Depth)
PulumiのCrossGuardだけでも強力だが、さらに外部の静的解析ツール(例: `Checkov` や `Tfsec` のPulumi対応版、あるいは `Conftest` (Rego))をパイプラインに組み込むことで、多層防御を構築する。
Pulumiは `pulumi preview –json` コマンドで、実行されるインフラの全計画(Plan)をJSON形式で完全に出力できる。このJSON出力をサードパーティ製Linterに食わせることで、IaCコードの段階では見抜けなかった動的生成値に起因する脆弱性をもキャッチできる。
CI/CDでのパイプライン統合手順(GitHub Actionsのベストプラクティス)
チーム開発において、このポリシーチェックを「うっかりバイパス」されない仕組みこそがテックリードの腕の見せ所だ。以下のワークフローは、プルリクエスト作成時に自動でCrossGuardと静的解析を走らせ、違反があれば即座にPRをマージ不可にする。
name: Pulumi Security Guardrails Pipeline
on:
pull_request:
branches:
- main
paths:
- ‘infrastructure/’
jobs:
security-check:
name: Policy as Code & Static Analysis
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./infrastructure
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ’20’
cache: ‘npm’
- name: Install Dependencies
run: npm ci
- name: Setup Pulumi CLI
uses: pulumi/actions@v5
- name: Run Pulumi Preview with Policy Pack Enforcement
uses: pulumi/actions@v5
with:
command: preview
stack: production
# CrossGuardのポリシーパックを指定してプレビューを実行
policy-pack: ../security-policies
comment: true
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# 万が一ポリシー違反があれば、上記ステップが非ゼロ終了コードで落ちるため、
# 後続のジョブやマージは自動的にブロックされる。
—
4. チーム開発で共有すべき設定とベストプラクティス
組織全体でこのセキュリティ自動化をスケールさせるためには、ルールの共有と例外処理のハンドリングが不可欠である。
1. 設定の共有化(組織レベルのポリシー管理)
各リポジトリに個別のセキュリティルールを書かせるのはDRY原則に反する。セキュリティポリシーパック(先ほどの `security-policies`)は独立したプライベートNPMパッケージまたはGitリポジトリとして管理し、各インフラリポジトリからは依存関係としてインポート・参照させること。これにより、セキュリティチームがポリシーをアップデートした瞬間、全プロダクトのCI/CDで一斉に最新のルールが強制される。
2. やむを得ない場合の「エスケープハッチ(例外処理)」の設計
厳格すぎるセキュリティは時として開発を殺す。「どうしても一時的にワイルドカードが必要」というレガシーシステムとの統合や急な障害対応のため、明示的な例外コメントやタグが付与されている場合のみポリシーをスルーする仕組み(エスケープハッチ)を用意する。
// 例: メタデータに bypassReason が記載されている場合は警告に留める、または許可する
if (args.props.tags && args.props.tags[“SecurityBypassReason”]) {
console.warn(`[WARN] セキュリティポリシーの例外が許可されました: ${args.props.tags[“SecurityBypassReason”]}`);
return; // バイパス
}
※注意: このエスケープハッチの乱用を防ぐため、バイパスタグが付いたPRにはセキュリティチームへの自動メンション(Code Owners機能等)が飛ぶように設定しておくのがプロの技だ。
—
最後に:セキュリティは「仕組み」で担保せよ
「気をつけてコードを書いてください」「レビューでしっかり見つけてください」という精神論に頼ったインフラ管理の時代は終わった。
Pulumiの強力なプログラマビリティとCrossGuardを組み合わせることで、「セキュアに書く方が楽で、危険なコードはそもそもデプロイできない世界」をコードの力で強制的に作り上げることができる。
今すぐ手元のリポジトリにPolicy as Codeを導入し、レビュー指摘の赤入れに費やしていた時間を、真に価値のあるプロダクト開発の時間へと奪い返してほしい。