TFE and Sentinel: Provisioning Policy for Data Sovereignty in the Cloud
Infrastructure as code with HashiCorp Terraform enables operators to automate provisioning at scale. This comes with risks, as every action can have larger effects. Sentinel policy as code places guardrails to protect users from creating infrastructure changes that fall outside of business, security, and compliance policies. This blog will take a look at writing and enforcing a policy using Terraform Enterprise to restrict provisioning resources in certain availability zones to ensure data sovereignty.
» About Policy as Code
Sentinel, the HashiCorp framework for policy as code management, is built to be embedded in existing software to enable fine-grained, logic-based policy decisions. A policy describes under what circumstances certain behaviors are allowed. Sentinel is embedded into the Enterprise version of each of the HashiCorp products and was first introduced at HashiConf. We will look at the details of a specific Sentinel policy in this blog; Sentinel basics are covered in: Announcing Sentinel, HashiCorp’s Policy as Code Framework, Why Policy as Code, and Terraform Enterprise: Applying policy as code to infrastructure provisioning.
» Scenario
Our example company falls under regulation regarding the sovereignty of their data and they are legally bound to ensure that no data ever leaves the country. Since AWS regions often span multiple countries we need a slightly more complex policy than the one used to restrict region in our previous post. Let’s take a look at how we can use Sentinel to ensure that we always adhere to this regulation.
This example demonstrates a Sentinel policy which ensures aws_instance is only placed in the allowed availability zones. In cases where the policy check fails, Terraform Enterprise will not allow the terraform apply
to occur. A logged error will be reported and indicate to the user they are violating a hard-mandatory (cannot override) policy, and the user cannot proceed with their actions until the value has been corrected. Below is the example policy for this scenario.
# NOTE that you must explicitly specify availability_zone on all aws_instances
import "tfplan"
# Get all AWS instances from all modules
aws_instances = func() {
instances = []
for tfplan.module_paths as path {
instances += values(tfplan.module(path).resources.aws_instance) else []
}
return instances
}
# Allowed availability zones
allowed_zones = [
"us-east-1a",
"us-east-1b"
]
# Rule to restrict availability zones and region
main = rule {
all aws_instances() as _, instances {
all instances as index, r {
r.applied.availability_zone in allowed_zones
}
}
}
» Pre-requisites
You must explicitly specify availability_zone for aws_instance or this policy will fail. The good news is that Sentinel can be used to validate that this is done as well. The demo code for this blog can be found here for reference. For simplicity, the example code below is for a single instance:
provider "aws" {
region = "${var.region}"
}
resource "aws_instance" "demo_nodes" {
ami = "${var.ami}"
instance_type = "${var.instance_type}"
count = "${var.instance_count}"
availability_zone = "${var.availability_zone}"
tags {
Name = "${var.tag_server_name}"
}
}
output "demo_nodes" {
value = "${aws_instance.demo_nodes.*.public_ip}"
}
This code must be in a public git repo or other compatible source control management solution for Terraform Enterprise.
» Using the policy in your environment
» Step1: Create New Workspace
In this section, create a new workspace and link it to your terraform-demo
repository. In the Terraform Enterprise UI, navigate to create a new workspace view at https://atlas.hashicorp.com/app/:organization/workspaces/new
in your browser, where :organization
is the name of the organization you will create the workspace in.
Enter terraform-demo
as the workspace name and select the terraform-demo
GitHub repository you forked when completing the prerequisite steps or the repo you created. Click Create the workspace
.
Navigate to https://atlas.hashicorp.com/app/:organization/terraform-demo/variables
in your browser, where :organization
is the name of the organization you created the workspace in. Add AWS_ACCESS_KEY_ID
, AWS_SECRET_ACCESS_KEY
variables with values that provide you access to AWS and the Save
.
If you were using the repo from the prerequisites, your variable setup will look similar to this to cause an intentional failure. The availibility_zone variable is the value we are checking.
» Step 2: Create New Policy
In this section a new policy will be created. Navigate to Create new policy
view at https://atlas.hashicorp.com/app/:organization/settings/policies
in your browser, where :organization
is the name of the organization you will configure the policy for.
Enter geo_fencing_lock
as the policy name, set the enforcement mode to hard mandatory and copy the below policy into the policy code text area. Save the policy.
# NOTE that you must explicitly specify availability_zone on all aws_instances
import “tfplan”
# Get all AWS instances from all modules
get_aws_instances = func() {
instances = []
for tfplan.module_paths as path {
instances += values(tfplan.module(path).resources.aws_instance) else []
}
return instances
}
# Allowed availability zones
allowed_zones = [
"us-east-1a",
"us-east-1b",
"us-east-1c",
"us-east-1d",
"us-east-1e",
"us-east-1f",
]
aws_instances = get_aws_instances()
# Rule to restrict availability zones and region
region_allowed = rule {
all aws_instances as _, instances {
all instances as index, r {
r.applied.availability_zone in allowed_zones
}
}
}
# Main rule that requires other rules to be true
main = rule {
(region_allowed) else true
}
» Step 3: Queue Plan and Review Log
In this section we will see the policy enforced during a Terraform run. The policy is saved for the organization and it will be used for all future Terraform runs. Navigate to https://atlas.hashicorp.com/app/:organization/terraform-demo/runs
in your browser, where :organization
is the name of the organization. Queue a plan.
» Validate Policy Check
With the Terraform plan complete, review the plan and policy output by navigating to https://atlas.hashicorp.com/app/:organization/terraform-demo/latest
in your browser, where :organization
is the name of the organization. Click the “View Plan” and “View Check” buttons to view the log of the plan and policy check stages.
As demonstrated above, the geo-fencing (preventing infrastructure being provisioned to a non-allowed availability zone) was successful. With the intentional failure on the region us-west-1b we are unable to provision infrastructure to any availability zone not specifically identified.
The policy check log will be similar to below:
Sentinel Result: false
Sentinel evaluated to false because one or more Sentinel policies evaluated
to false. This false was not due to an undefined value or runtime error.
1 policies evaluated.
## Policy 1: geo_fencing_lock.sentinel (hard-mandatory)
Result: false
FALSE - geo_fencing_lock.sentinel:37:1 - Rule "main"
FALSE - geo_fencing_lock.sentinel:29:5 - all aws_instances as _, instances {
all instances as index, r {
r.applied.availability_zone in allowed_zones
}
}
As this policy is hard-mandatory (cannot-override) it is impossible for operators to circumvent. By design we have now locked our infrastructure into a specified regional zone. If you wish to validate this you can update your region back to “us-east-1b” and re-run the plan.
» Validate Policy Check
With the Terraform plan complete, review the plan and policy output by navigating to https://atlas.hashicorp.com/app/:organization/terraform-demo/latest
in your browser, where :organization
is the name of the organization. Click the “View Plan” and “View Check” buttons to view the log of the plan and policy check stages.
The policy check stage of the Terraform run will have passed as the hard-mandatory availability zone check evaluated the policy code against the Terraform configuration and the us-east-1b variable complies with the policy definition. The policy check log will be similar to the below:
Sentinel Result: true
This result means that Sentinel policies returned true and the protected
behavior is allowed by Sentinel policies.
1 policies evaluated.
## Policy 1: geo_fencing_lock.sentinel (hard-mandatory)
Result: true
TRUE - geo_fencing_lock.sentinel:37:1 - Rule "main"
TRUE - geo_fencing_lock.sentinel:29:5 - all aws_instances as _, instances {
all instances as index, r {
r.applied.availability_zone in allowed_zones
}
}
TRUE - geo_fencing_lock.sentinel:28:1 - Rule "region_allowed"
Click “Confirm & Apply” to complete the Terraform run and provision infrastructure in AWS.
» Conclusion
Sentinel policy as code management is an important part of infrastructure automation and removes the need for manual policy enforcement and associated ticketing queues. For those already using Terraform Enterprise try using the above example. If you would like to sign-up for a trial or to learn more about Terraform Enterprise, visit https://www.hashicorp.com/products/terraform.
Sign up for the latest HashiCorp news
More blog posts like this one
5 ways to improve DevEx and security for infrastructure provisioning
Still using manual scripting and provisioning processes? Learn how to accelerate provisioning using five best practices for Infrastructure Lifecycle Management.
Fix the developers vs. security conflict by shifting further left
Resolve the friction between dev and security teams with platform-led workflows that make cloud security seamless and scalable.
HashiCorp at AWS re:Invent: Your blueprint to cloud success
If you’re attending AWS re:Invent in Las Vegas, Dec. 2 - Dec. 6th, visit us for breakout sessions, expert talks, and product demos to learn how to take a unified approach to Infrastructure and Security Lifecycle Management.