Visor de Riesgo de Plan Terraform

Clasifica cambios del plan Terraform por nivel de riesgo, destacando operaciones destructivas

Terraform Plan Risk Analysis

Terraform plan risk analysis is the practice of evaluating terraform plan output to identify dangerous changes before they reach production infrastructure. Every terraform apply execution modifies real cloud resources β€” databases, security groups, load balancers, IAM policies β€” and a single destructive operation can cause data loss, service outages, or security breaches. A plan risk viewer parses the plan output (JSON or text format), classifies each resource change by risk level, highlights destructive operations like destroy and replace, and provides a blast radius assessment showing how many resources are affected by the proposed changes. This pre-apply review step transforms Terraform workflows from "hope nothing breaks" to informed, risk-aware infrastructure changes.

The difference between a routine infrastructure update and a catastrophic incident often comes down to whether someone carefully reviewed the plan output. A production database marked for replacement due to a parameter change, a security group deletion that cascades to dependent resources, or an IAM policy modification that locks out entire teams β€” these are all scenarios where the plan clearly showed the danger but went unreviewed or was missed in hundreds of lines of plan output. Automated risk analysis surfaces these critical changes immediately, categorizing each modification by its potential impact and drawing attention to the changes that require human judgment before proceeding. Teams that integrate plan risk analysis into their CI/CD pipelines catch destructive changes at the pull request stage, where the cost of intervention is a code review comment rather than an incident response page.

Understanding Terraform Plan Actions and Risk Levels

Terraform plans describe infrastructure changes using a set of actions that each carry different risk profiles. Understanding these actions is fundamental to assessing plan risk:

  • Create (+): Adding new resources is generally low risk. The resource does not exist yet, so there is no data loss or service disruption. However, creating resources with overly permissive configurations (public security groups, unencrypted storage) introduces security risk even though the operation itself is safe.
  • Update in-place (~): Modifying an existing resource without recreating it. Risk varies by resource type and attribute β€” changing a tag is negligible risk, but modifying a security group rule or database parameter can affect running workloads. In-place updates are medium risk by default, elevated to high when touching sensitive attributes.
  • Destroy (-): Permanently removing a resource. This is high risk for any stateful resource (databases, storage volumes, encryption keys) because the data is irrecoverable. Destroying stateless resources (compute instances, load balancers) is lower risk when the infrastructure can be recreated, but still causes service interruption.
  • Replace (destroy + create) (-/+ or +/-): The highest risk action β€” Terraform must destroy the existing resource and create a new one because certain attributes cannot be updated in place. For databases, this means data loss unless backups are current. For DNS records, this causes propagation delays. The forced recreation often surprises teams who expected an in-place update.
  • Read (<=): Data source reads carry no risk β€” they query existing infrastructure without modification. These are informational only and can be safely ignored during risk assessment.

Risk level classification combines the action type with the resource type. A destroy on an aws_instance (stateless, replaceable) is medium risk, while a destroy on an aws_db_instance (stateful, contains data) is critical risk. The same action has dramatically different consequences depending on what it targets.

Sensitive Resource Categories and Blast Radius

Certain resource types demand extra scrutiny during plan review because changes to them have outsized consequences. A risk viewer classifies resources into sensitivity tiers:

  • Data stores (Critical): Resources that hold persistent data β€” aws_db_instance, aws_rds_cluster, aws_dynamodb_table, aws_s3_bucket, aws_elasticache_cluster, google_sql_database_instance, azurerm_mssql_database. Any destroy or replace action on these resources risks permanent data loss. Even updates can trigger unexpected restarts or failovers.
  • Security and access (High): Resources controlling network access and permissions β€” aws_security_group, aws_iam_role, aws_iam_policy, aws_vpc, aws_subnet, google_compute_firewall, azurerm_network_security_group. Modifications can lock out legitimate traffic, expose internal services, or escalate privileges. Changes often cascade to dependent resources.
  • DNS and networking (High): Resources that affect service reachability β€” aws_route53_record, aws_lb, aws_cloudfront_distribution, google_dns_record_set. Changes can cause DNS propagation delays, break TLS certificates, or redirect traffic unexpectedly.
  • Encryption and secrets (High): Resources managing cryptographic material β€” aws_kms_key, aws_secretsmanager_secret, google_kms_crypto_key, azurerm_key_vault. Destroying encryption keys renders all encrypted data permanently inaccessible.
  • Compute and stateless (Medium): Replaceable resources β€” aws_instance, aws_lambda_function, aws_ecs_service, google_compute_instance. Disruption is temporary if auto-scaling or deployment pipelines can recreate them, but causes service interruption during the transition.

Blast radius measures the total scope of impact from a plan. It considers the number of resources affected, their sensitivity tiers, and dependency relationships. A plan modifying 2 resources might have a larger blast radius than one modifying 20, if those 2 resources are a production database and its encryption key. Risk viewers calculate blast radius scores that combine resource count, sensitivity weighting, and action severity to produce a single summary metric indicating whether a plan needs routine approval or emergency review.

Integrating Plan Risk Analysis into CI/CD Workflows

Plan risk analysis provides the most value when integrated into automated workflows where it gates infrastructure changes before they can be applied:

  • Pull request gating: Run terraform plan -out=plan.tfplan and terraform show -json plan.tfplan in CI pipelines. Parse the JSON output to generate a risk summary that appears as a PR comment. High-risk plans require additional approvers or manual review before merging.
  • Risk-based approval policies: Define organizational policies that map risk levels to approval requirements. Low-risk plans (tag updates, new resources) can auto-approve. Medium-risk plans (in-place updates to compute) require one reviewer. High-risk plans (database changes, security modifications) require team lead and on-call engineer approval. Critical-risk plans (data store destruction) require written justification and backup verification.
  • Change windows: Plans with high blast radius should only be applied during maintenance windows. The risk viewer can enforce scheduling policies β€” flagging plans that exceed a blast radius threshold when submitted outside of approved change windows.
  • Drift detection context: When Terraform detects drift (resources modified outside of Terraform), the plan output includes changes to reconcile state. Risk analysis helps distinguish intentional drift correction from unexpected resource modifications that might indicate a security incident or unauthorized manual changes.
  • Plan comparison: Comparing consecutive plans for the same workspace reveals whether a change is growing in scope unexpectedly. A plan that affected 3 resources last week now affecting 30 suggests an unintended dependency cascade or a module change with broader impact than expected.

Teams adopting plan risk analysis report catching destructive changes 3-5x more frequently during code review compared to manual plan inspection. The structured risk categorization makes it immediately obvious which changes require careful consideration versus which are routine updates that can be approved quickly, reducing both review fatigue and oversight of critical changes.

Reading Terraform Plan JSON Output

The terraform show -json command produces a structured JSON representation of a saved plan that contains all the information needed for automated risk analysis:

  • resource_changes array: Each entry describes a single resource modification with fields for address (resource identifier), type (resource type like aws_instance), change.actions (array of actions like ["delete", "create"] for replacement), and change.before / change.after containing the full resource state before and after the change.
  • Action interpretation: Actions are represented as arrays β€” ["create"] for new resources, ["update"] for in-place changes, ["delete"] for destruction, ["delete", "create"] for replacement (destroy-before-create), and ["create", "delete"] for create-before-destroy replacement. The action array directly maps to risk severity.
  • Attribute-level diffs: The change.before and change.after objects show exactly which attributes are changing. Comparing these reveals whether a database is changing its engine_version (potentially requiring restart) or just its tags (zero-impact). This granularity enables precise risk scoring based on which specific attributes are modified.
  • Module context: Resources include their module path, showing whether a change originates from root configuration or a nested module. Module-level changes often affect multiple resources simultaneously, increasing blast radius. A single variable change in a shared module can cascade to dozens of resources across multiple environments.

Code Examples

Terraform Plan JSON Structure and Risk Classification

{
  "format_version": "1.2",
  "terraform_version": "1.7.0",
  "resource_changes": [
    {
      "address": "aws_db_instance.production",
      "type": "aws_db_instance",
      "name": "production",
      "provider_name": "registry.terraform.io/hashicorp/aws",
      "change": {
        "actions": ["delete", "create"],
        "before": {
          "identifier": "prod-db",
          "engine": "postgres",
          "engine_version": "14.9",
          "instance_class": "db.r5.xlarge",
          "storage_encrypted": true,
          "allocated_storage": 500
        },
        "after": {
          "identifier": "prod-db",
          "engine": "postgres",
          "engine_version": "16.1",
          "instance_class": "db.r6g.xlarge",
          "storage_encrypted": true,
          "allocated_storage": 500
        }
      }
    },
    {
      "address": "aws_security_group.api",
      "type": "aws_security_group",
      "name": "api",
      "change": {
        "actions": ["update"],
        "before": {
          "ingress": [
            { "from_port": 443, "to_port": 443, "cidr_blocks": ["10.0.0.0/8"] }
          ]
        },
        "after": {
          "ingress": [
            { "from_port": 443, "to_port": 443, "cidr_blocks": ["0.0.0.0/0"] }
          ]
        }
      }
    },
    {
      "address": "aws_instance.worker[0]",
      "type": "aws_instance",
      "name": "worker",
      "index": 0,
      "change": {
        "actions": ["update"],
        "before": { "instance_type": "t3.medium", "tags": { "env": "prod" } },
        "after": { "instance_type": "t3.large", "tags": { "env": "prod" } }
      }
    },
    {
      "address": "aws_cloudwatch_log_group.app",
      "type": "aws_cloudwatch_log_group",
      "name": "app",
      "change": {
        "actions": ["create"],
        "before": null,
        "after": { "name": "/app/production", "retention_in_days": 30 }
      }
    }
  ]
}

Output:

Risk Analysis Summary:
━━━━━━━━━━━━━━━━━━━━
Blast Radius: HIGH (4 resources, 2 critical changes)

πŸ”΄ CRITICAL β€” aws_db_instance.production
   Action: REPLACE (destroy + create)
   Risk: Production database will be DESTROYED and recreated
   Impact: Potential data loss, service outage during recreation
   Recommendation: Verify backups, schedule maintenance window

🟠 HIGH β€” aws_security_group.api
   Action: UPDATE
   Risk: Ingress opened from 10.0.0.0/8 β†’ 0.0.0.0/0 (internet-facing)
   Impact: API exposed to public internet traffic
   Recommendation: Verify this is intentional, consider WAF/rate limiting

🟑 MEDIUM β€” aws_instance.worker[0]
   Action: UPDATE (in-place)
   Risk: Instance type change may require restart
   Impact: Brief service interruption during resize

🟒 LOW β€” aws_cloudwatch_log_group.app
   Action: CREATE
   Risk: New resource, no existing infrastructure affected
   Impact: None

Generating and Parsing Terraform Plan Output

# Generate a plan and save it to a binary file
terraform plan -out=tfplan

# Convert the binary plan to JSON for analysis
terraform show -json tfplan > plan.json

# Quick summary: count changes by action type
cat plan.json | jq '
  .resource_changes
  | group_by(.change.actions)
  | map({
      action: .[0].change.actions | join(","),
      count: length,
      resources: [.[].address]
    })
'

# Filter for destructive changes only (destroy or replace)
cat plan.json | jq '
  .resource_changes
  | map(select(
      .change.actions == ["delete"] or
      .change.actions == ["delete","create"] or
      .change.actions == ["create","delete"]
    ))
  | map({address, type, actions: .change.actions})
'

# Identify sensitive resource types being modified
cat plan.json | jq '
  .resource_changes
  | map(select(
      .type | test("db_instance|rds_cluster|dynamodb|s3_bucket|security_group|iam_role|iam_policy|kms_key|secretsmanager")
    ))
  | map({address, type, actions: .change.actions})
'

Standards & Specifications

  • Terraform Plan JSON Output Format β€” Official HashiCorp specification for the JSON representation of Terraform plans, including resource_changes structure and action types
  • Terraform CLI Plan Command β€” Documentation for terraform plan command options including -out flag for saving plans and -json flag for machine-readable output
  • Terraform Resource Lifecycle β€” HashiCorp documentation on resource lifecycle meta-arguments including create_before_destroy, prevent_destroy, and ignore_changes
  • Terraform State and Resource Addressing β€” Specification for resource address format used in plan output to uniquely identify resources within modules and count/for_each instances