How do I use Conftest with Terrahaxs?

In this guide we are going to walk you through the steps to incorporate conftest into your Terrahaxs workflow.

Step 1: Create your policies

Create a directory named policy in the root of your repository and add your policies to it.

As an example, we are going to create a policy which checks your resources for proper tags. First we’ll create a main.rego file:

# main.rego

package main

import data.tags_validation

module_address[i] = address {
    changeset := input.resource_changes[i]
    address := changeset.address
}

tags_pascal_case[i] = resources {
    changeset := input.resource_changes[i]
    tags  := changeset.change.after.tags
    resources := [resource | resource := module_address[i]; val := tags[key]; not tags_validation.key_val_valid_pascal_case(key, val)]
}

tags_contain_minimum_set[i] = resources {
    changeset := input.resource_changes[i]
    tags := changeset.change.after.tags
    resources := [resource | resource := module_address[i]; not tags_validation.tags_contain_proper_keys(changeset.change.after.tags)]
}

deny[msg] {
    resources := tags_contain_minimum_set[_]
    resources != []
    msg := sprintf("Invalid tags (missing minimum required tags) for the following resources: %v", [resources])
}

deny[msg] {
    resources := tags_pascal_case[_]
    resources != []
    msg := sprintf("Invalid tags (not pascal case) for the following resources: %v", [resources])
}

Notice this file imports import data.tags_validation, which we define in tags.rego:

# tags.rego
package tags_validation

minimum_tags = {"ApplicationRole", "Owner", "Project"}

key_val_valid_pascal_case(key, val) {
    is_pascal_case(key)
    is_pascal_case(val)
}

is_pascal_case(string) {
    re_match(`^([A-Z][a-z0-9]+)+`, string)
}

tags_contain_proper_keys(tags) {
    keys := {key | tags[key]}
    leftover := minimum_tags - keys
    leftover == set()
}

Step 2: Test your policies

Before deploying the policies we want to validate they work as expected. We’ll create two test files - one for main.rego and one for tags.rego.

Here are the test files:

# main_test.rego
package main

test_tags_pascal_case {
    deny == set() with input as {"resource_changes": [{ "address": "module.one", "change": { "after": { "tags": { "ApplicationRole": "ArtifactRepository", "Owner": "Ssi", "Project": "Artifacts", "Country": "Ng" }}}}]}
}

test_tags_pascal_case_with_wrong_value_format {
    deny with input as { "resource_changes": [{ "address": "module.one", "change": { "after": { "tags": { "ApplicationRole": "artifactRepository", "Owner": "Ssi", "Project": "Artifacts", "Country": "Ng" }}}}]}
}

test_tags_pascal_case_with_wrong_key_format {
    deny with input as { "resource_changes": [{ "address": "module.one", "change": { "after": { "tags": { "ApplicationRole": "ArtifactRepository", "owner": "Ssi", "Project": "Artifacts", "Country": "Ng" }}}}]}
}

test_tags_contain_minimum_set {
    deny == set() with input as { "resource_changes": [{ "address": "module.one", "change": { "after": { "tags": { "ApplicationRole": "ArtifactRepository", "Owner": "Ssi", "Project": "Artifacts" }}}}]}
}

test_tags_contain_minimum_set_with_extra_tags {
    deny == set() with input as { "resource_changes": [{ "address": "module.one", "change": { "after": { "tags": { "ApplicationRole": "ArtifactRepository", "Owner": "Ssi", "Project": "Artifacts", "Country": "Ng" }}}}]}
}

test_tags_contain_minimum_set_without_minimum {
    deny with input as { "resource_changes": [{ "address": "module.one", "change": { "after": { "tags": { "ApplicationRole": "ArtifactRepository", "Project": "Artifacts", "Country": "Ng" }}}}]}
}

and our tags test:

# test_tags.rego
package tags_validation

test_tags_valid_pascal_case {
    tags := { "ApplicationRole": "ArtifactRepository" }
    val := tags[key]
    key_val_valid_pascal_case(key, val)
}

test_tags_valid_pascal_case_lower_case_key {
    tags := { "applicationRole": "ArtifactRepository" }
    val := tags[key]
    not key_val_valid_pascal_case(key, val)
}

test_tags_valid_pascal_case_lower_case_value {
    tags := { "ApplicationRole": "artifactRepository" }
    val := tags[key]
    not key_val_valid_pascal_case(key, val)
}


test_tags_valid_pascal_case_lower_case_value_multiple_tags {
    tags := { "ApplicationRole": "artifactRepository", "Project": "Artifacts" }
    val := tags[key]
    not key_val_valid_pascal_case(key, val)
}

test_tags_contain_proper_keys {
    tags := { "ApplicationRole": "ArtifactRepository", "Project": "Artifacts", "Owner": "Ssi", "Country": "Ng" }
    tags_contain_proper_keys(tags)
}

test_tags_contain_proper_keys_missing_key {
    tags := { "ApplicationRole": "ArtifactRepository", "Project": "Artifacts", "Country": "Ng" }
    not tags_contain_proper_keys(tags)
}

After creating these files we can now run conftest verify policy from the root of the project. We should see an output like this:

conftest verify policy                        

12 tests, 12 passed, 0 warnings, 0 failures, 0 exceptions, 0 skipped

Step 3: Update your workflow to include Conftest

The third and final step is to create a custom workflow to include Conftest into your CI/CD pipeline.

Here is what an example workflow should look like:

version: 3
defaults:
  workflow: myworkflow
  roles:
    aws: arn:aws:iam::530084066544:role/terrahaxs-worker
projects:
- dir: conftest
  workspace: default
  terraform_version: v1.0.11
workflows:
  myworkflow:
    plan:
      steps:
      - run: terraform fmt -check -diff .
      - init
      - plan:
          extra_args: ["-lock=false"]
      - show
      - run: conftest test $SHOWFILE --policy $CLONE_DIR/policy --no-color
        name: Conftest

Note that conftest runs against a json plan created by using the terraform show command. It is important to include - show before running your conftest command.

After updating your workflow, the next time Terrahaxs runs you will see the output of conftest.

conftest

Notice that if the conftest command doesn’t pass then a user will not have the option to apply their changes.


And that’s it!

After following these three steps you can now use conftest to enforce standards and security policies.

Recent Articles

blog-image
A Comprehensive AWS Resource Tagging Strategy

Tagging is a powerful tool that provides control, visibility, and streamlined management of resources.

blog-image
Parallelizing Infrastructure Management: The Terrahaxs Advantage

A major limitation of existing Terraform CI/CD tools in the number of projects you are able to plan/apply simultaneously.

blog-image
Choosing the Right Terraform Loop: count vs for_each

Terraform is a popular infrastructure as code (IaC) tool that allows you to manage and automate your infrastructure resources.