# IaC, plan review and drift management

Move from formatting checks to actual change decisions. Assess Terraform plan/state sensitivity, the applied plan and console drift independently.

Original AI-assisted SecNet educational content. Fictional local examples; not a standards provider publication, endorsement or compliance evidence. Page exercises do not create account progress.

Sources checked: 2026-10-21

## From source to applied change

Passing formatting or validation does not prove an appropriate business change. Review module origin, provider version, actual plan and unknown risk fields. Bind the plan applied in production to the reviewed plan; do not silently reuse approval after regeneration. This material runs no planning commands and searches no environment variables, cloud profiles or credentials.

## State and output sensitivity

Keeping state out of Git is insufficient protection. Design backend access, encryption, versioning, locking and audit together. Terraform show -json can expose sensitive values in plaintext; do not move real output into shared reports or this exercise. Use a fictional change table instead. Include backup state copies and CI artifacts in the sensitive-data inventory.

## Drift and emergency changes

An unchanged repository can coexist with a new console-created network rule. Track live differences, approval and reconciliation separately. Emergency changes need ownership, expiry and rollback. Do not assume a later plan deleting an unfamiliar change is automatically correct. Assess security and service impact before returning to approved state, and learn why manual intervention was needed.

## Worked example: a different plan after approval

A reviewer approves plan P1, then the pipeline changes a variable and generates P2. Formatting does not create approval for P2. appliedPlanMatched=false rejects this case. Adding state to .gitignore cannot fix a publicly readable backend; the second model exposes backend access. Record plan identity/digest, provider version, risk-field knownness and authorized decision in the workpaper. Examples use abstract evidence fields and do not claim to parse Terraform JSON plan format.

## Working sequence

1. Verify module and provider origins.
2. Review risky and unknown plan fields.
3. Restrict state and artifact access.
4. Match approval to the plan being applied.
5. Close live drift through owned reconciliation.

## 1. Right format, wrong plan

Formatting does not establish that the reviewed plan was applied. Unknown risk values or a changed plan require review; this model does not execute Terraform.

This code is a local policy model; it does not replace target/cluster testing.

### Weak implementation
```js
s=>s.formatted
```

### Corrected implementation
```js
s=>s.formatted&&s.planReviewed&&s.appliedPlanMatched&&!s.unknownRiskValues
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "reviewed-plan",
      "input": {
        "formatted": true,
        "planReviewed": true,
        "appliedPlanMatched": true,
        "unknownRiskValues": false
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "format-only",
      "input": {
        "formatted": true,
        "planReviewed": false,
        "appliedPlanMatched": false,
        "unknownRiskValues": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "changed-plan",
      "input": {
        "formatted": true,
        "planReviewed": true,
        "appliedPlanMatched": false,
        "unknownRiskValues": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "reviewed-plan",
      "input": {
        "formatted": true,
        "planReviewed": true,
        "appliedPlanMatched": true,
        "unknownRiskValues": false
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "format-only",
      "input": {
        "formatted": true,
        "planReviewed": false,
        "appliedPlanMatched": false,
        "unknownRiskValues": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "changed-plan",
      "input": {
        "formatted": true,
        "planReviewed": true,
        "appliedPlanMatched": false,
        "unknownRiskValues": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## 2. Outside Git but exposed

gitIgnored does not restrict backend access. Require encryption, locking, scoped access and audit evidence together; encryption cannot correct public authorization.

This code is a local policy model; it does not replace target/cluster testing.

### Weak implementation
```js
s=>s.gitIgnored
```

### Corrected implementation
```js
s=>s.gitIgnored&&s.backendAccessScoped&&s.encrypted&&s.locking&&s.auditEnabled
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "controlled-state",
      "input": {
        "gitIgnored": true,
        "backendAccessScoped": true,
        "encrypted": true,
        "locking": true,
        "auditEnabled": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "public-backend",
      "input": {
        "gitIgnored": true,
        "backendAccessScoped": false,
        "encrypted": true,
        "locking": true,
        "auditEnabled": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "no-lock",
      "input": {
        "gitIgnored": true,
        "backendAccessScoped": true,
        "encrypted": true,
        "locking": false,
        "auditEnabled": true
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "controlled-state",
      "input": {
        "gitIgnored": true,
        "backendAccessScoped": true,
        "encrypted": true,
        "locking": true,
        "auditEnabled": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "public-backend",
      "input": {
        "gitIgnored": true,
        "backendAccessScoped": false,
        "encrypted": true,
        "locking": true,
        "auditEnabled": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "no-lock",
      "input": {
        "gitIgnored": true,
        "backendAccessScoped": true,
        "encrypted": true,
        "locking": false,
        "auditEnabled": true
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## 3. Same code, changed environment

noCodeDiff does not exclude live drift. Review the live difference, assign emergency-change ownership and establish reconciliation before claiming closure.

This code is a local policy model; it does not replace target/cluster testing.

### Weak implementation
```js
s=>s.noCodeDiff
```

### Corrected implementation
```js
s=>s.noCodeDiff&&s.liveDiffReviewed&&s.emergencyChangeOwned&&s.reconciled
```

### Synthetic input and actual model output
```json
{
  "before": [
    {
      "name": "reconciled",
      "input": {
        "noCodeDiff": true,
        "liveDiffReviewed": true,
        "emergencyChangeOwned": true,
        "reconciled": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "console-change",
      "input": {
        "noCodeDiff": true,
        "liveDiffReviewed": false,
        "emergencyChangeOwned": false,
        "reconciled": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    },
    {
      "name": "unresolved-drift",
      "input": {
        "noCodeDiff": true,
        "liveDiffReviewed": true,
        "emergencyChangeOwned": true,
        "reconciled": false
      },
      "expected": false,
      "observed": true,
      "matched": false
    }
  ],
  "after": [
    {
      "name": "reconciled",
      "input": {
        "noCodeDiff": true,
        "liveDiffReviewed": true,
        "emergencyChangeOwned": true,
        "reconciled": true
      },
      "expected": true,
      "observed": true,
      "matched": true
    },
    {
      "name": "console-change",
      "input": {
        "noCodeDiff": true,
        "liveDiffReviewed": false,
        "emergencyChangeOwned": false,
        "reconciled": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    },
    {
      "name": "unresolved-drift",
      "input": {
        "noCodeDiff": true,
        "liveDiffReviewed": true,
        "emergencyChangeOwned": true,
        "reconciled": false
      },
      "expected": false,
      "observed": false,
      "matched": true
    }
  ]
}
```

## Exit evidence

- Reviewed/applied plan identity and risk record
- State access policy and drift reconciliation evidence

## References

- [HashiCorp · Terraform show](https://developer.hashicorp.com/terraform/cli/commands/show)
- [Google Cloud · Workload Identity Federation practices](https://docs.cloud.google.com/iam/docs/best-practices-for-using-workload-identity-federation)
