Skip to content
This repository has been archived by the owner on May 9, 2021. It is now read-only.

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
robertpeteuil committed Jun 5, 2020
0 parents commit fd4fdce
Show file tree
Hide file tree
Showing 6 changed files with 529 additions and 0 deletions.
18 changes: 18 additions & 0 deletions README.md
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).
120 changes: 120 additions & 0 deletions global/enforce-mandatory-tags.sentinel
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
}
104 changes: 104 additions & 0 deletions global/restrict-availability-zones.sentinel
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
}
102 changes: 102 additions & 0 deletions global/restrict-ec2-instance-type.sentinel
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
}
Loading

0 comments on commit fd4fdce

Please sign in to comment.