DEV Community

Securing Infrastructure as Code with Checkov: A Practical SAST Approach

Introduction
Infrastructure as Code (IaC) has revolutionized how we manage and deploy cloud resources. However, with great power comes great responsibility. Security misconfigurations in IaC templates can lead to serious vulnerabilities in production environments. This is where Static Application Security Testing (SAST) tools like Checkov come into play.

In this article, we'll explore how to implement Checkov, a powerful open-source SAST tool, to scan and secure your Infrastructure as Code across multiple platforms including Terraform, CloudFormation, Kubernetes, and more.

What is Checkov?
Checkov is a static code analysis tool developed by Bridgecrew (now part of Palo Alto Networks) that scans infrastructure code for security and compliance misconfigurations. It supports:

Terraform
CloudFormation
Kubernetes
Helm
ARM Templates
Serverless Framework
Docker
And many more...
Why Use SAST for Infrastructure as Code?
Early Detection: Identify security issues before they reach production
Compliance: Ensure adherence to industry standards (CIS, PCI-DSS, HIPAA)
Cost Reduction: Fix issues early in the development cycle
Automation: Integrate seamlessly into CI/CD pipelines
Policy as Code: Define and enforce organizational security policies

Getting Started with Checkov
Installation
Checkov can be installed using pip:

pip install checkov
Or using Docker:

docker pull bridgecrew/checkov
Basic Usage
Scan a Terraform directory:

checkov -d /path/to/terraform
Scan a specific file:

checkov -f main.tf
Practical Example: Securing Terraform Code
Let's examine a vulnerable Terraform configuration and how Checkov identifies the issues.

Vulnerable Configuration

main.tf - INSECURE EXAMPLE

resource "aws_s3_bucket" "data_bucket" {
bucket = "my-data-bucket"

# Missing encryption
# Missing versioning
# Missing logging
}

resource "aws_security_group" "web_sg" {
name = "web-security-group"
description = "Security group for web servers"

ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"] # Too permissive!
}
}

resource "aws_db_instance" "database" {
allocated_storage = 20
engine = "mysql"
instance_class = "db.t3.micro"
username = "admin"
password = "hardcodedpassword123" # Hardcoded password!
skip_final_snapshot = true

# Missing encryption
# Publicly accessible by default
}
Running Checkov
checkov -f main.tf --output json --output-file results.json
Checkov Output
Checkov will identify multiple security issues:

Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"
Check: CKV_AWS_19: "Ensure the S3 bucket has server-side encryption enabled"
Check: CKV_AWS_21: "Ensure the S3 bucket has versioning enabled"
Check: CKV_AWS_23: "Ensure Security Group does not allow ingress from 0.0.0.0/0 to port 22"
Check: CKV_AWS_16: "Ensure RDS database is encrypted at rest"
Check: CKV_AWS_41: "Ensure RDS instance has IAM authentication enabled"
Secure Configuration
Now let's fix these issues:

main.tf - SECURE VERSION

resource "aws_s3_bucket" "data_bucket" {
bucket = "my-data-bucket"
}

resource "aws_s3_bucket_versioning" "data_bucket_versioning" {
bucket = aws_s3_bucket.data_bucket.id
versioning_configuration {
status = "Enabled"
}
}

resource "aws_s3_bucket_server_side_encryption_configuration" "data_bucket_encryption" {
bucket = aws_s3_bucket.data_bucket.id

rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}

resource "aws_s3_bucket_logging" "data_bucket_logging" {
bucket = aws_s3_bucket.data_bucket.id

target_bucket = aws_s3_bucket.log_bucket.id
target_prefix = "log/"
}

resource "aws_security_group" "web_sg" {
name = "web-security-group"
description = "Security group for web servers"

ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"] # Restricted to internal network
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

resource "aws_db_instance" "database" {
allocated_storage = 20
engine = "mysql"
instance_class = "db.t3.micro"
username = "admin"
password = var.db_password # Using variable instead
skip_final_snapshot = false

storage_encrypted = true
iam_database_authentication_enabled = true
publicly_accessible = false

backup_retention_period = 7
enabled_cloudwatch_logs_exports = ["error", "general", "slowquery"]
}
Integrating Checkov into CI/CD
GitHub Actions Example
name: IaC Security Scan

on: [push, pull_request]

jobs:
checkov:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3

  - name: Set up Python
    uses: actions/setup-python@v4
    with:
      python-version: '3.9'

  - name: Install Checkov
    run: pip install checkov

  - name: Run Checkov
    run: |
      checkov -d . --output cli --output json --output-file checkov-report.json

  - name: Upload results
    uses: actions/upload-artifact@v3
    if: always()
    with:
      name: checkov-report
      path: checkov-report.json
Enter fullscreen mode Exit fullscreen mode

GitLab CI Example
checkov:
stage: test
image: bridgecrew/checkov:latest
script:
- checkov -d . --output cli --output junitxml --output-file checkov-report.xml
artifacts:
reports:
junit: checkov-report.xml
Custom Policies
Checkov allows you to create custom policies using Python:

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

class S3BucketEncryption(BaseResourceCheck):
def init(self):
name = "Ensure S3 bucket is encrypted with KMS"
id = "CKV_AWS_CUSTOM_1"
supported_resources = ['aws_s3_bucket']
categories = [CheckCategories.ENCRYPTION]
super().init(name=name, id=id, categories=categories,
supported_resources=supported_resources)

def scan_resource_conf(self, conf):
    if 'server_side_encryption_configuration' in conf:
        encryption_config = conf['server_side_encryption_configuration'][0]
        if 'rule' in encryption_config:
            rule = encryption_config['rule'][0]
            if 'apply_server_side_encryption_by_default' in rule:
                encryption = rule['apply_server_side_encryption_by_default'][0]
                if encryption.get('sse_algorithm') == ['aws:kms']:
                    return CheckResult.PASSED
    return CheckResult.FAILED
Enter fullscreen mode Exit fullscreen mode

check = S3BucketEncryption()
Best Practices
Run Checkov Early: Integrate it into your development workflow
Use Suppressions Wisely: Document why certain checks are suppressed
Keep Checkov Updated: New checks are added regularly
Set Baseline: Use --baseline to track improvements over time
Combine with Other Tools: Use alongside dynamic testing and manual reviews
Educate Your Team: Ensure developers understand the security implications
Suppressing False Positives
Sometimes you need to suppress legitimate findings:

resource "aws_security_group" "special_case" {
name = "special-sg"

#checkov:skip=CKV_AWS_23:This SG is for internal load balancer
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
Measuring Success
Track these metrics to measure the effectiveness of Checkov:

Vulnerability Detection Rate: Number of issues found per scan
Fix Time: Time from detection to remediation
False Positive Rate: Percentage of findings that are not actual issues
Coverage: Percentage of infrastructure code being scanned
Compliance Score: Pass rate against security benchmarks
Conclusion
Implementing SAST tools like Checkov is essential for maintaining secure infrastructure as code. By catching security issues early in the development cycle, you can:

Reduce security risks in production
Ensure compliance with industry standards
Foster a security-first culture
Save time and money on remediation
Start small by integrating Checkov into one project, measure the results, and gradually expand coverage across your organization. Security is a journey, not a destination, and tools like Checkov are invaluable companions along the way.

References
Checkov Official Documentation
OWASP Source Code Analysis Tools
Terraform Security Best Practices
CIS Benchmarks

Top comments (0)