In previous posts, we covered the basics of Sentinel, how to write a simple policy and a real-world application of Sentinel. In this post, we'll cover how to integrate Sentinel with Terraform Cloud and how to use it to enforce a cost management policy.
If you haven't read parts 1, 2 and 3, I recommend you do so before continuing. You can find them here:
In this post, we'll cover the following:
- Using Sentinel to enforce a cost management policy
- Using the Terraform Cloud UI to view policy violations
- Integrating a Sentinel VCS repository with Terraform Cloud
In the previous post we saw how to use Sentinel to enforce tagging policies, in this section, we'll use Sentinel to enforce cost management, and use a common deployment convention by integrating with a VCS provider.
A built-in function of Terraform Cloud and Terraform Enterprise is the cost estimation functionality that estimates the cost of infrastructure changes. Coupled with Sentinel We can enforce a cost threshold on changes to your infrastructure. Let's take a look at another example, this time instead of limiting ourselves to the Sentinel Playground we'll create a GitHub repository and integrate it directly with Terraform Cloud or Enterprise.
In this example, we'll create a policy that limits a workspace budget to a maximum cost of $100 per month using both Sentinel and Terraform Cost Estimation.
In my example, I'll be using GitHub but you can use any VCS provider that Terraform Cloud or Enterprise supports. If you don't have a GitHub account, you can create one here. Once you have an account, you can create a new repository by clicking the +
in the top right corner and selecting New repository
. Give your repository a name, I'll call mine sentinel-policies-blog
then click Create repository
.
Before we start writing our policy let's break down the requirements:
- We need to be able to get the cost estimate from Terraform Cloud or Enterprise
- We need to be able to compare the cost estimate to a threshold
- We need to be able to return a pass or fail result
Before we can write our policy, we need to know what data is available to us. We can find that out by generating some mock data. In Terraform Cloud/Enterprise create a new workspace and write some basic Terraform to spin up an EC2 instance. I'm fairly confident that an AWS instance size of m5.24xlarge
will be sufficiently higher than our threshold, so let's use that to both generate our mock data and provide a failing test case.
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "demo" {
ami = data.aws_ami.ubuntu.id
instance_type = "m5.24xlarge"
tags = {
"Name" = "Demo Instance"
"TTL" = "1h"
}
}
Once you've created your workspace, and have run a plan (I wouldn't recommend applying, especially if you used a m5.24xlarge
instance size like I did) you can go to the Runs tab and Download Sentinel mocks
.
In the root of your repository, create a new file called restrict-expensive-workspaces.sentinel
. Previously we've been using import tfplan
and import tfplan/v2 as tfplan
to check for misconfigurations. The tfplan
mock data is exactly that, the plan that Terraform generates, but that doesn't contain cost data. To get cost data we need to use tfrun
since cost estimation is only available on runs.
Here's the portion of the mock-tfrun.sentinel
mock data that contains the cost estimate:
cost_estimate = {
"delta_monthly_cost": "3428.352",
"prior_monthly_cost": "0.0",
"proposed_monthly_cost": "3428.352",
}
And here's our policy restrict-expensive-workspaces.sentinel
:
import "tfrun"
import "decimal"
proposed_monthly_cost = decimal.new(tfrun.cost_estimate.proposed_monthly_cost)
main = rule {
proposed_monthly_cost.less_than_or_equals(1000)
}
As we've done in the past, let's break down this code:
- We import the
tfrun
mock data frommock-tfrun.sentinel
- We import the
decimal
library - We create a new decimal value from the
proposed_monthly_cost
value in thetfrun
mock data - We create a rule that checks if the
proposed_monthly_cost
is less than or equal to 1000
Let's take everything we've created here and create a new git repository, push it to our VCS provider of choice and integrate it with Terraform Cloud or Enterprise.
First, let's create a new folder locally, I'll call mine sentinel-policies-blog
then initialize a new git repository, layout our folder structure and add a boilerplate .gitignore
file:
mkdir sentinel-policies-blog
cd sentinel-policies-blog
git init
mkdir -p restrict-expensive-workloads/test
touch sentinel.hcl
curl -s https://www.toptal.com/developers/gitignore/api/terraform > .gitignore
Let's use our cost management policy ./restrict-expensive-workspaces/restrict-expensive-workspaces.sentinel
. Next, let's edit sentinel.hcl
and add the following:
policy "restrict-expensive-workspaces" {
source = "./restrict-expensive-workspaces/restrict-expensive-workspaces.sentinel"
enforcement_level = "hard-mandatory"
}
Next extract the contents of the mocks .tar.gz
file you downloaded from Terraform Cloud/Enterprise into the restrict-expensive-workloads
folder.
Okay, that was a lot. We should end up with a folder structure that looks like this:
.
├── restrict-expensive-workspaces
│ ├── mock-tfconfig-v2.sentinel
│ ├── mock-tfconfig.sentinel
│ ├── mock-tfplan-v2.sentinel
│ ├── mock-tfplan.sentinel
│ ├── mock-tfrun.sentinel
│ ├── mock-tfstate-v2.sentinel
│ ├── mock-tfstate.sentinel
│ └── restrict-expensive-workspaces.sentinel
└── sentinel.hcl
With all of this in place we're ready to push our code to our VCS provider. If you're using GitHub, you can create a new repository by clicking the +
in the top right corner and selecting New repository
. Give your repository a name, I'll call mine sentinel-policies-blog
then click Create repository
. Next, copy the git remote add
command that GitHub provides and run it locally.
git add .
git commit -m "Initial commit"
git push -u origin main
In Terraform Cloud, navigate to your Organization then click Settings
.
Next, click Policy Sets
then Connect a new policy set
.
Select your VCS provider, or Connect to a different VCS provider
if you're using a different provider. Next, select the repository you created in the previous step, then click Connect policy set
.
Congratulations! You've successfully completed this section by creating a new policy, uploaded it to a VCS provider and connected it to Terraform Cloud. In the next, and final section we'll bring in some standardizations and reusable modules to make our policies more maintainable.