Terraform
Write a Sentinel policy for a Terraform deployment
When writing a Sentinel policy, you can validate your policy's restrictions against Sentinel imports, which access mock data. Sentinel can use several types of imports from the HCP Terraform API: configuration, plan, state, and run.
Note
HCP Terraform Free Edition includes one policy set of up to five policies. In HCP Terraform Plus Edition, you can connect a policy set to a version control repository or create policy set versions via the API. Refer to HCP Terraform pricing for details.
In this tutorial, you will review a Sentinel policy and test it in the Sentinel CLI using pre-generated mock import data. The example policy enforces EC2 instance type and tag restrictions.
Prerequisites
For this tutorial, you will need:
Clone example Terraform configuration
In your terminal, clone the example code
repository. This
repository contains an example Sentinel policy and a tfplan/v2
Sentinel mock
import.
$ git clone https://github.com/hashicorp-education/learn-sentinel-write-policy
Navigate to the directory.
$ cd learn-sentinel-write-policy
Review the tfplan/v2
import mock data structure
Tip
To learn how to generate Sentinel mock data used in this tutorial, review the Generate Policy Mock Data tutorial.
You will use the tfplan/v2
import type to test your policy. This import type
provides access to Terraform plan data, which represents the changes that
Terraform needs to make to infrastructure to match the written configuration.
The tfplan
import lets you determine if the proposed changes satisfy
your policy criteria.
Open the file named mock-tfplan-v2.sentinel
in your text editor. Find the
resource_changes
collection. This Terraform data is a key/value collection
for all of the proposed resource changes required by your configuration. The data below is
truncated, but your file should contain this collection with these values.
mock-tfplan-v2.sentinel
terraform_version = "1.0.1"
## ...
resource_changes = {
"aws_instance.ubuntu": {
"address": "aws_instance.ubuntu",
"change": {
"actions": [
"create",
],
"after": {
"ami": "ami-0fb1e27304d83032f",
"credit_specification": [],
"disable_api_termination": null,
"ebs_optimized": null,
"get_password_data": false,
"hibernation": null,
"iam_instance_profile": null,
"instance_initiated_shutdown_behavior": null,
"instance_type": "t2.micro",
"monitoring": null,
"source_dest_check": true,
"tags": {
"Name": "Provisioned by Terraform",
},
"timeouts": null,
"user_data": null,
"user_data_base64": null,
"volume_tags": null,
},
## ...
},
"deposed": "",
"index": null,
"mode": "managed",
"module_address": "",
"name": "ubuntu",
"provider_name": "registry.terraform.io/hashicorp/aws",
"type": "aws_instance",
},
## ...
}
Terraform captures the attributes of any created or modified resource in the plan. You can use this data in your Sentinel policies to verify that the policy appropriately restricts resources or attributes.
When testing a policy, select the appropriate import for your policy's
restrictions. For example, you can use a tfrun
import to test a policy that
restricts resources based on cost estimation. You can use a tfconfig/v2
import to test a policy that limits the number of providers to use in
configuration.
Define a mock in Sentinel
Now open the sentinel.hcl
file and review the configuration.
mock "tfplan/v2" {
module {
source = "mock-tfplan-v2.sentinel"
}
}
This defines a new
mock named
tfplan/v2
containing the data from the mock-tfplan-v2.sentinel
file. Your
Sentinel policy will import this mock to test the policy.
Review your policy
Sentinel tests your requirements against your imported data, resulting in a
Pass
or Fail
. When writing policies, you can use
variables to store
values and
operators
to compare logical expressions.
Open the restrict-aws-instances-type-and-tag.sentinel
file, which contains
the Sentinel policy.
This Sentinel policy enforces the following infrastructure requirements:
- EC2 instances must have a
Name
tag. - EC2 instances must be of type
t2.micro
,t2.small
, ort2.medium
.
If your EC2 instances do not meet all of these criteria, Sentinel will flag the
run with a FAIL
.
restrict-aws-instances-type-and-tag.sentinel
# Imports mock data
import "tfplan/v2" as tfplan
# Get all AWS instances from all modules
ec2_instances = filter tfplan.resource_changes as _, rc {
rc.type is "aws_instance" and
(rc.change.actions contains "create" or rc.change.actions is ["update"])
}
# Mandatory Instance Tags
mandatory_tags = [
"Name",
]
# Allowed Types
allowed_types = [
"t2.micro",
"t2.small",
"t2.medium",
]
# Rule to enforce "Name" tag on all instances
mandatory_instance_tags = rule {
all ec2_instances as _, instance {
all mandatory_tags as mt {
instance.change.after.tags contains mt
}
}
}
# Rule to restrict instance types
instance_type_allowed = rule {
all ec2_instances as _, instance {
instance.change.after.instance_type in allowed_types
}
}
# Main rule that requires other rules to be true
main = rule {
(instance_type_allowed and mandatory_instance_tags) else true
}
This Sentinel policy can be divided into seven sections.
The
import
statement imports the mock data, defined insentinel.hcl
. This mock data simulates a Terraform plan that provisions an EC2 instance.The
ec2_instances
variable filters the mock data for EC2 instances that the plan will create or modify.Sentinel determines the specific resources or data to evaluate from the import based on a
filter expression
. In the above policy,ec2_instances
uses afilter
expression. The expression returns a map oftfplan.resource_changes
: a selector that searches thetfplan
data collection for a field calledresource_changes
for EC2 instances created or updated in the plan data.The
mandatory_tags
variable contains the list of required tags.The
allowed_types
variable contains the list of permitted EC2 instances types.The
mandatory_instance_tags
rule ensures that EC2 instances have aName
tag. The rule loops through the filteredec2_instances
and verifies that all EC2 instances have the tags defined in themandatory_tags
variable.Tip
You can open
mock-tfplan-v2.sentinel
and verify thatinstance.change.after.tags
contains theName
tag. This allows you to determine which attributes are available for testing. Notice thatinstance
corresponds to each item in the filteredec2_instances
variable.mock-tfplan-v2.sentinel
resource_changes = { "aws_instance.ubuntu": { "address": "aws_instance.ubuntu", "change": { ## ... "after": { ## .. "tags": { "Name": "Provisioned by Terraform", }, ## .. } ## ... }, ## ... }, }
The
instance_type_allowed
rule ensures that EC2 instances aret2.micro
,t2.small
, ort2.medium
. The rule loops through the filteredec2_instances
and checks whether the instance'sinstance_type
isin
theallowed_types
.The
main
rule evaluates both themandatory_instance_tags
andinstance_type_allowed
rules. If both are true, Sentinel allows the policy to pass. The Sentinel policy divides rules this way to keep the main rule short and allows you to evaluate your policy based on multiple rule criteria.
Run your policy in the Sentinel CLI
Now, use the apply
command to test your policy against the mock data and
ensure your policy performs as expected.
$ sentinel apply restrict-aws-instances-type-and-tag.sentinel
Pass - restrict-aws-instances-type-and-tag.sentinel
The execution passed because the infrastructure changes in your plan data satisfy your policy criteria.
Next steps
You reviewed the parts of a Sentinel policy and used mock data to validate a policy. To learn more about Sentinel, review the following resources:
- Learn how to Generate Mock Policy Data
- Review how to Test a Sentinel Policy
- Learn how to Upload a Sentinel Policy Set to HCP Terraform
- Learn more about the different Terraform-specific
import
types.