OPA Series Part 4: Simple Policies for Scalr

Scalr
scalr
Published in
4 min readMay 1, 2023

By Ryan Fee

In this article, we are providing a series of simple templates that implement a number of common policy requirements. The templates are easily adaptable for resources and attributes, and each template is set to work for top-level attributes of a resource.

Common Policy Requirements

This initial set of templates covers the following.

  • actions-blacklist.rego: Black list for Actions, Create, Update Delete
  • array-blacklist.rego: Black list for values of an array type attribute
  • array-whitelist.rego: White list for values of an array type attribute
  • attribute_check.rego: Check that an attribute has been specified and with a non-null value
  • attribute_value_regex.rego: Check attribute value matches a regular expression
  • numeric-range.rego: Check an attribute numeric value is within range (>=min, <=max or both)
  • resource-type-blacklist.rego: Black list of resource types
  • resource-type-whitelist.rego: White list of resource types
  • scalar-blacklist.rego: Black list for values of a scalar type attribute
  • scalar-whitelist.rego: White list for values of a scalar type attribute

In general, these templates can be configured simply by setting the resources, attribute,s and …list variables as in this example:

resource := "{resource_name}"

# The planned value for this scalr attribute
attribute := "{attribute_name}"

# Is checked against this blacklist. If the value IS present in the list the policy is violated.
# This can be a single value list, and can be numerics, booleans or strings
black_list := [
"{value}",
"{value}",
]

The templates can all be pulled from this GitHub repository. Below are a few examples with expanded descriptions of the OPA logic.

Generic Policy Templates

These templates make use of the capability to reference an attribute via a variable as shown in this code snippet

attribute = "key_name"
r = tfplan.resource_changes[_]
item = r.change.after[attribute]

The variable “item” would now contain the value of the “key_name” attribute, or would be undefined if the attribute does not exist.

scalr-blacklist.rego

This policy implements a black list for values of a scalar type attribute.

# Implements a blacklist on a scalar attribute

package terraform

import input.tfplan as tfplan

resource := "{resource_name}"

# The planned value for this scalr attribute
attribute := "{attribute_name}"

# Is checked against this blacklist. If the value IS present in the list the policy is violated.
# This can be a single value list, and can be numerics, booleans or strings
black_list := [
"{value}",
"{value}",
]

# Check if value is in black list for the attribute
deny[reason] {
r = tfplan.resource_changes[_]
r.mode == "managed"
r.type == resource
black_list[_] == r.change.after[attribute]

reason := sprintf("%-40s :: %s value '%s' is not allowed", [r.address, attribute, r.change.after[attribute]])
}

The critical rule here is

black_list[_] == r.change.after[attribute]

This iterates on all the values in the black_list and if a match with the attribute value is found then the reason is assigned in the next rule.

array-whitelist.rego

This policy implements a white list for values of an array type attribute.

# Implements a whitelist on a array attribute

package terraform

import input.tfplan as tfplan

resource := "{resource_name}"

# The planned value for this array attribute
attribute := "{attribute_name}"

# Is checked against this whitelist.
# If any of the values ARE NOT present in the list the policy is violated.
# This can be a single value list, and can be numerics, booleans or strings
white_list := [
"{value}",
"{value}",
]

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

# Check if value is in white list for the attribute
deny[reason] {
r = tfplan.resource_changes[_]
r.mode == "managed"
r.type == resource
list_item = r.change.after[attribute][_]
not array_contains(white_list, list_item)

reason := sprintf("%-40s :: %s value '%s' is not allowed", [r.address, attribute, list_item])
}

This policy differs from the first one in two respects. Firstly it’s a white list, so we need to check for matches to any values, and secondly the attribute is an array, so we need iterate on that array as well as the white list

The critical lines are

list_item = r.change.after[attribute][_]
not array_contains(white_list, list_item)

The resource attribute is also an array, so we need to start an iteration on that as well. We can use the same variable to reference the attribute and simply suffix the expression with “[_]” to start the iteration.

Because it’s a white list we can’t do a simple iteration on the array. The attribute value will only match zero or one item in the array, and all the non-matching values would drop through and set the reason. So to make this work we need a helper rule (array_contains) that will return true if there is any match to the array. We only want to set the reason if there is no match, so we negate the function call with a “not”

The examples above and other templates can be pulled from this GitHub repository. If you are interested in more examples, Scalr maintains an ever-expanding library of OPA policy examples in our GitHub repository. Feel free to make a PR and contribute or create an issue if there is an example you would like to see. Learn more about Scalr and how we can help here.

--

--

Scalr
scalr
Editor for

Scalr is a remote backend for Terraform with full CLI support, integration with OPA, a hierarchical configuration model and quality of life features