Examples & Use Cases
Overview
Scalr implements OPA checks in two different stages: pre-plan and/or post-plan. The pre-plan stage can only evaluate information available before the plan, such as the run source, who executed the run, VCS details, and more. The post-plan stage can evaluate everything included in the pre-plan as well as information included in the terraform plan JSON, specifically deployment details about the resources being created, changed, and deleted.
The following policies are a few examples, to see more, visit this repo.
Pre-Plan
The pre-plan checks execute right before the Terraform plan stage. At this stage, Scalr has access to the tfrun data, which OPA can evaluate. The tfrun data contains information such as the run source, the user who executed the run, commit authors, workspace name, and anything else that is relates to how the run execution started.
Prevent a Destructive Run
If you are using the Scalr provider to manage Scalr workspaces, you may want to put checks in to make sure that users are not destroying workspaces that have active state:
# Enforces that workspaces are tagged with the names of the providers.
package terraform
import input.tfplan as tfplan
deny["Can not destroy workspace with active state"] {
resource := tfplan.resource_changes[_]
"delete" == resource.change.actions[count(resource.change.actions) - 1]
"scalr_workspace" == resource.type
resource.change.before.has_resources
}
Prevent Auto-Apply
Want to prevent auto-apply runs from happening in an environment such as prod? The following policy will check to see if the workspace setting has auto-apply enabled:
# Allprod runs must be approved
package terraform
import input.tfrun as tfrun
# Deny if auto-apply is enabled
deny["auto-apply is not allowed"] {
tfrun.workspace.auto_apply == true
}
Limit Run Sources
Want to ensure only approved users can use the Terraform or Tofu CLI to kick off runs? You can limit run sources with this policy:
package terraform
import input.tfrun as tfrun
allowed_cli_users = ["d.johnson", "j.smith"]
array_contains(arr, elem) {
arr[_] = elem
}
get_basename(path) = basename{
arr := split(path, "/")
basename:= arr[count(arr)-1]
}
deny["User is not allowed to perform runs from Terraform CLI"] {
"cli" == tfrun.source
not array_contains(allowed_cli_users, tfrun.created_by.username)
}
Post-Plan
The post-plan checks execute right after the Terraform plan stage. At this stage, Scalr has access to the tfrun and tfplan data, which OPA can evaluate. The tfrun data contains information such as the run source, the user who executed the run, commit authors, workspace name, and anything else that relates to how the run execution started.
Limit Module Source
Want to enforce where modules are being pulled from for specific resources? The following module will evaluate the module source and prevent resources from being pulled from sources they shouldn't be:
# Enforce that specificied resource types are only created by specific modules and not in the root module.
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"
}
array_contains(arr, elem) {
arr[_] = elem
}
deny[reason] {
resource := tfplan.resource_changes[_]
action := resource.change.actions[count(resource.change.actions) - 1]
array_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]
array_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]
)
}
Enforce Tagging
Do you have standard tags that must be applied to all resources? The tagging policy will check to ensure those tags are part of the Terraform code:
# Enforces a set of required tag keys. Values are bot checked
package terraform
import input.tfplan as tfplan
required_tags = ["owner", "department"]
array_contains(arr, elem) {
arr[_] = elem
}
get_basename(path) = basename{
arr := split(path, "/")
basename:= arr[count(arr)-1]
}
# Extract the tags catering for Google where they are called "labels"
get_tags(resource) = labels {
# registry.terraform.io/hashicorp/google -> google
provider_name := get_basename(resource.provider_name)
"google" == provider_name
labels := resource.change.after.labels
} else = tags {
tags := resource.change.after.tags
} else = empty {
empty := {}
}
deny[reason] {
resource := tfplan.resource_changes[_]
action := resource.change.actions[count(resource.change.actions) - 1]
array_contains(["create", "update"], action)
tags := get_tags(resource)
# creates an array of the existing tag keys
existing_tags := [ key | tags[key] ]
required_tag := required_tags[_]
not array_contains(existing_tags, required_tag)
reason := sprintf(
"%s: missing required tag %q",
[resource.address, required_tag]
)
}
Black List a Provider
If you want to control which providers should not be used, you may want to blacklist specific providers:
# Prevent specified providers from being used
package terraform
import input.tfplan as tfplan
# Blacklisted Terraform providers
not_allowed_provider = [
"azurerm"
]
array_contains(arr, elem) {
arr[_] = elem
}
get_basename(path) = basename{
arr := split(path, "/")
basename:= arr[count(arr)-1]
}
deny[reason] {
resource := tfplan.resource_changes[_]
action := resource.change.actions[count(resource.change.actions) - 1]
array_contains(["create", "update"], action) # allow destroy action
# registry.terraform.io/hashicorp/aws -> aws
provider_name := get_basename(resource.provider_name)
array_contains(not_allowed_provider, provider_name)
reason := sprintf(
"%s: provider type %q is not allowed",
[resource.address, provider_name]
)
}
Buckets Must Be Private
Want to make sure all of your S3 buckets are private? The following policy blocks public buckets:
# Check S3 bucket is not public
package terraform
import input.tfplan as tfplan
deny[reason] {
r = tfplan.resource_changes[_]
r.mode == "managed"
r.type == "aws_s3_bucket"
r.change.after.acl == "public"
reason := sprintf("%-40s :: S3 buckets must not be PUBLIC",
[r.address])
}
Allowed Regions
In this example, we want to restrict which regions users are allows to deploy to depending on the cloud provider used:
# Enforce a list of allowed locations / availability zones for each provider
package terraform
import input.tfplan as tfplan
allowed_locations = {
"aws": ["us-east-1", "us-east-2"],
"azurerm": ["eastus", "eastus2"],
"google": ["us-central1-a", "us-central1-b", "us-west1-a"]
}
array_contains(arr, elem) {
arr[_] = elem
}
get_basename(path) = basename{
arr := split(path, "/")
basename:= arr[count(arr)-1]
}
eval_expression(plan, expr) = constant_value {
constant_value := expr.constant_value
} else = reference {
ref = expr.references[0]
startswith(ref, "var.")
var_name := replace(ref, "var.", "")
reference := plan.variables[var_name].value
}
get_location(resource, plan) = aws_region {
# registry.terraform.io/hashicorp/aws -> aws
provider_name := get_basename(resource.provider_name)
"aws" == provider_name
provider := plan.configuration.provider_config[_]
"aws" = provider.name
region_expr := provider.expressions.region
aws_region := eval_expression(plan, region_expr)
} else = azure_location {
provider_name := get_basename(resource.provider_name)
"azurerm" == provider_name
azure_location := resource.change.after.location
} else = google_zone {
provider_name := get_basename(resource.provider_name)
"google" == provider_name
google_zone := resource.change.after.zone
}
deny[reason] {
resource := tfplan.resource_changes[_]
location := get_location(resource, tfplan)
provider_name := get_basename(resource.provider_name)
not array_contains(allowed_locations[provider_name], location)
reason := sprintf(
"%s: location %q is not allowed",
[resource.address, location]
)
}
Example Repo
See more examples in the following repo:https://github.com/Scalr/sample-tf-opa-policies/tree/master
Updated 18 days ago