Skip to content

Commit 2e20317

Browse files
authored
Merge pull request hashicorp#68 from hashicorp/more-sentinel-policies
added some policies and updated README.md files
2 parents 7d63534 + 6aeff35 commit 2e20317

10 files changed

+188
-2
lines changed

governance/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# Governance with Terraform Sentinel Policies
2-
The files under this directory provide some sample Sentinel policies for several clouds including AWS, Microsoft Azure, and Google Cloud Platform (GCP), as well as some for VMware. Sentinel gives operations teams the governance capabilities they need to ensure that all infrastructure provisioned with Terraform Enterprise complies with their organization's provisioning rules.
2+
Sentinel gives operations teams the governance capabilities they need to ensure that all infrastructure provisioned with Terraform Enterprise complies with their organization's provisioning rules. The files under this directory provide some sample Sentinel policies for several clouds including AWS, Microsoft Azure, Google Cloud Platform (GCP), and VMware. The external directory also includes an example of an external data source and a Sentinel policy which checks the result of that data source.

governance/aws/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# Sentinel Policies for AWS
2-
The sample Sentinel policy files in this directory can be used with Terraform Enterprise to ensure that provisioned AWS VPCs and EC2 instances comply with your organization's provisioning rules.
2+
The sample Sentinel policy files in this directory can be used with Terraform Enterprise to ensure that provisioned AWS VPCs, EC2 instances, S3 buckets, Lambda functions, and other resources comply with your organization's provisioning rules. There are also policies that restrict AMI owner IDs and the region or availability zones into which resources can be provisioned.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import "tfplan"
2+
3+
# Get all S3 buckets from all modules
4+
get_s3_buckets = func() {
5+
buckets = []
6+
for tfplan.module_paths as path {
7+
buckets += values(tfplan.module(path).resources.aws_s3_bucket) else []
8+
}
9+
return buckets
10+
}
11+
12+
s3_buckets = get_s3_buckets()
13+
14+
# Allowed S3 ACLs
15+
# Don't allow public-read-write
16+
allowed_acls = [
17+
"private",
18+
]
19+
20+
# Rule to restrict S3 bucket ACLs
21+
acl_allowed = rule {
22+
all s3_buckets as _, instances {
23+
all instances as index, r {
24+
r.applied.acl in allowed_acls
25+
}
26+
}
27+
}
28+
29+
# Rule to require server-side encryption
30+
require_encryption = rule {
31+
all s3_buckets as _, instances {
32+
all instances as index, r {
33+
(length(r.applied.server_side_encryption_configuration) > 0 and r.applied.server_side_encryption_configuration[0]["rule"][0].apply_server_side_encryption_by_default[0].sse_algorithm is "aws:kms") else false
34+
}
35+
}
36+
}
37+
38+
# Main rule that requires other rules to be true
39+
main = rule {
40+
(acl_allowed and require_encryption) else true
41+
}
42+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import "tfplan"
2+
3+
# Get all Lambda functions from all modules
4+
get_lambda_functions = func() {
5+
lambdas = []
6+
for tfplan.module_paths as path {
7+
lambdas += values(tfplan.module(path).resources.aws_lambda_function) else []
8+
}
9+
return lambdas
10+
}
11+
12+
lambda_functions = get_lambda_functions()
13+
14+
# Rule to require KMS key
15+
require_kms_key = rule {
16+
all lambda_functions as _, instances {
17+
all instances as index, r {
18+
(r.applied.kms_key_arn is not "") else false
19+
}
20+
}
21+
}
22+
23+
# Rule to require VPC
24+
require_vpc = rule {
25+
all lambda_functions as _, instances {
26+
all instances as index, r {
27+
(length(r.applied.vpc_config) > 0 and
28+
length(r.applied.vpc_config[0].security_group_ids) > 0 and
29+
length(r.applied.vpc_config[0].subnet_ids) > 0) else false
30+
}
31+
}
32+
}
33+
34+
# Main rule that requires other rules to be true
35+
main = rule {
36+
(require_kms_key and require_vpc) else true
37+
}
38+

governance/azure/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
# Sentinel Policies for Azure
22
The sample Sentinel policy files in this directory can be used with Terraform Enterprise to ensure that provisioned Azure security groups, VMs, and ACS clusters comply with your organization's provisioning rules.
3+
4+
The restrict-current-azure-vms.sentinel policy is interesting because it actually checks VMs that have already been provisioned using the tfstate import and because it only prints the VMs that are not from an allowed publisher. It achieves the latter by using double negation (two nots) and "any" instead of "all". (For those familiar with logic, we are using one of De Morgan's laws: `not(P or Q) <-> (not P) and (not Q)`.)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import "tfstate"
2+
3+
# Get VMs that already exist in the state of this workspace
4+
get_vms = func() {
5+
vms = []
6+
for tfstate.module_paths as path {
7+
vms += values(tfstate.module(path).resources.azurerm_virtual_machine) else []
8+
}
9+
return vms
10+
}
11+
12+
# List of allowed publishers
13+
allowed_publishers = [
14+
"RedHat",
15+
"Canonical",
16+
]
17+
18+
vms = get_vms()
19+
20+
# This rule uses a double negative expression (two nots) so that
21+
# only VMs that are NOT in the list of approved publishers
22+
# will be printed.
23+
# If we had used all instead of any and left out the nots,
24+
# only the valid VMs would have been printed.
25+
# Or, if we had done that and put the print() statement before
26+
# testing the VM's publisher, all VMs would have been printed.
27+
vm_publisher_allowed = rule {
28+
not (
29+
any vms as _, instances {
30+
any instances as index, r {
31+
(r.attr.storage_image_reference[0].publisher not in allowed_publishers) and print("Existing VM publisher ", r.attr.storage_image_reference[0].publisher, "for VM ", r.attr.name, "is invalid")
32+
}
33+
})
34+
}
35+
36+
main = rule {
37+
(vm_publisher_allowed) else true
38+
}

governance/external/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This repository contains an example of using a data source that calls a function together with a Sentinel policy that checks the result returned by the function call. While the example here just trivially runs a shell script that returns different values based on the account number passed to it by the Terraform code, a customer could actually call an external API to capture real data and then have Sentinel check the result.
2+
3+
There is a commented out line, `#(length(check_account_balance) > 0) and`, which if uncommented would require the check_balance data source to be present in every single workspace in the current organization. Enable this with caution since any workspace that did not have this data source would cause hard-mandatory failure of this policy.

governance/external/check_account.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/bin/bash
2+
3+
#set -e
4+
5+
CODE=$1
6+
7+
case $CODE in
8+
1)
9+
BALANCE=100
10+
;;
11+
2)
12+
BALANCE=0
13+
;;
14+
*)
15+
BALANCE=0
16+
;;
17+
esac
18+
19+
echo "{ \"balance\": \"$BALANCE\" }"

governance/external/check_account.tf

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
terraform {
2+
required_version = ">= 0.11.7"
3+
}
4+
5+
variable "account_code" {
6+
description = "code of cloud account: can be 1 or 2"
7+
}
8+
9+
10+
# Add fake resource to make sure that TFE runs this each time
11+
resource "null_resource" "fake" {
12+
triggers {
13+
uuid = "${uuid()}"
14+
}
15+
}
16+
17+
data "external" "check_balance" {
18+
program = ["./check_account.sh", "${var.account_code}"]
19+
}
20+
21+
output "balance" {
22+
value = "${data.external.check_balance.result["balance"]}"
23+
}
24+
25+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import "tfplan"
2+
3+
# Get instances of from root module
4+
check_account_balance = tfplan.state.data.external.check_balance
5+
6+
# Rule to validate that account has balance > 50
7+
balance_test = rule {
8+
# If you wanted every single workspace to include the check_balance
9+
# data source, you could uncomment the following line. Use caution!
10+
#(length(check_account_balance) > 0) and
11+
all check_account_balance as _, r {
12+
print( r.attr.result.balance ) and (int(r.attr.result.balance) else 0) > 50
13+
}
14+
}
15+
16+
# Main rule that requires other rules to be true
17+
main = rule {
18+
(balance_test) else true
19+
}

0 commit comments

Comments
 (0)