Skip to content

Commit ebe8c8a

Browse files
committed
enhance provider policies
1 parent 01c5057 commit ebe8c8a

18 files changed

+938
-54
lines changed

governance/third-generation/cloud-agnostic/allowed-providers.sentinel

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
# This policy uses the tfconfig/v2 import to restrict providers to those
22
# in an allowed list.
33

4+
# It used to only use the providers collection of the tfconfig/v2 import, but
5+
# that did not process resources and data sources from allowed providers
6+
# when no provider block was included in the Terraform configuration. So, it now
7+
# also explicitly allows resources and data sources from allowed providers using
8+
# the resources collection of the tfconfig/v2 import.
9+
410
# Import common-functions/tfconfig-functions/tfconfig-functions.sentinel
511
# with alias "config"
612
import "tfconfig-functions" as config
713

14+
# Standard strings import
15+
import "strings"
16+
817
# List of allowed providers
918
allowed_list = ["aws", "local", "null", "random", "terraform", "tfe"]
1019

@@ -17,9 +26,55 @@ violatingProviders = config.filter_attribute_not_in_list(allProviders,
1726
"name", allowed_list, false)
1827

1928
# Print any violations
20-
config.print_violations(violatingProviders["messages"], "Provider")
29+
prohibitedProvidersCount = length(violatingProviders["messages"])
30+
if prohibitedProvidersCount > 0 {
31+
config.print_violations(violatingProviders["messages"], "Provider")
32+
}
33+
34+
# Initialize resource and data source counts
35+
prohibitedResourcesCount = 0
36+
prohibitedDataSourcesCount = 0
37+
38+
# Find all resources
39+
allResources = config.find_all_resources()
40+
41+
# Filter to disallowed resources
42+
prohibitedResources = filter allResources as address, r {
43+
strings.split(r.type, "_")[0] not in allowed_list
44+
}
45+
46+
# Print violations and increment counts for resources
47+
if length(prohibitedResources) > 0 {
48+
print("Resources from providers are not allowed unless they are in", allowed_list)
49+
prohibitedResourcesCount += length(prohibitedResources)
50+
for prohibitedResources as address, r {
51+
print("Resource", address, "from provider", strings.split(r.type, "_")[0],
52+
"is not allowed.")
53+
} // end for prohibitedResources
54+
} // end if
55+
56+
57+
# Find all data sources
58+
allDataSources = config.find_all_datasources()
59+
60+
# Filter to disallowed data sources
61+
prohibitedDataSources = filter allDataSources as address, r {
62+
strings.split(r.type, "_")[0] not in allowed_list
63+
}
64+
65+
# Print violations and increment counts for data sources
66+
if length(prohibitedDataSources) > 0 {
67+
print("Data sources from providers are not allowed unless they are in", allowed_list)
68+
prohibitedDataSourcesCount += length(prohibitedDataSources)
69+
for prohibitedDataSources as address, r {
70+
print("Data source", address, "from provider", strings.split(r.type, "_")[0],
71+
"is not allowed.")
72+
} // end for prohibitedDataSources
73+
} // end if
2174

2275
# Main rule
76+
violations = prohibitedProvidersCount + prohibitedResourcesCount +
77+
prohibitedDataSourcesCount
2378
main = rule {
24-
length(violatingProviders["messages"]) is 0
79+
violations is 0
2580
}
Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
# This policy uses the tfconfig/v2 import to prohibit providers
22
# from a prohibited list
33

4+
# It used to only use the providers collection of the tfconfig/v2 import, but
5+
# that did not block use of resources and data sources from prohibited providers
6+
# when no provider block was included in the Terraform configuration. So, it now
7+
# also blocks resources and data sources from prohibited providers using the
8+
# resources collection of the tfconfig/v2 import.
9+
410
# Import common-functions/tfconfig-functions/tfconfig-functions.sentinel
511
# with alias "config"
612
import "tfconfig-functions" as config
713

814
# List of prohibited providers
9-
prohibited_list = ["external", "http"]
15+
prohibited_list = ["external", "http", "null"]
1016

11-
# Get all providers
17+
# Get all providers from providers collection
1218
allProviders = config.find_all_providers()
1319

1420
# Filter to providers with violations
@@ -17,9 +23,44 @@ violatingProviders = config.filter_attribute_in_list(allProviders,
1723
"name", prohibited_list, false)
1824

1925
# Print any violations
20-
config.print_violations(violatingProviders["messages"], "Provider")
26+
prohibitedProvidersCount = length(violatingProviders["messages"])
27+
if prohibitedProvidersCount > 0 {
28+
config.print_violations(violatingProviders["messages"], "Provider")
29+
}
30+
31+
# Initialize resource and data source counts
32+
prohibitedResourcesCount = 0
33+
prohibitedDataSourcesCount = 0
34+
35+
# Iterate over prohibited providers to find resources and data sources
36+
for prohibited_list as p {
37+
38+
# Process resources for current provider
39+
prohibitedResources = config.find_resources_by_provider(p)
40+
if length(prohibitedResources) > 0 {
41+
print("Resources from provider", p, "are not allowed.")
42+
prohibitedResourcesCount += length(prohibitedResources)
43+
for prohibitedResources as address, r {
44+
print("Resource", address, "from provider", p, "is not allowed.")
45+
} // end for prohibitedResources
46+
} // end if
47+
48+
# Process data sources for current provider
49+
50+
prohibitedDataSources = config.find_datasources_by_provider(p)
51+
if length(prohibitedDataSources) > 0 {
52+
print("Data sources from provider", p, "are not allowed.")
53+
prohibitedDataSourcesCount += length(prohibitedDataSources)
54+
for prohibitedDataSources as address, r {
55+
print("Data source", address, "from provider", p, "is not allowed.")
56+
} // end for prohibitedDataSources
57+
} // end if
58+
59+
} // end for providers
2160

2261
# Main rule
62+
violations = prohibitedProvidersCount + prohibitedResourcesCount +
63+
prohibitedDataSourcesCount
2364
main = rule {
24-
length(violatingProviders["messages"]) is 0
65+
violations is 0
2566
}

governance/third-generation/cloud-agnostic/test/allowed-providers/fail.hcl renamed to governance/third-generation/cloud-agnostic/test/allowed-providers/fail-with-provider-blocks.hcl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module "tfconfig-functions" {
44

55
mock "tfconfig/v2" {
66
module {
7-
source = "mock-tfconfig-fail.sentinel"
7+
source = "mock-tfconfig-fail-with-provider-blocks.sentinel"
88
}
99
}
1010

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module "tfconfig-functions" {
2+
source = "../../../common-functions/tfconfig-functions/tfconfig-functions.sentinel"
3+
}
4+
5+
mock "tfconfig/v2" {
6+
module {
7+
source = "mock-tfconfig-fail-without-provider-blocks.sentinel"
8+
}
9+
}
10+
11+
test {
12+
rules = {
13+
main = false
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import "strings"
2+
3+
providers = {}
4+
5+
resources = {
6+
"google_compute_instance.demo": {
7+
"address": "google_compute_instance.demo",
8+
"config": {
9+
"boot_disk": {
10+
"constant_value": null,
11+
},
12+
"machine_type": {
13+
"references": [
14+
"var.machine_type",
15+
],
16+
},
17+
"name": {
18+
"references": [
19+
"var.instance_name",
20+
],
21+
},
22+
"network_interface": {
23+
"constant_value": null,
24+
},
25+
"zone": {
26+
"references": [
27+
"var.gcp_zone",
28+
],
29+
},
30+
},
31+
"count": {},
32+
"depends_on": [],
33+
"for_each": {},
34+
"mode": "managed",
35+
"module_address": "",
36+
"name": "demo",
37+
"provider_config_key": "google",
38+
"provisioners": [],
39+
"type": "google_compute_instance",
40+
},
41+
"data.external.example": {
42+
"address": "data.external.example",
43+
"config": {
44+
"program": {
45+
"references": [
46+
"random_id.random",
47+
],
48+
},
49+
},
50+
"count": {},
51+
"depends_on": [],
52+
"for_each": {},
53+
"mode": "data",
54+
"module_address": "",
55+
"name": "example",
56+
"provider_config_key": "external",
57+
"provisioners": [],
58+
"type": "external",
59+
},
60+
}
61+
62+
provisioners = {}
63+
64+
variables = {
65+
"aws_region": {
66+
"default": "us-east-1",
67+
"description": "AWS region",
68+
"module_address": "",
69+
"name": "aws_region",
70+
},
71+
}
72+
73+
outputs = {}
74+
75+
module_calls = {}
76+
77+
strip_index = func(addr) {
78+
s = strings.split(addr, ".")
79+
for s as i, v {
80+
s[i] = strings.split(v, "[")[0]
81+
}
82+
83+
return strings.join(s, ".")
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import "strings"
2+
3+
providers = {}
4+
5+
resources = {
6+
"aws_instance.web": {
7+
"address": "aws_instance.web",
8+
"config": {
9+
"ami": {
10+
"references": [
11+
"data.aws_ami.ubuntu",
12+
],
13+
},
14+
"instance_type": {
15+
"constant_value": "t2.medium",
16+
},
17+
"tags": {
18+
"constant_value": {
19+
"Name": "HelloWorld",
20+
},
21+
},
22+
},
23+
"count": {},
24+
"depends_on": [],
25+
"for_each": {},
26+
"mode": "managed",
27+
"module_address": "",
28+
"name": "web",
29+
"provider_config_key": "aws",
30+
"provisioners": [],
31+
"type": "aws_instance",
32+
},
33+
"data.aws_ami.ubuntu": {
34+
"address": "data.aws_ami.ubuntu",
35+
"config": {
36+
"filter": [
37+
{
38+
"name": {
39+
"constant_value": "name",
40+
},
41+
"values": {
42+
"constant_value": [
43+
"ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*",
44+
],
45+
},
46+
},
47+
{
48+
"name": {
49+
"constant_value": "virtualization-type",
50+
},
51+
"values": {
52+
"constant_value": [
53+
"hvm",
54+
],
55+
},
56+
},
57+
],
58+
"most_recent": {
59+
"constant_value": true,
60+
},
61+
"owners": {
62+
"references": [
63+
"var.owners",
64+
],
65+
},
66+
},
67+
"count": {},
68+
"depends_on": [],
69+
"for_each": {},
70+
"mode": "data",
71+
"module_address": "",
72+
"name": "ubuntu",
73+
"provider_config_key": "aws",
74+
"provisioners": [],
75+
"type": "aws_ami",
76+
},
77+
}
78+
79+
provisioners = {}
80+
81+
variables = {
82+
"aws_region": {
83+
"default": "us-east-1",
84+
"description": "AWS region",
85+
"module_address": "",
86+
"name": "aws_region",
87+
},
88+
}
89+
90+
outputs = {}
91+
92+
module_calls = {}
93+
94+
strip_index = func(addr) {
95+
s = strings.split(addr, ".")
96+
for s as i, v {
97+
s[i] = strings.split(v, "[")[0]
98+
}
99+
100+
return strings.join(s, ".")
101+
}

governance/third-generation/cloud-agnostic/test/allowed-providers/pass.hcl renamed to governance/third-generation/cloud-agnostic/test/allowed-providers/pass-with-provider-blocks.hcl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module "tfconfig-functions" {
44

55
mock "tfconfig/v2" {
66
module {
7-
source = "mock-tfconfig-pass.sentinel"
7+
source = "mock-tfconfig-pass-with-provider-blocks.sentinel"
88
}
99
}
1010

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module "tfconfig-functions" {
2+
source = "../../../common-functions/tfconfig-functions/tfconfig-functions.sentinel"
3+
}
4+
5+
mock "tfconfig/v2" {
6+
module {
7+
source = "mock-tfconfig-pass-without-provider-blocks.sentinel"
8+
}
9+
}
10+
11+
test {
12+
rules = {
13+
main = true
14+
}
15+
}

governance/third-generation/cloud-agnostic/test/prohibited-providers/fail.hcl renamed to governance/third-generation/cloud-agnostic/test/prohibited-providers/fail-with-provider-blocks.hcl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module "tfconfig-functions" {
44

55
mock "tfconfig/v2" {
66
module {
7-
source = "mock-tfconfig-fail.sentinel"
7+
source = "mock-tfconfig-fail-with-provider-blocks.sentinel"
88
}
99
}
1010

0 commit comments

Comments
 (0)