infrastructure-as-code-guardian · v1.0.0 · 2026-10-28 · sha256 287c6bd80206d90c
infrastructure-as-code-guardian v1.0.0A
Immutable. This exact content never changes and is served at /api/v1/blob/287c6bd80206d90c.
---
name: infrastructure-as-code-guardian
description: Use this skill when authoring, securing, or managing infrastructure as code in Terraform, Pulumi, CloudFormation, Ansible, or Bicep — or when selecting an IaC tool, reviewing modules for security, detecting drift, optimizing cost, or planning a multi-tool migration. Covers AWS, Azure, GCP, and hybrid cloud, with per-tool authoring patterns, CIS/SOC 2 security checklists, drift detection, and cost guardrails across the full IaC lifecycle.
version: 1.0.0
author: Skill Foundry
platforms:
- terraform
- pulumi
- cloudformation
- ansible
- bicep
- aws
- azure
- gcp
- kubernetes
- crossplane
- opentofu
tags:
- infrastructure-as-code
- iac
- terraform
- pulumi
- cloudformation
- ansible
- bicep
- security-hardening
- drift-detection
- state-management
- cost-optimization
- migration
- cicd
- aws
- azure
- gcp
- devops
- sre
- platform-engineering
- cis-benchmark
- compliance
- opentofu
---
# Infrastructure as Code Guardian
Universal IaC agent skill—author, secure, manage, and migrate infrastructure across Terraform, Pulumi, CloudFormation, Ansible, and Bicep.
---
## Activation Triggers
### Positive Triggers (activate on these)
1. "Write a Terraform module for…"
2. "Secure this IaC codebase" / "Harden my infra config"
3. "Check for drift in staging" / "Run a drift detection workflow"
4. "Migrate from CloudFormation to Terraform" (or any cross-tool migration)
5. "Add cost optimization tags to my IaC" / "Reduce cloud costs via IaC"
6. "Set up remote state for this project" / "Fix state locking"
7. "Generate a least-privilege IAM policy from this Terraform"
8. "Review this Pulumi stack for security issues" / "IaC security audit"
9. "Create an Ansible playbook from this existing infra"
10. "Write a Bicep module and convert to ARM" / "Author Bicep for Azure"
11. "Set up GitOps pipeline for infrastructure" / "CI/CD for IaC"
12. "This Terraform plan shows drift—help me reconcile" / "Infra state is out of sync"
13. "What IaC tool should I use for…" / "Pulumi vs Terraform for this project"
14. "Convert Kubernetes YAML manifests to Crossplane compositions"
15. "Generate compliance report for my infrastructure" / "CIS benchmark for IaC"
### Near-Miss Negatives (do NOT activate)
1. "Deploy this Docker container to production" — operational deploy, not IaC authoring
2. "Write a Python script to list S3 buckets" — script authoring, not infrastructure definition
3. "How do I SSH into this EC2 instance?" — operational troubleshooting, not IaC
### Ambiguous Cases (ask clarifying question)
- "Fix my cloud" → ask: "Are you looking to fix IaC definitions, or troubleshoot a running deployment?"
- "Setup AWS" → ask: "Do you want to author infrastructure code, or configure an AWS account manually?"
---
## Tool Selection Decision Tree
When choosing an IaC tool, evaluate the following in order. Present your reasoning explicitly.
```
START
│
├─ Is managed-by-CSP-only acceptable? (no external state)
│ ├─ YES, AWS-only → CloudFormation / AWS CDK
│ ├─ YES, Azure-only → Bicep / ARM
│ └─ NO (multi-cloud needed) → Continue ▼
│
├─ Is the team primarily developers comfortable with
│ general-purpose languages?
│ ├─ YES → Pulumi (TypeScript, Python, Go, C#)
│ └─ NO → Continue ▼
│
├─ Is this a greenfield project with strong community
│ / module ecosystem needs?
│ ├─ YES → Terraform (HCL) or OpenTofu
│ └─ NO → Continue ▼
│
├─ Is configuration management the primary goal
│ (installing packages, managing config files on VMs)?
│ ├─ YES → Ansible
│ └─ NO (declarative infra) → Continue ▼
│
├─ Are you operating inside a Kubernetes-native
│ platform team?
│ ├─ YES → Crossplane
│ └─ NO → Terraform (default fallback with largest ecosystem)
│
└─ OUTCOME: Recommend tool with reasoning.
Always note: Terraform (76% market share) is the safest default.
Pulumi is the fastest-growing (45% YoY) and best for dev-centric teams.
```
### Quick Comparison Matrix
| Criterion | Terraform | Pulumi | CFn | Bicep | Ansible |
|------------------------|-------------|--------------|-------------|-------------|-------------|
| **Language** | HCL | TS/Py/Go/C# | YAML/JSON | Bicep DSL | YAML |
| **State Management** | Explicit | Explicit | Implicit | Implicit | N/A |
| **Multi-Cloud** | Yes | Yes | AWS only | Azure only | Yes |
| **Ecosystem Size** | Largest | Growing fast | Large (AWS) | Medium | Large |
| **Learning Curve** | Moderate | Low (for devs)| Steep | Low | Low |
| **Drift Detection** | plan | refresh | Drift Detect| what-if | check mode |
| **Secret Handling** | Vault/sops | ESC/Vault | KMS/Secrets | Key Vault | Vault/sops |
| **CI/CD Integration** | Excellent | Excellent | Good | Good | Good |
---
## Authoring Guidelines per Tool
### Terraform (HCL)
**Structure:**
```hcl
# Standard module layout
modules/
networking/
main.tf # Primary resources
variables.tf # Input variables with types + descriptions
outputs.tf # Exported values
versions.tf # Provider + Terraform version constraints
README.md # Usage, examples, API surface
```
**HCL Patterns to Enforce:**
- Always pin provider versions in `versions.tf` with `>=` lower bound and `~>` pessimistic constraint
- Use `for_each` over `count` for resource iteration (deterministic keys prevent destroy/recreate cascades)
- Prefix all outputs with the module name to avoid collisions: `output "vpc_id"` → `output "networking_vpc_id"`
- Use `dynamic` blocks sparingly—prefer explicit blocks for readability
- Every variable must have `type` and `description`; sensitive vars must have `sensitive = true`
- Use `terraform-docs` to generate README from variable/output metadata
- Apply `lifecycle { prevent_destroy = true }` on stateful resources (databases, storage buckets)
- Use `lifecycle { ignore_changes = [tags["LastModified"]] }` for externally-managed tag mutations
**State Management:**
```hcl
# Remote state with locking (S3 + DynamoDB example)
terraform {
backend "s3" {
bucket = "myorg-terraform-state-${var.environment}"
key = "networking/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-state-locks"
encrypt = true
}
}
```
**Anti-Patterns to Flag:**
- `terraform plan | sh` (blind apply) → recommend `terraform apply` with review
- Hardcoded ARNs → use `data` sources or `var.*` references
- Missing `required_providers` block → enforce in `versions.tf`
- Embedding secrets in `.tf` files → recommend Vault/sops/ESC
---
### Pulumi (TypeScript / Python)
**TypeScript Patterns:**
```typescript
// Component Resource pattern (Pulumi best practice)
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
interface VpcComponentArgs {
cidrBlock: string;
enableDnsHostnames?: boolean;
tags?: pulumi.Input<{ [key: string]: pulumi.Input<string> }>;
}
export class VpcComponent extends pulumi.ComponentResource {
public readonly vpc: aws.ec2.Vpc;
public readonly publicSubnets: aws.ec2.Subnet[];
public readonly privateSubnets: aws.ec2.Subnet[];
constructor(name: string, args: VpcComponentArgs, opts?: pulumi.ComponentResourceOptions) {
super("myorg:networking:Vpc", name, {}, opts);
this.vpc = new aws.ec2.Vpc(`${name}-vpc`, {
cidrBlock: args.cidrBlock,
enableDnsHostnames: args.enableDnsHostnames ?? true,
tags: { ...args.tags, Name: `${name}-vpc` },
}, { parent: this });
// Register outputs
this.registerOutputs({
vpc: this.vpc,
publicSubnets: this.publicSubnets,
});
}
}
```
**Python Patterns:**
```python
from pulumi import ComponentResource, ResourceOptions, Output
from pulumi_aws import ec2
class VpcComponent(ComponentResource):
def __init__(self, name: str, args: VpcComponentArgs, opts: ResourceOptions = None):
super().__init__("myorg:networking:Vpc", name, {}, opts)
self.vpc = ec2.Vpc(
f"{name}-vpc",
cidr_block=args.cidr_block,
enable_dns_hostnames=True,
tags={"Name": f"{name}-vpc"},
opts=ResourceOptions(parent=self),
)
self.register_outputs({"vpc": self.vpc})
```
**Pulumi-Specific Guardrails:**
- Use `ComponentResource` for all reusable abstractions (never raw `new Resource()` in stacks)
- Stack references for cross-stack data: `new pulumi.StackReference("org/project/stack")`
- Use `pulumi.output` and `apply()` for computed values; avoid `Promise.all` anti-patterns
- Enable Pulumi ESC for secret management; never hardcode secrets in stack configs
- Use `pulumi policy pack` for organization-wide compliance (e.g., `--policy-pack awsguard`)
- Handle `ResourceOptions` propagation: `protect`, `deleteBeforeReplace`, `ignoreChanges`
---
### CloudFormation (YAML)
**Best-Practice Structure:**
```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: >
Production VPC stack with public/private subnets, NAT gateways,
and VPC endpoints for S3 and DynamoDB. (SOC 2 compliant)
Parameters:
Environment:
Type: String
AllowedValues: [dev, staging, prod]
Description: Deployment environment name
VpcCidr:
Type: String
Default: "10.0.0.0/16"
AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/([0-9]|[1-2][0-9]|3[0-2]))$"
Mappings:
SubnetConfig:
us-east-1:
PublicA: "10.0.1.0/24"
PrivateA: "10.0.10.0/24"
Conditions:
IsProduction: !Equals [!Ref Environment, "prod"]
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Environment
Value: !Ref Environment
- Key: ManagedBy
Value: CloudFormation
Outputs:
VpcId:
Value: !Ref VPC
Export:
Name: !Sub "${AWS::StackName}-VpcId"
```
**CloudFormation Rules:**
- Always use `!Sub` over `Fn::Join` for readability
- Export stack outputs with unique names: `${AWS::StackName}-ResourceId`
- Use `Mappings` for region-specific values; `Conditions` for environment branching
- Enable `TerminationProtection` on production stacks
- Use `AWS::CloudFormation::StackSet` for multi-region/multi-account deployments
- Run `cfn-lint` and `cfn-nag` in pre-commit; integrate into CI pipeline
- Avoid embedding secrets in Parameters—use `AWS::SecretsManager::Secret` or `resolve:ssm-secure:/path`
---
### Ansible
**Playbook Structure:**
```yaml
---
# site.yml — Entry-point playbook
- name: Configure production web servers
hosts: webservers
become: true
vars_files:
- vars/production.yml
roles:
- common
- nginx
- node_exporter
pre_tasks:
- name: Verify minimum disk space
ansible.builtin.assert:
that: ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_available') | first > 1073741824
fail_msg: "Insufficient disk space on / "
post_tasks:
- name: Send deployment notification
ansible.builtin.uri:
url: "{{ slack_webhook_url }}"
method: POST
body: '{"text": "Deployment complete on {{ inventory_hostname }}"}'
```
**Ansible IaC Patterns:**
- Use `ansible.builtin.*` FQCNs for all modules (future-proof against collections changes)
- Separate inventory by environment: `inventories/production/hosts.yml`
- Encrypt secrets with `ansible-vault`; never store plaintext in vars files
- Use `check_mode: true` for dry-run validation before production applies
- `--diff` flag for configuration change previews (acts as drift detection)
- Idempotency is mandatory—every task must produce the same result on repeat runs
- Use `ansible-lint` with production ruleset; integrate into pre-commit hooks
---
### Bicep
**Module Patterns:**
```bicep
// modules/network.bicep
param location string = resourceGroup().location
param vnetName string
param addressPrefix string = '10.0.0.0/16'
param subnetPrefixes array = ['10.0.1.0/24', '10.0.2.0/24']
param tags object = {}
@description('Tags to apply to all resources, merged with env tags')
param environmentTags object
var effectiveTags = union(tags, environmentTags)
resource vnet 'Microsoft.Network/virtualNetworks@2024-01-01' = {
name: vnetName
location: location
tags: effectiveTags
properties: {
addressSpace: {
addressPrefixes: [addressPrefix]
}
subnets: [for (prefix, i) in subnetPrefixes: {
name: 'subnet-${i}'
properties: {
addressPrefix: prefix
}
}]
}
}
output vnetId string = vnet.id
output vnetNameOut string = vnet.name
```
**Bicep Best Practices:**
- Use `@description()` decorator on all params and outputs
- Use Bicep parameter files (`.bicepparam`) for environment-specific values
- Run `bicep build` before deployment to catch compile-time errors
- Use `what-if` deployment for change preview (Azure's equivalent of `terraform plan`)
- Pin API versions explicitly; avoid `@latest`-style references in production
- Use modules with semantic version references via module registries (`br:*`)
- Use `existing` keyword for referencing externally-managed resources
---
## Security Hardening Checklist
Execute this checklist on every IaC codebase review. Flag violations with severity and fix suggestions.
### 1. Least Privilege IAM (CRITICAL)
- [ ] No `*` in Action arrays—use explicit action lists
- [ ] No `*` in Resource arrays (except where unavoidable like CloudWatch Logs)
- [ ] No `"Effect": "Allow"` combined with `"Resource": "*"` and `"Action": "*"` (triple-star anti-pattern)
- [ ] S3 bucket policies have explicit `Principal`; no `"Principal": {"AWS": "*"}` unless intentional public access
- [ ] IAM Roles use `AssumeRolePolicyDocument` with least-privilege trust relationships
- [ ] IAM Policies follow the "250 IAM best practices" checklist: condition keys, source IP restriction, MFA enforcement
**Terraform Fix Example:**
```hcl
# BAD — triple-star anti-pattern
data "aws_iam_policy_document" "bad" {
statement {
effect = "Allow"
actions = ["*"]
resources = ["*"]
}
}
# GOOD — least privilege with conditions
data "aws_iam_policy_document" "good" {
statement {
effect = "Allow"
actions = [
"s3:GetObject",
"s3:PutObject",
]
resources = ["${aws_s3_bucket.app.arn}/uploads/*"]
condition {
test = "StringEquals"
variable = "s3:x-amz-acl"
values = ["bucket-owner-full-control"]
}
}
}
```
### 2. Secret Management (CRITICAL)
- [ ] No secrets in `.tf`, `.ts`, `.yaml`, `.bicep` files (check with `gitleaks` or `trufflehog`)
- [ ] Database passwords, API keys, private keys stored in: HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, Pulumi ESC
- [ ] State files encrypted at rest (S3 SSE, Azure storage encryption, GCS CMEK)
- [ ] State files encrypted in transit (TLS 1.2+)
- [ ] `sensitive = true` on all Terraform output values containing secrets
- [ ] Ansible vault encrypts all `vars/secrets.yml` files
- [ ] Pulumi stack config secrets marked with `--secret` flag
**Detection Script:**
```bash
# Quick secret-in-code scan
gitleaks detect --source . --no-git --verbose 2>/dev/null || \
trufflehog filesystem . --no-update --fail 2>/dev/null
```
### 3. Encryption (HIGH)
- [ ] S3 buckets: SSE-S3 minimum, SSE-KMS recommended; `block_public_access` enabled
- [ ] RDS/Database: storage_encrypted = true; encryption at rest enabled
- [ ] EBS volumes: `encrypted = true` by default (enforce via SCP or AWS Config rule)
- [ ] EKS/Container: secrets encryption with KMS; envelope encryption on etcd
- [ ] ELB/ALB: HTTPS listeners only; HTTP → HTTPS redirect; minimum TLS 1.2
- [ ] CloudFront: `viewer_protocol_policy = "redirect-http-to-https"`
- [ ] KMS key rotation enabled (automatic annual rotation or custom schedule)
### 4. Network Isolation (HIGH)
- [ ] Security groups: no `0.0.0.0/0` ingress unless intentional (e.g., public ALB)
- [ ] Security groups: restricted egress to necessary CIDRs/prefix-lists only
- [ ] NACLs: stateless rules as defense-in-depth layer
- [ ] Private subnets: no direct internet route; NAT Gateway for outbound-only
- [ ] VPC endpoints for AWS services (S3, DynamoDB) to avoid internet traversal
- [ ] WAF attached to all public-facing ALBs and CloudFront distributions
- [ ] Network firewall or equivalent for egress filtering in production
### 5. Logging & Monitoring (MEDIUM)
- [ ] CloudTrail enabled in all regions with log file validation
- [ ] VPC Flow Logs enabled on all VPCs
- [ ] S3 access logs enabled on sensitive buckets
- [ ] Load balancer access logs enabled
- [ ] GuardDuty enabled in all regions
- [ ] Security Hub enabled with CIS benchmark standard
- [ ] Config rules for continuous compliance monitoring
### 6. Cost Optimization Guardrails (MEDIUM)
- [ ] Resource tagging strategy enforced: `Environment`, `Owner`, `CostCenter`, `Project`
- [ ] Auto-scaling configured with min/max bounds (not static instance counts)
- [ ] Reserved Instances / Savings Plans for predictable workloads
- [ ] S3 lifecycle policies: transition to IA/Glacier, expire old versions
- [ ] NAT Gateway count minimized (use single-AZ for dev, multi-AZ for prod)
- [ ] RDS: stop-dev-instances automation for non-production after hours
- [ ] Lambda: memory and timeout tuned (not default 128MB/3s for everything)
---
## State Management Best Practices
### Terraform / OpenTofu
1. **Remote State is Mandatory.** No local state files in production repos. Use S3 + DynamoDB (AWS), Azure Storage (Azure), GCS (GCP).
2. **State File Isolation.** One state file per environment (dev/staging/prod), per region, and per logical boundary (networking, compute, data).
3. **State Locking.** Always enable DynamoDB locks (AWS) or equivalent. Prevents concurrent applies that corrupt state.
4. **State Encryption.** Enable server-side encryption on state storage. `encrypt = true` in backend config.
5. **Workspace Strategy.** Use separate backends or directory separation. Avoid `terraform workspace` for long-lived environments—they share the same backend and credentials.
6. **State Access Control.** Restrict state bucket access to CI/CD service roles and senior engineers. No developer IAM users with direct state read/write.
7. **State Versioning.** Enable bucket versioning on state storage for rollback capability.
8. **State Import Hygiene.** `terraform import` should be followed immediately by cleanup of manual resource configs. Document every import in a migration log.
9. **State Refresh.** Run `terraform apply -refresh-only` before plan to reconcile external changes.
10. **Never Edit State Files.** Use `terraform state mv`, `terraform state rm`, `terraform state pull`—never edit JSON state manually.
### Pulumi
1. **Pulumi Cloud or Self-Managed Backend.** Pulumi Cloud (SaaS) is the default and recommended. For self-managed, use S3/Azure/GCS with the Pulumi service managing locking.
2. **Stack Configuration.** Separate stacks per environment. Use `pulumi config set --secret` for sensitive values.
3. **State Export.** `pulumi stack export` for backup; `pulumi stack import` for recovery.
4. **Resource Protection.** `protect: true` on stateful resources (RDS, DynamoDB) to prevent accidental deletion.
5. **State History.** Pulumi Cloud retains deployment history; self-managed backends use object versioning.
### CloudFormation
1. **Change Sets.** Always create a Change Set and review before executing. Never direct-update production stacks.
2. **Stack Policies.** Define stack policies that prevent updates/replacements of stateful resources during routine changes.
3. **Nested Stacks.** Use nested stacks for large deployments to stay within resource limits (500 resources per stack). Export shared outputs via `Fn::ImportValue`.
4. **StackSets.** For multi-account/multi-region: use StackSets with service-managed permissions.
### Cross-Cutting Rules
- **Never share state between tools.** A resource created by Terraform should not be managed by Pulumi in the same state.
- **State backup is non-negotiable.** Automate daily state file backups to a separate, immutable bucket.
- **State access audit.** Log all state file reads/writes. Alert on unexpected access patterns.
---
## Drift Detection Workflow
Drift = configuration in code ≠ actual infrastructure state. Drift causes outages, security gaps, and compliance failures.
### Standard Workflow
```
1. IDENTIFY
├─ Terraform: terraform plan -detailed-exitcode
├─ Pulumi: pulumi refresh --diff
├─ CFn: aws cloudformation detect-stack-drift
├─ Bicep: az deployment group what-if
└─ Ansible: ansible-playbook --check --diff site.yml
2. CLASSIFY
├─ EXPECTED DRIFT (auto-scaling events, tag auto-updates)
│ └─ Update code to match reality (terraform import / pulumi import)
└─ UNEXPECTED DRIFT (manual console changes, security group widens)
└─ Investigate audit logs → Reconcile or revert
3. RECONCILE
├─ Prefer code-as-truth: update infrastructure to match code
├─ Import manual changes: terraform import / pulumi import / CFn resource import
└─ Document exceptions: lifecycle { ignore_changes = [...] }
4. PREVENT
├─ Restrict console access (break-glass only, with auto-revert)
├─ Run drift detection on schedule (hourly for prod, daily for staging)
├─ Alert on drift via CloudWatch/PagerDuty/Slack
└─ Auto-remediate with AWS Config rules or OPA policies
```
### CI/CD Integration
```yaml
# GitHub Actions — Drift Check Job
drift-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Plan (Drift Check)
id: plan
run: |
terraform init -backend-config="key=${{ env.TF_STATE_KEY }}"
terraform plan -detailed-exitcode -out=tfplan
continue-on-error: true
- name: Alert on Drift
if: steps.plan.outcome == 'failure'
uses: slackapi/slack-github-action@v2
with:
payload: '{"text": "🚨 Drift detected in ${{ github.repository }} — ${{ env.ENVIRONMENT }}"}'
```
### Drift Remediation Patterns
| Drift Type | Action |
|----------------------------|---------------------------------------------------|
| Manual SG rule addition | Revert manually; audit CloudTrail; enforce SCP |
| Auto-scaling group change | Update `desired_count` in IaC to match |
| Tag mutation by external tool | Add `lifecycle { ignore_changes = [tags] }` |
| IAM policy widened | Immediate revert; security incident response |
| RDS instance type changed | Update IaC to match; verify change window approval |
| S3 bucket policy changed | Revert; enable `block_public_access`; alert |
---
## Migration Patterns (Cross-Tool)
### CloudFormation → Terraform
1. Use `terraform import` with resource mapping:
```bash
terraform import aws_vpc.main vpc-0a1b2c3d4e5f67890
```
2. Alternatively: `former2` (ex-cloudformer) → generates Terraform/AWS CDK from existing infra
3. Post-import: `terraform plan` to verify no diffs; then remove CloudFormation stack after confirming resources are imported (use `DeletionPolicy: Retain` during transition)
### Terraform → Pulumi
1. Use `pulumi import` with Terraform state:
```bash
pulumi import --from terraform terraform.tfstate
```
2. Use `tf2pulumi` to convert HCL to TypeScript/Python: `tf2pulumi ./modules/*.tf`
3. Validate with `pulumi preview` before going live
### ARM → Bicep
1. `bicep decompile` for automated conversion: `az bicep decompile --file template.json`
2. Manual review required—Bicep decompilation is not lossless for complex templates
---
## Quick Reference
### Terraform One-Liners
```bash
terraform fmt -recursive -diff # Format all .tf files, show diffs
terraform validate # Syntax check
terraform plan -out=tfplan # Generate plan file
terraform apply tfplan # Apply from plan (safe)
terraform plan -detailed-exitcode # Returns 2 if drift exists
terraform state list # List all managed resources
terraform state rm resource.addr # Stop managing (don't destroy)
terraform import resource.addr resource-id # Adopt existing resource
terraform output -json # Machine-readable outputs
terraform console # Interactive REPL
terraform graph | dot -Tpng > graph.png # Dependency visualization
```
### Pulumi One-Liners
```bash
pulumi preview --json # Machine-readable plan
pulumi up --yes --skip-preview # Quick apply (with caution)
pulumi refresh --diff # Detect drift
pulumi stack export --file state.json # Backup state
pulumi config set aws:region us-east-1 # Set config
pulumi config set --secret dbPassword # Set secret
pulumi import --from terraform state.tfstate # Migrate from Terraform
pulumi policy pack # Generate policy pack skeleton
```
### CloudFormation One-Liners
```bash
aws cloudformation validate-template --template-body file://template.yaml
aws cloudformation create-change-set --stack-name prod --change-set-name review-me
aws cloudformation detect-stack-drift --stack-name prod
aws cloudformation describe-stack-resource-drifts --stack-name prod
cfn-lint template.yaml # Third-party linter
cfn_nag_scan --input-path template.yaml # Security scan
```
### Security Scanning One-Liners
```bash
# Terraform
tfsec . # Terraform security scanner
checkov -d . # Policy-as-code (any IaC tool)
trivy config . # Misconfiguration scanner
# CloudFormation
cfn_nag_scan --input-path template.yaml
# Ansible
ansible-lint site.yml
# Bicep
az bicep lint --file main.bicep
# Universal secret scanning
gitleaks detect --source . --no-git
trufflehog filesystem . --no-update
```
---
## Common Pitfalls
### 1. Hardcoded Secrets (CRITICAL — #1 cause of security incidents)
**Symptoms:** passwords, API keys, private keys in `.tf`, `.ts`, `.yaml` files.
**Fix:** Use Vault dynamic secrets, AWS Secrets Manager, Pulumi ESC, or Azure Key Vault with data-source lookups.
**Detection:** `gitleaks detect --source . --verbose`
### 2. Overly Permissive IAM (CRITICAL)
**Symptoms:** `"Action": "*"`, `"Resource": "*"` in IAM policy documents.
**Fix:** Explicit action lists; resource ARN constraints; IAM Access Analyzer validation.
**Tool:** `checkov -d . --check CKV_AWS_*` for Terraform; `cfn_nag_scan` for CloudFormation.
### 3. Missing Lifecycle Rules (HIGH)
**Symptoms:** Database destroyed during routine update because `prevent_destroy` was absent.
**Fix:** Every stateful resource needs:
```hcl
lifecycle {
prevent_destroy = true
}
```
### 4. No Remote State (HIGH)
**Symptoms:** `terraform.tfstate` in git repository; state file conflicts between teammates.
**Fix:** Configure remote backend with locking before the first `terraform apply`.
### 5. State-Only Changes Without Infrastructure Updates (MEDIUM)
**Symptoms:** `terraform state rm` used to "fix" issues instead of updating code.
**Fix:** Always update `.tf` files first; only use state manipulation as an emergency bridge.
### 6. Unpinned Provider Versions (MEDIUM)
**Symptoms:** CI pipeline breaks because provider released a breaking change.
**Fix:** Pin both Terraform core and provider versions with `required_providers` blocks.
### 7. Monolithic State Files (MEDIUM)
**Symptoms:** Plan times >10 minutes; blast radius covers entire infrastructure.
**Fix:** Split state by environment, region, and logical domain. Use `terraform output` and data sources for cross-state references.
### 8. Ignoring Drift (MEDIUM)
**Symptoms:** `terraform plan` shows unexpected changes long after initial apply.
**Fix:** Schedule drift checks; restrict console access; use SCPs to prevent manual modifications.
### 9. Missing Resource Tagging (LOW — Cost Impact: HIGH)
**Symptoms:** Untaggable resources lead to untracked cloud spend.
**Fix:** Enforce tags with Terraform `default_tags` on provider block or Pulumi `transformations`. Cost allocation tags in AWS Billing.
### 10. Inappropriate `count` Usage (LOW)
**Symptoms:** Changing a list element reorders resources and triggers destroy/recreate.
**Fix:** Use `for_each` with a map or `toset()`—deterministic keys prevent cascading changes.
---
## SEO Metadata
### Primary Keywords
- infrastructure as code
- IaC security hardening
- Terraform best practices
- Pulumi patterns
- drift detection IaC
- IaC state management
- CloudFormation to Terraform migration
- IaC CI/CD pipeline
### Secondary Keywords
- Bicep module patterns
- Ansible playbook structure
- IaC cost optimization
- IaC compliance checklist
- CIS benchmark IaC
- least privilege IAM IaC
- OpenTofu migration
- Crossplane compositions
### Search Intent Alignment
- **Informational:** "What is the best IaC tool for multi-cloud?" → Decision Tree section
- **Tutorial:** "How to secure Terraform code" → Security Hardening Checklist
- **Comparative:** "Terraform vs Pulumi vs CloudFormation" → Comparison Matrix
- **Troubleshooting:** "How to fix Terraform drift" → Drift Detection Workflow
- **Migration:** "How to migrate CloudFormation to Terraform" → Migration Patterns