Skip to content

Commit 9c72881

Browse files
authored
Merge pull request hashicorp#266 from hashicorp/fix-policy-set-example
improve policy-set example for script
2 parents 638ea88 + 613d1ab commit 9c72881

File tree

2 files changed

+173
-1
lines changed

2 files changed

+173
-1
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# This policy uses the Sentinel tfplan/v2 and tfconfig/v2 imports to require
2+
# that all S3 bucket policies are implemented with the aws_iam_policy_document
3+
# data source and that those policies require buckets to only be accessible
4+
# via HTTPS and from specific VPC endpoints.
5+
6+
# Import common-functions/tfplan-functions/tfplan-functions.sentinel
7+
# with alias "plan"
8+
import "tfplan-functions" as plan
9+
10+
# Import common-functions/tfconfig-functions/tfconfig-functions.sentinel
11+
# with alias "config"
12+
import "tfconfig-functions" as config
13+
14+
# Restricted S3 Actions
15+
restricted_s3_actions = ["s3:ListBucket", "s3:GetObject", "s3:PutObject"]
16+
17+
# Allowed VPC Endpoints
18+
allowed_vpc_endpoints = ["vpce-111111", "vpce-222222", "vpce-333333"]
19+
20+
# Get all S3 buckets in configuration
21+
allS3Buckets = config.find_resources_by_type("aws_s3_bucket")
22+
23+
# Filter to S3 buckets with the policy argument set since it is not mandatory.
24+
# Don't print violation messages
25+
S3BucketsWithPolicy = config.filter_attribute_not_in_list(
26+
allS3Buckets, "config.policy", ["null"], false)
27+
28+
# Filter to S3 buckets that set policy but do not set it to an instance of
29+
# the aws_iam_policy_document data source.
30+
# Print violation messages which will now only be for those buckets that
31+
# set policy to something other than an instance of aws_iam_policy_document
32+
S3BucketsWithInvalidPolicy = config.filter_attribute_does_not_match_regex(
33+
S3BucketsWithPolicy["items"], "config.policy",
34+
"^data\\.aws_iam_policy_document\\.(.*)$", true)
35+
if length(S3BucketsWithInvalidPolicy["messages"]) > 0 {
36+
print("All aws_s3_bucket resources must get their policy from an instance of",
37+
"the data_iam_policy_document data source.")
38+
}
39+
40+
# Get all S3 bucket policies in configuration
41+
allS3BucketPolicies = config.find_resources_by_type("aws_s3_bucket_policy")
42+
43+
# Filter to S3 bucket policies that set policy but do not set it to an instance
44+
# of the aws_iam_policy_document data source.
45+
# Print violation messages for s3 bucket policies that set policy to something
46+
# other than an instance of aws_iam_policy_document
47+
# Note that we do not need to screen out instances of aws_s3_bucket_policy that
48+
# are missing the policy argument because it is mandatory.
49+
S3BucketPoliciesWithInvalidPolicy = config.filter_attribute_does_not_match_regex(
50+
allS3BucketPolicies, "config.policy",
51+
"^data\\.aws_iam_policy_document\\.(.*)$", true)
52+
if length(S3BucketPoliciesWithInvalidPolicy["messages"]) > 0 {
53+
print("All aws_s3_bucket_policy resources must get their policy from",
54+
"an instance of the data_iam_policy_document data source.")
55+
}
56+
57+
# Find instances of aws_iam_policy_document
58+
allIAMPolicyDocuments = plan.find_datasources("aws_iam_policy_document")
59+
60+
# Validate IAM Policy Documents
61+
policyDocumentsValidated = true
62+
for allIAMPolicyDocuments as address, pd {
63+
64+
# Find the statements of the current policy document
65+
statements = plan.find_blocks(pd, "statement")
66+
67+
# Determine if any statements include restricted S3 actions
68+
statementsWithS3actions = plan.filter_attribute_contains_items_from_list(
69+
statements, "actions", restricted_s3_actions, false)
70+
if length(statementsWithS3actions["resources"]) is 0 {
71+
# No statements included restricted S3 actions, so policy document is ok.
72+
break
73+
}
74+
75+
# Test each restricted S3 action separately to make sure some statement
76+
# in the current policy document has the desired conditions.
77+
for restricted_s3_actions as s3_action {
78+
# Filter to statements of the current policy document that have an S3 action
79+
statementsWithS3action = plan.filter_attribute_contains_items_from_list(
80+
statements, "actions", [s3_action], false)
81+
82+
# Search for deny statement with aws:SecureTransport set to false
83+
foundSecureTransportCondition = false
84+
for statementsWithS3action["resources"] as _, statement {
85+
if foundSecureTransportCondition {
86+
break
87+
}
88+
if statement.effect else "" is "Deny" {
89+
for statement.condition else [] as _, condition {
90+
if condition.test is "Bool" and
91+
condition.variable is "aws:SecureTransport" and
92+
"false" in condition.values {
93+
foundSecureTransportCondition = true
94+
break
95+
} // end if secureTransport condition test
96+
} // end for condition
97+
} // end if statement.effect is Deny
98+
} // end for statementsWithS3actions
99+
100+
# If no statement had a valid condition for aws:SecureTransport,
101+
# Then current policy document is invalid.
102+
if not foundSecureTransportCondition {
103+
print("IAM policy document", address, "has statements that apply to S3",
104+
"buckets and/or objects, but does not have a Deny statement that",
105+
"requires aws:SecureTransport to be false for action", s3_action)
106+
policyDocumentsValidated = false
107+
}
108+
109+
# Search for deny statement with valid aws:SourceVpce
110+
foundVPCECondition = false
111+
foundInvalidVPCE = false
112+
for statementsWithS3action["resources"] as _, statement {
113+
if foundVPCECondition {
114+
break
115+
}
116+
if statement.effect else "" is "Deny" {
117+
for statement.condition else [] as _, condition {
118+
if condition.test is "StringNotEquals" and
119+
condition.variable is "aws:SourceVpce" {
120+
foundInvalidVPCE = false
121+
for condition.values as value {
122+
if value not in allowed_vpc_endpoints {
123+
foundInvalidVPCE = true
124+
break
125+
} // end value check
126+
} // end for condition.values
127+
if not foundInvalidVPCE {
128+
foundVPCECondition = true
129+
break
130+
} // end not foundInvalidVPCE
131+
} // end if SourceVpce condition test
132+
} // end for condition
133+
} // end if statement.effect is Deny
134+
} // end for statementsWithS3actions
135+
136+
# If no statement had a valid condition for aws:SourceVpce,
137+
# Then current policy document is invalid.
138+
if foundVPCECondition is false and foundInvalidVPCE is false {
139+
print("IAM policy document", address, "has statements that apply to S3",
140+
"buckets and/or objects, but does not have a Deny statement that",
141+
"restricts aws:SourceVpce to VPC endpoints in", allowed_vpc_endpoints,
142+
"for action", s3_action)
143+
policyDocumentsValidated = false
144+
}
145+
if foundVPCECondition is false and foundInvalidVPCE is true {
146+
print("IAM policy document", address, "has statements that apply to S3",
147+
"buckets and/or objects and does have a Deny statement that",
148+
"restricts aws:SourceVpce to VPC endpoints, but it includes at",
149+
"least one VPC endpoint that is not in", allowed_vpc_endpoints,
150+
"for action", s3_action)
151+
policyDocumentsValidated = false
152+
}
153+
154+
} // end for restricted_s3_actions
155+
} // end for allIAMPolicyDocuments
156+
157+
# Main rule
158+
validated = length(S3BucketsWithInvalidPolicy["messages"]) is 0 and
159+
length(S3BucketPoliciesWithInvalidPolicy["messages"]) is 0 and
160+
policyDocumentsValidated
161+
main = rule {
162+
validated is true
163+
}

operations/sentinel-policies-scripts/policy-set/sentinel.hcl

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@ module "tfplan-functions" {
22
source = "https://raw.githubusercontent.com/hashicorp/terraform-guides/master/governance/third-generation/common-functions/tfplan-functions/tfplan-functions.sentinel"
33
}
44

5-
policy "enforce-mandatory-tags" {
5+
module "tfconfig-functions" {
6+
source = "https://raw.githubusercontent.com/hashicorp/terraform-guides/fix-policy-set-example/governance/third-generation/common-functions/tfconfig-functions/tfconfig-functions.sentinel"
7+
}
8+
9+
policy "restrict-s3-bucket-policies" {
10+
source = "./restrict-s3-bucket-policies.sentinel"
11+
enforcement_level = "advisory"
12+
}
13+
14+
policy "restrict-sagemaker-notebooks" {
615
source = "./restrict-sagemaker-notebooks.sentinel"
716
enforcement_level = "soft-mandatory"
817
}

0 commit comments

Comments
 (0)