Going to AWS Summit London? 🇬🇧🇬🇧

Meet us there →

General

Infrastructure as Code (IaC) and Policy as Code Scanning for Vulnerabilities

Infrastructure as Code (IaC) and Policy as Code Scanning for Vulnerabilities

In a prior post, we explored automated and manual code testing with DevSecOps. That article briefly touched on the importance of IaC scanning and policy as code. In this article, we’re going to take a much closer look with practical examples of running scans that check for Infrastructure as Code misconfigurations or policy violations.

We will cover:

  1. What is IaC scanning
  2. Infrastructure as code scanning tools
  3. Policy as Code scanning
  4. Demo: Running Terraform IaC and Policy as Code scans
  5. Creating custom policies
  6. Integrating scanning tools with CI/CD pipeline

What is IaC Scanning?

Infrastructure as Code (IaC) is the practice of defining and managing infrastructure resources, like servers, networks, and storage, using code-based templates or configuration files. By defining infrastructure through code, it becomes much easier for organizations to automate, scale, and achieve deployment and management consistency for their infrastructure.

IaC scanning is the process of analyzing and evaluating the security and compliance of infrastructure code. It focuses on examining the code and configurations that define the infrastructure to identify potential security vulnerabilities, misconfigurations, and compliance violations. It helps organizations validate that their infrastructure deployments follow security best practices, industry standards, and regulatory requirements.

The scanning process works just like other types of static application security testing (SAST) tools, but with a focus on IaC. The results of these scans provide actionable insights and recommendations to address issues, which gives engineers the chance to remediate potential risks before the infrastructure gets deployed.

Why do you need IaC scanning?

Just like SAST is an important part of verifying application code security, IaC scanning is an important part of verifying infrastructure code security and quality.

By integrating IaC scanning into the development and deployment pipelines, organizations can identify and address security issues very early in the process, reducing the chances of vulnerabilities being deployed to production environments and getting one step closer to achieving shift-left security.

Infrastructure as Code (IaC) scanning tools

Terraform, OpenTofu, Pulumi, AWS CloudFormation, Helm, and Ansible are all examples of modern Infrastructure as Code tools that many organizations use and that Spacelift supports. They provide a fantastic way of controlling environments and infrastructure that lives in AWS, Azure, GCP, etc.

Because this infrastructure gets provisioned and managed through code instead of through manual processes, infrastructure specifications live in configuration files. This is wonderful because it makes it much easier to create, edit, and distribute those specifications. With that said, we can make mistakes as we are writing those specifications, and we may not realize it until the infrastructure has already been provisioned in production.

To significantly reduce the odds of a major mistake making it to production (think leaking secrets, opening up permissions, etc.), we can use static code analysis tools specifically created for Infrastructure as Code.

Examples of Iac scanning tools include:

  • Checkov – a static analysis tool specifically designed for IaC environments, particularly for cloud-native technologies. It helps identify and prevent misconfigurations, security risks, and compliance violations by scanning IaC files (ie: Terraform configurations), and providing detailed reports on potential issues.
  • tfsec – also a security scanning tool that focuses on Terraform configurations to identify potential security risks and misconfigurations. It analyzes the Terraform configuration files and provides actionable recommendations based on industry best practices and common security issues. Read more in our What is tfsec? article.
  • TFLint – a powerful linter for Terraform configurations. It analyzes Terraform config files to enforce best practices and coding standards and to find potential errors for the major cloud providers (AWS, Azure, and GCP). For example, it can help enforce consistent formatting and naming conventions and warn about unused declarations.

For this article, we’ll be using and demonstrating the tool Checkov, but the concepts apply across the board.

Check out other most useful Terraform tools.

Policy as Code scanning

Another aspect of detecting issues with infrastructure is to use Policy as Code. Policy as Code expresses rules using code. This extends the IaC approach by also covering rules governing that infrastructure and the platform that manages it.

You can think of it as creating guardrails for your infrastructure and platforms.

As an example of creating guardrails for platforms, with Spacelift, you can use policies to decide who can approve or reject a run, and how a run can be approved.

As an example for infrastructure guardrails, we could create a policy that prevents creating a public Access Control List (ACL) for Amazon S3 buckets, which would help prevent accidentally making buckets that contain sensitive information public. We’ll demonstrate how to do this in just a moment.

Using both IaC and Policy as Code scanning can help you catch issues before they even get committed to your repositories and before they reach production. So let’s take a look at how this works.

Demo: Running Terraform IaC and Policy as Code Scans

Let’s run through hands-on demonstrations of how we can scan Terraform configuration files for policy violations or for potential security misconfigurations.

For this demo, you can use just about any Terraform configuration file that you come across, but I’ll be using one of MoreThanCertified’s projects.

To perform the scans, we’re going to use an open-source tool called Checkov by Bridgecrew. Checkov offers IaC and Policy as Code scanning and will be a perfect tool for our demo.

Let’s take a look at the directory titled 08-a_security_group in that repository, and let’s click on the main.tf file.

In this file, let’s focus our attention on lines 50 – 67:

resource "aws_security_group" "mtc_sg" {
  name        = "public_sg"
  description = "public security group"
  vpc_id      = aws_vpc.mtc_vpc.id
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

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

This part of the Terraform configuration is creating a Security Group to allow access to AWS resources via SSH port 22 from all IP addresses (0.0.0.0/0). This is a common configuration used for demonstrations and teaching, because everyone’s IP address is going to be different and so that value would need to get modified based on your specific needs. With that said, it’s a configuration that really shouldn’t be used in production.

Instead, if you have one specific IP address that will get used to SSH into an instance, you would want to specify that IP address. Or, if you have a range of IPs, you could specify that range…and so on.

We’ve actually just outlined a scanning policy that we can create to make sure that we’re not letting engineers use 0.0.0.0/0 for CIDR blocks associated with SSH port 22 security groups.

When we run Checkov, or any other scanner, we would definitely want it to find this issue and report it so we can fix it before pushing it to production. Let’s see if it does!

Step 1: Setting up Checkov

There are a few ways that we can use, run, or install Checkov on our systems.

Pip install

The first method we’ll look at is using pip install, or more specifically:

pip3 install checkov

This requires having Python ≥ 3.7 installed

Homebrew install

If you are running a MacOS machine, then you can use homebrew:

brew install checkov

Docker

If you prefer, you could also use Checkov with Docker:

docker pull bridgecrew/checkov
docker run --tty --rm --volume /user/tf:/tf --workdir /tf bridgecrew/checkov --directory /tf

Step 2: Setting up Terraform

There are a few ways that we can use, run, or install Terraform on our systems. If you already have it set up on your system, feel free to skip forward to “Initializing our Terraform project.”

Linux (Debian-based Systems)

Ensure that your system is up to date and you have installed the gnupg, software-properties-common, and curl packages. These packages are used to verify HashiCorp’s GPG signature and install HashiCorp’s Debian package repository.

sudo apt-get update && sudo apt-get install -y gnupg software-properties-common

Install the HashiCorp GPG key:

wget -O- https://apt.releases.hashicorp.com/gpg | \
gpg --dearmor | \
sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg

Verify the fingerprint:

gpg --no-default-keyring \
--keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg \
--fingerprint

The gpg command will report the fingerprint:

/usr/share/keyrings/hashicorp-archive-keyring.gpg
-------------------------------------------------
pub   rsa4096 XXXX-XX-XX [SC]
AAAA AAAA AAAA AAAA
uid           [ unknown] HashiCorp Security (HashiCorp Package Signing) <security+packaging@hashicorp.com>
sub   rsa4096 XXXX-XX-XX [E]

Refer to the Official Packaging Guide for the latest public signing key. You can also verify the key on Security at HashiCorp under Linux Package Checksum Verification.

As long as everything looks good, you can add the official HashiCorp repository to your system:

echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list

Download the package information from HashiCorp:

sudo apt update

You’re now ready to install Terraform:

sudo apt-get install terraform

You can make sure it installed successfully with:

terraform -help

Or, if you prefer a manual installation, you can view Terraform installation instructions.

Homebrew Terraform install

If you are running a MacOS machine, then you can use homebrew:

brew tap hashicorp/tap

brew install hashicorp/tap/terraform

You can make sure it installed successfully with:

terraform -help

Step 3: Initializing our Terraform project

Next, we’ll copy the code from this page and make a main.tf file to put that code in it.

After that, run the following command within that directory:

terraform init

You should see an output like the following:

Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v5.1.0...
- Installed hashicorp/aws v5.1.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Being able to execute this Terraform file would require additional configuration, but we have everything we need to run Checkov, which is the purpose of this article. So let’s do that now.

Step 4: Running Checkov

Checkov can scan entire directories or individual files. In this case, we only have one file, so we can scan it like this:

checkov --file ./main.tf

Your results should look similar to the following, although they may be a bit different depending on versions and other factors::

_               _              
   ___| |__   ___  ___| | _______   __
  / __| '_ \\ / _ \\/ __| |/ / _ \\ \\ / /
 | (__| | | |  __/ (__|   < (_) \\ V / 
  \\___|_| |_|\\___|\\___|_|\\_\\___/ \\_/  
                                      
By bridgecrew.io | version: 2.3.270 
Update available 2.3.270 -> 2.3.273
Run pip3 install -U checkov to update 

terraform scan results:

Passed checks: 3, Failed checks: 3, Skipped checks: 0

Check: CKV_AWS_260: "Ensure no security groups allow ingress from 0.0.0.0:0 to port 80"
	PASSED for resource: aws_security_group.mtc_sg
	File: /main.tf:50-67
	Guide: https://docs.bridgecrew.io/docs/ensure-aws-security-groups-do-not-allow-ingress-from-00000-to-port-80
Check: CKV_AWS_277: "Ensure no security groups allow ingress from 0.0.0.0:0 to port -1"
	PASSED for resource: aws_security_group.mtc_sg
	File: /main.tf:50-67
	Guide: <https://docs.bridgecrew.io/docs/ensure-aws-security-group-does-not-allow-all-traffic-on-all-ports>
Check: CKV_AWS_25: "Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389"
	PASSED for resource: aws_security_group.mtc_sg
	File: /main.tf:50-67
	Guide: <https://docs.bridgecrew.io/docs/networking_2>
Check: CKV_AWS_130: "Ensure VPC subnets do not assign public IP by default"
	FAILED for resource: aws_subnet.mtc_public_subnet
	File: /main.tf:11-20
	Guide: <https://docs.bridgecrew.io/docs/ensure-vpc-subnets-do-not-assign-public-ip-by-default>

		11 | resource "aws_subnet" "mtc_public_subnet" {
		12 |   vpc_id                  = aws_vpc.mtc_vpc.id
		13 |   cidr_block              = "10.123.1.0/24"
		14 |   map_public_ip_on_launch = true
		15 |   availability_zone       = "us-west-2a"
		16 | 
		17 |   tags = {
		18 |     Name = "dev-public"
		19 |   }
		20 | }

Check: CKV_AWS_24: "Ensure no security groups allow ingress from 0.0.0.0:0 to port 22"
	FAILED for resource: aws_security_group.mtc_sg
	File: /main.tf:50-67
	Guide: <https://docs.bridgecrew.io/docs/networking_1-port-security>

		50 | resource "aws_security_group" "mtc_sg" {
		51 |   name        = "public_sg"
		52 |   description = "public security group"
		53 |   vpc_id      = aws_vpc.mtc_vpc.id
		54 |   ingress {
		55 |     from_port   = 22
		56 |     to_port     = 22
		57 |     protocol    = "tcp"
		58 |     cidr_blocks = ["0.0.0.0/0"]
		59 |   }
		60 | 
		61 |   egress {
		62 |     from_port   = 0
		63 |     to_port     = 0
		64 |     protocol    = "-1"
		65 |     cidr_blocks = ["0.0.0.0/0"]
		66 |   }
		67 | }

Check: CKV_AWS_23: "Ensure every security groups rule has a description"
	FAILED for resource: aws_security_group.mtc_sg
	File: /main.tf:50-67
	Guide: <https://docs.bridgecrew.io/docs/networking_31>

		50 | resource "aws_security_group" "mtc_sg" {
		51 |   name        = "public_sg"
		52 |   description = "public security group"
		53 |   vpc_id      = aws_vpc.mtc_vpc.id
		54 |   ingress {
		55 |     from_port   = 22
		56 |     to_port     = 22
		57 |     protocol    = "tcp"
		58 |     cidr_blocks = ["0.0.0.0/0"]
		59 |   }
		60 | 
		61 |   egress {
		62 |     from_port   = 0
		63 |     to_port     = 0
		64 |     protocol    = "-1"
		65 |     cidr_blocks = ["0.0.0.0/0"]
		66 |   }
		67 | }

Let’s break down what’s happening, starting with these lines:

Check: CKV_AWS_260: "Ensure no security groups allow ingress from 0.0.0.0:0 to port 80"
	PASSED for resource: aws_security_group.mtc_sg
	File: /main.tf:50-67
	Guide: <https://docs.bridgecrew.io/docs/ensure-aws-security-groups-do-not-allow-ingress-from-00000-to-port-80>

We can see that Checkov is checking CKV_AWS_260 which has a description of “Ensure no security groups allow ingress from 0.0.0.0:0 to port 80".

For more details about Checkov’s IDs (such as CKV_AWS_260)and what they mean, please refer to this documentation.

We can then see that the check PASSED for the resource aws_security_group.mtc_sg. Checkov even includes a reference guide so you can see a description of this check.

The next few checks were also successful, but then we came across our first failed check:

Check: CKV_AWS_130: "Ensure VPC subnets do not assign public IP by default"
	FAILED for resource: aws_subnet.mtc_public_subnet
	File: /main.tf:11-20
	Guide: <https://docs.bridgecrew.io/docs/ensure-vpc-subnets-do-not-assign-public-ip-by-default>

		11 | resource "aws_subnet" "mtc_public_subnet" {
		12 |   vpc_id                  = aws_vpc.mtc_vpc.id
		13 |   cidr_block              = "10.123.1.0/24"
		14 |   map_public_ip_on_launch = true
		15 |   availability_zone       = "us-west-2a"
		16 | 
		17 |   tags = {
		18 |     Name = "dev-public"
		19 |   }
		20 | }

OK! So that was not one of the issues we mentioned above — and this is a great demonstration of why tools like these are helpful. A human being might give the configurations a quick look to find obvious issues, but they can just as easily miss them.

Here, Checkov is warning us that we are auto-assigning public IP addresses to resources being launched in that specific subnet. It even shows us exactly what code triggered this failure, and we can see the setting causing this issue:

map_public_ip_on_launch = true

The next failure is:

Check: CKV_AWS_24: "Ensure no security groups allow ingress from 0.0.0.0:0 to port 22"
	FAILED for resource: aws_security_group.mtc_sg
	File: /main.tf:50-67
	Guide: <https://docs.bridgecrew.io/docs/networking_1-port-security>

		50 | resource "aws_security_group" "mtc_sg" {
		51 |   name        = "public_sg"
		52 |   description = "public security group"
		53 |   vpc_id      = aws_vpc.mtc_vpc.id
		54 |   ingress {
		55 |     from_port   = 22
		56 |     to_port     = 22
		57 |     protocol    = "tcp"
		58 |     cidr_blocks = ["0.0.0.0/0"]
		59 |   }
		60 | 
		61 |   egress {
		62 |     from_port   = 0
		63 |     to_port     = 0
		64 |     protocol    = "-1"
		65 |     cidr_blocks = ["0.0.0.0/0"]
		66 |   }
		67 | }

Here, Checkov caught exactly what we were looking for: the overly permissive SSH security group rule. Perfect!

But wait, Checkov found something else:

Check: CKV_AWS_23: "Ensure every security groups rule has a description"
	FAILED for resource: aws_security_group.mtc_sg
	File: /main.tf:50-67
	Guide: <https://docs.bridgecrew.io/docs/networking_31>

		50 | resource "aws_security_group" "mtc_sg" {
		51 |   name        = "public_sg"
		52 |   description = "public security group"
		53 |   vpc_id      = aws_vpc.mtc_vpc.id
		54 |   ingress {
		55 |     from_port   = 22
		56 |     to_port     = 22
		57 |     protocol    = "tcp"
		58 |     cidr_blocks = ["0.0.0.0/0"]
		59 |   }
		60 | 
		61 |   egress {
		62 |     from_port   = 0
		63 |     to_port     = 0
		64 |     protocol    = "-1"
		65 |     cidr_blocks = ["0.0.0.0/0"]
		66 |   }
		67 | }

Here, Checkov is recommending that we provide descriptions to every security group rule. Technically, descriptions are optional in Terraform and AWS, but practically, they should exist because they make maintaining resources much easier in the long term.

Step 5: Fixing the issues

Now that we’ve identified three separate issues let’s go ahead and fix them so we can re-run the scans and see if our fix worked.

Open up the main.tf file and make your changes.

After making my changes of:

# Under resource "aws_subnet" "mtc_public_subnet"
map_public_ip_on_launch = false

# Under resource "aws_security_group" "mtc_sg" {
ingress {
    description = "SSH ingress"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/24"]
  }

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

Let’s re-run the scan:

checkov --file ./main.tf

We’ll now get a clean bill of health!

_               _              
   ___| |__   ___  ___| | _______   __
  / __| '_ \\ / _ \\/ __| |/ / _ \\ \\ / /
 | (__| | | |  __/ (__|   < (_) \\ V / 
  \\___|_| |_|\\___|\\___|_|\\_\\___/ \\_/  
                                      
By bridgecrew.io | version: 2.3.270 
Update available 2.3.270 -> 2.3.273
Run pip3 install -U checkov to update 

terraform scan results:

Passed checks: 6, Failed checks: 0, Skipped checks: 0

Check: CKV_AWS_130: "Ensure VPC subnets do not assign public IP by default"
	PASSED for resource: aws_subnet.mtc_public_subnet
	File: /main.tf:11-20
	Guide: <https://docs.bridgecrew.io/docs/ensure-vpc-subnets-do-not-assign-public-ip-by-default>
Check: CKV_AWS_260: "Ensure no security groups allow ingress from 0.0.0.0:0 to port 80"
	PASSED for resource: aws_security_group.mtc_sg
	File: /main.tf:50-69
	Guide: <https://docs.bridgecrew.io/docs/ensure-aws-security-groups-do-not-allow-ingress-from-00000-to-port-80>
Check: CKV_AWS_24: "Ensure no security groups allow ingress from 0.0.0.0:0 to port 22"
	PASSED for resource: aws_security_group.mtc_sg
	File: /main.tf:50-69
	Guide: <https://docs.bridgecrew.io/docs/networking_1-port-security>
Check: CKV_AWS_23: "Ensure every security groups rule has a description"
	PASSED for resource: aws_security_group.mtc_sg
	File: /main.tf:50-69
	Guide: <https://docs.bridgecrew.io/docs/networking_31>
Check: CKV_AWS_277: "Ensure no security groups allow ingress from 0.0.0.0:0 to port -1"
	PASSED for resource: aws_security_group.mtc_sg
	File: /main.tf:50-69
	Guide: <https://docs.bridgecrew.io/docs/ensure-aws-security-group-does-not-allow-all-traffic-on-all-ports>
Check: CKV_AWS_25: "Ensure no security groups allow ingress from 0.0.0.0:0 to port 3389"
	PASSED for resource: aws_security_group.mtc_sg
	File: /main.tf:50-69
	Guide: <https://docs.bridgecrew.io/docs/networking_2>

Creating custom policies

While Checkov includes, by default, many scanning policies pre-built by the Checkov team, you may need to create your own custom policies based on your organization’s needs. There are a few ways of creating custom policies:

  1. Using Python – to check for the status of configuration attributes
  2. Using YAML – to check for both the status of configuration attributes and to check the connection state between types of resources

For this demonstration, we’ll be using Python. Let’s create a new directory:

mkdir custom_policies

Then, in that directory, we need to set up a file named:

cd custom_policies

vim __init__.py

(If you’re not comfortable with vim, feel free to use your favorite text editor.)

In that file, we need to add the following:

from os.path import dirname, basename, isfile, join
import glob
modules = glob.glob(join(dirname(__file__), "*.py"))
__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]

From there, we can add our custom checks by creating new files, like this:

Vim CustomVPCCIDRBlockCheck.py

In that file, paste the following:

from typing import Any

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

class CustomVPCCIDRBlockCheck(BaseResourceCheck):
    def __init__(self) -> None:
        name = "Ensure VPC CIDR block range is within the specified range"
        id = "CUSTOM_POLICY.AWS_VPC_CIDR_BLOCK"
        supported_resources = ("aws_vpc",)
        categories = (CheckCategories.NETWORKING,)
        super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)

    def scan_resource_conf(self, conf: dict[str, Any]) -> CheckResult:
        if 'cidr_block' in conf.keys():
            cidr_block = conf['cidr_block'][0]
            if cidr_block.startswith(("10.0.")):
                return CheckResult.PASSED
        return CheckResult.FAILED

check = CustomVPCCIDRBlockCheck()

This basic check will ensure that VPCs are using a CIDR block range within a specific range. In this particular example, we’re looking to make sure it starts with 10.0.. Going back to our main.tf file, the VPC CIDR block range is configured as: 10.123.0.0/16 which should fail this check. Let’s see if it does!

First, change back to the parent directory:

cd ..

Then run Checkov with this command:

checkov --file ./main.tf --external-checks-dir custom_policies

The result:

Check: CUSTOM_POLICY.AWS_VPC_CIDR_BLOCK: "Ensure VPC CIDR block range is within the specified range"
	FAILED for resource: aws_vpc.mtc_vpc
	File: /main.tf:1-9

		1 | resource "aws_vpc" "mtc_vpc" {
		2 |   cidr_block           = "10.123.0.0/16"
		3 |   enable_dns_hostnames = true
		4 |   enable_dns_support   = true
		5 | 
		6 |   tags = {
		7 |     Name = "dev"
		8 |   }
		9 | }

As we can see, Checkov failed this custom policy check just like it was supposed to.

Custom policy: Private ACLs for PCI S3 buckets

As another example, from Checkov’s documentation, we could use Checkov to check and make sure that any S3 bucket tagged with “PCI” has private ACLs.

vim custom_policies/S3PCIPrivateACL.py
from __future__ import annotations

from typing import Any

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

class S3PCIPrivateACL(BaseResourceCheck):
    def __init__(self) -> None:
        name = "Ensure PCI Scope buckets has private ACL (enable public ACL for non-pci buckets)"
        id = "CKV_AWS_999"
        supported_resources = ("aws_s3_bucket",)
        # CheckCategories are defined in models/enums.py
        categories = (CheckCategories.BACKUP_AND_RECOVERY,)
        guideline = "Follow the link to get more info <https://docs.bridgecrew.io/docs>"
        super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources, guideline=guideline)

    def scan_resource_conf(self, conf: dict[str, list[Any]]) -> CheckResult:
        """
            Looks for ACL configuration at aws_s3_bucket and Tag values:
            <https://www.terraform.io/docs/providers/aws/r/s3_bucket.html>
        :param conf: aws_s3_bucket configuration
        :return: <CheckResult>
        """
        tags = conf.get("tags")
        if tags and isinstance(tags, list):
            tags = tags[0]
            if tags.get("Scope") == "PCI":
                acl_block = conf['acl']
                if acl_block in [["public-read"], ["public-read-write"], ["website"]]:
                    return CheckResult.FAILED
        return CheckResult.PASSED

check = S3PCIPrivateACL()

This new policy we just created will check to make sure that any S3 bucket tagged as containing PCI is set to have a private ACL. If it has a public ACL, then Checkov should raise a red flag.

I won’t run it because our sample Terraform file doesn’t have any S3 resources, but if you did, this could be a valuable check.

Integrating scanning tools with CI/CD pipeline

We’ve been running the tool manually, which your developers could also do, but what if they forget? What if we don’t have to add yet another task to their checklist?

We can integrate Checkov (or whatever other IaC scanning tool you’re using) into our pipeline using a few different approaches. As an example, it has a VSCode extension that you can use directly within your IDE so that developers don’t have to leave the tool they’re already in to run a scan.

As another example, we could have it run during a merge request review or even as part of our build process. 

This way, you have scans automatically and regularly running instead of having to rely on developers remembering.

Check out also how to integrate security tools with Spacelift using custom inputs.

Key points

We’ve now taken a look at how we can use tools to run IaC scans for misconfigurations or common issues. As we learned, we can even create our very own custom policies that look for issues based on our organization’s policies. This helps demonstrate just how powerful static code analysis tools can be, especially when they are custom-built for certain stages of the DevSecOps pipeline.

Terraform Management Made Easy

Spacelift effectively manages Terraform state, more complex workflows, supports policy as code, programmatic configuration, context sharing, drift detection, resource visualization and includes many more features.

Start free trial