IaC Security Scanning: Terraform, Bicep & ARM Template Security
Prevent cloud misconfigurations with IaC security scanning. Complete guide to Checkov, Terrascan, KICS for Terraform, Bicep, and ARM templates.
Cloud misconfigurations are one of the most common causes of security breaches. The good news? Most misconfigurations can be caught before deployment through automated scanning of your Infrastructure as Code. Here's how to implement effective IaC security scanning in your pipelines.
Why Shift Left on Infrastructure Security?
Finding a misconfiguration in code review costs almost nothing. Finding it after deployment costs time, money, and potentially your reputation. IaC scanning catches issues like publicly accessible storage accounts, databases without encryption, missing network security group rules, overly permissive IAM policies, missing logging and monitoring, and non-compliant resource configurations.
Popular IaC Scanning Tools
Several excellent tools exist for scanning Infrastructure as Code. Here's a comparison of the most popular options:
Checkov
Best for: Comprehensive coverage across multiple IaC types
Supports: Terraform, CloudFormation, ARM, Bicep, Kubernetes, Dockerfile
# Install
pip install checkov
# Scan Terraform directory
checkov -d ./terraform
# Scan with specific framework
checkov -d ./infrastructure --framework terraform
# Output to JSON for CI/CD parsing
checkov -d ./terraform -o json > results.json Terrascan
Best for: Policy as code with OPA/Rego
Supports: Terraform, Kubernetes, Helm, Dockerfiles
# Install
brew install terrascan
# Scan with Azure policies
terrascan scan -t azure -i terraform
# Custom policy path
terrascan scan -p ./policies KICS (Keeping Infrastructure as Code Secure)
Best for: Extensive query library with easy customisation
Supports: Terraform, CloudFormation, ARM, Ansible, Kubernetes
# Using Docker
docker run -v $(pwd):/path checkmarx/kics:latest scan -p /path
# Filter by severity
docker run -v $(pwd):/path checkmarx/kics:latest scan -p /path --fail-on high,medium tfsec
Best for: Terraform-specific deep analysis
Supports: Terraform only (now part of Trivy)
# Install
brew install tfsec
# Basic scan
tfsec .
# Soft fail on warnings
tfsec . --soft-fail Integrating with Azure DevOps
Here's how to add IaC scanning to your Azure DevOps pipeline:
trigger:
branches:
include:
- main
paths:
include:
- 'terraform/**'
stages:
- stage: SecurityScan
jobs:
- job: IaCScan
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.10'
- script: |
pip install checkov
checkov -d ./terraform \
--framework terraform \
--output cli \
--output junitxml \
--output-file-path ./results \
--soft-fail-on LOW
displayName: 'Run Checkov'
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/results_junitxml.xml'
failTaskOnFailedTests: true
condition: always() Integrating with GitHub Actions
name: IaC Security Scan
on:
pull_request:
paths:
- 'terraform/**'
- 'bicep/**'
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: terraform/
framework: terraform
soft_fail: false
output_format: sarif
output_file_path: results.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: results.sarif Writing Custom Policies
Built-in rules cover common issues, but you'll likely need custom policies for organisation-specific requirements:
Checkov Custom Check (Python)
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckResult, CheckCategories
class RequireResourceTags(BaseResourceCheck):
def __init__(self):
name = "Ensure all resources have required tags"
id = "CUSTOM_001"
supported_resources = ['*']
categories = [CheckCategories.CONVENTION]
super().__init__(name=name, id=id, categories=categories,
supported_resources=supported_resources)
def scan_resource_conf(self, conf):
required_tags = ['Environment', 'CostCenter', 'Owner']
tags = conf.get('tags', [{}])
if isinstance(tags, list) and len(tags) > 0:
tags = tags[0]
for tag in required_tags:
if tag not in tags:
return CheckResult.FAILED
return CheckResult.PASSED
check = RequireResourceTags() KICS Custom Query (Rego)
package Cx
# Ensure storage accounts use private endpoints
CxPolicy[result] {
resource := input.document[i].resource.azurerm_storage_account[name]
not has_private_endpoint(name)
result := {
"documentId": input.document[i].id,
"searchKey": sprintf("azurerm_storage_account[%s]", [name]),
"issueType": "MissingAttribute",
"keyExpectedValue": "Storage account should use private endpoint",
"keyActualValue": "Storage account does not have private endpoint"
}
}
has_private_endpoint(storage_name) {
pe := input.document[_].resource.azurerm_private_endpoint[_]
contains(pe.private_service_connection[_].private_connection_resource_id, storage_name)
} Handling False Positives
Every scanning tool will produce false positives. Handle them systematically:
Inline Suppressions
# For Checkov
resource "azurerm_storage_account" "public_assets" {
#checkov:skip=CKV_AZURE_35: Public access required for CDN
name = "publicassets"
public_network_access_enabled = true
} Baseline Files
For existing infrastructure with known issues, create a baseline and only fail on new violations:
# Generate baseline
checkov -d ./terraform --create-baseline
# Run with baseline (only new issues fail)
checkov -d ./terraform --baseline baseline.json Best Practices
Fail the build on high and critical severity findings, don't let severe issues reach production. Run scans on pull requests to give developers immediate feedback. Consider using multiple tools, as different tools catch different issues. Document every suppression with a comment explaining why, and review suppressions regularly since what was acceptable six months ago might not be acceptable now. Track metrics over time to monitor trends in findings.
Summary
IaC security scanning is one of the highest-value security controls you can implement. It's relatively easy to set up, runs automatically, and catches issues when they're cheapest to fix. Start with one tool, integrate it into your pipeline, and expand from there.
The goal isn't zero findings, it's ensuring that no known misconfigurations make it to production. With proper scanning in place, you can deploy with confidence knowing your infrastructure meets your security standards.
Want more insights?
Explore our other articles or subscribe to our newsletter for the latest cloud security guidance.