Skip to main content

Checkov, Trivy and tflint

Checkov, Trivy and tflint read code, not clouds: they flag insecure or invalid configuration before anything is planned. The Azure Enterprise Baseline and the Azure Web App and Data and ETL Blueprints run them in pre-commit and again in CI, and the code repositories scan every image they build.

What it does​

tflint lints OpenTofu code with rule sets, here the Terraform recommended preset and the azurerm ruleset, which knows valid SKUs, sizes and arguments. Checkov evaluates resources against hundreds of security policies (encryption, network exposure, logging) and fails on a violation unless it is suppressed. Trivy scans configuration for misconfigurations and container images for known vulnerabilities in OS packages and language libraries.

How BuiltForProd uses it​

ScannerVersionConfigurationRuns in
tflint0.64.0 with tflint-ruleset-azurerm 0.32.0.tflint.hcl: own modules only (call_module_type = "none"); terraform_required_version and terraform_required_providers off, because root.hcl generates versions.tfPre-commit and the lint-and-scan job of every infrastructure plan.yml
Checkov3.3.19.checkov.yaml: framework terraform, skips the generated .terragrunt-cache and .terragrunt-stack foldersPre-commit and plan.yml over modules/, as a hard gate
Trivytrivy-action 0.36.0Configuration scan; image scan of CRITICAL and HIGH with a fix availablePre-commit in the infrastructure repositories; the Baseline's plan.yml (reported, Checkov is the gate); the image job of each code repository's ci.yml (fails the build)

The code repositories also run Checkov 3.3.22 in pre-commit over the whole repository, Dockerfiles and workflows included. The lint-and-scan job runs on a GitHub-hosted runner with no Azure access, before any plan, so a finding stops the pull request without touching state; see GitHub Actions.

Suppressing a finding. A check that does not apply is skipped in-line, on the resource, with the reason beside it, so the exception is reviewed with the code it excuses. The Azure Container Registry shows the pattern: each Premium-only control is skipped with the setting that turns it on.

Azure/acme-azure-platform-baseline/modules/container-registry/main.tf (lines 55-77)
resource "azurerm_container_registry" "this" {
#checkov:skip=CKV_AZURE_139: public network access is the default because the code pipelines push from GitHub-hosted runners (Entra authentication only); public_network_access_enabled = false with Premium and private_endpoints is the switch
#checkov:skip=CKV_AZURE_165: geo-replication is a Premium option for a second region, not a baseline default
#checkov:skip=CKV_AZURE_164: Docker Content Trust is being retired by Azure; image signing with Notation is the supply-chain option
#checkov:skip=CKV_AZURE_166: quarantine needs Premium and a scanner gate; Defender for Containers scans the registry (security.hcl)
#checkov:skip=CKV_AZURE_167: the retention policy is Premium only; on Standard the purge-untagged task enforces the same untagged_retention_days
#checkov:skip=CKV_AZURE_233: zone redundancy is Premium only; zone_redundancy_enabled applies with sku = "Premium"
#checkov:skip=CKV_AZURE_237: dedicated data endpoints are Premium only and needed only behind a firewall allow-list
name = var.registry_name
resource_group_name = azurerm_resource_group.this.name
location = azurerm_resource_group.this.location
sku = var.sku
admin_enabled = false
anonymous_pull_enabled = false
public_network_access_enabled = var.public_network_access_enabled
network_rule_bypass_option = "AzureServices"
zone_redundancy_enabled = local.premium ? var.zone_redundancy_enabled : false
retention_policy_in_days = local.premium ? var.untagged_retention_days : null
quarantine_policy_enabled = local.premium ? var.quarantine_policy_enabled : null
# AcrPull and AcrPush (the blueprints grant AcrPull) are honored only in the legacy registry permission mode;
# the ABAC repository mode would need the Container Registry Repository Reader/Writer roles everywhere.
role_assignment_mode = "LegacyRegistryPermissions"
tags = var.tags

Repository-wide skips are rare: the Baseline's .checkov.yaml skips only CKV_TF_1, because every module is local.

Terms you will see​

TermMeaning
RulesetA tflint plugin of provider-specific rules, here azurerm.
Check IDA Checkov policy identifier such as CKV_AZURE_139.
In-line skip#checkov:skip=<id>: <reason> on the resource it applies to.
ignore-unfixedTrivy setting that leaves out vulnerabilities with no fix yet.
Lint and scanThe first job of every infrastructure plan.yml.

Where to read more​