A Terraform security checklist
A secure Terraform setup comes down to seven things: remote encrypted state with locking, no secrets in variables or state, least-privilege credentials for the pipeline, pinned provider and module versions, policy checks in CI, a human reading the plan before apply, and a routine check for drift. If all seven are in place, the most common problems are covered.
The checklist
1. Remote, encrypted state with locking
State belongs in a remote backend, encrypted at rest, with locking turned on so two applies cannot run at once and corrupt it. HashiCorp documents how state locking works per backend. Read access should be limited to the pipeline and a few named people, and access should be logged.
2. No secrets in variables or state
Keep passwords and keys out of variable files and outputs, and remember that state stores the values of generated secrets in plain text. See HashiCorp’s guide to managing sensitive data; recent Terraform versions add ephemeral values and write-only arguments that keep a secret out of state altogether. Where those are not available, let the cloud provider generate and store the secret in its secrets manager, and pass Terraform a reference rather than the value.
3. Least-privilege provider credentials
The pipeline that runs apply is often the most powerful identity in the account. Give it short-lived credentials through workload identity instead of a stored access key, split plan and apply into separate roles, and keep production credentials out of branches anyone can push to.
4. Pinned provider and module versions
Commit the dependency lock file and pin modules to a version or commit. An unpinned module is someone else’s code running with your cloud permissions.
5. Policy as code in CI
Run rules against every plan: no public storage, no wildcard IAM, encryption and logging on by default. Start them as warnings, then make the important ones block the merge.
6. Plan review before apply
Someone other than the author reads the plan output as well as the code diff. The plan shows what will actually be destroyed or replaced, which the diff often hides.
7. Drift detection
Changes made by hand in the console mean the code no longer describes the estate. A scheduled plan that expects no changes is the simplest way to notice.
Where teams usually slip
The state bucket is the common one. It is set up early, before anyone thinks about who else can read it, and it quietly accumulates every database password the stack has generated. The second is the pipeline role, granted broad rights to get the first deploy working and never narrowed. Both are quick to fix once someone looks.
If you want a second pair of eyes on the whole setup, that is our Terraform and infrastructure-as-code security review. For the deployed side, see the cloud security configuration review.
How the work is bounded
The scope is agreed in writing before work starts, and the engagement is quoted in writing with it.
SecHB does not issue certifications, attestations or audit opinions: those come from accredited certification bodies, CPA firms and QSAs. The work here is what an organization does to be ready for them.
Nothing here is legal advice. Where a question turns on the law, the work is done alongside the client’s counsel, not instead of them.
Questions we are asked
Does Terraform encrypt secrets in state?
Not by default. Terraform writes resource attributes to state in plain text, and marking a value sensitive only hides it from plan and apply output. Protection comes from the backend: encryption at rest, narrow read access and logged access.
Which policy-as-code tool should we use?
The one your team will keep maintaining. Any engine that can read a plan as JSON and fail the pipeline will do; the value is in the rules you write and keep current, not in the engine.
How often should we check for drift?
Daily for production is a sensible default: a scheduled plan that expects no changes and raises an alert when it finds some. Weekly is reasonable for environments that rarely change.
Checking your own setup
Send a short description of your Terraform layout and pipeline. The reply says which of the seven are likely gaps. More pieces are indexed at writing.