Infrastructure as Code is convenient because it turns cloud infrastructure into something we can version, review, and reuse. With Terraform, for example, a team can describe its networks, servers, and storage in readable configuration files instead of setting up every resource manually.
There is one catch: repeatable infrastructure is not necessarily secure infrastructure. Terraform may accept a configuration even when it opens an administrative port to the entire internet or creates storage without the protection the project needs. The code works, but it also reproduces the mistake every time it is used.
That was the reason I chose to test Checkov. It is an open-source static analysis tool for Infrastructure as Code. The idea is similar to SAST for application source code: inspect the files early, point out risky decisions, and fix them before they become deployed resources.
In this article, I use a small Terraform example to see what Checkov detects and how the reported problems can be corrected.
What is Checkov?
Checkov reads IaC files and evaluates them against security and compliance policies. Terraform is not its only supported format; it can also analyze technologies such as CloudFormation, Kubernetes, Helm, ARM templates, and Dockerfiles.
What I found most useful is that the scan does not require the infrastructure to exist. It can be performed while the configuration is still being written. Checkov also:
- detects insecure configurations before cloud resources are created;
- provides the policy identifier and the location of each problem;
- can run locally, in a pre-commit workflow, or in CI/CD;
- supports custom policies for organization-specific requirements;
- can produce machine-readable reports such as JSON, JUnit XML, and SARIF.
This is a practical example of shift-left security: security feedback arrives while changing the code, not after a vulnerable resource has already reached the cloud.
Practical scenario
For the test, I used a simple scenario: an AWS S3 bucket for application data and a security group for administrative access. I intentionally left several weak settings in the first version:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "app_data" {
bucket = "demo-app-data-change-this-name"
}
resource "aws_s3_bucket_public_access_block" "app_data" {
bucket = aws_s3_bucket.app_data.id
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
resource "aws_security_group" "administration" {
name = "administration"
description = "Administrative access"
ingress {
description = "SSH from anywhere"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
Figure 1. Insecure Terraform configuration used for the first Checkov scan.
At first glance, there is nothing especially unusual about this file. Looking more carefully, however, reveals three important problems:
- Public-access protections for the S3 bucket are disabled.
- S3 versioning is absent, and no encryption configuration is declared explicitly in the file.
- SSH port 22 is open to every IPv4 address.
Installing and running Checkov
I installed Checkov from PyPI:
pip install checkov
Once it is installed, the shortest way to scan the current directory is:
checkov -d .
For this example, the scan can also be limited to the policies related to the resources being tested:
checkov -d . --check CKV_AWS_19,CKV_AWS_21,CKV_AWS_24,CKV_AWS_53,CKV_AWS_54,CKV_AWS_55,CKV_AWS_56
The output separates passed checks from failed ones. More importantly, it identifies the affected Terraform resource and shows where the problem appears in the file.
In my first scan, Checkov reported 1 passed check and 6 failed checks. The failed policies covered the four disabled S3 public-access controls, missing bucket versioning, and unrestricted SSH access. The encryption-at-rest policy passed even though the file did not declare a separate encryption resource; this is an important reminder to interpret the actual scanner result rather than assume that every omitted setting will fail.
Figure 2. First Checkov result: 1 passed check and 6 failed checks.
The useful part is the timing. These findings appeared before terraform apply. Nothing had to be created in AWS just to discover that the configuration was unsafe.
Correcting the Terraform configuration
I then changed the configuration instead of skipping the warnings. The revised version is:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
provider "aws" {
region = "us-east-1"
}
variable "administrator_cidr" {
description = "Trusted network allowed to use SSH"
type = string
validation {
condition = var.administrator_cidr != "0.0.0.0/0"
error_message = "SSH access must not be open to the entire internet."
}
}
resource "aws_kms_key" "app_data" {
description = "KMS key for application data"
enable_key_rotation = true
}
resource "aws_s3_bucket" "app_data" {
bucket = "demo-app-data-change-this-name"
}
resource "aws_s3_bucket_versioning" "app_data" {
bucket = aws_s3_bucket.app_data.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "app_data" {
bucket = aws_s3_bucket.app_data.id
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = aws_kms_key.app_data.arn
sse_algorithm = "aws:kms"
}
}
}
resource "aws_s3_bucket_public_access_block" "app_data" {
bucket = aws_s3_bucket.app_data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_security_group" "administration" {
name = "administration"
description = "Administrative access from a trusted network"
ingress {
description = "SSH from the approved network"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.administrator_cidr]
}
}
Figure 3. Relevant section of the corrected Terraform configuration.
The changes are fairly direct:
- blocks public S3 access;
- enables object versioning;
- encrypts stored data with AWS KMS;
- enables KMS key rotation;
- restricts SSH access to an approved network;
- validates that the administrator network is not the entire internet.
After saving the changes, I ran the same scan again. This time, Checkov reported 7 passed checks, 0 failed checks, and 0 skipped checks.
Figure 4. Final Checkov result after correcting the Terraform configuration.
This second result is also useful evidence for a pull request because it shows that the original findings were actually addressed.
Adding Checkov to GitHub Actions
Running the command manually is helpful during development, although it depends on each person remembering to do it. A CI/CD check makes the process more consistent. The following GitHub Actions workflow scans the Terraform code after a push and when someone opens a pull request:
name: Checkov IaC Scan
on:
push:
pull_request:
permissions:
contents: read
jobs:
checkov:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: .
framework: terraform
quiet: true
soft_fail: false
Here, soft_fail: false makes the job fail when a policy violation is found. The finding becomes visible during code review instead of being silently ignored. For a production repository, I would also pin the action to a reviewed release or commit SHA rather than relying permanently on the master reference.
Limitations and responsible use
Checkov is useful, but a green result should not be confused with proof that the whole environment is secure. Static analysis can produce false positives, and some Terraform values are only known during planning or deployment. A scanner also cannot understand every business decision behind a resource.
For those reasons, I would use Checkov together with:
- peer review;
-
terraform validateand plan review; - secret scanning;
- least-privilege IAM design;
- cloud configuration monitoring;
- documented and time-limited exceptions;
- runtime testing and incident monitoring.
One lesson from the exercise is that failed checks should be investigated, not automatically suppressed. Sometimes an exception is justified, but it should have a reason, an owner, a narrow scope, and an expiration date. Otherwise, a temporary workaround can quietly become a permanent weakness.
Conclusion
This small test showed why Terraform files deserve the same security attention as application code. Checkov detected unrestricted SSH access, missing S3 versioning, and public-access settings that should not have been disabled. The comparison was clear: the first scan returned six failed checks, while the corrected version passed all seven selected policies. All of those problems could be discussed and corrected before creating a single cloud resource.
Checkov does not replace good architecture or careful review. What it does provide is a fast and repeatable first line of defense. Used locally and in CI/CD, it helps catch ordinary configuration mistakes while they are still inexpensive to fix—which is exactly where a security tool is most useful.
References
- OWASP, “Source Code Analysis Tools”: https://owasp.org/www-community/Source_Code_Analysis_Tools
- Checkov documentation, “What is Checkov?”: https://www.checkov.io/1.Welcome/What%20is%20Checkov.html
- Checkov documentation, “Installing Checkov”: https://www.checkov.io/2.Basics/Installing%20Checkov.html
- Checkov documentation, “Terraform Scanning”: https://www.checkov.io/7.Scan%20Examples/Terraform.html
- Checkov policy index for Terraform: https://www.checkov.io/5.Policy%20Index/terraform.html
- HashiCorp, “What is Terraform?”: https://developer.hashicorp.com/terraform/intro
Suggested publication tags: terraform, devsecops, security, aws, infrastructureascode




Top comments (0)