Originally posted on Sokube’s blog.
The importance of testing your code as soon as possible
In the Agile world, development teams are required to reduce delivery time as much as possible while continuously improving the quality of the code produced.
Indeed, it is important that at each release, the version is documented, tested, validated and secure before being deployed.
For this, it is important that these steps are integrated as early as possible in the continuous integration pipeline before even reaching the testing teams.
We use the term “shift left testing”.
The attention paid to code quality is different from a traditional approach, which is established late, mainly before the deployment of the application:
Source: https://devopedia.org/shift-left
In a “shift left testing” approach, we take into account the quality and security of the code from the development environment:
The benefits of such approach are numerous:
- reduced development costs
- increased efficiency of the teams involved (developers, testers)
- improved code quality (syntax, best practices, security etc.)
- bugs and security vulnerabilities identified quickly
It is important to remember that this should in no way replace security and code quality checks during CI. This practice improves productivity by pushing pre-tested code before validation during a Pull Request.
In this article, we will detail this approach in the context of IaC (Infrastructure as Code) with Terraform.
The goal would be to integrate into our CI/CD chain tools to test and validate the quality and security of our Terraform code as soon as possible.
We will see that it is possible to automatically launch some of these tools even before the code is pushed into the GIT repository by the developer.
Terraform context
Before getting into the heart of the matter, it seems important to me to recall certain Terraform best practices directly related to the subject of this blog post.
In our context of shift left testing, it would be wise to integrate the following aspects as soon as possible:
- frequently document the Terraform code
- format our code (style, syntax, indentation etc.)
- make the code secure (vulnerabilities, password detection etc.)
- validate the conformity of the code (monitoring of Terraform standards, good practices to be respected according to each provider used)
- Estimate the costs on our infrastructure and assess the financial impacts of the changes made
I also invite you to consult this site dedicated to the use of this tool for more information on good practices.
Pre-commits presentation and installation
Using GIT pre-commits allows you to call hooks before each commit that will automatically launch custom scripts or commands.
These will allow us, for example, to check the syntax of our Terraform code, or as seen previously, to test the security and quality of the code.
To be able to use the pre-commits, it is necessary to install the pre-commit package on your development environment (here WSL).
Just run the following pip command:
pip install pre-commit
Presentation of the Terraform tools used
terraform fmt / terraform validate
These two tools integrated into the Terraform CLI will allow us to format our code and validate it before using it.
Checkov
Example :
By running the following command on a Terraform project provisioning resources in the Azure cloud, we can get extensive Checkov analysis on our code.
checkov --directory my_terraform_directory
To begin, Checkov displays the result of its analysis (here 16 validated points but 40 problematic) as well as the details of the validated points:
Then, the improvement points are displayed with a direct link to the documentation to improve its Terraform manifests:
As you can see from the documentation provided by Checkov, the storage account should contain a network access rule, set to deny by default:
Terraform-docs
Example :
In this example, we will generate documentation in markdown format based on Terraform code used to provision AWS and Consul resources.
Just go to our directory containing our Terraform manifests and run the following command:
terraform-docs markdown table . > Readme.md
The content of our generated file is as follows:
We observe the generation of a complete documentation, containing all the Terraform blocks organized in the form of detailed tables!
By the way, If you add the following tags to your Readme.md, your terraform documentation will automatically be pushed between these tags!
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> <!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
This allows you to keep a static part in your documentation and let terraform-docs dynamically fill in the part relating to Terraform.
Terragrunt
Example :
It is possible to take advantage of Terragrunt (via its terragrunt.hcl file) in different situations:
- Unlike Terragrunt, Terraform does not support dynamic backend configurations and it is therefore necessary to declare its backends several times depending on its environment.
- Similarly, Terraform cannot include default environment variables without specifying them in the apply command.
- Terragrunt is able to launch multiple configuration applications using a single command
- Finally, Terragrunt allows to launch additional commands via hooks before and after the apply
In the context of this article, I did not use Terragrunt. However, pre-commit-terraform allows it to be integrated.
Terrascan
Example :
By running the following command in a directory containing our Terraform manifests:
terrascan scan
We can observe some points of improvement detected by terrascan.
We observe here 3 non-conformities including 2 critical ones.
One of the advantages of terrascan is that it presents the different possible improvements with levels of criticality. This point can allow, for example, to block a pipeline when a point considered critical by terrascan is detected.
TFLint
Description
Documentation
TFLint allows to detect certain errors (like illegal instance types) for the main cloud providers (AWS/Azure/GCP). This tool detects obsolete syntaxes, unused declarations. , including naming conventions.
Example :
In this example, TFLint automatically detects that the syntax used in the Terraform manifests for AWS is deprecated (in this case the instance type):
This tool will allow us to automatically observe changes in cloud providers and then adapt our code to avoid using obsolete resources or declaring variables unnecessarily.
TFSec
Example :
In this example, tfsec detects a critical vulnerability related to an azure storage container:
Links are provided to remedy the various problems.
Like terrascan, tfsec classifies issues by severity. This point can be used, for example, to block a pipeline when a point considered critical is detected.
Infracost
Example :
By using infracost, it is first possible to display the total cost of our infrastructure.
Here an AWS example:
Then it is possible, using the diff argument, to estimate the price reflected by our changes:
This tool is therefore intended to display the total price of our infrastructure or to measure the financial impact of changes made to our manifests. It is therefore possible to take advantage of this to block a Terraform pipeline when the costs generated by our modifications are too high and thus avoid hazardous expenses.
In my opinion, this kind of tool has more of its place in CI than in the development environment.
Indeed, to launch a cost estimate on their own laptop, the developer must keep the API keys locally. It seems to me more interesting to deal with this point, from a global point of view in the CI/CD pipeline.
Setting up a Terraform pre-commit hook
Now that we have described the different tools called by terraform-pre-commit, we can get down to business: using these tools in GIT pre-commits!
For this, it is important to have:
- pre-commit installed (reminder : pip install pre-commit)
- all the tools you want to call present on your workstation
Run the following commands to globally install the pre-commit hook:
# Pre-commit Init
DIR=~/.git-template
git config --global init.templateDir ${DIR}
pre-commit init-templatedir -t pre-commit ${DIR}
# Pre-commit configuration (example)
cat <<EOF > .pre-commit-config.yaml
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.64.0 # Get the latest from: https://github.com/antonbabenko/pre-commit-terraform/releases
hooks:
- id: terraform_fmt
args:
- --args=-no-color
- --args=-diff
- --args=-write=false
- id: terraform_docs
args:
- --hook-config=--create-file-if-not-exist=true # you can pass args to hooks !
- id: checkov
- id: terrascan
- id: terraform_tfsec
# - To complete ! Use what you want / need !!!
EOF
We can now test our pre-commit hook!
pre-commit run -a
We observe that our tools are automatically called.
Terraform fmt and Terraform docs did not encounter any problems but tfsec detected security flaws in our code:
In this example, before the developer can push their code to the GIT repository, the latter has been formatted, documented and tested!
Do not hesitate to consult the pre-commit-terraform documentation to know all the possible configurations!
Conclusion
As we have seen, it is relatively simple to add and take advantage of pre-commits in a Terraform development context. This best practice will allow us to move security testing earlier in our CI pipeline.
However, there is always the possibility of ignoring pre-commits (git commit . -m ‘quick fix’ –no-verify or simply not installing the necessary tools.)
So it is very important not to ignore these tools in our CI pipelines!
It may therefore be wise not to make the process too complicated for developers. For example by limiting to code formatting, validation and critical flaws detected by tools such as TFSec or Checkov. The code submitted during Pull Requests will only be cleaner and at least tested.
Sources
https://www.terraform-best-practices.com/