Defining Policies
Note: Sentinel policies are a paid feature, available as part of the Team & Governance upgrade package. Learn more about Terraform Cloud pricing here.
Sentinel Policies for Terraform are defined using the Sentinel policy language. A policy can include imports which enable a policy to access reusable libraries, external data and functions. Terraform Cloud provides four imports to define policy rules for the plan, configuration, state, and run associated with a policy check.
- tfplan - This provides access to a Terraform plan, the file
created as a result of
terraform plan
. The plan represents the changes that Terraform needs to make to infrastructure to reach the desired state represented by the configuration. - tfconfig - This provides access to a Terraform configuration, the set of "tf" files that are used to describe the desired infrastructure state.
- tfstate - This provides access to the Terraform state, the file used by Terraform to map real world resources to your configuration.
- tfrun - This provides access to data associated with a run in Terraform Cloud, such as the run's workspace.
Terraform Cloud allows you to create mocks of these imports from plans for use with the mocking or testing features of the Sentinel CLI. For more information, see Mocking Terraform Sentinel Data.
Note: Terraform Cloud does not currently support custom imports.
Useful Functions and Idioms for Terraform Sentinel Policies
The following functions and idioms will be useful as you start writing Sentinel policies for Terraform.
Iterate over Modules and Find Resources
The most basic Sentinel task for Terraform is to enforce a rule on all resources of a given type. Before you can do that, you need to get a collection of all the relevant resources from all modules. The easiest way to do that is to copy and use a function like the following into your policies:
Note: This example uses the tfplan
import. You can find similar
functions that iterate over the tfconfig
and tfstate
imports
here.
You can call this function to get all resources of a desired type by passing the type as a string in quotes:
This example function does several useful things while finding resources:
- It checks every module (including the root module) for resources of the
specified type by iterating over the
module_paths
namespace. The top-levelresources
namespace is more convenient, but it only reveals resources from the root module. - It iterates over the named resources and resource
instances
found in each module, starting with
tfplan.module(path).resources[type]
which is a series of nested maps keyed by resource names and instance counts. - It uses the Sentinel
else
operator to recover fromundefined
values which would occur for modules that don't have any resources of the specified type. - It builds a flat
resources
map of all resource instances of the specified type. Using a flat map simplifies the code used by Sentinel policies to evaluate rules. - It computes an
address
variable for each resource instance and uses this as the key in theresources
map. This allows writers of Sentinel policies to print the full address of each resource instance that violate a policy, using the same address format used in plan and apply logs. Doing this tells users who see violation messages exactly which resources they need to modify in their Terraform code to comply with the Sentinel policies. - It sets the value of the
resources
map to the data associated with the resource instance (r
). This is the data that Sentinel policies apply rules against.
Validate Resource Attributes
Once you have a collection of resources instances of a desired type indexed by their addresses, you usually want to validate that one or more resource attributes meets some conditions by iterating over the resource instances.
While you could use Sentinel's all
and any
expressions
directly inside Sentinel rules, your rules would only report the first violation
because Sentinel uses short-circuit logic. It is therefore usually preferred to
use a for
loop outside
of your rules so that you can report all violations that occur. You can do this
inside functions or directly in the policy itself.
Here is a function that calls the find_resources_from_plan
function and
validates that the instance types of all EC2 instances being provisioned are in
a given list:
The boolean variable validated
is initially set to true
, but it is set to
false
if any resource instance violates the condition requiring that the
instance_type
attribute be in the allowed_types
list. Since the function
returns true
or false
, it can be called inside Sentinel rules.
Note that this function prints a warning message for every resource instance that violates the condition. This allows writers of Terraform code to fix all violations after just one policy check. It also prints warnings when the attribute being evaluated is computed and does not evaluate the condition in this case since the applied value will not be known.
While this function allows a rule to validate an attribute against a list, some
rules will only need to validate an attribute against a single value; in those
cases, you could either use a list with a single value or embed that value
inside the function itself, drop the allowed_types
parameter from the function
definition, and use the is
operator instead of the in
operator to compare
the resource attribute against the embedded value.
Write Rules
Having used the standardized find_resources_from_plan
function and having
written your own function to validate that resources instances of a specific
type satisfy some condition, you can define a list with allowed values and write
a rule that evaluates the value returned by your validation function.
Validate Multiple Conditions in a Single Policy
If you want a policy to validate multiple conditions against resources of a
specific type, you could define a separate validation function for each
condition or use a single function to evaluate all the conditions. In the latter
case, you would make this function return a list of boolean values, using one
for each condition. You can then use multiple Sentinel rules that evaluate
those boolean values or evaluate all of them in your main
rule. Here is a
partial example:
Similar functions and policies can be written to restrict Terraform configurations using the tfconfig import and to restrict Terraform state using the tfstate import.