terraform

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

By submitting this form, you acknowledge and agree that HashiCorp will process your personal information in accordance with the Privacy Policy.