Skip to content

docs: add Terraform drift detection tutorial#300

Open
mintlify[bot] wants to merge 1 commit into
mainfrom
mintlify/9dc5c128
Open

docs: add Terraform drift detection tutorial#300
mintlify[bot] wants to merge 1 commit into
mainfrom
mintlify/9dc5c128

Conversation

@mintlify

@mintlify mintlify Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Adds a new tutorial covering Terraform drift detection with Kosli, based on the source Google Doc.

What's new

  • tutorials/terraform_drift_detection.mdx — full tutorial covering:
    • The two-mechanism model (statefile provenance + reconciliation via plan)
    • A vendor-neutral DIY detector with a complete GitHub Actions example
    • Hardening pitfalls (monitor the monitor, IaC coverage, race conditions, cadence, least privilege)
    • Turning detection into audit-grade evidence via Kosli Environment attestation
    • Walkthrough of the kosli-dev/tf reference wrapper and reusable workflows
    • Adapting to other CI systems and an implementation checklist
  • Nav entry added under Tutorials → Security, next to the existing "Detecting unauthorized Terraform changes" tutorial.

Live page will be at /tutorials/terraform_drift_detection.


Generated by Mintlify Agent.

Requested by: graham@kosli.com via Slack
Mintlify session: slack_1775036725.228749_D0AM66349C1

@mintlify mintlify Bot requested a review from a team as a code owner July 1, 2026 14:46
@claude

claude Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @mintlify[bot]'s task in 1m 43s —— View job


Review of PR #300 — Terraform drift detection tutorial

  • Read CLAUDE.md and doc-review skill
  • Read changed files
  • Check navigation entry
  • Check for relative links and style issues
  • Post review findings

Overall assessment

Strong tutorial. The content is well-structured, follows the Diataxis tutorial model (teaches by doing, builds incrementally), and the two-mechanism framing gives the reader a clear mental model before diving into implementation. Navigation entry is correctly placed under Tutorials > Security. All internal links resolve. No relative links found.

Findings

Critical

None.

Improvement (5 findings — all British spelling)

CLAUDE.md mandates American spelling. Five instances of British spelling found across the tutorial — inline comments with suggested fixes posted on each:

Line British American
25 recognise recognize
190 unauthorised unauthorized
214 unrecognised unrecognized
224 licence, (implicit unrecognised) license, unrecognized
246 unrecognised unrecognized

Fix all spelling →

Suggestion (1 finding)

  • Line 170: The GitHub Actions YAML example ends mid-step without closing the block cleanly — minor copy-paste friction for readers.

Summary

Category Count
Critical 0
Improvement 5
Suggestion 1

Top priority: Fix the British spellings (all mechanical, one find-and-replace). The rest of the tutorial — structure, accuracy, component usage, link format, frontmatter — is solid and ready to ship.


<CardGroup cols={2}>
<Card title="Mechanism A — statefile provenance" icon="fingerprint">
Prove that every statefile in use was produced by your pipeline. A statefile your pipeline doesn't recognise is, by definition, the product of an apply that bypassed CI. A plan can't catch this: a laptop apply updates the statefile and the world together, so they still agree and the plan comes back empty. Detecting it needs a record of where the statefile came from.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvement (style): CLAUDE.md requires American spelling. recogniserecognize.

Suggested change
Prove that every statefile in use was produced by your pipeline. A statefile your pipeline doesn't recognise is, by definition, the product of an apply that bypassed CI. A plan can't catch this: a laptop apply updates the statefile and the world together, so they still agree and the plan comes back empty. Detecting it needs a record of where the statefile came from.
Prove that every statefile in use was produced by your pipeline. A statefile your pipeline doesn't recognize is, by definition, the product of an apply that bypassed CI. A plan can't catch this: a laptop apply updates the statefile and the world together, so they still agree and the plan comes back empty. Detecting it needs a record of where the statefile came from.

</Accordion>

<Accordion title="Race conditions are bounded, not eliminated" icon="clock">
Two timing windows exist in theory. First, between an apply finishing and the statefile being captured for a provenance check, a concurrent unauthorised change could slip in. In practice the Terraform lock prevents a concurrent apply from starting until the lock is released, which keeps this theoretical. Second, if the statefile changes between a snapshot being taken and being evaluated, that snapshot is simply stale — the next cycle picks up the new state. Understand both; rely on locking and ordering to keep them harmless.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvement (style): British spelling — unauthorisedunauthorized.

Suggested change
Two timing windows exist in theory. First, between an apply finishing and the statefile being captured for a provenance check, a concurrent unauthorised change could slip in. In practice the Terraform lock prevents a concurrent apply from starting until the lock is released, which keeps this theoretical. Second, if the statefile changes between a snapshot being taken and being evaluated, that snapshot is simply stale — the next cycle picks up the new state. Understand both; rely on locking and ordering to keep them harmless.
Two timing windows exist in theory. First, between an apply finishing and the statefile being captured for a provenance check, a concurrent unauthorized change could slip in. In practice the Terraform lock prevents a concurrent apply from starting until the lock is released, which keeps this theoretical. Second, if the statefile changes between a snapshot being taken and being evaluated, that snapshot is simply stale — the next cycle picks up the new state. Understand both; rely on locking and ordering to keep them harmless.

Two independent conditions then report the same Kosli Environment as non-compliant, mapping cleanly onto the two mechanisms:

1. **The statefile in S3 no longer matches an attestation** — it was produced by an apply outside CI. The reporter Lambda detects this on its own, with no scheduled plan involved. This is **Mechanism A**, the one a plan can never catch.
2. **The drift marker no longer matches its attestation** — the periodic drift detector found a non-empty plan and overwrote `drift.plan.json` in S3 with fresh, un-attested content. The next snapshot sees an unrecognised artifact. This is **Mechanism B**.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvement (style): unrecognisedunrecognized (American spelling per CLAUDE.md).

Suggested change
2. **The drift marker no longer matches its attestation** — the periodic drift detector found a non-empty plan and overwrote `drift.plan.json` in S3 with fresh, un-attested content. The next snapshot sees an unrecognised artifact. This is **Mechanism B**.
2. **The drift marker no longer matches its attestation** — the periodic drift detector found a non-empty plan and overwrote `drift.plan.json` in S3 with fresh, un-attested content. The next snapshot sees an unrecognized artifact. This is **Mechanism B**.


## Part 3 — Reference implementation: `kosli-dev/tf`

Everything above is implemented at [github.com/kosli-dev/tf](https://github.com/kosli-dev/tf). It has two parts: a thin Terraform wrapper (`tf`) and a set of reusable GitHub Actions workflows, both open source under the MIT licence. The workflows attest the statefile and drift marker into a Kosli Environment, and the Kosli reporter Lambda is what continuously detects and reports drift. You can call the workflows directly, or borrow their shape for your own CI.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvement (style): Two British spellings: licencelicense, unrecognisedunrecognized.

Suggested change
Everything above is implemented at [github.com/kosli-dev/tf](https://github.com/kosli-dev/tf). It has two parts: a thin Terraform wrapper (`tf`) and a set of reusable GitHub Actions workflows, both open source under the MIT licence. The workflows attest the statefile and drift marker into a Kosli Environment, and the Kosli reporter Lambda is what continuously detects and reports drift. You can call the workflows directly, or borrow their shape for your own CI.
Everything above is implemented at [github.com/kosli-dev/tf](https://github.com/kosli-dev/tf). It has two parts: a thin Terraform wrapper (`tf`) and a set of reusable GitHub Actions workflows, both open source under the MIT license. The workflows attest the statefile and drift marker into a Kosli Environment, and the Kosli reporter Lambda is what continuously detects and reports drift. You can call the workflows directly, or borrow their shape for your own CI.

- **`apply.yml`** — the plan steps plus `tf apply`, then a reset-drift-detection job that writes a fresh `{sha, drift: false}` marker to S3 (the known-good baseline for the next drift run).
- **`detect-drift.yml`** — the detector. Reads the baseline marker, and only if `drift == false` runs a plan against the baseline SHA. A non-empty plan overwrites the marker with `{sha, drift: <timestamp>}`; otherwise it records a no-drift summary.

With Kosli enabled (the intended, full setup), `apply.yml` additionally attests the plan, the apply log, the statefile and the drift marker into your Kosli Environment. Those attested artifacts are what the Kosli reporter Lambda checks the live S3 files against — so an out-of-CI apply (an unrecognised statefile) or a flagged marker shows up as a non-compliant Environment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvement (style): unrecognisedunrecognized.

Suggested change
With Kosli enabled (the intended, full setup), `apply.yml` additionally attests the plan, the apply log, the statefile and the drift marker into your Kosli Environment. Those attested artifacts are what the Kosli reporter Lambda checks the live S3 files against — so an out-of-CI apply (an unrecognised statefile) or a flagged marker shows up as a non-compliant Environment.
With Kosli enabled (the intended, full setup), `apply.yml` additionally attests the plan, the apply log, the statefile and the drift marker into your Kosli Environment. Those attested artifacts are what the Kosli reporter Lambda checks the live S3 files against — so an out-of-CI apply (an unrecognized statefile) or a flagged marker shows up as a non-compliant Environment.

"s3://$STATE_BUCKET/terraform/$REPO/drift.plan.json"
curl -sf -X POST "$SLACK_WEBHOOK" -H 'Content-type: application/json' \
--data "{\"text\":\"Drift detected in $GITHUB_REPOSITORY against $SHA\"}"
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The YAML block ends abruptly without closing the jobs:detect:steps: structure — the last step (Alert + record drift) has no final dedent or trailing .... While GitHub Actions is forgiving about this, adding the closing empty line would make the example cleaner to copy-paste.

@mintlify

mintlify Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
kosli 🟢 Ready View Preview Jul 1, 2026, 2:52 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants