Home Industries Case Studies About Azure CSP Drop Table Pulse Get Started
Back to Insights
DevSecOps October 2025 6 min read

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.

Drop Table Team

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.