
Open Policy Agent (OPA) is a declarative policy language you can use across your cloud ecosystem to keep deployments under control. It's gotten popular with the Terraform community as a way to check Terraform plans and make sure DevOps teams are deploying to your organization's standards.
The first three parts of this series covered how to develop and test OPA policies, and walked through writing them for Terraform and Scalr.
This part gives you a set of ready-made templates for common policy requirements. You adapt each one to your own resource and attribute, and they're written to work on the top-level attributes of a resource.
This initial set of templates covers the following.
In general you configure these templates by setting the resources, attribute and ...list variables, like 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 repo (https://github.com/Scalr/sample-tf-opa-policies). Below are a few examples with expanded descriptions of the OPA logic.
These templates reference an attribute through a variable, as shown in this snippet.
attribute = "key_name"
r = tfplan.resource_changes[_]
item = r.change.after[attribute]The variable "item" now holds the value of the "key_name" attribute, or it's undefined if the attribute doesn't 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 ways. First it's a white list, so we need to check for matches to any values. Second the attribute is an array, so we need to 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 just iterate 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 returns true if there's any match to the array. We only want to set the reason if there's no match, so we negate the function call with a "not"
The examples above and other templates can be pulled from this Github repo (https://github.com/Scalr/sample-tf-opa-policies).
If you have not read parts one, two or three yet, please check them out. 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.
