Terraform static code analysis with tfsec and Jenkins
By: Raja Gopal
Tags: Static Code Analysis, Terraform

Photo: perforce.com

Hello All !! Welcome once again to my next story on static code analysis for terraform files using “Tfsec”.

What is Static Code Analysis?

It is the process where the source code is examined before it is run to capture bugs by analyzing them against a set of rules defined by the organization. This type of analysis reduces vulnerabilities because of a weak code. This analysis can be done manually but doing it automated with help of tools avoid the weakness getting escape from the scrutiny.

In today’s DevOps practice, infrastructure as a code plays a crucial part. Development teams who work in agile find it difficult if infrastructure provisioning happens in the traditional way taking weeks to get the required VM and other resources in place. To help them get going without delay, teams write their infrastructure as a code like VMware templates, ARM templates, CFT and Terraform templates. One of the most often used tool is Terraform which is more flexible with multiple clouds. Because of this flexibility, it is widely used for various clouds like Azure, AWS, GCP, VMware and etc.

Terraform manages infrastructure with the help of templates which are written in the language HCL which is the DSL language used for terraform. Since this is not a common programming language, the common static code analysis tools like SonarQube can’t examine the code and there is a challenge prevails for Terraform developers to examine the code.

So, what is the solution?

There are tools available specifically for Terraform templates in the open source community. They are tfsec, terraform-compliance, terraform-lint and etc. Out of these tools, tfsec is considered prominent and reliable with some predefined checks for Azure, AWS and GCP and also some General checks like sensitive data within a template.

Features of tfsec as given in their documentation, tfsec/tfsec: 🔒🌍 Security scanner for your Terraform code (github.com)

Checks for sensitive data inclusion across all providers

Checks for violations of AWS, Azure and GCP security best practice recommendations

Scans modules (currently only local modules are supported)

Evaluates expressions as well as literal values

Evaluates Terraform functions e.g. concat()

This tool is primarily designed to use in CI pipeline and it fits well. For this reason, it returns a non-zero exit code if a potential problem is detected and also the format for output can be any of json, csv, checkstyle, junit and sarif which can create test-results dashboard in popular CI tools.

“tfsec” scans a given directory and if directory is not specified, it scans the current working directory. Additionally, tfsec also scans values from the variable files (.tfvars) using the attribute --tfvars-file terraform.tfvars. Also tfsec can be integrated with github to scan the repository and generate github alerts.

Adding to the existing checks for each cloud providers, we can also write a custom check either in a JSON or YAML file and it can be saved as “_tfchecks.json” or “-tfchecks.yaml” and save it in a directory. The custom checks can be called during execution using --custom-check-dir.

Disable Checks

The checks can also be disabled by simply adding the -e attribute as below

tfsec . -e GEN001,GCP001,GCP002

Read further about tfsec and its custom checks in the following pages

tfsec/tfsec: 🔒🌍 Security scanner for your Terraform code (github.com)

Home | tfsec

Installation

Windows

choco install tfsec

Linux

#Run these commands as non-root user.

sudo apt-get update

sudo apt install linuxbrew-wrapper

#Add /home/linuxbrew/.linuxbrew/bin to the PATH

brew install tfsec



In Action

Let’s see a working example of tfsec. In our scenario, we are using Jenkins pipeline and Github as CI and repository. Developers work in a “Developer” branch and when they commit any code, the pipeline triggers and it runs tfsec on the Terraform template. When there are no vulnerabilities found in the code, the branch is merged into main but if any vulnerability is found, the test case fails and it terminates the pipeline. Below is the Terraform code with a vulnerable network security group which has a rule that allows all network traffic on port 22.

resource "azurerm_network_security_rule" "rule22" { direction = "Inbound" source_address_prefix = "0.0.0.0/0" network_security_group_name = var.nsgName access = "Allow" priority = 100 destination_address_prefix = "*" destination_port_range = "22" protocol = "TCP" }

The pipeline code for jenkins is below

pipeline{ agent { kubernetes { yamlFile 'agentpod.yml' } } stages{ stage("Test Terraform files"){ steps{ echo "========Executing Test case for Terraform files=======" container('tfsec'){ dir('terraform') { sh "echo \$(pwd)" sh "tfsec -f junit > tfsec_test.xml" } } } post{ always{ echo "========always========" dir('terraform') { junit checksName: 'Terraform security checks', testResults: "tfsec_test.xml" } } success{ echo "Terraform test case passed" } failure{ echo "Terraform test case failed" } } } stage("Commit to Main"){ steps{ echo "====++++executing Deploy Terraform++++====" container('tfsec'){ sh "git checkout main" sh "git merge developer" withCredentials([usernamePassword(credentialsId: 'jenkinspasswordgit', passwordVariable: 'git_password', usernameVariable: 'git_user')]){ sh "git push http://$git_user:$git_password@github.com/rajagopalanrg/iaas" sh "git tag $BUILD_NUMBER" } } } post{ always{ echo "====++++always++++====" } success{ echo "====++++Deploy Terraform executed successfully++++====" } failure{ echo "====++++Deploy Terraform execution failed++++====" } } } } }

The tfsec finds the vulnerability with the code and it fails the test case

Let’s change the template to deny all incoming traffic on port 22.

resource "azurerm_network_security_rule" "rule22" { direction = "Inbound" source_address_prefix = "0.0.0.0/0" network_security_group_name = var.nsgName access = "Deny" priority = 100 destination_address_prefix = "*" destination_port_range = "22" protocol = "TCP" }

Re-run the pipeline and the CI passes. The “developer” branch is merged to the “main” branch.

I also like to give example of a custom check. Save the below text in a json file as describe above and include it with tfsec command.

{

"checks": [

{

"code": "CUS001",

"description": "Custom check to ensure the CostCentre tag is applied to EC2 instances",

"requiredTypes": [

"resource"

],

"requiredLabels": [

"azurerm_network_security_rule"

],

"severity": "ERROR",

"matchSpec": {

"name": "tags",

"action": "contains",

"value": "CostCentre"

},

"errorMessage": "The required CostCentre tag was missing"

}

]

}

Thanks for reading!!

For training and consultation on Terraform/Pulumi/Kubernetes/Azure, drop an email to support@cloudlego.com