AWS Terraform のベストプラクティス(業界標準の望ましい実装方法)による作成ガイダンスと、移行スキルによって生成された Terraform のポリシーゲート(承認・検査機能)です。terraform/ ディレクトリへの書き込みが行われるあらゆる段階で読み込まれます。 まず「何を出力するか」という指針ルールとセキュリティの基準仕様として機能し、その後、実際に書き込んだ後の検証フェーズで確定的な検査結果を示します。 **読み取り専用です** — 生成された Terraform が基準を満たしているかを報告するだけで、.tf ファイルを編集したり、.phase-status.json に手を加えたり、フェーズの完了を判定することはありません。terraform fmt/init/validate といった既存ツールを補完する役割であり、これらに代わるものではありません。
Best-practice authoring guidance AND a read-only policy gate for AWS Terraform generated by a migration skill. Load during any phase that writes a terraform/ directory — first as the "what to emit" posture rules + security-baseline spec, then after writing as the deterministic policy verdict. Read-only: it reports whether the generated Terraform passes; it never edits .tf files, never touches .phase-status.json, and never decides phase completion. Complements (does not replace) terraform fmt/init/validate.
共有編集ガイドと検証判定の生成ツールです(ワークフロー(処理の流れ)ではありません)。AWS Terraform を生成するフェーズで、以下の2つの質問に答えます:
編集前 — 「生成される terraform/ フォルダはどのセキュリティ方針に従うべきか?」
(セキュリティ規則 + アカウント強化仕様の baseline.tf)
編集後 — 「生成された terraform/ はポリシーをクリアしたか?」
(判定結果(読み取り専用)+ 機械が読める形の詳細報告)
このスキルは、呼び出し元の生成フロー内の2つの箇所で使用されます。その間に呼び出し元が自分たちの Terraform 編集作業を挿みます。呼び出し元がこのスキルを読み込むときに、どの箇所かを指定し、対応する部分を読んでください:
| 呼び出し元の状態 | 読み込む場所 | 理由 |
|---|---|---|
terraform/ を編集する直前 |
Part 1 → references/security-posture-rules.md |
「何を出力するか」という AWS 編集ルール(チェック対象 + 編集時に必須 + コンプライアンス条件付き) |
terraform/ 編集完了、検証準備完了 |
Part 2 → references/terraform-validation.md + ゲートスクリプトを実行 |
フォーマット → 初期化 → 検証 → ポリシーの流れと、読み取り専用の判定結果 |
このスキルが述べることはすべてクラウドプロバイダに依存しない AWS Terraform です。GCP や Heroku の検出やファイル読み込みは呼び出し元の役割です。ルールが呼び出し元が知っている事実(宣言されたコンプライアンス枠組みなど)を必要とする場合、呼び出し元は 呼び出し元コンテキスト信号 として渡します。詳しくは references/security-posture-rules.md の「呼び出し元コンテキスト信号」を参照してください。
このユニットは検証判定を作る専用、編集はしません。書き込み操作は出力する JSON 判定だけです。特に以下はしません:
.tf ファイルの編集・フォーマット・書き直し(修正は呼び出し元の責任).phase-status.json やその他の実行状態ファイルの読み書き(インタプリター(実行環境)管理)呼び出し元(マイグレーション(移行)スキルの生成フェーズ)の責任:修正と再試行のループで .tf を編集、terraform fmt 自動適用、再試行・スキップ・中止の選択肢、フェーズ完了ゲート、すべての .phase-status.json 書き込み。呼び出し元スキルの生成フェーズを参照し、判定結果をこれらの決定に活かしてください。
利用者:
gcp-to-aws(文章形式の生成)とheroku-to-aws(DSL(定義言語)形式の生成)。契約はクラウドプロバイダに依存しません。各呼び出し元が自分たちの生成の流儀に合わせて2つの箇所を組み込みます——gcp-to-aws は文章ステップで、heroku-to-aws はフラグメント(断片)ステップ + インタプリター強制の fail-closed_postconditionsアサート(検証)で。
terraform/ 編集前に読み込む)references/security-posture-rules.md のセキュリティ方針を満たす Terraform を生成してください。
これは「良い AWS Terraform とは何か」というルールです。従うと Part 2 のチェックは自動的に通ります。このユニットは呼び出し元のファイルを読みません——呼び出し元が渡すコンテキスト信号だけを使います。
対象範囲。
security-posture-rules.mdは3つのレベルで説明します:
- チェック対象(Part 2 で静的に確認):ALB(ロードバランサー) TLS、パブリックデータベース禁止、RDS + ElastiCache の暗号化、パブリック DB ポート禁止、パブリック管理・データストアポート禁止、IAM ワイルドカード禁止。
- 編集時必須(ゲートでは確認できない、ただし必須):削除保護、シークレットマネージャー経由のマスターパスワード、S3 強化、Fargate/EKS/ECR 設定、プライベートサブネット配置、バックアップ、基本的な監視。
- コンプライアンス条件付き(呼び出し元が
soc2/pci/hipaa/fedrampを宣言したときに出力):VPC フローログ、S3 アクセスログ、シークレット自動ローテーション、カスタマー管理の KMS(暗号化キー)。呼び出し元自身の 生成の責務(将来ここに移行する候補):アカウント強化の
baseline.tfレイヤー(CloudTrail、GuardDuty、Config、Security Hub)。
terraform/ 編集後に実行)生成されたディレクトリに対して読み取り専用チェッカーを実行してください。スクリプトパスはプラグインルートからの相対パスで指定してください(プラグインが他のスクリプトにも使う同じ慣例):
python3 "$PLUGIN_ROOT/skills/tf-best-practices/scripts/validate-terraform-policy.py" "$TERRAFORM_DIR" --json "$VERDICT_PATH"
$TERRAFORM_DIR — 必須、呼び出し元が指定:生成された terraform/ ディレクトリ(例:$MIGRATION_DIR/terraform)。このスキルは自動探索しません——呼び出し元が Terraform を書き込んだパスを必ず指定してください。--json $VERDICT_PATH — オプション。呼び出し元が自分たちの validation-report.json にマージできる、機械が読む形の判定結果を書き込みます。ポリシーチェックは大きな検証の流れの1段階です(フォーマット → 初期化 → 検証 → ポリシー)。全体の流れ——オフライン時のフォールバック動作や、ポリシー判定が validation-report.json にどう反映されるか——は references/terraform-validation.md に書かれています。その流れは説明的なもので、呼び出し元 がフォーマット・初期化・検証を実行、修正と再試行のループ、レポート書き込みを管理します。このユニットは読み取り専用ポリシーステップ + 判定形式だけを提供します。
| コード | 標準出力 | 意味 | 呼び出し元の処置 |
|---|---|---|---|
0 |
POLICY_OK |
ポリシー満たしている | 進行する |
1 |
POLICY_FAIL |
違反がある | violations[] を読む、該当 .tf を編集、再実行(呼び出し元の再試行回数制限内) |
2 |
(使用エラー) | パス不正・IO エラー | ユーザーに表示。成功としない |
--json){
"check": "policy",
"policy_status": "POLICY_OK | POLICY_FAIL",
"violations": [
{
"check": "policy",
"rule": "alb_https_listener | alb_http_redirect | no_tf_files",
"file": "compute.tf",
"line": 7,
"severity": "error",
"summary": "人間が読める違反説明",
"fix_hint": "呼び出し元が適用できる具体的な修正方法"
}
]
}
各 violations[] エントリは実行可能な証拠です。file + line + fix_hint で、呼び出し元は何を編集するか正確にわかります。呼び出し元が編集を適用。このユニットは報告するだけです。
すべてのルールは曖昧さに対して失敗しない——確実かつ明確なブロック内リテラル(直値)証拠のときだけ発火し、有効なスタックは絶対にブロックされません(POLICY_FAIL は呼び出し元にとって完了を止めるゲートなので、誤検知が実際のマイグレーションを止めてしまいます)。
インターネット公開 ALB の TLS 方針(ALB は internal がない・false・変数の場合にインターネット公開——安全に失敗):
alb_https_listener — 443 に HTTPS リスナーがあり、certificate_arn と forward アクション がある必要があります。alb_http_redirect — HTTP :80 リスナーは HTTPS へredirect する必要があります。forward でターゲットに送ってはいけません。内部向け ALB(internal = true)は対象外。Elastic Beanstalk(AWS の PaaS)の ALB はこれらのルール対象外です。 このルールはスタンドアロン
aws_lb_listenerブロックを検査します。EB の LoadBalanced 環境は ALB をaws_elastic_beanstalk_environmentのsettingブロックから作られ、静的チェッカーはそれを読みません——つまり、EB 専用の設計はリスナールール を「無条件パス」します(検査するリスナーがない)。EB リスナー/TLS 方針は編集時必須で、チェック対象ではありません。(フィクスチャ(テスト用データ)のgood-heroku-eb-onlyとgood-heroku-eb-singleinstanceこれを示し、good-heroku-eb-loadbalancedはスタンドアロン ALB を持つので実際のブロックでリスナールールが実行されます。)
マネージドデータベースの公開と暗号化(aws_db_instance、aws_rds_cluster):
rds_not_public — publicly_accessible = true を設定しない(ない・変数 → 安全に失敗)。rds_encryption_at_rest — storage_encrypted = true を設定。欠落または直値 false で発火(RDS のデフォルトは未暗号化)。変数は安全に失敗。S3 はチェック対象外(2023年1月からデフォルト SSE-S3)。ElastiCache 暗号化(aws_elasticache_replication_group):
elasticache_encryption_at_rest — at_rest_encryption_enabled = true を設定。欠落または直値 false で発火、変数は安全に失敗。aws_elasticache_cluster(Memcached)はチェック対象外。セキュリティグループのインバウンド許可:
db_sg_no_public_ingress — インライン aws_security_group のインバウンドで 5432/3306 を許可する場合、0.0.0.0/0 や ::/0 を許可してはいけません。sg_no_public_admin_ingress — インラインインバウンドは、厳選された公開禁止ポート(22、3389、6379、11211、27017、9200/9300、5601)を 0.0.0.0/0 や ::/0 に開いてはいけません。Web(80/443)とアプリ・ゲームポートは対象外。DB ポートは上記ルール対象。どちらも cidr_blocks と ipv6_cidr_blocks を独立して確認し、IPv4 が安全でも IPv6 が開いていないか見ます。どちらも、別リソース aws_security_group_rule / aws_vpc_security_group_ingress_rule は安全に失敗(相関なし)。IAM 最小権限の原則(aws_iam_policy、aws_iam_role_policy、aws_iam_group_policy、aws_iam_user_policy):
no_wildcard_iam — Allow ステートメントは Action/Resource の "*" を使ってはいけません。A shared authoring guide and verdict producer, not a workflow. It answers two questions for a phase that generates AWS Terraform:
terraform/ follow?"
(the posture rules + the baseline.tf account-hardening spec)terraform/ pass policy?" (a deterministic,
read-only verdict + a machine-readable report)This skill is entered at two touchpoints in the caller's Generate flow, with the caller's own terraform-authoring work in between. The caller states which touchpoint it is at when it loads this skill, and reads the corresponding part:
| Caller context | Load | Why |
|---|---|---|
About to author terraform/ (before writing) |
Part 1 → references/security-posture-rules.md |
The "what to emit" AWS authoring rules (gate-enforced + authoring-only + compliance-conditional). |
terraform/ written, ready to validate (after writing) |
Part 2 → references/terraform-validation.md + run the gate script |
The fmt → init → validate → policy protocol and the read-only verdict. |
Everything this skill states is source-cloud-agnostic (pure AWS Terraform). Any GCP/Heroku
detection or artifact reading is the caller's job; where a rule needs a caller-known fact (e.g.
declared compliance frameworks), the caller passes it as a caller-context signal — see
references/security-posture-rules.md § Caller-context signals.
This unit is a verdict producer, never a mutator. Its entire write surface is the JSON verdict it is asked to emit. Specifically it MUST NOT:
.tf file (the caller owns remediation),.phase-status.json or any run-state file (interpreter-owned),The caller (a migration skill's Generate phase) owns: the fix-and-retry loop that edits
the .tf it generated, terraform fmt auto-apply, the retry/skip/abort prompt, the
Phase Completion gate, and every .phase-status.json write. See the consuming skill's
generate phase for how the verdict feeds those decisions.
Consumers:
gcp-to-aws(prose Generate) andheroku-to-aws(DSL Generate). The contract is source-agnostic; each caller wires the two touchpoints in its own Generate idiom — gcp-to-aws as prose steps, heroku-to-aws as a fragment step plus a fail-closed_postconditionsassert enforced by the interpreter.
terraform/)Emit generated Terraform that satisfies the posture in
references/security-posture-rules.md.
These are the "what good AWS Terraform looks like" rules. Following them makes the Part 2 gate pass by construction. This unit does not read the caller's artifacts — it consumes only caller-context signals the caller passes in.
Scope.
security-posture-rules.mdcovers, in three tiers:
- Gate-enforced (Part 2 verifies statically): ALB TLS, no-public-database, RDS + ElastiCache encryption-at-rest, no-public-DB-port ingress, no-public admin/datastore-port ingress, no-wildcard-IAM.
- Authoring-only (not gate-checkable, still required):
deletion_protection, master-password-via-Secrets-Manager, S3 hardening, Fargate/EKS/ECR settings, private-subnet placement, backups, baseline monitoring.- Compliance-conditional (emitted when the caller declares
soc2/pci/hipaa/fedramp): VPC flow logs, S3 access logging, secret rotation, customer-managed KMS.Still the caller's own generation concern (candidates to migrate here later): the account-hardening
baseline.tflayer (CloudTrail, GuardDuty, Config, Security Hub).
terraform/)Run the read-only checker against the generated directory. Resolve the script path relative to
the plugin root ($PLUGIN_ROOT/skills/tf-best-practices/scripts/...), the same convention the
plugin uses for its other scripts:
python3 "$PLUGIN_ROOT/skills/tf-best-practices/scripts/validate-terraform-policy.py" "$TERRAFORM_DIR" --json "$VERDICT_PATH"
$TERRAFORM_DIR — required, caller-supplied: the generated terraform/ directory
(e.g. $MIGRATION_DIR/terraform). This skill never defaults or discovers it — the caller
always passes the path it wrote Terraform to.--json $VERDICT_PATH — optional; writes a machine-readable verdict the caller can merge
into its own validation-report.json.The policy check is one stage of a larger validation flow (fmt → init → validate → policy).
The full protocol — including offline-fallback behavior and how the policy verdict maps into a
validation-report.json — is documented in
references/terraform-validation.md. That protocol is
descriptive: the caller owns the fmt/init/validate execution, the fix-and-retry loop, and
the report write; this unit contributes only the read-only policy stage + verdict shape.
| Exit | stdout | Meaning | Caller does |
|---|---|---|---|
0 |
POLICY_OK |
posture satisfied | proceed |
1 |
POLICY_FAIL |
violations present | read violations[], edit the named .tf sites, re-run (caller's retry budget) |
2 |
(usage error) | bad path / IO | surface to user; do not treat as pass |
--json){
"check": "policy",
"policy_status": "POLICY_OK | POLICY_FAIL",
"violations": [
{
"check": "policy",
"rule": "alb_https_listener | alb_http_redirect | no_tf_files",
"file": "compute.tf",
"line": 7,
"severity": "error",
"summary": "human-readable violation",
"fix_hint": "concrete remediation the caller can apply"
}
]
}
Each violations[] entry is actionable evidence — file + line + fix_hint tell the
caller exactly what to edit. The caller applies the edit; this unit only reports.
Every rule is fail-open on ambiguity — it fires only on unambiguous, in-block literal
evidence, so a valid stack is never falsely blocked (a POLICY_FAIL is a hard completion gate
for the caller, so a false positive would block a real migration).
Internet-facing ALB TLS posture (an ALB is internet-facing when internal is absent,
false, or variable-driven — fail-safe):
alb_https_listener — must have an HTTPS listener on 443 with certificate_arn and a
forward action.alb_http_redirect — an HTTP :80 listener must redirect to HTTPS, never forward
to targets. Internal ALBs (internal = true) are exempt.Elastic Beanstalk ALBs are invisible to these rules. The ALB rules inspect standalone
aws_lb_listenerblocks. An EB LoadBalanced environment provisions its ALB fromaws_elastic_beanstalk_environmentsettingblocks, which the static checker does not read — so a pure-EB design passes the ALB rules vacuously (no listener to inspect). EB listener/TLS posture is therefore authoring-only, not gate-enforced. (Fixturesgood-heroku-eb-onlyandgood-heroku-eb-singleinstancedocument this;good-heroku-eb-loadbalancedcarries a standalone ALB so the listener rules are exercised on real blocks.)
Managed database exposure & encryption (aws_db_instance, aws_rds_cluster):
rds_not_public — must not set publicly_accessible = true (absent/variable → fail-open).rds_encryption_at_rest — must set storage_encrypted = true; missing or literal false
fires (RDS defaults to unencrypted), variable-driven fails open. S3 is not checked (default
SSE-S3 since Jan 2023).ElastiCache encryption (aws_elasticache_replication_group):
elasticache_encryption_at_rest — must set at_rest_encryption_enabled = true; missing
or literal false fires, variable-driven fails open. aws_elasticache_cluster (Memcached)
not checked.Security group ingress:
db_sg_no_public_ingress — an inline aws_security_group ingress covering 5432/3306
must not allow 0.0.0.0/0 or ::/0.sg_no_public_admin_ingress — an inline ingress must not open a curated never-public
admin/datastore port (22, 3389, 6379, 11211, 27017, 9200/9300, 5601) to
0.0.0.0/0 or ::/0. Web (80/443) and app/game ports are not flagged; DB ports are
handled by the rule above. Both check cidr_blocks and ipv6_cidr_blocks independently, so a
benign IPv4 list does not mask an open IPv6 one. Both: separate aws_security_group_rule /
aws_vpc_security_group_ingress_rule resources fail open (not correlated).IAM least-privilege (aws_iam_policy, aws_iam_role_policy, aws_iam_group_policy,
aws_iam_user_policy):
no_wildcard_iam — an Allow statement must not use Action/Resource "*".
The one narrow exception is an isolated
elasticbeanstalk:CreateStorageLocation statement with Resource = "*" because
AWS does not support resource-level permissions for that action.
aws_iam_policy_document data sources and assume-role trust policies fail open.The checker is a zero-dependency static HCL reader (no
terraform init, no provider download) — it runs even when the registry is unreachable. It uses brace-depth matching for nested blocks, so a valid HTTPS listener written with a nestedforward { ... }block is not a false failure.
fixtures/terraform-policy/ holds intentionally-shaped Terraform used by
scripts/test_validate_terraform_policy.py:
bad-http-forward/ — internet-facing ALB that forwards plaintext HTTP → MUST POLICY_FAIL.internal-alb-only/ — internal ALB on HTTP → MUST POLICY_OK (HTTP allowed internally).good-https-redirect/ — the correct pattern → POLICY_OK.These are deliberately non-compliant test data (never deployed). They are excluded from the
repo-wide checkov scan via .checkov.yaml skip-path; do not "harden" them — doing so
breaks the tests that assert the failure paths.
# from skills/tf-best-practices/
uv run --python 3.12 --with pytest python -m pytest scripts/test_validate_terraform_policy.py -q
原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。