Pre-Commit Hooks for Terraform

Enforcing Terraform Linting and Security Standards Using Pre-Commit Hooks

Frank Kerschbaumer
Slalom Build
7 min readNov 20, 2020

--

Terraform has various independent native and open-source tools that check for code smells, enforce policies, and identify common security flaws. These tools help create consistent and curated Infrastructure as Code when used in conjunction with a sprinkling of automation. We can use Pre-Commit Hooks as an on-demand tool to integrate and use these tools before checking code into source control.

The Tools

Terraform Lint

TFLint is a linter that checks for possible errors, best practices, etc. It will also help identify provider-specific issues before errors occur during a Terraform run. For example, if Terraform is creating an EC2 instance and a developer accidentally references an invalid instance type, TFLint would flag it as an error.

TFSec

TFSec uses static analysis of your Terraform templates to spot potential security issues. TFSec checks for sensitive data inclusion across all providers and for violations of AWS, Azure, and GCP security best practice recommendations based on specific rulesets. For example, you would see an error similar to this if Terraform is creating a security group that is open to the world:

Checkov

Checkov is a static code analysis tool used for infrastructure-as-code. It has wide ranging use-cases like Terraform, Terraform plan, Cloudformation, Kubernetes, Dockerfile, Serverless or ARM Templates. In the case of its usage in the configuration of the pre-commit hook, it will be used as a tool to cover generic Terraform security and compliance. Checkov can also regulate the creation, management, and updates of IaaS, PaaS or SaaS managed through Terraform. As an example, this is a type of error you would get if an S3 bucket is configured incorrectly:

Terraform Docs

Terraform Docs is a utility to automatically generate documentation from Terraform modules and base repositories in various output formats. Where this tool shines is in conjunction with the Pre-Commit hook by auto-populating the output of the tool into a repo’s README file which is described in detail later in the article.

Terraform Fmt

Terraform Fmt is a Terraform command that is available natively that is used to rewrite Terraform configuration files to a canonical format and style. This command applies a subset of the Terraform language style conventions, along with other minor adjustments for readability.

As an example, this would make sure that a resource has a consistent syntax for readability.

Before Terraform Fmt:

After Terraform Fmt:

Terraform Validate

Terraform Validate is “a native Terraform command that validates the configuration files in a directory, referring only to the configuration and not accessing any remote services such as remote state, provider APIs, etc.”

Terraform Validate can be used as a process dry-run and will check for any glaring flaws before a Terraform run. For example, If you have a variable that is currently unavailable or incorrectly referenced, Terraform validate will catch that error.

Setting Up the Pre-Commit Hook

Now that we know the available tooling independent of each other, let’s see what this looks like in practice. For our example, we will be using a Terraform repo with a file structure as shown below as well as creating.pre-commit-config.yaml and .tflint.hcl:

In order to use Pre-Commit with these tools:

  1. The Terraform code resides in a git initialized directory.
  2. There must be a localized .pre-commit-config.yaml that contains configuration for the pre-commit utility.
  3. An optional rules list in a file called .tflint.hcl to enable enforcing tags on AWS resources.

Initial Setup

To install all of the Terraform tools using brew we will run the following commands from a Macbook with Brew installed:

Add the Example Pre-commit Config File

Here is an example .pre-commit-config.yaml that we will use:

We also set specific arguments to use TFlint. The --module flag is extremely useful for a Terraform repo that contains a module by running a check based on the input variables passed to a module. The --module flag invokes a provider’s API to do a more detailed inspection. For example, it can run a check against an IAM profile name defined somewhere in your code as a hard-coded value. If the repo does not contain any modules, the argument is omitted.

In the yaml file, notice how we set rev:to<VERSION>. After creating the file, the pre-commit autoupdate command will pull the latest release from the respective git repositories by its most recent git tag and populate that into the <VERSION> flag. Keeping the version pinned is a great recommendation but incremental updates are also fine based on new features and releases to each pre-commit repo.

Add the .tflint File

TFLint also allows some custom rules to be defined by a specific ruleset. One rule that we can use is enforcing tags on resources based on required keys which we will put into our .tflint.hcl.

This rule only checks for the tagging keys rather than the value. This will also not check if tags are being set as a variable since those are not available to the linter at runtime and only populated to the resources on a Terraform run.

Updating the README

To allow terraform-docs to auto-populate values into the repo’s README, we will add in these two lines:

Here is an example of a pre-commit run with auto-populated values from a README file:

Running the Pre-Commit Hook

Once we have everything set up we can then either install the pre-commit hook or run it manually. For this example, we will be running the pre-commit hook manually.

When running this command from the root of our Terraform folder we may get an output similar to this:

We can see that terraform fmt fixed a syntax issue relating to Terraform code that was within the main.tf file.

Terraform docs will usually “fail” on a first run. The reported failure reports back a change within the README based on changes or additions to the populated values as they were pulled from the Terraform repo.

Closing Thoughts

By using Pre-Commit hooks, we can stitch together the various open-source and Terraform native tooling in an automated fashion using a single tool. This reduces the burden of downstream CI by putting the onus on the enforcement provided by Pre-Commit hooks. An additional benefit of using this process is quick identification of small issues upon each commit, which allows for cleaner pull requests and less review time.

Overall, Pre-Commit hooks go a long way toward increasing confidence in Terraform code that is submitted to a repo, reaping invaluable improvement in both developer proficiency and code quality.

Further Reading

Introduction to Terraform

Pre-commit framework

Pre-commit Terraform

--

--

Frank Kerschbaumer
Slalom Build

Cloud, DevOps and Security Senior Engineer at Slalom Build