Skip to content

Commit f13bb7e

Browse files
authored
Merge pull request #1 from hashicorp/master
updated
2 parents ba1c196 + b09698f commit f13bb7e

File tree

64 files changed

+12768
-38
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+12768
-38
lines changed

governance/third-generation/README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,23 @@ Additionally, it contains [Policy Set](https://www.terraform.io/docs/cloud/senti
55

66
These policies and the Terraform Sentinel v2 imports they use can only be used with Terraform 0.12 and above.
77

8-
These policies use the new Terraform Sentinel v2 imports. They also use a new feature called [Sentinel Modules](https://docs.hashicorp.com/sentinel/extending/modules) which allows Sentinel functions and rules to be defined in one file and used by Sentinel policies in other files.
8+
These policies use the Terraform Sentinel v2 imports. They also use [Sentinel Modules](https://docs.hashicorp.com/sentinel/extending/modules) which allow Sentinel functions and rules to be defined in one file and used by Sentinel policies in other files.
99

10-
To learn more about the new Terraform Sentinel v2 imports, see this [blog post](https://www.hashicorp.com/blog/terraform-sentinel-v2-imports-now-in-technology-preview).
10+
To learn more about the Terraform Sentinel v2 imports, see this [blog post](https://www.hashicorp.com/blog/terraform-sentinel-v2-imports-now-in-technology-preview).
1111

1212
To learn more about Sentinel Modules, see this [blog post](https://discuss.hashicorp.com/t/sentinel-v0-15-0-introducing-modules/6579).
1313

1414
## Using These Policies with Terraform Cloud and Terraform Enterprise
1515
These policies and the common functions they use can be used as organized with the current version of Terraform Cloud (TFC) and with Terraform Enterprise (TFE) v202011-1 and higher. That version was released on November 10, 2020. It added the Sentinel 0.16.0 runtime which introduced the option of using HCL instead of JSON configuration files.
1616

17-
All the JSON Sentinel configuration files were replaced with new HCL equivalent files on January 20, 2021. If you running a version of Terraform Enterprise (TFE) betweeen v202006-1 and v202010-2 and would like to use these policies, you should use the most recent version of this repository before January 20, 2021 which included the JSON configuration files.
17+
All the JSON Sentinel configuration files were replaced with HCL equivalent files on January 20, 2021. If you running a version of Terraform Enterprise (TFE) betweeen v202006-1 and v202010-2 and would like to use these policies, you should use the most recent version of this repository before January 20, 2021 which included the JSON configuration files.
1818

1919
## Important Characterizations of the New Policies
20-
These new third-generation policies have several important characteristics:
21-
1. As mentioned above, they use the new Terraform Sentinel v2 imports, which are more closely aligned with Terraform 0.12's data model and leverage the recently added [filter expression](https://docs.hashicorp.com/sentinel/language/collection-operations/#filter-expression), and make it easier to restrict policies to specific operations performed by Terraform against resources.
22-
1. The new policies use new, parameterized functions defined in four [Sentinel modules](https://docs.hashicorp.com/sentinel/extending/modules). Since they are defined in modules, their implementations do **not** need to be pasted into the policies. This is a **HUGE** improvement over the second-generation common functions!
20+
These third-generation policies have several important characteristics:
21+
1. As mentioned above, they use the Terraform Sentinel v2 imports, which are more closely aligned with Terraform 0.12's data model and leverage the recently added [filter expression](https://docs.hashicorp.com/sentinel/language/collection-operations/#filter-expression), and make it easier to restrict policies to specific operations performed by Terraform against resources.
22+
1. The policies use parameterized functions defined in four [Sentinel modules](https://docs.hashicorp.com/sentinel/extending/modules). Since they are defined in modules, their implementations do **not** need to be pasted into the policies. This is a **HUGE** improvement over the second-generation common functions!
2323
1. A related benefit of using functions from modules is that the policies themselves do not have any `for` loops or `if/else` conditionals. This makes it easier for users to understand the sample policies and to write their own policies that copy them.
24-
1. The new policies have been written in a way that causes all violations to be reported. This means a user who violates a policy will be informed about all of their violations in a single shot without having to run multiple Sentinel CLI tests or TFC/TFE plans.
24+
1. The policies have been written in a way that causes all violations to be reported. This means a user who violates a policy will be informed about all of their violations in a single shot without having to run multiple Sentinel CLI tests or TFC/TFE plans.
2525
1. The policies print out the full address of each resource instance that does violate a rule in the same format that is used in plan and apply logs, namely `module.<A>.module.<B>.<type>.<name>[<index>]`. (Note that `index` will only be present if multiple instances of a resource are defined either with the `count` or the `for_each` meta-arguments.) This allows writers of Terraform code to quickly determine the resources they need to fix even if the violations occur in modules that they did not write.
2626
1. They are written in a way that makes Sentinel's default output much less verbose. Users looking at Sentinel policy violations that occur during their runs will get all the information they need from the messages explicitly printed from the policies using Sentinel's `print` function. (Sentinel's default output that reports `TRUE` or `FALSE` for various rules and boolean expressions used by them along with Sentinel policy line numbers is really only useful to the policy's author.)
2727
1. The common function `evaluate_attribute`, which is in the tfplan-functions.sentinel and tfstate-functions.sentinel modules, can evaluate the values of any attribute of any resource even if it is deeply nested inside the resource. It does this by calling itself recursively.
@@ -88,10 +88,11 @@ The `tfconfig-functions` module has several types of functions:
8888
* `find_*_by_provider` functions that find resources or data sources created by a specific provider.
8989
* The `find_outputs_by_sensitivity` function that finds outputs based on their `sensitive` setting.
9090
* The `find_descendant_modules` function that finds all module addresses called directly or indirectly by a specific module including that module itself. Calling `find_descendant_modules("")` will return all module addresses within the Terraform configuration.
91-
* Various filter functions such as `filter_attribute_not_in_list` and `filter_attribute_in_list` that are similar to the filter functions in the tfplan-functions module. However, these can only be used against top-level attributes of the items in the collection passed to them. Those collections can be any type of entity covered by the tfconfig/v2 import including resources, data sources, providers, provisioners, variables, outputs, and module calls. The filter functions return a map consisting of 2 items:
91+
* Various filter functions such as `filter_attribute_not_in_list` and `filter_attribute_in_list` that are similar to the filter functions in the tfplan-functions module. However, these can only be used against top-level attributes of the items in the collection passed to them or against items directly under the `config` map of items. Those collections can be any type of entity covered by the tfconfig/v2 import including resources, data sources, providers, provisioners, variables, outputs, and module calls. The filter functions return a map consisting of 2 items:
9292
* `items`: a map consisting of items that violate a condition.
9393
* `messages`: a map of violation messages associated with the items.
9494
* The same `to_string` and `print_violations` functions that are in the tfplan-functions module.
95+
* A `get_module_source` function that computes the source of a module from its address. This is used in the [restrict-resources-by-module-source.sentinel](./cloud-agnostic/restrict-resources-by-module-source.sentinel) policy to restrict creation of resources based on the actual module sources.
9596

9697
Documentation for each individual function can be found in this directory:
9798
* [tfconfig-functions](./common-functions/tfconfig-functions/docs)

governance/third-generation/aws/aws-functions/aws-functions.sentinel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ validate_provider_in_allowed_regions = func(p, regions) {
279279
module_segments = strings.split(p.module_address, ".")
280280
num_segments = length(module_segments)
281281
parent_module = strings.join(module_segments[0:num_segments-2], ".")
282-
current_module_name = module_segments[num_segments -1]
282+
current_module_name = module_segments[num_segments-1]
283283

284284
# Find module call that called current module
285285
if parent_module is "" {
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# This policy uses the Sentinel tfplan/v2 import to validate that no security group
2+
# rules have the have SSH open to CIDR "0.0.0.0/0" for ingress rules. It covers both the
3+
# aws_security_group and the aws_security_group_rule resources which can both
4+
# define rules.
5+
6+
# Same targeting as terraform-foundational-policies-library/cis/
7+
# aws/networking/aws-cis-4.2-networking-deny-public-rdp-acl-rules
8+
# but provides greater detail of violations
9+
10+
# Import the tfplan/v2 import, but use the alias "tfplan"
11+
import "tfplan/v2" as tfplan
12+
13+
# Import common-functions/tfplan-functions/tfplan-functions.sentinel
14+
# with alias "plan"
15+
import "tfplan-functions" as plan
16+
17+
# Import for looping through the Security Group Rules
18+
import "types"
19+
20+
# Variables and print() can be edited to tailor this policy as needed
21+
forbidden_cidrs = ["0.0.0.0/0"]
22+
forbidden_port = 3389
23+
forbidden_to_port = 3389
24+
forbidden_from_port = 3389
25+
26+
# Get all Security Group Ingress Rules
27+
aws_security_group_rules = filter tfplan.resource_changes as address, rc {
28+
rc.type is "aws_security_group_rule" and
29+
rc.mode is "managed" and rc.change.after.type is "ingress" and
30+
(rc.change.actions contains "create" or rc.change.actions contains "update" or
31+
rc.change.actions contains "read" or rc.change.actions contains "no-op")
32+
}
33+
34+
# Validate Security Group Rules
35+
violatingSGRulesCount = 0
36+
for aws_security_group_rules as address, sgr {
37+
38+
# Validate that each SG rule does not have disallowed value
39+
# Since cidr_blocks is optional and could be computed,
40+
# We check that it is present and really a list
41+
# before checking whether it contains "0.0.0.0/0"
42+
if sgr.change.after.cidr_blocks else null is not null and
43+
types.type_of(sgr.change.after.cidr_blocks) is "list" and
44+
sgr.change.after.cidr_blocks contains "0.0.0.0/0" and
45+
sgr.change.after.from_port else null is not null and
46+
sgr.change.after.from_port <= forbidden_from_port and
47+
sgr.change.after.to_port else null is not null and
48+
sgr.change.after.to_port >= forbidden_to_port{
49+
violatingSGRulesCount += 1
50+
print("SG Rule Ingress Violation:", address, "has port", forbidden_port,
51+
"(RDP) open to", forbidden_cidrs, "that is not allowed")
52+
print(" Ingress Rule", "has from_port with value", sgr.change.after.from_port,
53+
"that is less than or equal to", forbidden_from_port)
54+
print(" and Ingress Rule has to_port with value", sgr.change.after.to_port,
55+
"that is greater than or equal to", forbidden_to_port)
56+
} else if sgr.change.after.cidr_blocks else null is not null and
57+
types.type_of(sgr.change.after.cidr_blocks) is "list" and
58+
sgr.change.after.cidr_blocks contains "0.0.0.0/0" and
59+
sgr.change.after.to_port else null is not null and
60+
sgr.change.after.to_port is forbidden_port{
61+
violatingSGRulesCount += 1
62+
print("SG Rule Ingress Violation:", address, "has port", forbidden_port,
63+
"(RDP) open to", forbidden_cidrs, "that is not allowed")
64+
print(" Ingress Rule", "has to_port with value", sgr.change.after.to_port)
65+
}
66+
} // end of SG Rules
67+
68+
# Get all Security Groups
69+
allSGs = plan.find_resources("aws_security_group")
70+
71+
# Validate Security Groups
72+
violatingSGsCount = 0
73+
for allSGs as address, sg {
74+
75+
# Find the ingress rules of the current SG
76+
ingressRules = plan.find_blocks(sg, "ingress")
77+
78+
# Filter to violating CIDR blocks
79+
# Warnings will not be printed for violations since the last parameter is false
80+
violatingCidr = plan.filter_attribute_contains_items_from_list(ingressRules,
81+
"cidr_blocks", forbidden_cidrs, false)
82+
83+
# Filter to violating Service port
84+
# Warnings will not be printed for violations since the last parameter is false
85+
violatingToPort = plan.filter_attribute_is_value(ingressRules,
86+
"to_port", forbidden_to_port, false)
87+
88+
# Filter to violating Service port
89+
# Warnings will not be printed for violations since the last parameter is false
90+
violatingFromPortLess = plan.filter_attribute_less_than_equal_to_value(ingressRules,
91+
"from_port", forbidden_from_port, false)
92+
93+
# Filter to violating Service port
94+
# Warnings will not be printed for violations since the last parameter is false
95+
violatingToPortGreater = plan.filter_attribute_greater_than_equal_to_value(ingressRules,
96+
"to_port", forbidden_to_port, false)
97+
98+
# Print violation messages
99+
if length(violatingCidr["messages"]) > 0 and length(violatingFromPortLess["messages"]) > 0 and
100+
length(violatingToPortGreater["messages"]) > 0{
101+
violatingSGsCount += 1
102+
print("SG Ingress Violation:", address, "has port", forbidden_port,
103+
"(RDP) open to", forbidden_cidrs, "that is not allowed")
104+
###Uncomment below if you want to show the CIDRs as a separate message as well
105+
# plan.print_violations(violatingCidr["messages"], " Ingress Rule")
106+
plan.print_violations(violatingFromPortLess["messages"], " Ingress Rule")
107+
plan.print_violations(violatingToPortGreater["messages"], " and Ingress Rule")
108+
} else if length(violatingCidr["messages"]) > 0 and length(violatingToPort["messages"]) > 0{
109+
violatingSGsCount += 1
110+
print("SG Ingress Violation:", address, "has port", forbidden_port,
111+
"(RDP) open to", forbidden_cidrs, "that is not allowed")
112+
###Uncomment below if you want to show the CIDRs as a separate message as well
113+
# plan.print_violations(violatingCidr["messages"], " Ingress Rule")
114+
plan.print_violations(violatingToPort["messages"], " Ingress Rule")
115+
}
116+
} // end for SGs
117+
118+
validated = violatingSGsCount is 0 and violatingSGRulesCount is 0
119+
main = rule {
120+
validated is true
121+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# This policy uses the Sentinel tfplan/v2 import to validate that no security group
2+
# rules have the have SSH open to CIDR "0.0.0.0/0" for ingress rules. It covers both the
3+
# aws_security_group and the aws_security_group_rule resources which can both
4+
# define rules.
5+
6+
# Same targeting as terraform-foundational-policies-library/cis/
7+
# aws/networking/aws-cis-4.1-networking-deny-public-ssh-acl-rules
8+
# but provides greater detail of violations
9+
10+
# Import the tfplan/v2 import, but use the alias "tfplan"
11+
import "tfplan/v2" as tfplan
12+
13+
# Import common-functions/tfplan-functions/tfplan-functions.sentinel
14+
# with alias "plan"
15+
import "tfplan-functions" as plan
16+
17+
# Import for looping through the Security Group Rules
18+
import "types"
19+
20+
# Variables and print() can be edited to tailor this policy as needed
21+
forbidden_cidrs = ["0.0.0.0/0"]
22+
forbidden_port = 22
23+
forbidden_to_port = 22
24+
forbidden_from_port = 22
25+
26+
# Get all Security Group Ingress Rules
27+
aws_security_group_rules = filter tfplan.resource_changes as address, rc {
28+
rc.type is "aws_security_group_rule" and
29+
rc.mode is "managed" and rc.change.after.type is "ingress" and
30+
(rc.change.actions contains "create" or rc.change.actions contains "update" or
31+
rc.change.actions contains "read" or rc.change.actions contains "no-op")
32+
}
33+
34+
# Validate Security Group Rules
35+
violatingSGRulesCount = 0
36+
for aws_security_group_rules as address, sgr {
37+
38+
# Validate that each SG rule does not have disallowed value
39+
# Since cidr_blocks is optional and could be computed,
40+
# We check that it is present and really a list
41+
# before checking whether it contains "0.0.0.0/0"
42+
if sgr.change.after.cidr_blocks else null is not null and
43+
types.type_of(sgr.change.after.cidr_blocks) is "list" and
44+
sgr.change.after.cidr_blocks contains "0.0.0.0/0" and
45+
sgr.change.after.from_port else null is not null and
46+
sgr.change.after.from_port <= forbidden_from_port and
47+
sgr.change.after.to_port else null is not null and
48+
sgr.change.after.to_port >= forbidden_to_port{
49+
violatingSGRulesCount += 1
50+
print("SG Rule Ingress Violation:", address, "has port", forbidden_port,
51+
"(SSH) open to", forbidden_cidrs, "that is not allowed")
52+
print(" Ingress Rule", "has from_port with value", sgr.change.after.from_port,
53+
"that is less than or equal to", forbidden_from_port)
54+
print(" and Ingress Rule has to_port with value", sgr.change.after.to_port,
55+
"that is greater than or equal to", forbidden_to_port)
56+
} else if sgr.change.after.cidr_blocks else null is not null and
57+
types.type_of(sgr.change.after.cidr_blocks) is "list" and
58+
sgr.change.after.cidr_blocks contains "0.0.0.0/0" and
59+
sgr.change.after.to_port else null is not null and
60+
sgr.change.after.to_port is forbidden_port{
61+
violatingSGRulesCount += 1
62+
print("SG Rule Ingress Violation:", address, "has port", forbidden_port,
63+
"(SSH) open to", forbidden_cidrs, "that is not allowed")
64+
print(" Ingress Rule", "has to_port with value", sgr.change.after.to_port)
65+
}
66+
} // end of SG Rules
67+
68+
# Get all Security Groups
69+
allSGs = plan.find_resources("aws_security_group")
70+
71+
# Validate Security Groups
72+
violatingSGsCount = 0
73+
for allSGs as address, sg {
74+
75+
# Find the ingress rules of the current SG
76+
ingressRules = plan.find_blocks(sg, "ingress")
77+
78+
# Filter to violating CIDR blocks
79+
# Warnings will not be printed for violations since the last parameter is false
80+
violatingCidr = plan.filter_attribute_contains_items_from_list(ingressRules,
81+
"cidr_blocks", forbidden_cidrs, false)
82+
83+
# Filter to violating Service port
84+
# Warnings will not be printed for violations since the last parameter is false
85+
violatingToPort = plan.filter_attribute_is_value(ingressRules,
86+
"to_port", forbidden_to_port, false)
87+
88+
# Filter to violating Service port
89+
# Warnings will not be printed for violations since the last parameter is false
90+
violatingFromPortLess = plan.filter_attribute_less_than_equal_to_value(ingressRules,
91+
"from_port", forbidden_from_port, false)
92+
93+
# Filter to violating Service port
94+
# Warnings will not be printed for violations since the last parameter is false
95+
violatingToPortGreater = plan.filter_attribute_greater_than_equal_to_value(ingressRules,
96+
"to_port", forbidden_to_port, false)
97+
98+
# Print violation messages
99+
if length(violatingCidr["messages"]) > 0 and length(violatingFromPortLess["messages"]) > 0 and
100+
length(violatingToPortGreater["messages"]) > 0{
101+
violatingSGsCount += 1
102+
print("SG Ingress Violation:", address, "has port", forbidden_port,
103+
"(SSH) open to", forbidden_cidrs, "that is not allowed")
104+
###Uncomment below if you want to show the CIDRs as a separate message as well
105+
# plan.print_violations(violatingCidr["messages"], " Ingress Rule")
106+
plan.print_violations(violatingFromPortLess["messages"], " Ingress Rule")
107+
plan.print_violations(violatingToPortGreater["messages"], " and Ingress Rule")
108+
} else if length(violatingCidr["messages"]) > 0 and length(violatingToPort["messages"]) > 0{
109+
violatingSGsCount += 1
110+
print("SG Ingress Violation:", address, "has port", forbidden_port,
111+
"(SSH) open to", forbidden_cidrs, "that is not allowed")
112+
###Uncomment below if you want to show the CIDRs as a separate message as well
113+
# plan.print_violations(violatingCidr["messages"], " Ingress Rule")
114+
plan.print_violations(violatingToPort["messages"], " Ingress Rule")
115+
}
116+
} // end for SGs
117+
118+
validated = violatingSGsCount is 0 and violatingSGRulesCount is 0
119+
main = rule {
120+
validated is true
121+
}

0 commit comments

Comments
 (0)