Skip to content

Commit 716bf2b

Browse files
Matt HolmesEdmund Dipple
authored andcommitted
Adding ability to filter resources by property value (elmundio87#23)
* Adding ability to filter resources by property value, get a properties value for additional validation and validate json
1 parent 6ab106d commit 716bf2b

File tree

4 files changed

+132
-2
lines changed

4 files changed

+132
-2
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
variable "bucket_name" {
2+
default = "defaultvalue"
3+
}
4+
5+
resource "aws_s3_bucket" "invalidjson" {
6+
policy = <<POLICY
7+
{
8+
"Version": "2012-10-17",
9+
"Statement": [
10+
{
11+
"Sid": "enforceTLS",
12+
"Effect": "Deny",
13+
"Principal": "*",
14+
"Action": "s3:*",
15+
"Resource": "arn:aws:s3:::examplebucketname/*",
16+
"Condition": {
17+
"Bool": {
18+
"aws:SecureTransport": "false"
19+
}
20+
}
21+
POLICY
22+
}
23+
24+
resource "aws_s3_bucket" "validjsonwithvariable" {
25+
policy = <<POLICY
26+
{
27+
"Version": "2012-10-17",
28+
"Statement": [
29+
{
30+
"Sid": "enforceTLS",
31+
"Effect": "Deny",
32+
"Principal": "*",
33+
"Action": "s3:*",
34+
"Resource": "arn:aws:s3:::${var.bucket_name}/*",
35+
"Condition": {
36+
"Bool": {
37+
"aws:SecureTransport": "false"
38+
}
39+
}
40+
}]
41+
}
42+
POLICY
43+
}
44+
45+
resource "aws_s2_bucket" "blankpolicy" {
46+
policy = ""
47+
}
48+
49+
resource "aws_s2_bucket" "nopolicy" {}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
resource "aws_s3_bucket" "private_bucket" {
2+
acl = "private"
3+
4+
policy = <<POLICY
5+
{INVALID JSON {}
6+
POLICY
7+
}
8+
9+
resource "aws_s3_bucket" "public_bucket" {
10+
acl = "public"
11+
}
12+
13+
resource "aws_s3_bucket" "tagged_bucket" {
14+
tags {
15+
Tag1 = "Tag1"
16+
CustomTag = "CustomValue"
17+
Tag2 = "Tag2"
18+
}
19+
20+
policy = <<POLICY
21+
{INVALID JSON {}
22+
POLICY
23+
}

terraform_validate/functional_test.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ def test_nested_resource_property_value_matches_regex(self):
164164
with self.assertRaisesRegexp(AssertionError, expected_error):
165165
validator.resources('aws_instance').property('nested_resource').property('value').should_match_regex('[a-z]')
166166

167+
def test_resource_property_invalid_json(self):
168+
validator = t.Validator(os.path.join(self.path, "fixtures/invalid_json"))
169+
expected_error = self.error_list_format("[aws_s3_bucket.invalidjson.policy] is not valid json")
170+
with self.assertRaisesRegexp(AssertionError, expected_error):
171+
validator.resources('aws_s3_bucket').property('policy').should_contain_valid_json()
172+
167173
def test_variable_substitution(self):
168174
validator = t.Validator(os.path.join(self.path, "fixtures/variable_substitution"))
169175
validator.enable_variable_expansion()
@@ -174,7 +180,6 @@ def test_variable_substitution(self):
174180
validator.disable_variable_expansion()
175181
validator.resources('aws_instance').property('value').should_equal('${var.test_variable}')
176182

177-
178183
def test_missing_variable_substitution(self):
179184
validator = t.Validator(os.path.join(self.path, "fixtures/missing_variable"))
180185
validator.enable_variable_expansion()
@@ -380,3 +385,23 @@ def test_encryption_scenario(self):
380385
with self.assertRaisesRegexp(AssertionError,expected_error):
381386
validator.resources("aws_ebs_volume_invalid2").should_have_properties("encrypted")
382387
validator.resources("aws_ebs_volume_invalid2").property("encrypted")
388+
389+
def test_with_property(self):
390+
validator = t.Validator(os.path.join(self.path, "fixtures/with_property"))
391+
392+
expected_error = self.error_list_format("[aws_s3_bucket.private_bucket.policy] is not valid json")
393+
394+
private_buckets = validator.resources("aws_s3_bucket").with_property("acl", "private")
395+
396+
with self.assertRaisesRegexp(AssertionError, expected_error):
397+
private_buckets.property("policy").should_contain_valid_json()
398+
399+
def test_with_nested_property(self):
400+
validator = t.Validator(os.path.join(self.path, "fixtures/with_property"))
401+
402+
expected_error = self.error_list_format("[aws_s3_bucket.tagged_bucket.policy] is not valid json")
403+
404+
tagged_buckets = validator.resources("aws_s3_bucket").with_property("tags", ".*'CustomTag':.*'CustomValue'.*")
405+
406+
with self.assertRaisesRegexp(AssertionError, expected_error):
407+
tagged_buckets.property("policy").should_contain_valid_json()

terraform_validate/terraform_validate.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
import re
44
import warnings
5+
import json
56

67
# def deprecated(func):
78
# '''This is a decorator which can be used to mark functions
@@ -69,6 +70,9 @@ def __init__(self, validator):
6970
self.properties = []
7071
self.validator = validator
7172

73+
def tfproperties(self):
74+
return self.properties
75+
7276
def property(self, property_name):
7377
errors = []
7478
list = TerraformPropertyList(self.validator)
@@ -234,6 +238,18 @@ def should_match_regex(self,regex):
234238
if len(errors) > 0:
235239
raise AssertionError("\n".join(sorted(errors)))
236240

241+
def should_contain_valid_json(self):
242+
errors = []
243+
for property in self.properties:
244+
actual_property_value = self.validator.substitute_variable_values_in_string(property.property_value)
245+
try:
246+
json_object = json.loads(actual_property_value)
247+
except:
248+
errors.append("[{0}.{1}.{2}] is not valid json".format(property.resource_type, property.resource_name, property.property_name))
249+
250+
if len(errors) > 0:
251+
raise AssertionError("\n".join(sorted(errors)))
252+
237253
def bool2str(self,bool):
238254
if str(bool).lower() in ["true"]:
239255
return "True"
@@ -254,6 +270,8 @@ def __init__(self,resource_type,resource_name,property_name,property_value):
254270
self.property_name = property_name
255271
self.property_value = property_value
256272

273+
def get_property_value(self, validator):
274+
return validator.substitute_variable_values_in_string(self.property_value)
257275

258276
class TerraformResource:
259277

@@ -266,7 +284,7 @@ class TerraformResourceList:
266284

267285
def __init__(self, validator, resource_types, resources):
268286
self.resource_list = []
269-
287+
270288
if type(resource_types) is not list:
271289
all_resource_types = list(resources.keys())
272290
regex = resource_types
@@ -280,6 +298,7 @@ def __init__(self, validator, resource_types, resources):
280298
for resource in resources[resource_type]:
281299
self.resource_list.append(TerraformResource(resource_type,resource,resources[resource_type][resource]))
282300

301+
self.resource_types = resource_types
283302
self.validator = validator
284303

285304
def property(self, property_name):
@@ -309,6 +328,20 @@ def find_property(self, regex):
309328
resource.config[property]))
310329
return list
311330

331+
def with_property(self, property_name, regex):
332+
list = TerraformResourceList(self.validator, self.resource_types, {})
333+
334+
if len(self.resource_list) > 0:
335+
for resource in self.resource_list:
336+
for property in resource.config:
337+
if(property == property_name):
338+
tf_property = TerraformProperty(resource.type,resource.name,property_name,resource.config[property_name])
339+
actual_property_value = self.validator.substitute_variable_values_in_string(tf_property.property_value)
340+
if self.validator.matches_regex_pattern(actual_property_value, regex):
341+
list.resource_list.append(resource)
342+
343+
return list
344+
312345
def should_have_properties(self, properties_list):
313346
errors = []
314347

0 commit comments

Comments
 (0)