OPA
OPA
April 10, 2023

Terraform Modules - Define, Enforce, Report

By
Ryan Fee

Terraform Modules are reusable, shareable, and configurable packages of Terraform code that encapsulate a set of resources that work together to achieve specific functionality. Creating and using the module is straightforward, but are you doing so in a scalable way? There are three phases of modules usage:

  • Defining
  • Enforcing 
  • Reporting

First, you need to define what modules can be used by your organization. Defining a module involves writing Terraform configuration files that specify the resources to be created and their configuration parameters. Your organization might decide to copy code directly from the public Terraform registry, use some pieces of the public modules, or start a module from scratch. There is no right or wrong way when it comes to this, but you do want to define the modules in a module registry. A module registry will help your end users know which modules have been approved and how to use them, which reduces the amount of snowflake modules you have within your organization. All TACOs support module registries.

Next, you’ll want to enforce the modules. Just because you have a module registry doesn’t necessarily mean that your users will use those modules. Enforcement can be done through Open Policy Agent and Scalr. The main policy that comes to mind is to enforce the source of the module. In the example below you’ll see that if an AWS DB or S3 resource is created, it must use the module that is defined in the policy:


#This policy will forbid resources from getting created unless done so through a module and a specific source.
package terraform

import input.tfplan as tfplan


# Map of resource types which must be created only using module with corresponding module source
resource_modules = {
    "aws_db_instance": "terraform-aws-modules/rds/aws",
    "aws_s3_bucket": "scalr-demo.scalr.io/acc-sscctbisjkl35b8/s3-bucket/aws"
}

contains(arr, elem) {
  arr[_] = elem
}

deny[reason] {
    resource := tfplan.resource_changes[_]
    action := resource.change.actions[count(resource.change.actions) - 1]
    contains(["create", "update"], action)
    module_source = resource_modules[resource.type]
    not resource.module_address
    reason := sprintf(
        "%s cannot be created directly. Module '%s' must be used instead",
        [resource.address, module_source]
    )
}

deny[reason] {
    resource := tfplan.resource_changes[_]
    action := resource.change.actions[count(resource.change.actions) - 1]
    contains(["create", "update"], action)
    module_source = resource_modules[resource.type]
    parts = split(resource.module_address, ".")
    module_name := parts[1]
    actual_source := tfplan.configuration.root_module.module_calls[module_name].source
    not actual_source == module_source
    reason := sprintf(
        "%s must be created with '%s' module, but '%s' is used",
        [resource.address, module_source, actual_source]
    )
}

Lastly, you’ll want to report on module usage. Reporting will catch anything that the defining and enforcing phases were not able to catch:

  • Understanding which modules are used the most and which ones can be deprecated.
  • Ensure compliance by knowing if users are working around your controls in some way.
  • Knowing if workspaces are on older versions and getting them upgraded.

Reporting really brings your overall operations together to give you a report card on how your modules management is doing.

These three phases will allow you to scale your module usage over time. You might not need all of them on day one, but using a solution that will incorporate them over time will be a major factor as your Terraform usage grows.

Get started using modules to define, enforce and report on Terraform using Scalr today.

Start using the Terraform platform of the future.

A screenshot of the modules page in the Scalr Platform