CoursesCheckov & IaC scanningFrameworks: Terraform, k8s, more

Frameworks: Terraform, k8s, more

One tool, many IaC types.

Intermediate10 min · lesson 4 of 12

A good building inspector carries one clipboard and walks the whole site: wiring, plumbing, fire doors, drainage. Checkov works the same way. One binary reads Terraform HCL (HashiCorp Configuration Language, the language Terraform files are written in), Kubernetes YAML, Helm charts, Dockerfiles, CloudFormation, and ARM/Bicep (Azure Resource Manager templates and the newer Bicep syntax that compiles down to them). It works out what each file builds, then applies the checks that fit.

Say your CI job (continuous integration, the automated build that runs on every push) used to finish in four minutes. Someone drops a Helm chart into a sibling folder of your monorepo (one git repository holding many projects) and suddenly the job times out. None of your code changed. Checkov spotted a new file type, switched on another parser, and started scanning vendor code as if you had written it. Framework allow-lists are how you stop that.

Breadth is great until CI crawls. With no --framework flag, Checkov auto-detects: it guesses every parser that might apply, Helm, Kustomize, secrets, all of it. --framework is the allow-list. --skip-framework is the block-list. Between them you scan only what you own. Helm and Kustomize carry a second trap around rendered manifests, covered below.

Auto-detect, or name your frameworks

Point Checkov at a directory and it sorts files by extension and content, then runs the checks that match. You declare nothing. That is lovely for a quick look on a laptop and painful in CI on a large repository. --framework terraform kubernetes narrows it to two parsers. --skip-framework helm secrets drops the slow or redundant ones. Scoping buys you speed, and something more useful than speed: repeatability. When a scoped run's numbers move, your code moved, not the file-type guesser.

terminal
$ checkov -d . --framework terraform kubernetes
$ checkov -d . --skip-framework helm kustomize secrets
output
terraform scan results:
Passed checks: 812, Failed checks: 37, Skipped checks: 9
kubernetes scan results:
Passed checks: 204, Failed checks: 11, Skipped checks: 2

Helm and Kustomize: render first

A Helm chart is a half-written file. It is full of placeholders that mean nothing until values get poured in, so there is no security setting to judge yet. Checkov does not read those raw. It runs helm template and kustomize build for you, gets finished Kubernetes manifests back, and runs the kubernetes checks over those. Which means the helm and kustomize binaries have to be on your PATH (the list of directories your shell searches when you type a command). If they are not, Checkov switches those frameworks off and prints a warning to the console, and that warning never reaches the JSON (JavaScript Object Notation) output. Scan the overlay that actually ships to production, rather than the chart's default values.

terminal
$ checkov -d ./charts/my-app --framework helm
$ checkov -d ./k8s/overlays/prod --framework kustomize
output
helm scan results:
Passed checks: 45, Failed checks: 2, Skipped checks: 0
Check: CKV_K8S_22: "Use read-only root filesystem"
FAILED for resource: Deployment.default.my-app
File: /charts/my-app/templates/deployment.yaml#rendered:42
Mixed repo through one scan
1Walk repo
find .tf, YAML, Dockerfile, charts
2Classify
terraform, k8s, helm, dockerfile…
3Render if needed
helm template / kustomize build
4Run checks
one merged report
Auto-detect is handy on a laptop. --framework is what makes CI fast and repeatable.

Beyond Terraform and Kubernetes

One install covers a lot more ground. Dockerfiles (does it set a USER, does it declare a HEALTHCHECK, does it reach for ADD where COPY would do), CloudFormation, ARM/Bicep, Serverless, GitHub Actions and GitLab CI pipeline files. Scanning a rendered Terraform plan is its own lesson (cv-plan), and the secrets and sca frameworks (software composition analysis, meaning the third-party libraries you depend on) overlap with other tools in cv-overlap. Here is why the names matter: the framework name is the exact token you pass to --framework. Type it wrong and Checkov parses nothing and hands you a cheerful green summary.

terminal
$ checkov -f Dockerfile
$ checkov -d .github/workflows --framework github_actions
output
dockerfile scan results:
Check: CKV_DOCKER_2: "Ensure HEALTHCHECK instructions have been added"
FAILED for resource: /Dockerfile.
Check: CKV_DOCKER_3: "Ensure that a user for the container has been created"
FAILED for resource: /Dockerfile.
github_actions scan results:
Check: CKV_GHA_7: "The build output cannot be affected by user parameters"
FAILED for resource: /.github/workflows/deploy.yml:CI/CD Pipeline
Rendered findings do not point at your templates
A Helm or Kustomize finding gives you a line number in the rendered manifest, not in the template you would edit. Mapping it back is manual work. Worse, if helm or kustomize is missing from PATH, that framework is switched off and the only clue is a console warning that never appears in the JSON. And a plain checkov -d . cheerfully scans vendored charts you did not write, so scope the run with --framework and skip-path.

Single-file scans with -f

When a pull request touches one manifest or one module, dragging the whole tree through the scanner to check two lines is wasted time. checkov -f main.tf scans that one file and infers the framework from the extension and the contents. In a pre-commit hook (a script git runs before it lets you commit), -f keeps the scan on the staged files, so a two-line Dockerfile edit does not rescan the entire monorepo.

terminal
$ checkov -f modules/networking/main.tf
$ checkov -f k8s/deployment.yaml
output
terraform scan results:
Passed checks: 18, Failed checks: 1
kubernetes scan results:
Passed checks: 22, Failed checks: 2

download-external-modules

A Terraform module pulled from the registry or a git URL is a sealed box until its source is on disk. Checkov sees the module block and the inputs you passed, and nothing else. Turn on --download-external-modules true (or set the same thing in .checkov.yaml) and it fetches the source, then checks what the module actually creates. Leave it off and a bucket with access logging disabled, three levels down inside someone else's module, never appears in your report. That false negative is common in CI jobs that only scan the root module directory.

terminal
$ checkov -d . --download-external-modules true --framework terraform --compact
output
Passed checks: 890, Failed checks: 41, Skipped checks: 9
Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"
FAILED for resource: module.logging.aws_s3_bucket.this
File: /.external_modules/.../main.tf:12-20

CI scoping checklist

Before you freeze a --framework line in CI, inventory what the repository actually holds: Terraform roots, Kubernetes overlays, Helm charts, Dockerfiles, GitHub Actions workflows. Then exclude what you did not write. skip-path vendor/, skip-path .terraform/, and any third-party chart directory. Scan code you cannot change and you own findings you cannot fix.

Framework scoping is a promise to your future self. If last month's job scanned terraform and kubernetes, this month's has to scan the same set, or your SARIF diff (Static Analysis Results Interchange Format, the standard file CI tools use to publish findings) blends real regressions with "we added a Dockerfile and dockerfile checks appeared out of nowhere". Write the chosen line into .checkov.yaml and revisit it whenever the repo layout changes. A new Helm chart or workflow file does not walk into scope on its own once you have turned auto-detect off.

The empty-section trap deserves a habit of its own. A framework that ran and found nothing and a framework that never ran look nearly identical in JSON. The difference sits on stderr, the error stream that your CI console captures but your report file does not. Read the console log alongside the SARIF, every run, not only when something looks wrong.

Serverless, ARM, Bicep and GitLab CI behave exactly like Terraform here: correct token, correct path, optional skip-path for generated artefacts. Big multi-cloud estates often run one Checkov job per cloud partition with its own framework list instead of one monolithic scan. The AWS directory and the Azure directory then get fast feedback and obvious owners.

The pipeline frameworks earn their place too. GitHub Actions and GitLab CI checks (the CKV_GHA_* family) catch unpinned action versions, over-broad token permissions and unsafe workflow patterns before anyone hits merge, which is the same early-warning value as the Terraform checks. Scan a Dockerfile next to the deployment manifest that runs it and you catch "this image runs as root" and "this pod allows privilege escalation" in a single step, which is why --framework dockerfile,kubernetes is such a common pairing.

Keep a comment in .checkov.yaml listing the framework tokens this repo uses. Breadth is the reason Checkov replaced three-tool chains in plenty of shops, and breadth is also the reason a new contributor guesses a token, parses zero resources, and reports that everything is clean.

Try this

Run all three. An explicit framework list first, then a scoped run that skips vendor paths, then the same scan as JSON so you can read the failed count and resource count per framework. Watch which summary blocks appear and which quietly do not. A wrong token does not throw an error; it gives you a green run over zero resources, and recognising that on sight is the whole point of the exercise.

terminal
$ checkov -d . --framework terraform,kubernetes,dockerfile --compact --quiet
$ checkov -d . --framework terraform --skip-path vendor/ --skip-path .terraform/ --compact
$ checkov -d . -o json | jq 'if type=="array" then .[] | {framework:.check_type, failed:.summary.failed, resources:.summary.resource_count} else . end'
output
terraform: Passed 812, Failed 37
kubernetes: Passed 204, Failed 11
dockerfile: Passed 12, Failed 2
{
"framework": "terraform",
"failed": 37,
"resources": 214
}

Takeaway

Treat framework selection as a contract rather than a convenience flag. Auto-detect on your laptop is fine. In CI, name the frameworks in --framework or in .checkov.yaml so that a missing Helm binary or a mistyped token cannot silently drop half your estate out of the gate. skip-path on vendor/ and .terraform/ keeps the worklist to files your team can actually edit.

If helm or kustomize warnings are already scrolling past on stderr in your pipeline, pick one: install the binaries in the CI image, or stop naming those frameworks in the gate. Claiming chart coverage you do not have is worse than admitting the gap. Next comes suppressions (cv-suppress), so the findings you cannot fix this sprint stay visible and ticketed instead of quietly muted.

Quick check
01Why does Checkov need the helm binary on PATH before --framework helm does anything?
Incorrect — nothing is bundled and no licence exists. Checkov calls the helm binary you installed.
Correct — a chart full of {{ .Values }} placeholders has no concrete settings to judge until it is rendered.
Incorrect — once rendered, the output is checked with the ordinary kubernetes check set.
Incorrect — the whole rendered workload gets checked, not the chart metadata.
02Your root module calls a module from the Terraform registry and Checkov reports no findings inside it. What is most likely missing from the run?
Incorrect — Checkov cannot judge a module whose source it never fetched. It saw the module block and stopped there.
Incorrect — a directory of .tf files gets parsed as terraform either way. The gap is the module's contents, not the parser.
Correct — without it you check the module block shell only, and findings inside the child module go unreported.
Incorrect — shortening the output would not change what Checkov actually evaluated inside the module.
03A CI run prints terraform and kubernetes sections, no helm section at all, and the console log says the helm framework was skipped. What do you do next?
Incorrect — a skipped framework never ran, so there is nothing clean about it.
Correct — Checkov shells out to helm template, so no binary means no scan. Install it or stop claiming the coverage.
Incorrect — helm scanning is open source and needs no platform key.
Incorrect — passing checks still show up as counts in the summary rather than as a missing section.

Related