Skip to content

Configuration & posture

IaC & drift control

Infrastructure as code is only a security control if the running estate actually matches the code that is supposed to define it. This sub-topic looks at the gap between intended state, expressed in Terraform, CloudFormation, Bicep, or Pulumi, and the live configuration an attacker or auditor would actually encounter. Drift enters through manual console edits, break-glass fixes never merged back, resources stood up outside the pipeline, and providers silently changing defaults underneath a stack. We also treat the IaC supply chain itself as attack surface: the state files, the pipeline that runs apply, and the policy-as-code guardrails meant to stop bad configuration before it is ever created. The question is not whether you use IaC, but whether the code is genuinely authoritative and whether divergence is detected on a cadence rather than discovered mid-incident.

Overview

What this check looks like in practice

When infrastructure is defined as code, the code is the intended state — and drift is when the running environment no longer matches it. Manual console changes, emergency fixes never merged back, and resources created outside the pipeline all erode the guarantees that IaC is supposed to provide. We look at whether the estate is actually governed by its code and whether drift is detected rather than discovered during an incident.

Engineer reviewing cloud configuration and infrastructure-as-code on dual monitors at a tidy office desk
Configuration hygiene

Why it matters

When code and reality diverge, every downstream assurance built on that code quietly becomes false: the reviewed pull request no longer describes production, and the next apply can revert a hand-made security fix or wipe an emergency mitigation an engineer forgot to commit. Attackers exploit the gap directly, because an over-broad rule added by hand in the console never went through review and rarely triggers an alert, so it lives on as an unaudited foothold. Terraform and CloudFormation state is a high-value target in its own right, commonly holding resource identifiers, connection strings, and sometimes plaintext secrets, so a world-readable or unencrypted state bucket hands an attacker a map of the estate and occasionally the keys to it. Auditors and cyber-insurers probe change management first, because a governed pipeline with drift detection and approval gates is the clearest evidence that configuration is controlled rather than improvised. A pipeline identity with unconstrained permissions is also one of the most powerful roles in the account, and it is frequently the least monitored.

What we assess

What we look for

  • Production resources modified by hand in the console or CLI instead of through the pipeline that owns them, and whether those out-of-band changes are visible in control-plane logs
  • Whether a drift-detection mechanism (scheduled Terraform plan, CloudFormation drift detection, AWS Config, Azure Deployment Stacks, GCP Cloud Asset Inventory) exists and is actually run on a defined cadence rather than ad hoc
  • Where IaC state is stored, and whether it is encrypted at rest, versioned, access-controlled to a minimal principal set, and protected by state locking to prevent concurrent-apply corruption
  • Secrets, connection strings, and credentials embedded in code or serialized into state files rather than referenced from a secrets manager
  • Policy-as-code and preventative guardrails (Service Control Policies, Azure Policy deny effects, GCP Organization Policy constraints, OPA/Conftest, CloudFormation Guard) that block non-compliant resources before creation, not just report them after
  • Resources with no code owner at all, created outside any pipeline and unmanaged, so no review or drift check ever applies to them
  • The permissions and trust of the CI/CD identity that runs apply, whether plan output is reviewed and approved before apply, and whether the pipeline is scoped to least privilege
  • Evidence that drift and change events are reconciled: an audit trail showing divergence was detected, triaged, and either merged back into code or reverted

Across your clouds

AWS, Azure & Google Cloud

AWS

We check CloudFormation stack drift detection (DetectStackDrift) and AWS Config for continuous divergence tracking, confirm Terraform state in S3 is encrypted with versioning and locking (S3-native lockfile or the classic DynamoDB lock table), and verify Service Control Policies and AWS Control Tower guardrails enforce standards before resources are created. CloudTrail is reviewed to surface console changes made outside the pipeline, and the CodePipeline/CodeBuild or GitHub Actions OIDC role that runs apply is examined for least privilege.

Azure

We assess Azure Policy in deny mode (not just audit) as a preventative guardrail and review Deployment Stacks with deny-settings, which lock managed resources against drift and are the Azure-native answer for Bicep and ARM, which are stateless and have no state file to protect. Where Terraform is used, we confirm its state in Azure Storage is encrypted, network- and RBAC-restricted, and blob-lease locked. Defender for Cloud and Azure Resource Graph help identify resources created outside IaC, and the deploying service principal or managed identity is checked for over-broad Contributor or Owner assignments.

Google Cloud

We check Organization Policy Service constraints as preventative guardrails, use Cloud Asset Inventory to compare live state against the intended IaC baseline, and confirm Terraform state in a GCS bucket is encrypted with object versioning and native state locking enabled. Policy-as-code (gcloud terraform vet, formerly terraform-validator, with the Config Validator policy-library, or OPA/Conftest) is reviewed for pre-apply enforcement, and the Cloud Build or pipeline service account that runs apply is examined for least-privilege scoping instead of broad project-level roles like Editor or Owner.

Example finding

Terraform state bucket unencrypted and readable beyond the pipeline, with a production security group hand-edited in the console and never reconciled

Risk: The state file exposes resource identifiers and embedded secrets to anyone with the broad read grant, while the untracked console change means the security group's open rule was never reviewed, is invisible to code review, and will be silently reverted or preserved unpredictably on the next apply, leaving a foothold no one owns.

Fix: Restrict the state backend to the pipeline principal only, enable encryption, versioning, and state locking, then run drift detection to catch the console change, reconcile it by either merging the intended rule into code or reverting it, and add a preventative guardrail so future out-of-band changes to that resource type are blocked or flagged.

Remediation

How to close it

  1. 1 Establish an authoritative source of truth: inventory every in-scope resource, identify which ones have no code owner, then bring unmanaged resources under IaC or explicitly decommission them.
  2. 2 Harden the IaC state and pipeline: encrypt and version state backends, enable state locking, restrict access to a minimal principal set, move embedded secrets into a secrets manager, and scope the apply identity to least privilege.
  3. 3 Turn on drift detection and run it on a schedule (scheduled plan, CloudFormation drift detection, AWS Config, Deployment Stacks, or Cloud Asset Inventory reconciliation) so divergence is surfaced continuously rather than during an incident.
  4. 4 Add preventative guardrails as policy-as-code: enforce SCPs, Azure Policy deny effects, Organization Policy constraints, or OPA checks in the pipeline so non-compliant resources are blocked before creation, not remediated after.
  5. 5 Close the manual-change path: require changes to flow through reviewed pull requests and approved plans, and treat any out-of-band console edit as an exception that must be reconciled back into code with an audit trail.
  6. 6 Establish a reconciliation cadence: assign an owner to triage detected drift, decide merge-or-revert for each divergence, and retain the evidence as change-management artifacts for auditors and insurers.

Questions

We use Terraform for everything, so isn't our configuration already controlled?

Using IaC and being governed by IaC are different things. The control only holds if the running estate actually matches the code, if state is stored securely, if drift is detected on a cadence, and if manual changes cannot quietly bypass review. We check exactly those conditions, because in most estates some resources were created outside the pipeline and some live settings no longer match what the code says.

Do you run Terraform apply or change our pipeline during the assessment?

No. The assessment is read-only. We inspect your code, state configuration, drift-detection setup, guardrails, and pipeline permissions, and we compare intended state against live configuration using provider-native tooling, without executing an apply or altering production. Any remediation is a separate, agreed step your team runs, or one we support after you decide what to close.

Why does IaC state security matter as much as the drift itself?

State files frequently contain resource identifiers, connection strings, and sometimes plaintext secrets, so an exposed or unencrypted state backend is both a map of your estate and a potential credential leak. It is also the mechanism an attacker or a mistaken apply can use to change infrastructure at scale, which is why we treat the state backend and the apply identity as first-class attack surface, not just configuration detail.

See where you stand

A health check finds and prioritizes real exposure. It is not a certification or a promise you will never be breached — closing the findings is what changes your risk.

Related domain surfaces

The rest of what we assess

The same read-only assessment covers every domain — see what else it looks at.