This repository has been archived by the owner on Jun 28, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
policy.sentinel
executable file
·120 lines (96 loc) · 3.6 KB
/
policy.sentinel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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
}