Securing Terraform Modules with tfsec

Infrastructure as Code (IaC) patterns have enabled velocity, repeatability, and codification of best practices for our environments. However, using IaC has introduced new challenges, especially around security. Securing manually deployed infrastructure is already difficult. This problem rapidly multiplies when organizations adopt IaC patterns, since they must now contend with the complexity of code and the proliferation of environments enabled by this increased velocity.

Codifying our environments doesn’t have to result in insecure configurations. On the contrary, expressing environment definitions via code has the potential to automate the process of scanning for security violations before they ever have the chance to cause harm. In this article, I’ll discuss one way to accomplish this in Terraform environments by using tfsec. Tfsec is an open source utility created by Aqua Security that performs static analysis of Terraform code to spot security misconfigurations. It has grown in popularity, and it’s even listed as a tool to adopt on the Thoughtworks Tech Radar. I’ll also show you how Cloudify integrates seamlessly with tfsec and allows you to enforce policy in your self-service environments.

Installing tfsec

Cloud native tools are commonly provided as single, easily installed binaries and tfsec is no different. You can download the latest release of tfsec for your platform from GitHub and execute it immediately:

$ sudo wget -o /dev/null -O /usr/bin/tfsec https://github.com/aquasecurity/tfsec/releases/download/v1.28.0/tfsec-linux-amd64
 
$ sudo chmod +x /usr/bin/tfsec
 
$ tfsec --version
v1.28.0

Built-in checks

Tfsec includes dozens of checks for common cloud misconfigurations. You can take advantage of these checks without any additional configuration: tfsec includes them right out of the box.

For example, let’s take a look at a simple Terraform module to deploy an EC2 instance. You can clone this example from our community GitHub:

$ git clone git@github.com:cloudify-community/tfsec-example.git
Cloning into 'tfsec-example'...
remote: Enumerating objects: 27, done.
remote: Counting objects: 100% (27/27), done.
remote: Compressing objects: 100% (16/16), done.
remote: Total 27 (delta 6), reused 27 (delta 6), pack-reused 0
Receiving objects: 100% (27/27), 5.65 KiB | 5.65 MiB/s, done.
Resolving deltas: 100% (6/6), done.
 
$ cd tfsec-example/

Checking the Terraform code against the default rules is as simple as running the tfsec command:

$ cd tf_module/ec2-instance/
 
$ tfsec
 
Result #1 CRITICAL Security group rule allows egress to multiple public internet addresses.
──────────────────────────────────────────────────────────────────────────────────────────
 main.tf:67
──────────────────────────────────────────────────────────────────────────────────────────
  49    resource "aws_security_group" "example_security_group" {
  .. 
  67  [     cidr_blocks = ["0.0.0.0/0"]
  .. 
  70    }
──────────────────────────────────────────────────────────────────────────────────────────
         ID aws-ec2-no-public-egress-sgr
     Impact Your port is egressing data to the internet
 Resolution Set a more restrictive cidr range
 
 More Information
 
 
 
 
 
 
 
- https://aquasecurity.github.io/tfsec/v1.28.0/checks/aws/ec2/no-public-egress-sgr/
 - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group
──────────────────────────────────────────────────────────────────────────────────────────
 
 
 timings
 ──────────────────────────────────────────
 disk i/o             16.762µs
 parsing              459.704µs
 adaptation           90.981µs
 checks               46.54541ms
 total                47.112857ms
 
 counts
 ──────────────────────────────────────────
 modules downloaded   0
 modules processed    1
 blocks processed     12
 files read           3
 
 results
 ──────────────────────────────────────────
 passed               7
 ignored              0
 critical             1
 high                 0
 medium               0
 low                  0
 
 7 passed, 1 potential problem(s) detected.

Tfsec immediately reports a problem with the security group, indicating that the egress range is open to the Internet. It even provides a link to the appropriate documentation with an explanation of the rule violation and remediation steps.

Ignoring Rules

You may find that the built-in checks don’t always line up with your organization’s security policies. For example, the previous check raised a critical error for an overly permissive egress rule in a security group. However, opening up egress to the public Internet is very common and may not violate your organization’s security posture.

Luckily, tfsec makes it easy to ignore rules. You can add a simple comment to the Terraform module next to the offending line, and tfsec will ignore the specified check:

egress {
   description = "Allow Egress"
   from_port   = 0
   to_port     = 0
   protocol    = "-1"
   cidr_blocks = ["0.0.0.0/0"] # tfsec:ignore:aws-ec2-no-public-egress-sgr
 }

Notice that the rule ID (aws-ec2-no-public-egress-sgr) matches the rule ID specified in the previous error from tfsec.

You can also specify checks to ignore via the -e command-line argument:

$ tfsec -e 'aws-ec2-no-public-egress-sgr'
 timings
 ──────────────────────────────────────────
 disk i/o             16.36µs
 parsing              364.079µs
 adaptation           81.842µs
 checks               46.946122ms
 total                47.408403ms
 
 counts
 ──────────────────────────────────────────
 modules downloaded   0
 modules processed    1
 blocks processed     12
 files read           3
 
 results
 ──────────────────────────────────────────
 passed               7
 ignored              1
 critical             0
 high                 0
 medium               0
 low                  0
 
 
No problems detected!

Custom Checks

The default tfsec checks are very useful, and the tfsec team at Aqua Security regularly adds new checks to their library. However, you will inevitably have an organizational policy that isn’t covered by the built-in checks. This use case can be addressed by developing your own custom checks.

For example, consider a use case where we want to restrict ingress IP addresses on security groups to ranges approved by our organization. We can express this requirement using a custom check.

Custom checks are defined next to Terraform code in the .tfsec/ directory. They can also be sourced from other directories or remote sources, but defining them in .tfsec/ is the quickest way to test out a custom check. Tfsec will automatically load files from this directory if they have a _tfsec.yaml or _tfsec.json suffix.

Let’s start by creating a custom check file at .tfsec/cidr_tfchecks.yaml. If you are using the directory from our GitHub repository, then this file already exists, but the check itself has been commented out. We can write our check using the custom check syntax:

---
checks:
- code: CIDR001
 description: Check to ensure that only approved IPs are allowed for ingress rules
 impact: Overly permissive ingress rules introduce additional avenues of compromise
 resolution: Use only approved IPs for ingress rules
 requiredTypes:
 - resource
 requiredLabels:
   - aws_security_group
 severity: CRITICAL
 matchSpec:
   name: ingress
   action: isPresent
   subMatch:
     name: cidr_blocks
     action: onlyContains
     value:
       - "203.0.113.10/32"
       - "203.0.113.47/32"
       - "203.0.113.98/32"
 errorMessage: Ingress access is permitted from a non-approved IP range
 relatedLinks:
 - http://wiki.example.com/security/approved-networks.html

This custom check will match aws_security_group Terraform resources and determine if their ingress rules only contain the listed IP addresses. If there are any other IP addresses in the ingress rules, then the check will fail and generate a failure with a critical severity level.

Custom checks can become fairly advanced, and the official custom check documentation is the best resource for understanding custom check syntax.

Tfsec will automatically load this custom check, so we don’t need to make any changes to the tfsec command syntax:

$ tfsec -e 'aws-ec2-no-public-egress-sgr'
 timings
 ──────────────────────────────────────────
 disk i/o             16.142µs
 parsing              382.505µs
 adaptation           84.251µs
 checks               48.135488ms
 total                48.618386ms
 
 counts
 ──────────────────────────────────────────
 modules downloaded   0
 modules processed    1
 blocks processed     12
 files read           3
 
 results
 ──────────────────────────────────────────
 passed               8
 ignored              1
 critical             0
 high                 0
 medium               0
 low                  0
 
 
No problems detected!

The check passes with the default configuration. Let’s add a simple terraform.tfvars file with an IP address that isn’t on the list of allowed IPs to test a failure case:

$ cat terraform.tfvars
ssh_ips = [ "203.0.113.10/32", "198.51.100.76/32" ]

Running tfsec with the --tfvars-file flag allows you to specify a Terraform variables file. The “198.51.100.76/32” address falls outside the list of addresses that are allowed by the custom check and an error is generated:

$ tfsec -e 'aws-ec2-no-public-egress-sgr' --tfvars-file terraform.tfvars
 
Result #1 CRITICAL Custom check failed for resource aws_security_group.example_security_group. Ingress access is permitted from a non-approved IP range
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  main.tf:49-70
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   49  ┌ resource "aws_security_group" "example_security_group" {
   50  │   name = "example_security_group"
   51  │
   52  │   description = "Example SG"
   53  │
   54  │   ingress {
   55  │ 	description = "Allow SSH"
   56  │ 	from_port   = 22
   57  └ 	to_port 	= 22
   ..  
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
      	ID custom-custom-cidr001
  	Impact Overly permissive ingress rules introduce additional avenues of compromise
  Resolution Use only approved IPs for ingress rules
 
  More Information
  - http://wiki.example.com/security/approved-networks.html
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 
 
  timings
  ──────────────────────────────────────────
  disk i/o         	25.227µs
  parsing          	428.994µs
  adaptation       	90.319µs
  checks           	50.148551ms
  total            	50.693091ms
 
  counts
  ──────────────────────────────────────────
  modules downloaded   0
  modules processed	1
  blocks processed 	12
  files read       	3
 
  results
  ──────────────────────────────────────────
  passed           	7
  ignored          	1
  critical         	1
  high             	0
  medium           	0
  low              	0
 
  7 passed, 1 ignored, 1 potential problem(s) detected.

Self-Service, Secure Environments with Cloudify

Cloudify’s rich integration with Terraform makes it easy to turn existing Terraform modules into self-service environments. Our Terraform plugin also includes native integration with tfsec, allowing you to enforce security policy as part of your overall self-service environment blueprints. Let’s take a look at how we can build security and governance into a self-service catalog item.

The example blueprint in the community repository has been preconfigured to work with a Terraform zip archive that is packaged alongside the blueprint. First, zip up the Terraform module that you have been working with:

$ cd tf_module/
$ zip -r terraform.zip ec2-instance/
  adding: ec2-instance/ (stored 0%)
  adding: ec2-instance/main.tf (deflated 54%)
  adding: ec2-instance/terraform.tfvars (deflated 4%)
  adding: ec2-instance/.tfsec/ (stored 0%)
  adding: ec2-instance/.tfsec/cidr_tfchecks.yaml (deflated 48%)
  adding: ec2-instance/outputs.tf (deflated 46%)
  adding: ec2-instance/variables.tf (deflated 58%)

Next, upload the blueprint:

$ cfy blueprint upload -b Tfsec-Example blueprint.yaml
Uploading blueprint blueprint.yaml...
 blueprint.yaml |######################################################| 100.0%
Blueprint `Tfsec-Example` upload started.
2022-09-23 14:48:33.384  CFY <None> Starting 'upload_blueprint' workflow execution
2022-09-23 14:48:33.456  LOG <None> INFO: Blueprint archive uploaded. Extracting...
2022-09-23 14:48:33.548  LOG <None> INFO: Blueprint archive extracted. Parsing...
2022-09-23 14:48:34.917  LOG <None> INFO: Blueprint parsed. Updating DB with blueprint plan.
2022-09-23 14:48:35.100  CFY <None> 'upload_blueprint' workflow execution succeeded
Blueprint uploaded. The blueprint's id is Tfsec-Example

The blueprint uses Cloudify’s native tfsec integration to automatically install the latest version of tfsec and run tfsec against the Terraform module during Cloudify workflows, such as the initial deployment:

     tfsec_config:
       config:
         exclude:
           - 'aws-ec2-no-public-egress-sgr'
       installation_source: https://github.com/aquasecurity/tfsec/releases/download/v1.28.0/tfsec-linux-amd64
       flags_override: []
       enable: True

Creating a new deployment of this environment will cause tfsec to run against any variables provided to Terraform, such as those included as part of the environment’s inputs. In this case, if a user provides an IP address that doesn’t meet the custom check, Cloudify will automatically fail the deployment:

Notice that the tfsec check fails, and Cloudify automatically fails the deployment of the Terraform module. The result of the tfsec check is visible to the user in the logs.

Wrapping Up

Tfsec is an excellent utility for enforcing governance and security policy for environments that are provisioned using Terraform. With dozens of built-in checks around security best practices and the ability to integrate custom checks, tfsec should be a basic building block of every Terraform workflow. Cloudify’s robust Terraform integration makes it easy to expose secure, repeatable Terraform environments to your end users with tfsec scanning built in.

Interested in learning more about how Cloudify can simplify and secure your Terraform environments? Contact us to book a demo and learn more about our platform.

comments

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Back to top