IaC & misconfiguration scanning
On this page
In plain terms
Modern infrastructure is defined in files: Terraform for cloud resources, Kubernetes manifests for workloads, Dockerfiles for images, Helm charts, CloudFormation. Infrastructure-as-Code (IaC) scanning, also called misconfiguration scanning, checks those files for insecure settings before they're applied to a real environment.
The most damaging cloud incidents are rarely exotic exploits; they're misconfigurations, a storage bucket left public, a security group open to the world, a container running as root.
Why it matters
An IaC misconfiguration is a production incident waiting for terraform apply. Because the
insecurity lives in a text file, it can be caught in code review, turning "we found an open database
in prod" into "we blocked a pull request." This is one of the highest-leverage places to shift
security left.
Where it fits
- Artifact: IaC definitions in your repo,
*.tf, Kubernetes YAML,Dockerfile, Helm, etc. - Stage: on the pull request that changes infrastructure, before it's provisioned.
How it works
A scanner parses each IaC file into a structured model and evaluates it against a library of
policies ("no public S3 buckets," "pods must set runAsNonRoot," "no :latest image tags").
Many tools let you write custom policies (often in Rego, the language of the
Open Policy Agent (OPA))
to encode your own organization's rules.
Popular & reputable tools
| Tool | Notes |
|---|---|
| Trivy (config/misconfig) | OSS; scans Terraform, K8s, Dockerfile, Helm |
| Checkov | OSS, from Bridgecrew/Prisma; large built-in policy set |
| tfsec | OSS, Terraform-focused (now folded into Trivy) |
| KICS | OSS, from Checkmarx; broad IaC coverage |
| kube-bench | OSS; CIS benchmarks for live clusters (see posture) |
Manifests are not the cluster
Scanning the manifests is not the same as checking the cluster. IaC scanning reads what you declared; a cluster drifts from that the first time someone changes a setting during an incident, and it was never covered for anything applied before the manifests existed.
The complementary check is a posture scan against the running system. The CIS Kubernetes Benchmark is the usual baseline, covering RBAC, service accounts, Pod Security Standards and network policies as they are.
Common pitfalls
- Defaults are noisy. Out-of-the-box policy packs flag many low-impact issues; tune to what matters for your environment.
- Scanning files ≠ scanning reality. IaC scanning catches what's declared; drift (manual changes in the console) needs runtime posture checks too.
- Custom policy is where the value is. The generic rules are table stakes; encoding your own guardrails is what prevents your recurring mistakes.
How Draugr fits. The iac control runs Trivy's misconfiguration scanner over a component's
repositories, Terraform and Kubernetes manifests and Dockerfiles and Helm, normalized to SARIF
alongside every other control and ranked by what the component declares rather than by the policy
pack's own severity.
Trivy evaluates a limited number of module calls in each Terraform module and exits clean without
checking the rest. When it skips one, the root module is listed under Caveats as unread, with
the calls it left out, as in 32 of 33 module calls checked; "zbad" skipped. With
failOnCaveats: [unread-files] in the descriptor's gate,
that fails the run.
Your own guardrails are the half worth having. checks takes paths to your Rego, and
Draugr reads each check's package line to know what to evaluate, so the rule that encodes the mistake your team
actually repeats sits beside the generic ones and gates the same way.
Files answer one question, and the other is a control of its own:
| What it reads | When it runs | |
|---|---|---|
iac | the manifests in the repository, at the revision being scanned | on the pull request, before anything reaches a cluster |
kubernetes | the cluster itself, through the Kubernetes API | on a schedule, against whatever is running now |
kubernetes is served by draugr-k8s-policies, native Go against the API rather than a
tool Draugr shells out to. Nothing to install, nothing created in your cluster, and a namespaces
list narrows it to the part you own where the cluster is shared. Every check in the benchmark's
policies section is reported either way: the ones it can settle carry a verdict read from the
cluster, and the rest say they need manual review, which is what CIS says about them. The report
states how many of each, so a clean result cannot be mistaken for a complete one.
Run both and the difference between their answers is your drift. See CIS benchmarks and posture for what the second one covers, and the control catalog for the scanners behind each.
Keep learning
- Container image scanning, the image the Dockerfile builds
- Infrastructure & CIS benchmarks. Checking live posture, not just files