Using Terrascan for Static Code Analysis of Your Infrastructure Code (part 1)

You’ve been working hard on a project trying to get it to production as soon as possible. Maybe, even spent some nights and weekends working extra hard on your terraform code to automatically provision your infrastructure and deploy your app.

Once you finally get it to prod, you feel accomplished and relieved. You feel like nothing can go wrong now. It’s Friday night and you’re ready to go home after a long week. And just when you’re about to leave, your security team calls you.

They found that one of your S3 buckets is leaking sensitive data to the public internet.

You freak out!

Quickly looking through the code to determine what went wrong, you find that one of your buckets has a public-read-write Access Control List (ACL).

resource "aws_s3_bucket" "my_s3_bucket" {
  bucket = "my-s3-bucket"
  acl    = "public-read-write"
  ...
}

You quickly change that to “private”, commit, and “terraform apply”. Luckily the security team informed you that after looking at the logs it seems like no one accessed the files in the bucket other than them.

You still feel terrible, and frustrated that you didn’t catch this before. What can you do to prevent these accidents from happening in the future?

Here’s some help

Infrastructure as code allows you to manage and provision infrastructure using the same development practices as application development. Scanning code for security weaknesses as soon as it’s written has been a long held best practice for secure software development.

To help avoid introducing security weaknesses when provisioning infrastructure, you should scan infrastructure code when using tools such as Terraform, CloudFormation, Pulumy, or the AWS Cloud Development Kit (CDK). Ideally, static code analysis happens on your desktop as soon as the code is written so that you receive immediate feedback and avoid checking in vulnerable code. It should also be integrated into your continuous integration pipeline to avoid provisioning infrastructure with security weaknesses.

One of my favorite tools to scan terraform HCL code for potential security weaknesses is terrascan. (Yes, I’m biased)

Terrascan is a python utility that has a number of security checks for misconfigurations in HCL (the configuration language used by terraform) including: public exposure, encryption, misconfigured security groups, and logging/monitoring misconfigurations.

Since you want to receive early feedback on security issues before they get committed into your version control repository, terrascan can be configured as a pre-commit hook.

Wait, what are pre-commit hooks?

Git hooks are scripts that run on your desktop to automatically verify your code before it’s committed to version control. A popular framework used to manage these is pre-commit.

You’ll need Python installed in your system in order to use both pre-commit and terrascan. The optimal way to install Python is using pyenv which can be installed using the pyenv-installer like this:

$ curl https://pyenv.run | bash

Once pyenv is installed the script will output the commands you need to add to your ~/.bashrc in order to load it automatically. Enter these into your current shell. Here’s an example:

$ export PATH="/home/ec2-user/.pyenv/bin:$PATH"
$ eval "$(pyenv init -)"
$ eval "$(pyenv virtualenv-init -)"

Lets install python 3.7.5 and set it as our default globally.

$ pyenv install 3.7.5
$ pyenv global 3.7.5

Now you can install pre-commit

$ pip install pre-commit

Ok, that was easy. Now how do I install terrascan?

First, lets create a directory and repository for the project that will contain your terraform code.

$ mkdir  my_secure_infra && cd my_secure_infra
$ git init .

The infrastructure files are going to be in a directory called “infra”.

$ mkdir infra

You’ll need a file called “.pre-commit-config.yaml” that will define the pre-commit hooks used by the project. Add the following into your file:

repos:
-   repo: https://github.com/cesar-rodriguez/terrascan
    rev: v0.2.0
    hooks:
    -   id: terrascan
        pass_filenames: false
        args: [-l=infra] #NOTE: that this is the directory you created earlier to host your terraform
        verbose: true

Now install the terrascan pre-commit

$ pre-commit install

Does this really work?

Let’s add a file called “infra/s3.tf” with some dummy terraform test everything is correctly setup.

resource "aws_s3_bucket" "my_s3_bucket" {
  bucket = "my-s3-bucket"
  acl    = "public-read-write"

  server_side_encryption_configuration {
    rule {
      apply_server_side_encryption_by_default {
        kms_master_key_id = "$"
        sse_algorithm = "aws:kms"
      }
    }
  }

  logging {
    target_bucket = "logging_bucket"
    target_prefix = "log/"
  }

  tags = {
    Name        = "my-s3-bucket"
    Environment = "production"
  }
}

Now try to commit the file

$ git add .
$ git commit -m "adds buggy terraform"

You should get an output like this:

[INFO] Initializing environment for https://github.com/cesar-rodriguez/terrascan.
[INFO] Installing environment for https://github.com/cesar-rodriguez/terrascan.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
terrascan................................................................Failed
- hook id: terrascan
- duration: 0.3s
- exit code: 4

terrascan version ?
Logging level set to error.
........................................................................
----------------------------------------------------------------------
Ran 72 tests in 0.010s

OK

Processed 1 files in /home/ec2-user/environment/my_secure_infra/infra


Processed on 01/12/2020 at 06:52
Results (took 0.24 seconds):

Failures: (1)
[high] [aws_s3_bucket.my_s3_bucket.acl] should not be 'public-read-write'. 
Is: 'public-read-write' in module infra, file 
/home/ec2-user/environment/my_secure_infra/infra/s3.tf

Errors: (0)

As you can see in the output, terrascan is raising an error due to the aws_s3_bucket resource containing a “public-read-write” ACL. Lets fix the issue by changing the “acl” parameter in line #3 to "private" and try again. 

Update the “infra/s3.tf” file as follows:

resource "aws_s3_bucket" "my_s3_bucket" {
  bucket = "my-s3-bucket"
  acl    = "private"

  server_side_encryption_configuration {
    rule {
      apply_server_side_encryption_by_default {
        kms_master_key_id = "$"
        sse_algorithm = "aws:kms"
      }
    }
  }

  logging {
    target_bucket = "logging_bucket"
    target_prefix = "log/"
  }

  tags = {
    Name        = "my-s3-bucket"
    Environment = "production"
  }
}

Then…

$ git add .
$ git commit -m "fixes bug"

Your output should look like this:

terrascan................................................................Passed
- hook id: terrascan
- duration: 0.3s

terrascan version ?
Logging level set to error.
........................................................................
----------------------------------------------------------------------
Ran 72 tests in 0.010s

OK

Processed 1 files in /home/ec2-user/environment/my_secure_infra/infra


Processed on 01/12/2020 at 06:54
Results (took 0.24 seconds):

Failures: (0)

Errors: (0)

[master 9e0a621] fixes bug
 1 file changed, 1 insertion(+)

Wow this is great!

Indeed it is! Using terrascan and pre-commits you’re able to detect issues as soon as the code is written. Relieving you from potential pain once vulnerable code is introduced into your environment. 

What if someone on my team doesn’t have terrascan installed? 

In the next post I’ll teach you how to configure terrascan as part of your CI/CD pipeline using GitHub Actions.

Do you use pre-commit? What are other pre-commit hooks you use when developing for AWS?

Cesar Rodriguez

Cesar Rodriguez is a Cloud Security Architect with 6+ years of experience securing cloud environments in the financial industry and 10+ years working in information security.

Previous
Previous

Using Terrascan for Static Code Analysis of Your Infrastructure Code (part 2)