diff --git a/samples/compliance-testing/README.md b/samples/compliance-testing/README.md new file mode 100644 index 00000000..61fc6cb4 --- /dev/null +++ b/samples/compliance-testing/README.md @@ -0,0 +1,137 @@ +# Terraform Compliance Testing + +“Compliance testing” also known as Conformance testing is a nonfunctional testing technique which is done to validate, whether the system developed meets the organization’s prescribed standards or not. Most software teams do an analysis to check that the standards are properly enforced and implemented. Often working simultaneously to improve the standards, which will, in turn, lead to better quality. + +## When to use Compliance Testing + +Compliance testing is performed to ensure the compliance of the deliverables of each phase of the development lifecycle. If you you have to enforce sufficient tests to validate the degree of compliance to the methodology and identify the violators. +It is important that the compliance check should be made right from the inception of the project than at the later stage because it would be difficult to correct the application when the requirement itself is not adequately documented. + +## How to do a compliance check + +Doing Compliance checks is quite straight forward. A set of standards and procedures are developed and documented for each phase of the development lifecycle. Deliverables of each phase need to compare against the standards and find out the gaps. Compliance testing is basically done through the inspection process and the outcome of the review process should be well documented. + +Let's apply that to a [Terraform](https://terraform.io) driven infrastructure by giving a more specific example: + +One of the problems you might have in your team is environments getting hosed when different people apply changes. One person works on a change and applies resources e.g. a VM to a test environment. Then someone else applies a different version of the code from their own machine, provision another version of that VM. Things get confusing and messy. + +An obvious response could be to call out a policy to require tags on resources where applicable and add a `role` and `creator` tag to the resource that is deployed. [Terraform-compliance](https://terraform-compliance.com) is a tool that helps you with that. It mainly focuses on negative testing instead of having fully-fledged functional tests that are mostly used for proving a component of code is performing properly. + +Fortunately, `terraform` is a marvellous abstraction layer for any API that creates/updates/destroys entities. `Terraform` also provides the capability to ensure everything is up-to-date between the local configuration and the remote API(s) responses. Since `terraform` is mostly used against Cloud APIs we still miss a way to ensure the code deployed against the infrastructure must follow specific policies - like HashiCorp currently provides with `Sentinel` for Enterprise Products. `Terraform-compliance` is providing a similar functionality only for terraform while it is free-to-use and it is Open Source. + +A sample compliance policy for the issue mentioned could be like this: `if you are working with Azure, you should not create a resource, without having any tags`.`Terraform-compliance` provides a test framework to create these policies that will be executed against your terraform plan file in a context where both developers and security teams can understand easily while reading it, by applying [Behaviour Driven Development](https://en.wikipedia.org/wiki/Behavior-driven_development) principles. + +## Get the Idea + +Going back to the example, the above will be translated into a BDD Feature and Scenario, as also seen in below: + +```Cucumber +if you are working with Azure, you should not create a resource, without having any tags +``` + +translates into: + +```Cucumber +Given I have resources that supports tags defined +Then it must contain tags +And its value must not be null +``` + +Further specific configurations are coming from the terraform code, as shown below ; + +```hcl +resource "random_uuid" "uuid" {} + +resource "azurerm_resource_group" "rg" { + name = "rg-hello-tf-${random_uuid.uuid.result}" + location = var.location + + tags = { + environment = "dev" + application = "Azure Compliance" + } +} +``` + + +The first policy could be written as a [BDD Feature Scenario](https://gherkin.io/docs/gherkin/reference/) like this: + +```Cucumber +Feature: Test tagging compliance # /target/src/features/tagging.feature + Scenario: Ensure all resources have tags + Given I have resource that supports tags defined + Then it must contain tags + And its value must not be null +``` + +Following a second scenario test for a specific tag + +```Cucumber +Scenario Outline: Ensure that specific tags are defined + Given I have resource that supports tags defined + When it has tags + Then it must contain + And its value must match the "" regex + + Examples: + | tags | value | + | Creator | .+ | + | application | .+ | + | role | .+ | + | environment | ^(prod\|uat\|dev)$ | +``` + +## How-to run this example + +The example above is taken from the [github.com/terrraform-testing](https://github.com/LeagueOfExtraordinaryHackers/terraform-testing/tree/compliance-testing/examples/compliance-testing) repository. + +After checkout the repo ... + +```bash +cd ./terraform-testing/examples/compliance-testing/src +``` +... we create a terraform planfile in `./src/ we going to test against. + +```bash +cd src/ +terraform init +terraform validate +terraform plan -out tf.out +terraform apply -target=random_uuid.uuid +docker pull eerkunt/terraform-compliance +``` + +Now we are ready to run the tests suite. + +```bash +docker run --rm -v $PWD:/target -it eerkunt/terraform-compliance -f features -p tf.out +``` + +### From Red to Green + +This should result in a failing test run: + +![tf-compliance-run-tagging-fail](assets/tf-compliance-run-tagging-fail.png) + +Make the test green again by adding all required tags to `main.tf`: + +```hcl + tags = { + Environment = "dev" + Application = "Azure Compliance" + Creator = "Azure Compliance" + Version = "Azure Compliance" + } + +``` + +Let's update the plan file again to reflect the updated state: + +```bash +terraform validate +terraform plan -out tf.out +``` + +Now, we should be green when running the tests suite again: + +![tf-compliance-run-tagging-succeed](assets/tf-compliance-run-tagging-succeed.png) diff --git a/samples/compliance-testing/assets/tf-compliance-run-tagging-fail.png b/samples/compliance-testing/assets/tf-compliance-run-tagging-fail.png new file mode 100644 index 00000000..06769a9d Binary files /dev/null and b/samples/compliance-testing/assets/tf-compliance-run-tagging-fail.png differ diff --git a/samples/compliance-testing/assets/tf-compliance-run-tagging-succeed.png b/samples/compliance-testing/assets/tf-compliance-run-tagging-succeed.png new file mode 100644 index 00000000..0103e124 Binary files /dev/null and b/samples/compliance-testing/assets/tf-compliance-run-tagging-succeed.png differ diff --git a/samples/compliance-testing/src/features/tagging.feature b/samples/compliance-testing/src/features/tagging.feature new file mode 100644 index 00000000..5025936f --- /dev/null +++ b/samples/compliance-testing/src/features/tagging.feature @@ -0,0 +1,19 @@ +Feature: Test tagging compliance + +Scenario: Ensure all resources have tags + Given I have resource that supports tags defined + Then it must contain tags + And its value must not be null + +Scenario Outline: Ensure that specific tags are defined + Given I have resource that supports tags defined + When it has tags + Then it must contain + And its value must match the "" regex + + Examples: + | tags | value | + | Creator | .+ | + | Application | .+ | + | Role | .+ | + | Environment | ^(prod\|uat\|dev)$ | diff --git a/samples/compliance-testing/src/main.tf b/samples/compliance-testing/src/main.tf new file mode 100644 index 00000000..dda3ddc1 --- /dev/null +++ b/samples/compliance-testing/src/main.tf @@ -0,0 +1,11 @@ +resource "random_uuid" "uuid" {} + +resource "azurerm_resource_group" "rg" { + name = "rg-hello-tf-${random_uuid.uuid.result}" + location = var.location + + tags = { + Environment = "dev" + Application = "Azure Compliance" + } +} \ No newline at end of file diff --git a/samples/compliance-testing/src/output.tf b/samples/compliance-testing/src/output.tf new file mode 100644 index 00000000..41e17d28 --- /dev/null +++ b/samples/compliance-testing/src/output.tf @@ -0,0 +1,3 @@ +output "resource_group_name" { + value = "${azurerm_resource_group.rg.name}" +} diff --git a/samples/compliance-testing/src/provider.tf b/samples/compliance-testing/src/provider.tf new file mode 100644 index 00000000..615cb43d --- /dev/null +++ b/samples/compliance-testing/src/provider.tf @@ -0,0 +1,3 @@ +provider "azurerm" { + features {} +} \ No newline at end of file diff --git a/samples/compliance-testing/src/tf_compliance.sh b/samples/compliance-testing/src/tf_compliance.sh new file mode 100644 index 00000000..5dca5c5c --- /dev/null +++ b/samples/compliance-testing/src/tf_compliance.sh @@ -0,0 +1,56 @@ +#!/bin/bash +#title :tf_compliance.sh +#description :Runs terraform-compliance a lightweight, security and compliance focused test framework against terraform to enable negative +# testing capability for your infrastructure-as-code. +#author :andreas.heuamier@microsoft.com +#date :20200602 +#version :0.1 +#usage :./tf_compliance.sh {WORK_DIR} +#bash_version :5.0.16(1)-release +# +set -euo pipfail + +run_terraform_plan(){ + local test_dir=$1 + terraform validate && terraform plan -out "${$test_dir}/tf.out" +} + +run_tf_compliance() { + local test_dir=$1 + local features_dir=$1 + [ -f "${test_dir}/tf.out" ] || run_terraform_plan "${test_dir}" + docker run --rm -v "${test_dir}":/target -it eerkunt/terraform-compliance -f "${features_dir}" -p tf.out +} + +####################################### +# find_folders_by() file pattern +# Globals: +# WORK_DIR -path +# Arguments: +# pattern - regex +# Outputs: +# Writes folders list to stdout +####################################### +find_folders_by() { + local pattern=${1:-"main.tf"} + find "${WORK_DIR}" -type f -name "${pattern}" -printf '%h\n' | sort -u +} + +####################################### +# Runs the trerraform compliance tool on all subdirectories +####################################### +run_main() { + for feature in $(find_folders_by "*.feature"); do + for folder in $(find_folders_by "main.tf"); do + run_tf_compliance "${folder}" "${feature}" & + done + wait + done +} + +####################################### +# Be able to run this one either as standalone or import as lib +####################################### +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + run_main +fi diff --git a/samples/compliance-testing/src/variables.tf b/samples/compliance-testing/src/variables.tf new file mode 100644 index 00000000..336ade40 --- /dev/null +++ b/samples/compliance-testing/src/variables.tf @@ -0,0 +1,5 @@ +variable location { + type = string + default = "westeurope" + description = "The Azure location where the resources will be created" +}