Terraform Skill
Anton BabenkoCommunityCreated by Anton Babenko
- Skill
- Tool integration
- Cloud and DevOps
- Apache-2.0
- Medium risk
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured workflow for managing Terraform and OpenTofu configurations. It emphasizes a diagnose-before-generate approach, requiring explicit context such as runtime versions, provider details, and environment criticality. It includes a response contract to ensure risk-aware planning, validation, and rollback procedures, while offering specific guidance on module hierarchy, state management, security, and testing strategies.
What it does
- Diagnoses infrastructure failure modes like identity churn, state corruption, and secret exposure.
- Enforces a response contract requiring version floors, risk assessment, and validation plans.
- Provides patterns for module development, directory layout, and naming conventions.
- Offers guidance on CI/CD pipeline stages, including plan artifact review and drift prevention.
- Facilitates safe state management, including backend selection and locking protocols.
- Supports semantic code navigation using terraform-ls or fallback search tools.
When to use it
- When writing or reviewing Terraform/OpenTofu modules and configurations.
- When debugging CI/CD pipeline failures or infrastructure drift.
- When planning complex refactors or state migrations.
- When configuring remote state backends or implementing security compliance checks.
Advantages
- Reduces risk of destructive changes through mandatory plan-destroy protocols.
- Improves code maintainability via standardized module and variable contracts.
- Ensures consistent security practices through integrated policy and secret handling.
- Provides clear version-aware guidance to prevent common configuration errors.
Usage examples
- “I am refactoring a module and worried about resource identity churn. How should I proceed?”
- The agent will guide you through using 'moved' blocks, comparing 'count' vs 'for_each' patterns, and validating the plan to ensure no unexpected resource destruction occurs.
- “How do I securely manage secrets in my Terraform configuration?”
- The agent will advise on using cloud-native secret managers, 'sensitive' variable flags, or 'write_only' arguments, and will check your implementation against security best practices.
- “Help me set up a CI/CD pipeline for my infrastructure.”
- The agent will provide a structured pipeline template including validation, testing, plan artifact generation, and environment-protected apply stages.
- “My state file is locked and I cannot run any operations. What should I do?”
- The agent will diagnose the state backend, verify locking mechanisms, and provide recovery steps based on your specific provider and backend configuration.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/antonbabenko/terraform-skill/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip terraform-skill.zip -d .agents/skills
Documentation by Anton Babenko
Terraform Skill for Claude
Diagnose-first guidance for Terraform and OpenTofu. Core file is a workflow; depth lives in references loaded on demand.
Response Contract
Every Terraform/OpenTofu response must include:
Assumptions & version floor — runtime (
terraformortofu), exact version, providers, state backend, execution path (local/CI/Cloud/Atlantis), environment criticality. State assumptions explicitly if the user did not provide them.Risk category addressed — one or more of: identity churn, secret exposure, blast radius, CI drift, compliance gaps, state corruption, provider upgrade risk, testing blind spots.
Chosen remediation & tradeoffs — what was chosen, what was traded off, why.
Validation plan — exact commands (
fmt -check,validate,plan -out, policy check) tailored to runtime and risk tier.Rollback notes — for any destructive or state-mutating change: how to undo, what evidence to keep.
Never recommend direct production apply without a reviewed plan artifact and approval.
Never run terraform destroy (targeted or full) without first running terraform plan -destroy and showing the user every resource that will be deleted — including implicit dependents pulled in via locals or for_each. Get explicit confirmation before proceeding. Never use -auto-approve on destroy.
Workflow
Capture execution context — runtime+version, provider(s), backend, execution path, environment criticality.
Diagnose failure mode(s) using the routing table below. If intent spans categories, load both references.
Load only the matching reference file(s) — do not preload depth the task does not need.
Propose fix with risk controls — why this addresses the mode, what could still go wrong, guardrails (tests/approvals/rollback).
Generate artifacts — HCL, migration blocks (
moved,import), CI changes, policy rules.Validate before finalizing — run validation commands tailored to risk tier.
Emit the Response Contract at the end.
Diagnose Before You Generate
| Failure category | Symptoms | Primary references |
|---|---|---|
| Identity churn | Resource addresses shift after refactor, count index churn, missing moved blocks | Code Patterns: count vs for_each, Code Patterns: moved blocks, Code Patterns: LLM mistakes |
| Secret exposure | Secrets in defaults, state, logs, CI artifacts | Security & Compliance, Code Patterns: write-only, State Management |
| Blast radius | Oversized stacks, shared prod/non-prod state, unsafe applies | State Management, Module Patterns |
| Destroy cascade | Targeted destroy deletes more than expected; locals referencing a targeted resource make all for_each consumers implicit dependents | Response Contract: plan-destroy first; State Management: Safe Destroy |
| CI drift | Local plan ≠ CI plan, apply without reviewed artifact, unpinned versions | CI/CD Workflows, Code Patterns: versions |
| Compliance gaps | Missing policy stage, no approval model, no evidence retention | Security & Compliance, CI/CD Workflows |
| Testing blind spots | Plan-only validation of computed values, set-type indexing, mock/real confusion | Testing Frameworks |
| State corruption / recovery | Stuck lock, backend migration, drift reconciliation | State Management |
| Provider upgrade risk | Breaking-change provider bump, unpinned modules | Code Patterns: versions, Module Patterns |
| Provider lifecycle | Removing a provider with resources still in state, orphaned resources, removed block usage | State Management: Provider Removal |
| Bootstrap / orchestration misuse | null_resource + local-exec for bootstrap, remote-exec for setup scripts, provisioner stdout leaking secrets in CI logs | Code Patterns: Provisioners as Last Resort |
| Navigation / safe-rename blind spots | Cannot locate symbol defs/refs semantically, value-symbol rename done as blind text replace, grep-only refactor missing refs, hallucinated rg shim | Code Intelligence |
| Cross-cloud / provider mapping | "What's the Azure/GCP equivalent of X", picking a backend/auth model per cloud | State Management: Cross-cloud equivalents |
When to Use This Skill
Activate when: creating or reviewing Terraform/OpenTofu configurations or modules, setting up or debugging tests, structuring multi-environment deployments, implementing IaC CI/CD, choosing module patterns or state organization, configuring or migrating remote state backends.
Don't use for: basic HCL syntax questions Claude already knows, provider API reference (link to docs), cloud-platform questions unrelated to Terraform/OpenTofu.
Core Principles
Module Hierarchy
| Type | When to Use | Scope |
|---|---|---|
| Resource module | Single logical group of connected resources | VPC + subnets, SG + rules |
| Infrastructure module | Collection of resource modules for a purpose | Multiple resource modules in one region/account |
| Composition | Complete infrastructure | Spans multiple regions/accounts |
Flow: resource → resource module → infrastructure module → composition.
Directory Layout
environments/ # prod/ staging/ dev/ — per-env configurations
modules/ # networking/ compute/ data/ — reusable modules
examples/ # minimal/ complete/ — docs + integration fixturesSeparate environments from modules. Use examples/ as both documentation and test fixtures. Keep modules small and single-responsibility.
See Module Patterns for architecture principles, naming conventions, variable/output contracts.
Naming Conventions (summary)
Descriptive resource names (
aws_instance.web_server, notaws_instance.main)Reserve
thisfor genuine singleton resources onlyPrefix variables with context (
vpc_cidr_block, notcidr)Standard files:
main.tf,variables.tf,outputs.tf,versions.tf
See Module Patterns: Variable Naming and Code Patterns: Block Ordering for examples.
Block Ordering (summary)
Resource blocks: count/for_each first → arguments → tags → depends_on → lifecycle.
Variable blocks: description → type → default → validation → nullable → sensitive.
See Code Patterns: Block Ordering & Structure for the full rules and examples.
Testing Strategy
Decision Matrix: Which Testing Approach?
| Situation | Approach | Tools | Cost |
|---|---|---|---|
| Quick syntax check | Static analysis | validate, fmt | Free |
| Pre-commit validation | Static + lint | validate, tflint, trivy, checkov | Free |
| Terraform 1.6+, simple logic | Native test framework | terraform test | Free-Low |
| Pre-1.6, or Go expertise | Integration testing | Terratest | Low-Med |
| Security/compliance focus | Policy as code | OPA, Sentinel | Free |
| Cost-sensitive workflow | Mock providers (1.7+) | Native tests + mocks | Free |
| Multi-cloud, complex | Full integration | Terratest + real infra | Med-High |
Native Test Rules (1.6+)
Before writing test code: validate resource schemas via Terraform MCP so assertions target real attributes.
command = plan— fast, for input-derived values onlycommand = apply— required for computed values (ARNs, generated names) and set-type nested blocksSet-type blocks cannot be indexed with
[0]— useforexpressions or materialize viacommand = applyCommon set types: S3 encryption rules, lifecycle transitions, IAM policy statements
See Testing Frameworks for static-analysis pipelines, native-test patterns, Terratest integration, mock providers, and the full LLM-mistake checklist.
Count vs For_Each — Quick Rule
| Scenario | Use | Why |
|---|---|---|
| Boolean condition (create / don't) | count = condition ? 1 : 0 | Optional singleton toggle |
| Items may be reordered or removed | for_each = toset(list) | Stable resource addresses |
| Reference by key | for_each = map | Named access |
| Multiple named resources | for_each | Better identity stability |
Never use list index as long-lived identity — removing a middle element reshuffles every address after it. For the decision matrix, safe migration playbook, moved block patterns, and known-at-plan failure cases, see Code Patterns: count vs for_each.
Locals for Dependency Management
Using try() in a local to prefer a conditional resource's attribute over its parent is a specialized but high-value pattern — it forces correct deletion order without explicit depends_on. Common use: VPC + secondary CIDR associations + subnets.
See Code Patterns: Locals for Dependency Management for the full pattern and worked example.
Module Development
Standard layout:
my-module/
├── README.md # Usage documentation
├── main.tf # Primary resources
├── variables.tf # Typed inputs with descriptions
├── outputs.tf # Output values
├── versions.tf # required_version + required_providers
├── examples/
│ ├── minimal/
│ └── complete/
└── tests/
└── module_test.tftest.hcl # or Go for TerratestVariable contracts: always description, always explicit type, use validation for complex constraints, use sensitive = true for secrets, prefer optional() with typed defaults (1.3+) over untyped map(any).
Output contracts: always description, mark sensitive outputs, expose stable subsets (not whole provider objects).
See Module Patterns for the full contract patterns, module release checklist, and LLM-mistake checklist.
CI/CD
Pipeline stages: validate → test → plan → apply (with environment protection).
Cost control: mock providers on PR validation, real-cloud integration only on main or scheduled, tag test resources, auto-cleanup.
Drift prevention: pin runtime and providers, commit .terraform.lock.hcl, apply the reviewed plan artifact from the plan stage (do not re-run plan inside the apply job), run policy/security stage on every path to apply.
See CI/CD Workflows for GitHub Actions, GitLab CI, and Atlantis templates plus the LLM-mistake checklist.
Security & Compliance
Essential checks:
trivy config .
checkov -d .Don't: store secrets in variables or .tfvars, use default VPC, skip encryption, open security groups to 0.0.0.0/0, use inline ingress/egress blocks in aws_security_group.
Do: source secrets from a cloud secret manager (AWS Secrets Manager / Azure Key Vault / GCP Secret Manager) or use write_only arguments on 1.11+, create dedicated VPCs, enforce encryption at rest and TLS, least-privilege SGs, use separate aws_vpc_security_group_{ingress,egress}_rule resources (e.g. AWS provider v5+).
Marking a variable sensitive = true masks display only — the value still lives in state. Use write_only / *_wo on 1.11+, or keep secret material out of Terraform entirely via runtime lookups.
See Security & Compliance for trivy/checkov pipelines, state-file hardening, compliance mappings, and the LLM-mistake checklist.
State Management
Never use local state in teams or production. Remote backends provide automatic locking, encryption, versioning, audit logging, and safe collaboration.
Choosing a Remote Backend
AWS example (Azure azurerm / GCP gcs / TF Cloud syntax: see State Management: Choosing a Remote Backend):
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/vpc/terraform.tfstate"
region = "us-east-1"
encrypt = true
use_lockfile = true # Native S3 locking, 1.10+
}
}On Terraform < 1.10, use dynamodb_table = "terraform-state-lock" instead of use_lockfile. Azure Storage, GCS, and Terraform Cloud all offer built-in locking - see the State Management reference for syntax. For choosing among backends and their locking models, see Choosing a Remote Backend.
State Organization
| Pattern | Use When | Example Path |
|---|---|---|
| Per environment | Different teams per env | prod/terraform.tfstate, staging/... |
| Per component | Independent lifecycles | prod/vpc/, prod/eks/, prod/rds/ |
| Hybrid (recommended) | Both benefits | prod/networking/, prod/compute/, staging/networking/ |
Split state when: different teams, different update cadences, or >500 resources. Combine when: tightly coupled resources, <100 resources, same lifecycle.
See State Management for locking, migration, multi-team isolation, disaster recovery, and the LLM-mistake checklist.
Version Management
| Component | Strategy | Example |
|---|---|---|
| Terraform runtime | Pin minor | required_version = "~> 1.9" |
| Providers | Pin major | version = "~> 5.0" |
| Modules (prod) | Pin exact | version = "5.1.2" |
| Modules (dev) | Allow patch | version = "~> 5.1" |
Commit .terraform.lock.hcl intentionally. Keep provider/runtime upgrades in a separate PR from functional changes. See Code Patterns: Version Management for constraint syntax and upgrade workflow.
Modern Terraform Features (1.0+)
| Feature | Min version | Common use |
|---|---|---|
try() | 0.13+ | Safe fallbacks, replaces element(concat()) |
nullable = false | 1.1+ | Prevent null silently overriding defaults |
moved blocks | 1.1+ | Refactor without destroy/recreate |
optional() with defaults | 1.3+ | Typed object attributes |
import blocks | 1.5+ | Declarative imports, reviewable in VCS |
check blocks | 1.5+ | Runtime assertions |
Native terraform test | 1.6+ | Built-in test framework |
| Mock providers | 1.7+ | Cost-free unit testing |
removed blocks | 1.7+ | Declarative resource removal |
| Provider-defined functions | 1.8+ | Provider-specific transformations (requires provider to declare functions) |
| Cross-variable validation | 1.9+ | Reference other var.* in validation blocks |
write_only arguments | 1.11+ | Secrets never stored in state |
| S3 native lock-file | 1.10+ | State locking without DynamoDB |
Before emitting a feature, verify the runtime floor. See Code Patterns: Feature Guard Table for the full table with common LLM error patterns per feature.
Runtime-Specific Guidance
Terraform 1.0-1.5 (OpenTofu starts at 1.6): Terratest for integration, static analysis + plan validation only (no native tests).
1.6+: native
terraform test/tofu testavailable — migrate simple unit tests, keep Terratest for complex integration.1.7+: mock providers cut test cost — mock for unit tests, real runs for final integration.
1.10+: S3 native lock-file (
use_lockfile) is the correct default for new configurations — DynamoDB locking is no longer required.1.11+:
write_onlyarguments for secret handling keep credentials out of state.Terraform vs OpenTofu: both supported. For licensing, governance, and feature delta, see Quick Reference: Terraform vs OpenTofu.
Code Intelligence (terraform-ls)
Semantic navigation for HCL. terraform-ls is optional; without it every row below degrades to a disclosed rg + Read fallback.
Self-contained terraform-ls layer of a generic code-intelligence discipline - apply the rows below directly. Recommended companion: the code-intelligence plugin (same antonbabenko/agent-plugins marketplace) carries the generic discipline (position anchoring, degradation gate, disclosure format, anti-phantom-shim) and ships /code-intelligence:doctor for readiness. If it is installed, defer to its generic protocol; this skill stays fully self-contained without it.
| Goal | Use | Tradeoff |
|---|---|---|
| Find definition / all references | terraform-ls goToDefinition / findReferences | Needs init + a position anchor |
| Rename value symbol (var/local/output/provider alias) | Manual: findReferences -> per-file fresh Read -> edit -> validate | No rename provider |
| Rename resource/module address | moved block + plan shows 0 destroy | Text rename forces destroy/recreate |
Exact text / known name / .tfvars / non-HCL | rg + Read | No semantic scope |
✅ Supported: goToDefinition, findReferences, documentSymbol, hover, workspaceSymbol.
❌ Unsupported: goToImplementation, call hierarchy, rename provider. Do not call these then report their absence as a finding.
✅ Prereq: local
terraform/tofuon PATH,terraform initrun; cold start may need one retry.✅ LSP calls are position-anchored (
file:line:character) - anchor withrgfirst, never symbol-name-only.❌ Do not claim "LSP broken, using rg" until the Degradation Gate passes; disclose any tool substitution on the first line.
Depth: Code Intelligence.
Reference Files
Progressive disclosure — essentials here, depth on demand:
Testing Frameworks — static analysis, native tests, Terratest, mock providers
Module Patterns — structure, variable/output contracts,
terraform_remote_staterules, release checklistCI/CD Workflows — GitHub Actions, GitLab CI, Atlantis, cost control
Security & Compliance — trivy/checkov, secrets handling, compliance mappings
State Management — backends, locking, migration, multi-team, recovery
Code Patterns — block ordering,
count/for_eachdeep dive, modern features, version management, localsCode Intelligence - terraform-ls capabilities, position-anchored calls, manual rename, degradation gate
Quick Reference — command cheat sheets, flowcharts, troubleshooting
License
Apache License 2.0. See LICENSE for full terms.
Copyright © 2026 Anton Babenko
Files
- ATTRIBUTION.mdAdded by AutoGineer946 B
- LICENSELicence file added by AutoGineer10.6 KiB
- SKILL.md19.9 KiB
- references/ci-cd-workflows.md13.8 KiB
- references/code-intelligence-lsp.md6.6 KiB
- references/code-patterns.md30.9 KiB
- references/module-patterns.md22.9 KiB
- references/quick-reference.md19.4 KiB
- references/security-compliance.md18.6 KiB
- references/state-management.md45.1 KiB
- references/testing-frameworks.md13.7 KiB
references/security-compliance.md
Security & Compliance
Part of: terraform-skill Purpose: Security best practices and compliance patterns for Terraform/OpenTofu
This document provides security hardening guidance and compliance automation strategies for infrastructure-as-code.
Table of Contents
Security Scanning Tools
Common Security Issues
Compliance Testing
Secrets Management
State File Security
Security Scanning Tools
Essential Security Checks
# Static security scanning
trivy config .
checkov -d .
# Compliance testing (policy-as-code against a terraform plan JSON)
terraform plan -out=tfplan && terraform show -json tfplan > tfplan.json
conftest test tfplan.json --policy policy/Trivy Integration
Install:
# macOS
brew install trivy
# Linux
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# In CI
- uses: aquasecurity/trivy-action@master
with:
scan-type: 'config'
scan-ref: '.'Note: Trivy now includes tfsec's rule set; tfsec itself is in maintenance mode since its absorption into Trivy (2022), but still receives maintenance releases. Both are maintained by Aqua Security.
Example Output:
Result #1 HIGH Security group rule allows egress to multiple public internet addresses
────────────────────────────────────────────────────────────────────────────────
security.tf:15-20
12 | resource "aws_security_group_rule" "egress" {
13 | type = "egress"
14 | from_port = 0
15 | to_port = 0
16 | protocol = "-1"
17 | cidr_blocks = ["0.0.0.0/0"]
18 | security_group_id = aws_security_group.this.id
19 | }Checkov Integration
# Run Checkov
checkov -d . --framework terraform
# Skip specific checks
checkov -d . --skip-check CKV_AWS_23
# Generate JSON report
checkov -d . -o json > checkov-report.jsonCommon Security Issues
❌ DON'T: Store Secrets in Variables
# BAD: Secret in plaintext
variable "database_password" {
type = string
default = "SuperSecret123!" # ❌ Never do this
}✅ DO: Use Secrets Manager
# Good: Reference secrets from AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/database/password"
}
resource "aws_db_instance" "this" {
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}<a id="secret-string-state-caveat"></a>
Note — data source
secret_stringpersists to state: Theaws_secretsmanager_secret_versiondata source readssecret_stringinto Terraform state during refresh.password_wo(AWS provider v5.71+, Terraform 1.11+) keeps the resource argument out of state, but the data source still persists the value. For true state exclusion:
Prefer
manage_master_user_password = true(AWS-managed, for RDS)Use
ephemeralproviders/resources (Terraform 1.10+)Inject via CI environment variable outside Terraform
Examples below use the data-source pattern; apply one of the alternatives above when the value must not land in state.
❌ DON'T: Use Default VPC
# BAD: Default VPC has public subnets
resource "aws_instance" "app" {
ami = "ami-12345"
subnet_id = "subnet-default" # ❌ Avoid default resources
}✅ DO: Create Dedicated VPCs
# Good: Custom VPC with private subnets
resource "aws_vpc" "this" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
}
resource "aws_subnet" "private" {
vpc_id = aws_vpc.this.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
}❌ DON'T: Skip Encryption
# BAD: Unencrypted S3 bucket
resource "aws_s3_bucket" "data" {
bucket = "my-data-bucket"
# ❌ No encryption configured
}✅ DO: Enable Encryption at Rest
# Good: Enable encryption
resource "aws_s3_bucket" "data" {
bucket = "my-data-bucket"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}SSE-S3 vs SSE-KMS:
AES256above is SSE-S3 (AWS-managed key, no per-request audit trail in CloudTrail). For regulated workloads (HIPAA/PCI/FedRAMP), preferaws:kmswith a customer-managed CMK + key rotation enabled.
❌ DON'T: Open Security Groups to Internet
# BAD: Security group open to internet on all protocols
resource "aws_security_group_rule" "allow_all" {
type = "ingress"
from_port = 0
to_port = 0
protocol = "-1" # ❌ All protocols (worst case)
cidr_blocks = ["0.0.0.0/0"] # ❌ Never do this
security_group_id = aws_security_group.this.id
}✅ DO: Use Least-Privilege Security Groups
# Good: Restrict to specific ports and sources
resource "aws_security_group_rule" "app_https" {
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"] # ✅ Internal only
security_group_id = aws_security_group.this.id
}❌ DON'T: Use Inline Security Group Rules
# BAD: Inline ingress/egress blocks
resource "aws_security_group" "web" {
name = "web-sg"
description = "Web server security group"
vpc_id = aws_vpc.this.id
ingress { # ❌ Inline rules cause issues
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"]
}
egress { # ❌ Avoid inline rules
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}✅ DO: Use Separate Security Group Rule Resources
Preferred (AWS provider v5+): Use aws_vpc_security_group_ingress_rule / aws_vpc_security_group_egress_rule:
# Best: Modern individual rule resources (AWS provider v5+)
resource "aws_security_group" "web" {
name = "web-sg"
description = "Web server security group"
vpc_id = aws_vpc.this.id
# No inline rules - managed separately
}
resource "aws_vpc_security_group_ingress_rule" "web_https" {
security_group_id = aws_security_group.web.id
description = "HTTPS from internal VPC"
cidr_ipv4 = "10.0.0.0/16"
from_port = 443
to_port = 443
ip_protocol = "tcp"
}
# Scope egress to needed ports when possible — avoid 0.0.0.0/0 with ip_protocol = "-1"
resource "aws_vpc_security_group_egress_rule" "web_https_out" {
security_group_id = aws_security_group.web.id
description = "HTTPS to external services"
cidr_ipv4 = "0.0.0.0/0"
from_port = 443
to_port = 443
ip_protocol = "tcp"
}Also acceptable: aws_security_group_rule (older but still supported):
resource "aws_security_group_rule" "web_https_ingress" {
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"]
security_group_id = aws_security_group.web.id
}Why avoid inline rules:
| Issue | Inline Rules | Separate Resources |
|---|---|---|
| Rule changes | Recreates entire SG (downtime) | Updates only the rule |
| Mixing approaches | Conflicts and overwrites | N/A - consistent pattern |
| Dynamic rules | Complex dynamic blocks needed | Native for_each per resource |
| State management | Rules buried in SG state | Each rule tracked separately |
| Conditional rules | Complex nested dynamics | Simple count or for_each |
Compliance Testing
Policy-as-code for Terraform plans
Generate a plan JSON and evaluate it with a policy engine. The modern, actively-maintained options are Conftest (OPA/Rego) and Open Policy Agent directly. The terraform-compliance BDD project is archived and no longer maintained; prefer Conftest/OPA for new work.
# Generate plan JSON
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
# Evaluate with Conftest (OPA under the hood)
conftest test tfplan.json --policy policy/Open Policy Agent (OPA)
# policy/s3_encryption.rego
package terraform.s3
# AWS provider v4+ moved S3 encryption to the separate
# aws_s3_bucket_server_side_encryption_configuration resource.
# Iterate those resources and verify the rule block sets an accepted algorithm.
valid_algorithms := {"aws:kms", "aws:kms:dsse", "AES256"}
# Collect buckets that have an encryption config with a valid algorithm
encrypted_buckets[bucket] {
sse := input.resource_changes[_]
sse.type == "aws_s3_bucket_server_side_encryption_configuration"
rule := sse.change.after.rule[_]
algo := rule.apply_server_side_encryption_by_default[_].sse_algorithm
valid_algorithms[algo]
bucket := sse.change.after.bucket
}
deny[msg] {
sse := input.resource_changes[_]
sse.type == "aws_s3_bucket_server_side_encryption_configuration"
rule := sse.change.after.rule[_]
algo := rule.apply_server_side_encryption_by_default[_].sse_algorithm
not valid_algorithms[algo]
msg := sprintf(
"S3 encryption config '%s' uses unsupported sse_algorithm '%s' (expected aws:kms or AES256)",
[sse.address, algo],
)
}
# Flag buckets that have no matching encryption configuration at all.
deny[msg] {
bucket := input.resource_changes[_]
bucket.type == "aws_s3_bucket"
bucket_name := bucket.change.after.bucket
not encrypted_buckets[bucket_name]
msg := sprintf(
"S3 bucket '%s' has no aws_s3_bucket_server_side_encryption_configuration",
[bucket.address],
)
}Secrets Management
AWS Secrets Manager Pattern
See the data-source secret_string persistence caveat above — both random_password.result and data-source reads of secret_string land in Terraform state. The recommended RDS pattern avoids both.
# Recommended: let RDS generate and manage the master password in Secrets Manager
resource "aws_kms_key" "db" {
description = "KMS CMK for RDS-managed master password"
enable_key_rotation = true
deletion_window_in_days = 30
}
resource "aws_db_instance" "this" {
# Option 1 (recommended): AWS-managed master password in Secrets Manager
manage_master_user_password = true
master_user_secret_kms_key_id = aws_kms_key.db.arn
# Option 2 (Terraform 1.11+ + AWS provider v5.71+): write-only password
# password_wo = ephemeral.random_password.db.result
# password_wo_version = 1
# ...
}If you need a manually-managed secret for a non-RDS consumer, keep the value out of state by sourcing it outside Terraform (CI env var, ephemeral resource, or a write-only argument) rather than via random_password + a data lookup:
# Only use this shape when the consumer cannot use manage_master_user_password
# and you are comfortable with the caveat linked above.
resource "aws_secretsmanager_secret" "app_api_key" {
name = "prod/app/api-key"
description = "Third-party API key"
recovery_window_in_days = 30
}
# secret_string populated out-of-band (console, CLI, or a write-only argument on
# providers that support it) — not via random_password stored in state.Environment Variables
# Never commit these
export TF_VAR_database_password="secret123"
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"In .gitignore:
*.tfvars
.env
secrets/State File Security
Encrypt State at Rest
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/vpc/terraform.tfstate"
region = "us-east-1"
encrypt = true # Enables SSE on PUT
kms_key_id = "arn:aws:kms:us-east-1:ACCOUNT:key/KEY-ID" # Customer-managed CMK
use_lockfile = true # Terraform 1.10+
}
}
encrypt = truealone is SSE-S3 (AWS-managed AES-256 key, no per-request CloudTrail audit trail). State often holds secrets, so pairencrypt = truewithkms_key_idpointing at a customer-managed CMK.use_lockfile = true(Terraform 1.10+) replaces the need for a DynamoDB lock table.
Secure State Bucket
resource "aws_s3_bucket" "terraform_state" {
bucket = "my-terraform-state"
}
# Enable versioning (protect against accidental deletion)
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
# Enable encryption — customer-managed KMS CMK with bucket key to control request costs
resource "aws_kms_key" "terraform_state" {
description = "KMS CMK for Terraform state bucket"
enable_key_rotation = true
deletion_window_in_days = 30
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.terraform_state.arn
}
bucket_key_enabled = true
}
}
# Note: for regulated workloads (HIPAA/PCI/FedRAMP), customer-managed KMS with
# rotation enabled is typically required — SSE-S3 (AES256) is usually insufficient.
# Block public access
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}Restrict State Access
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowListBucket",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/TerraformRole"
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-terraform-state"
},
{
"Sid": "AllowObjectRW",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/TerraformRole"
},
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:GetObjectVersion"
],
"Resource": "arn:aws:s3:::my-terraform-state/*"
},
{
"Sid": "DenyInsecureTransport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-terraform-state",
"arn:aws:s3:::my-terraform-state/*"
],
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
]
}s3:ListBucketmust target the bucket ARN; object actions must target/*— splitting avoids IAM silently no-op'ing the mismatched pairings.s3:DeleteObject+s3:GetObjectVersionare required to rotate state objects when versioning is enabled.The
Denystatement enforces TLS — any HTTP request is rejected regardless of other grants.
IAM Best Practices
✅ DO: Use Least Privilege
# Good: Specific permissions only
resource "aws_iam_policy" "app_policy" {
name = "app-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject"
]
Resource = "arn:aws:s3:::my-app-bucket/*"
}
]
})
}❌ DON'T: Use Wildcard Permissions
# BAD: Overly broad permissions
resource "aws_iam_policy" "bad_policy" {
policy = jsonencode({
Statement = [
{
Effect = "Allow"
Action = "*" # ❌ Never use wildcard
Resource = "*"
}
]
})
}Cross-cloud security map
| Concern | AWS | Azure | GCP |
|---|---|---|---|
| Secret manager | aws_secretsmanager_secret | azurerm_key_vault_secret | google_secret_manager_secret |
| Network firewalling | aws_security_group + aws_vpc_security_group_*_rule | azurerm_network_security_group + azurerm_network_security_rule | google_compute_firewall |
| Identity | IAM (aws_iam_role / aws_iam_policy) | RBAC (azurerm_role_assignment) | IAM (google_project_iam_*) |
| Encryption at rest | explicit (SSE / KMS) | default-on (optional CMK) | default-on (optional CMEK) |
Compliance Checklists
SOC 2 Compliance
[ ] Encryption at rest for all data stores
[ ] Encryption in transit (TLS/SSL)
[ ] IAM policies follow least privilege
[ ] Logging enabled for all resources
[ ] MFA required for privileged access (enforced at org/IdP level, not per-resource)
[ ] Regular security scanning in CI/CD
HIPAA Compliance
[ ] PHI encrypted at rest and in transit
[ ] Access logs enabled
[ ] Dedicated VPC with private subnets
[ ] Regular backup and retention policies
[ ] Audit trail for all infrastructure changes
PCI-DSS Compliance
[ ] Network segmentation (separate VPCs)
[ ] No default passwords
[ ] Strong encryption algorithms
[ ] Regular security scanning
[ ] Access control and monitoring
LLM Mistake Checklist — Security & Compliance
Common model mistakes to correct before returning security/compliance recommendations:
assumes
sensitive = truekeeps the value out of state — it only masks display; usewrite_only/*_woarguments on 1.11+ or an external secret lookupproposes plaintext defaults in
variableblocks or committed.tfvars"for demo convenience"echoes secrets through
provisionercommands orlocal-execstdout into CI logs (see Provisioners as Last Resort for the broader pattern)emits outputs that expose full connection strings or credentials (even when marked
sensitive)mentions a compliance framework (SOC 2, PCI, HIPAA, GDPR, FedRAMP) but provides no enforceable gate — no policy stage, no approval model, no evidence artifact
confuses security best practices with compliance evidence (an encrypted bucket is not the same as a retained audit artifact proving it)
omits artifact retention and access controls for plan JSON exports
ignores data-residency obligations for GDPR/FedRAMP contexts
Resources
Back to: Main Skill File
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
What our scanner noticed· Medium risk
- Instructions that use the network
- Downloads piped into a shell
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Anton Babenko
- Publisher
- Anton Babenko (Community)
- Source
- antonbabenko/terraform-skill · skills/terraform-skill · commit 9c1e92d
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Anton Babenko or Anton Babenko.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download terraform-skill.zip