This repository has been archived by the owner on May 9, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit fd4fdce
Showing
6 changed files
with
529 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Demonstration Terraform Enterprise Sentinel Policies | ||
|
||
Sample Sentinel Policies for Terraform Enterprise | ||
|
||
--- | ||
|
||
These policies are defined as [Policy Sets](https://www.terraform.io/docs/enterprise/sentinel/manage-policies.html) using Terraform Enterprise VCS integration. | ||
|
||
## Introduction to HashiCorp Sentinel | ||
|
||
- Introduction to Policy as Code and [Sentinel](https://docs.hashicorp.com/sentinel/concepts/policy-as-code) | ||
- Writing [Sentinel Policies](https://docs.hashicorp.com/sentinel/writing/) | ||
- Using the [Sentinel CLI](https://docs.hashicorp.com/sentinel/commands/) | ||
|
||
## Using Sentinel with Terraform Enterprise | ||
|
||
- Using [Sentinel Policies with Terraform Enterprise](https://www.terraform.io/docs/enterprise/sentinel/index.html). | ||
- Sample Terraform Sentinel policies and documentation located in [governance directory in terraform-guides](https://github.com/hashicorp/terraform-guides/tree/master/governance/second-generation). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
# This policy uses the Sentinel tfplan import to require that all EC2 instances | ||
# have all mandatory tags. | ||
|
||
# Note that the comparison is case-sensitive since AWS tags are case-sensitive. | ||
|
||
##### Imports ##### | ||
|
||
import "tfplan" | ||
import "strings" | ||
import "types" | ||
|
||
##### Functions ##### | ||
|
||
# Find all resources of a specific type from all modules using the tfplan import | ||
find_resources_from_plan = func(type) { | ||
|
||
resources = {} | ||
|
||
# Iterate over all modules in the tfplan import | ||
for tfplan.module_paths as path { | ||
# Iterate over the named resources of desired type in the module | ||
for tfplan.module(path).resources[type] else {} as name, instances { | ||
# Iterate over resource instances | ||
for instances as index, r { | ||
|
||
# Get the address of the instance | ||
if length(path) == 0 { | ||
# root module | ||
address = type + "." + name + "[" + string(index) + "]" | ||
} else { | ||
# non-root module | ||
address = "module." + strings.join(path, ".module.") + "." + | ||
type + "." + name + "[" + string(index) + "]" | ||
} | ||
|
||
# Add the instance to resources map, setting the key to the address | ||
resources[address] = r | ||
} | ||
} | ||
} | ||
|
||
return resources | ||
} | ||
|
||
# Validate that all instances of specified type have a specified top-level | ||
# attribute that contains all members of a given list | ||
validate_attribute_contains_list = func(type, attribute, required_values) { | ||
|
||
validated = true | ||
|
||
# Get all resource instances of the specified type | ||
resource_instances = find_resources_from_plan(type) | ||
|
||
# Loop through the resource instances | ||
for resource_instances as address, r { | ||
|
||
# Skip resource instances that are being destroyed | ||
# to avoid unnecessary policy violations. | ||
# Used to be: if length(r.diff) == 0 | ||
if r.destroy and not r.requires_new { | ||
print("Skipping resource", address, "that is being destroyed.") | ||
continue | ||
} | ||
|
||
# Determine if the attribute is computed | ||
# We check "attribute.%" and "attribute.#" because an | ||
# attribute of type map or list won't show up in the diff | ||
if (r.diff[attribute + ".%"].computed else false) or | ||
(r.diff[attribute + ".#"].computed else false) { | ||
print("Resource", address, "has attribute", attribute, | ||
"that is computed.") | ||
# If you want computed values to cause the policy to fail, | ||
# uncomment the next line. | ||
# validated = false | ||
} else { | ||
# Validate that the attribute is a list or a map | ||
# but first check if r.applied[attribute] exists | ||
if r.applied[attribute] else null is not null and | ||
(types.type_of(r.applied[attribute]) is "list" or | ||
types.type_of(r.applied[attribute]) is "map") { | ||
|
||
# Evaluate each member of required_values list | ||
for required_values as rv { | ||
if r.applied[attribute] not contains rv { | ||
print("Resource", address, "has attribute", attribute, | ||
"that is missing required value", rv, "from the list:", | ||
required_values) | ||
validated = false | ||
} // end rv | ||
} // end required_values | ||
|
||
} else { | ||
print("Resource", address, "is missing attribute", attribute, | ||
"or it is not a list or a map") | ||
validated = false | ||
} // end check that attribute is list or map | ||
|
||
} // end computed check | ||
} // end resource instances | ||
|
||
return validated | ||
} | ||
|
||
### List of mandatory tags ### | ||
mandatory_tags = [ | ||
"Name", | ||
"ttl", | ||
"owner", | ||
] | ||
|
||
### Rules ### | ||
|
||
# Call the validation function | ||
tags_validated = validate_attribute_contains_list("aws_instance", | ||
"tags", mandatory_tags) | ||
|
||
#Main rule | ||
main = rule { | ||
tags_validated | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# This policy uses the Sentinel tfplan import to restrict | ||
# the availability zones used by EC2 instances. | ||
# This can be used to restrict the region by only listing zones in one of them. | ||
|
||
##### Imports ##### | ||
|
||
import "tfplan" | ||
import "strings" | ||
|
||
##### Functions ##### | ||
|
||
# Find all resources of a specific type from all modules using the tfplan import | ||
find_resources_from_plan = func(type) { | ||
|
||
resources = {} | ||
|
||
# Iterate over all modules in the tfplan import | ||
for tfplan.module_paths as path { | ||
# Iterate over the named resources of desired type in the module | ||
for tfplan.module(path).resources[type] else {} as name, instances { | ||
# Iterate over resource instances | ||
for instances as index, r { | ||
|
||
# Get the address of the instance | ||
if length(path) == 0 { | ||
# root module | ||
address = type + "." + name + "[" + string(index) + "]" | ||
} else { | ||
# non-root module | ||
address = "module." + strings.join(path, ".module.") + "." + | ||
type + "." + name + "[" + string(index) + "]" | ||
} | ||
|
||
# Add the instance to resources map, setting the key to the address | ||
resources[address] = r | ||
} | ||
} | ||
} | ||
|
||
return resources | ||
} | ||
|
||
# Validate that all instances of a specified resource type being modified have | ||
# a specified top-level attribute in a given list | ||
validate_attribute_in_list = func(type, attribute, allowed_values) { | ||
|
||
validated = true | ||
|
||
# Get all resource instances of the specified type | ||
resource_instances = find_resources_from_plan(type) | ||
|
||
# Loop through the resource instances | ||
for resource_instances as address, r { | ||
|
||
# Skip resource instances that are being destroyed | ||
# to avoid unnecessary policy violations. | ||
# Used to be: if length(r.diff) == 0 | ||
if r.destroy and not r.requires_new { | ||
print("Skipping resource", address, "that is being destroyed.") | ||
continue | ||
} | ||
|
||
# Determine if the attribute is computed | ||
if r.diff[attribute].computed else false is true { | ||
print("Resource", address, "has attribute", attribute, | ||
"that is computed.") | ||
# If you want computed values to cause the policy to fail, | ||
# uncomment the next line. | ||
# validated = false | ||
} else { | ||
# Validate that each instance has allowed value | ||
if r.applied[attribute] else "" not in allowed_values { | ||
print("Resource", address, "has attribute", attribute, "with value", | ||
r.applied[attribute] else "", | ||
"that is not in the allowed list:", allowed_values) | ||
validated = false | ||
} | ||
} | ||
|
||
} | ||
return validated | ||
} | ||
|
||
##### Lists ##### | ||
|
||
# Allowed EC2 Zones | ||
allowed_zones = [ | ||
"us-east-1a", | ||
"us-east-1b", | ||
"us-east-1c", | ||
"us-east-1d", | ||
"us-east-1e", | ||
"us-east-1f", | ||
] | ||
|
||
##### Rules ##### | ||
|
||
# Call the validation function | ||
zones_validated = validate_attribute_in_list("aws_instance", "availability_zone", allowed_zones) | ||
|
||
# Main rule | ||
main = rule { | ||
zones_validated | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# This policy uses the Sentinel tfplan import to require that | ||
# all EC2 instances have instance types from an allowed list | ||
|
||
##### Imports ##### | ||
|
||
import "tfplan" | ||
import "strings" | ||
|
||
##### Functions ##### | ||
|
||
# Find all resources of a specific type from all modules using the tfplan import | ||
find_resources_from_plan = func(type) { | ||
|
||
resources = {} | ||
|
||
# Iterate over all modules in the tfplan import | ||
for tfplan.module_paths as path { | ||
# Iterate over the named resources of desired type in the module | ||
for tfplan.module(path).resources[type] else {} as name, instances { | ||
# Iterate over resource instances | ||
for instances as index, r { | ||
|
||
# Get the address of the instance | ||
if length(path) == 0 { | ||
# root module | ||
address = type + "." + name + "[" + string(index) + "]" | ||
} else { | ||
# non-root module | ||
address = "module." + strings.join(path, ".module.") + "." + | ||
type + "." + name + "[" + string(index) + "]" | ||
} | ||
|
||
# Add the instance to resources map, setting the key to the address | ||
resources[address] = r | ||
} | ||
} | ||
} | ||
|
||
return resources | ||
} | ||
|
||
# Validate that all instances of a specified resource type being modified have | ||
# a specified top-level attribute in a given list | ||
validate_attribute_in_list = func(type, attribute, allowed_values) { | ||
|
||
validated = true | ||
|
||
# Get all resource instances of the specified type | ||
resource_instances = find_resources_from_plan(type) | ||
|
||
# Loop through the resource instances | ||
for resource_instances as address, r { | ||
|
||
# Skip resource instances that are being destroyed | ||
# to avoid unnecessary policy violations. | ||
# Used to be: if length(r.diff) == 0 | ||
if r.destroy and not r.requires_new { | ||
print("Skipping resource", address, "that is being destroyed.") | ||
continue | ||
} | ||
|
||
# Determine if the attribute is computed | ||
if r.diff[attribute].computed else false is true { | ||
print("Resource", address, "has attribute", attribute, | ||
"that is computed.") | ||
# If you want computed values to cause the policy to fail, | ||
# uncomment the next line. | ||
# validated = false | ||
} else { | ||
# Validate that each instance has allowed value | ||
if r.applied[attribute] else "" not in allowed_values { | ||
print("Resource", address, "has attribute", attribute, "with value", | ||
r.applied[attribute] else "", | ||
"that is not in the allowed list:", allowed_values) | ||
validated = false | ||
} | ||
} | ||
|
||
} | ||
return validated | ||
} | ||
|
||
##### Lists ##### | ||
|
||
# Allowed EC2 Instance Types | ||
# We don't include t2.nano or t2.micro to illustrate overriding failed policy | ||
allowed_types = [ | ||
"t2.small", | ||
"t2.medium", | ||
"t2.large", | ||
] | ||
|
||
##### Rules ##### | ||
|
||
# Call the validation function | ||
instances_validated = validate_attribute_in_list("aws_instance", | ||
"instance_type", allowed_types) | ||
|
||
# Main rule | ||
main = rule { | ||
instances_validated | ||
} |
Oops, something went wrong.