In the last two parts of this series, we have described what Test Driven Development (TDD) is, and how it can be applied to infrastructure to validate that security controls are implemented. Generally, a TDD pipeline consists of a number of components:
|Part 1: Getting Started with TDD in AWS||Part 2: Test Driven Development for Secure Infrastructure|
- A testing framework (such as Chef Inspec, terratest, Sentinel, Open Policy Agent, Mocha, etc.)
- The testing code (which can be in any language depending on the framework used to implement test suites)
- The infrastructure as code (IaC) - such as Terraform, Pulumi, CDK, etc.
- A version control system (such as Git) preferably with methods to lock branches
- A means to run tests (such as a GitHub Actions, GitLab Runner, CircleCI, etc.
The purpose of this article is to put all of these parts together in a pipeline.
Choosing a Framework
Before starting to run (no pun intended) with TDD, choosing a proper framework should be top of mind. There are several drivers when it comes to choosing a TDD framework. The table below outlines these decisions relating to TDD for Infrastructure.
|Chef Inspec||HashiCorp Sentinel||Terraform Validator||Open Policy Agent (OPA)|
|Price||Open Source||Enterprise-only||Open Source||Open Source|
|Cloud(s)||AWS, Azure, GCP||AWS, Azure, GCP||GCP only||AWS, Azure, GCP|
|Language(s)||Ruby||Sentinel||YAML/Rego via Policy Library||Rego|
|Limited to Infrastructure?||Applications and OS-level support||Works with Vault, Consul, and Nomad||Only Infrastructure||Many applications|
A Comparison of Testing Frameworks for Infrastructure as Code
In this article, we will be using Chef Inspec. However, know that the steps to implement these test frameworks are similar.
Setting up the Code
One of the main value propositions for using TDD for IaC is to ensure that security requirements are clearly defined. Tests should be written first, then the infrastructure code should be built to let tests pass. The following diagram outlines the recommended steps to ensure success with setting up testable infrastructure code.
Security and compliance requirements start from business drivers. Meeting requirements highlighted by demonstrating compliance with regimes such as FedRAMP, GDPR, PCI-DSS, and HITRUST help break barriers to market entrance. What this means is that there should be good communication between compliance teams and the engineers that implement infrastructure. Having sessions for engineering teams to translate security requirements into tests (that may not yet pass since the infrastructure hasn’t been created/modified yet) using a selected framework is key to ensuring success.
Implementing Successful Version Control Patterns
Once tests are written, they should be checked into version control. This helps to answer questions regarding auditing, technical decisions, and compliance status of infrastructure components during development. There are numerous ways to achieve proper Git version control for infrastructure tests including: GitFlow, GitHub flow, GitLab flow, OneFlow, and Trunk-based development. The choice on what version control pattern varies depending on organization and project - and is outside of the scope of this article, however having a pattern that can enforce security checks (e.g. blocking merges to main/feature branches before tests pass) is important to success with Test-driven infrastructure.
Running with it!
In order to run tests and provide feedback effectively, some sort of system that can periodically or upon request, execute a test suite is essential. To achieve this, a system is typically implemented using a continuous integration (CI) system due to the vast ecosystem that many of them have as well as community support (and thus, ease of use).
GitHub is an excellent platform for projects that leverage the Git versioning system. Making this platform even better is the ability to perform actions depending on what happens within a particular version control pattern (such as merging branches, opening a pull request, and so on). To perform tests using Chef Inspec within GitHub Actions, the following code snippet can be used.
|# Triggers the workflow on push or pull request|
|branches: [ master ]|
|branches: [ master ]|
|# Test Job|
|if: github.ref != 'refs/heads/main'|
|# Setup environment|
|CHEF_LICENSE: accept-silent # silently accept the Chef InSpec license|
|# setup python|
|- uses: actions/checkout@v2|
|# pull credentials|
|- uses: GoogleCloudPlatform/github-actions/setup-gcloud@master|
|- name: "Install Chef Inspec"|
|run: curl https://omnitruck.chef.io/install.sh | sudo bash -s -- -P inspec|
|- name: "Run Infrastructure Tests"|
|run: inspec exec test/my-profile --input-file test/my-profile/attributes.yml --reporter=cli html:./report.html --show-progress -t gcp://|
|- uses: actions/upload-artifact@v2|
Code to Run Chef Inspec Tests with GitHub Actions
The Whole Picture
Using a pipeline that is well defined with business objectives in mind can be an effective way to ensure security in any infrastructure project that is defined with infrastructure as code. Using a test-driven approach ensures that security requirements are properly communicated from security/compliance teams to engineering teams for proper implementation in cloud environments. Using TDD is a large upfront investment that requires writing tests and establishing automation to be effective, but ultimately allows teams to move faster and more securely.