Scanning Terraform Code with Checkov: Comprehensive Guide

Scanning Terraform Code with Checkov: Comprehensive Guide

Learn how to enhance your Terraform code's security by using Checkov to scan it. Our step-by-step guide walks you through the process, ensuring your infrastructure is secure and compliant.

As we discuss the growth of Infrastructure as Code (IaC), we encounter challenges related to security, quality, and testing, all of which are critically important. To address these concerns, we can use static code analysis tools such as Checkov.

Infrastructure as code

Infrastructure is a part of application development. IaC tools like Terraform and CloudFormation help to create infrastructure through code. Managing infrastructure in the cloud with IaC is more efficient and easier to modify as per requirement changes.

Terraform is one of the most popular IaC tools on the market because of its simple syntax and its support for a wide range of cloud platforms, such as AWS, Microsoft Azure, and more. As with any other code development, IaC code can have vulnerabilities, so they must be addressed before deployment.

In this process, code analysis tools help us identify vulnerabilities in the early stages of development. Currently, only a few IaC code analysis tools are available on the market. However, Checkov is one of the most efficient and simple tools for static code analysis, specifically designed for IaC.

The need for IAC code analysis

There are many criteria that should be met when developing IaC code. For example.

  1. One of the common issues in IaC is misconfigurations, such as allowing traffic from all ports and IPs, excessive permissions given for resources, hard-coding secrets, and more
  2. Most organizations have their own coding standards and regulations. When a developer writes code, it should meet the organization's criteria. For example, tags, proper descriptions, directory structure, etc.
  3. Compliance is also very important for organizations. It helps to avoid unnecessary resource provisioning in cloud environments. For example, in AWS, different types of EC2 instances are available, and each has different costs. If someone provisions R5d.xLarge instance without necessity, it will lead to excessive expense.

Checkov Integration with Terraform

Checkov can integrate with Terraform in multiple ways.

  1. Checkov IDE plugins are available to help identify errors as you write code.
  2. Checkov can scan Terraform files to identify issues. We can also use the Terraform plan file to execute the scan with all the information.
  3. Automate the Checkov scan in CI/CD pipelines to validate Terraform code and maintain consistency. This helps developers get immediate feedback.

Checkov setup

Prerequisites

  1. Python 3
  2. jq
  3. Checkov CLI
  4. IaC language interpreter (Terraform, CloudFormation, etc)

To install the Checkov CLI, use the following command:

pip3 install checkov

Configuring Checkov

To scan a directory, use the following command: -d : IaC root directory

checkov -d terraform-aws/environments/dev/ec2/

To scan a specific file, use this command: -f: file

checkov -f terraform-aws/environments/dev/ec2/main.tf

Scan Terraform Plan

To scan a Terraform plan file and include the related line numbers, you need to install the JSON query:

sudo apt install -y jq

Initalize the terraform code:

terraform init

Redirect the Terraform plan output to a tf.plan file:

terraform plan -var-file=../../../vars/dev/ec2.tfvars -out tfplan.binary

convert the tf.plan file to JSON format:

terraform show -json tfplan.binary | jq > tfplan.json

Now, scan the terraform tf.json file:

checkov -f tfplan.json

Checkov Reports

Check: CKV_AWS_46: "Ensure no hard-coded secrets exist in EC2 user data"
        PASSED for resource: module.ec2.aws_instance.ec2_instance[0]
        File: /tfplan.json:164-184
        Guide: <https://docs.paloaltonetworks.com/content/techdocs/en_US/prisma/prisma-cloud/prisma-cloud-code-security-policy-reference/aws-policies/secrets-policies/bc-aws-secrets-1.html>
Check: CKV_AWS_8: "Ensure all data stored in the Launch configuration or instance Elastic Blocks Store is securely encrypted"       
        FAILED for resource: module.ec2.aws_instance.ec2_instance[0]
        File: /tfplan.json:164-184
        Guide: <https://docs.paloaltonetworks.com/content/techdocs/en_US/prisma/prisma-cloud/prisma-cloud-code-security-policy-reference/aws-policies/aws-general-policies/general-13.html>

Custom Policy

Checkov enables the creation of custom policies to meet specific requirements. Custom policies can be developed using supported languages such as Python and YAML.

Python custom policy: Security groups inbound CIDR should not be public.

from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckResult, CheckCategories

class NonPublicCidrBlockCheck(BaseResourceCheck):
    def __init__(self) -> None:
        name = "Ensure AWS security groups have non-public CIDR blocks"
        id = "CUSTOM_AWS_002"
        supported_resources = ("aws_security_group",)
        categories = (CheckCategories.NETWORKING,)
        guideline = "CIDR blocks in security group rules should not be set to 0.0.0.0/0."
        super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources, guideline=guideline)

    def scan_resource_conf(self, conf) -> CheckResult:
       
        ingress_rules = conf.get("ingress")
        if ingress_rules:
            for rule in ingress_rules:
                cidr_blocks = rule.get("cidr_blocks")
                if cidr_blocks and any("0.0.0.0/0" in block for block in cidr_blocks):
                    return CheckResult.FAILED
        return CheckResult.PASSED

non_public_cidr_check = NonPublicCidrBlockCheck()

How to use custom policies?

Here’s an example directory structure:

jenkins-terraform/
├── Jenkinsfile
├── README.md
├── custom_checks
│   ├── SecurityGroupDescription.py
│   ├── SecurityGroupInboundCIDR.py
│   └── __init__.py
├── main.tf
└── variables.tf

The main.tf file:

provider "aws" {
  region = var.aws_region
}

resource "aws_security_group" "instance_sg" {
  name        = "instance-sg"
  #description = "checkov test"

  ingress {
    from_port        = var.from_port
    to_port          = var.to_port
    protocol         = var.protocol
    cidr_blocks      = var.cidr_block
  }
}

The variables.tf file

variable "aws_region" {
  description = "The AWS region to create things in."
  default     = "us-west-2"
}

variable "from_port" {
  type        = number
  default     = 22
  description = "List of starting ports for cidr ingress rules of the EC2 security group."
}

variable "to_port" {
  type        = number
  default     = 22
  description = "List of ending ports for cidr ingress rules of the EC2 security group."
}

variable "protocol" {
  type        = string
  default     = "tcp"
  description = "List of protocols for cidr ingress rules of the EC2 security group."
}

variable "cidr_block" {
  type        = list(string)
  **default     = ["0.0.0.0/0"]**
  description = "List of CIDR blocks for cidr ingress rules of the EC2 security group."
}

To scan the code with a custom policy, use the following command:

checkov -d . --external-checks-dir custom_checks --check CUSTOM_AWS_*
Note: You can also scan with Terraform plan.

Checkov Report

terraform scan results:

Passed checks: 0, Failed checks: 2, Skipped checks: 0

Check: CUSTOM_AWS_001: "Ensure AWS security groups have descriptions"
	FAILED for resource: aws_security_group.instance_sg
	File: /main.tf:5-15
	Guide: Security groups should have meaningful descriptions for better understanding.

		5  | resource "aws_security_group" "instance_sg" {
		6  |   name        = "instance-sg"
		7  |   #description = "checkov test"
		8  | 
		9  |   ingress {
		10 |     from_port        = var.from_port
		11 |     to_port          = var.to_port
		12 |     protocol         = var.protocol
		13 |     cidr_blocks      = var.cidr_block
		14 |   }
		15 | }

Check: CUSTOM_AWS_002: "Ensure AWS security groups have non-public CIDR blocks"
	FAILED for resource: aws_security_group.instance_sg
	File: /main.tf:5-15
	Guide: CIDR blocks in security group rules should not be set to 0.0.0.0/0.

		5  | resource "aws_security_group" "instance_sg" {
		6  |   name        = "instance-sg"
		7  |   #description = "checkov test"
		8  | 
		9  |   ingress {
		10 |     from_port        = var.from_port
		11 |     to_port          = var.to_port
		12 |     protocol         = var.protocol
		13 |     cidr_blocks      = var.cidr_block
		14 |   }
		15 | }

Suppressing and skipping policies

Skip the custom policy check (Include the comment inside the code)

provider "aws" {
  region = var.aws_region
}

resource "aws_security_group" "instance_sg" {
  # checkov:skip=CUSTOM_AWS_001: ADD REASON
  name        = "instance-sg"
}

Output:

terraform scan results:

Passed checks: 0, Failed checks: 0, Skipped checks: 1

Check: CUSTOM_AWS_001: "Ensure AWS security groups have descriptions"
        SKIPPED for resource: aws_security_group.instance_sg
        Suppress comment:  ADD REASON
        File: /main.tf:5-8
        Guide: Security groups should have meaningful descriptions for better understanding.

To skip a check with a specific ID:

checkov -d terraform-aws/environments/dev/ec2/ --skip-check CKV2_AWS_5

Hard and Soft fail

These arguments will provide an error code of either 0 or 1 based on the scanning. This feature is very helpful when integrated with the CI/CD pipeline. Here, 0 means pass, and 1 means fail, If the scan results in an error code of 1, the pipeline will halt; if it receives 0, the provisioning will continue. These arguments can be paired up with policy IDs and severity levels.

—soft-fail

This argument will always give the error code 0 with the scan results.

checkov -d . --soft-fail 

—soft-fail-on

This argument will return an error code 0 if any failed checks match the list.

checkov -d . --soft-fail-on LOW,CKV_AWS_46

It will return an error code of 1 if any non-listed checks fail.

—hard-fail-on

This argument will return an error code of 1 if any failed checks match the list.

checkov -d . --hard-fail-on HIGH,CKV_AWS_8,CRITICAL,MEDIUM

It will return an error code 0 if any non-listed checks fail.

Important CLI commands

To check the code with a specific framework:

checkov -d terraform-aws/environment/dev/ec2 --framework terraform

To obtain the output with severity levels:

checkov -d terraform-aws/environments/dev/ec2/ --bc-api-key MY_API_KEY

To view the output with severity levels, you need to create an API key from Bridgecrew

To get the output with a specific severity level:

checkov -d terraform-aws/environments/dev/ec2/ --check HIGH --bc-api-key MY_API_KEY

To scan the code with a specific ID:

checkov -d terraform-aws/environments/dev/ec2/ --check CKV_AWS_46

To get the output in a specific format:

checkov -d terraform-aws/environments/dev/ec2/ --check CKV_AWS_46 --output json

Supports more output formats, such as JSON, CSV, JUnit XML, sarif, spdx, and more.

To learn more about the CLI arguments. please refer to the official documentation.

Checkov Config File

To avoid long Checkov CLI commands, we can use a Checkov configuration file to store all the scan arguments. We can also reuse it for multiple scans and make modifications, which can help avoid duplicating commands.

Create a configuration file. The configuration file supports the YAML format.

checkov -f tfplan.json --external-checks-dir ../../../custom_checks/ --check CUSTOM_AWS_* --create-config ./checkov.yml

This will automatically create the configuration file, checkov.yml .

block-list-secret-scan: []
branch: master
check:
- CUSTOM_AWS_*
download-external-modules: false
evaluate-variables: true
external-checks-dir:
- ../../../custom_checks/
external-modules-download-path: .external_modules
file:
- - tfplan.json
framework:
- all
mask: []
secrets-history-timeout: 12h
secrets-scan-file-type: []
summary-position: top

And the next time, you only have to mention the configuration file to scan the code.

checkov --config-file ./checkov.yml

The Checkov GUI output.

Conclusion

Combining these tools simplifies safer infrastructure provisioning in cloud environments; we can use this tool to scan Terraform code or integrate it with CI/CD tools to automate scanning.

I recommend integrating Checkov into your IaC code creation process to reduce risk and improve compliance.

About the author
Bibin Wilson

Bibin Wilson

Bibin Wilson (authored over 300 tech tutorials) is a cloud and DevOps consultant with over 12+ years of IT experience. He has extensive hands-on experience with public cloud platforms and Kubernetes.

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to DevOpsCube – Easy DevOps, SRE Guides & Reviews.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.

📩 Join 20K+ Engineers