From c22bf010033930636519bafea30978cf9a9ab36a Mon Sep 17 00:00:00 2001 From: Kevin Pullin Date: Fri, 5 May 2023 05:56:27 -0700 Subject: [PATCH] feat(allowlist): Support regexes in Tags to allow "or"-like conditional matching (#2300) Co-authored-by: Kevin Pullin Co-authored-by: Sergio Garcia --- docs/tutorials/allowlist.md | 13 +-- prowler/config/allowlist.yaml | 13 +-- .../providers/aws/lib/allowlist/allowlist.py | 22 +++-- .../aws/lib/allowlist/allowlist_test.py | 83 +++++++++++++------ 4 files changed, 85 insertions(+), 46 deletions(-) diff --git a/docs/tutorials/allowlist.md b/docs/tutorials/allowlist.md index d864bb43..db0d3062 100644 --- a/docs/tutorials/allowlist.md +++ b/docs/tutorials/allowlist.md @@ -7,9 +7,10 @@ You can use `-w`/`--allowlist-file` with the path of your allowlist yaml file, b ## Allowlist Yaml File Syntax - ### Account, Check and/or Region can be * to apply for all the cases - ### Resources is a list that can have either Regex or Keywords - ### Tags is an optional list containing tuples of 'key=value' + ### Account, Check and/or Region can be * to apply for all the cases. + ### Resources and tags are lists that can have either Regex or Keywords. + ### Tags is an optional list that matches on tuples of 'key=value' and are "ANDed" together. + ### Use an alternation Regex to match one of multiple tags with "ORed" logic. ########################### ALLOWLIST EXAMPLE ########################### Allowlist: Accounts: @@ -25,10 +26,10 @@ You can use `-w`/`--allowlist-file` with the path of your allowlist yaml file, b Regions: - "*" Resources: - - "test" # Will ignore every resource containing the string "test" and the tags 'test=test' and 'project=test' in account 123456789012 and every region + - "test" Tags: - - "test=test" # Will ignore every resource containing the string "test" and the tags 'test=test' and 'project=test' in account 123456789012 and every region - - "project=test" + - "test=test" # Will ignore every resource containing the string "test" and the tags 'test=test' and + - "project=test|project=stage" # either of ('project=test' OR project=stage) in account 123456789012 and every region "*": Checks: diff --git a/prowler/config/allowlist.yaml b/prowler/config/allowlist.yaml index 6c8f9512..d936759d 100644 --- a/prowler/config/allowlist.yaml +++ b/prowler/config/allowlist.yaml @@ -1,6 +1,7 @@ -### Account, Check and/or Region can be * to apply for all the cases -### Resources is a list that can have either Regex or Keywords -### Tags is an optional list containing tuples of 'key=value' +### Account, Check and/or Region can be * to apply for all the cases. +### Resources and tags are lists that can have either Regex or Keywords. +### Tags is an optional list that matches on tuples of 'key=value' and are "ANDed" together. +### Use an alternation Regex to match one of multiple tags with "ORed" logic. ########################### ALLOWLIST EXAMPLE ########################### Allowlist: Accounts: @@ -16,10 +17,10 @@ Allowlist: Regions: - "*" Resources: - - "test" # Will ignore every resource containing the string "test" and the tags 'test=test' and 'project=test' in account 123456789012 and every region + - "test" Tags: - - "test=test" # Will ignore every resource containing the string "test" and the tags 'test=test' and 'project=test' in account 123456789012 and every region - - "project=test" + - "test=test" # Will ignore every resource containing the string "test" and the tags 'test=test' and + - "project=test|project=stage" # either of ('project=test' OR project=stage) in account 123456789012 and every region "*": Checks: diff --git a/prowler/providers/aws/lib/allowlist/allowlist.py b/prowler/providers/aws/lib/allowlist/allowlist.py index 5967485c..c8eefe8a 100644 --- a/prowler/providers/aws/lib/allowlist/allowlist.py +++ b/prowler/providers/aws/lib/allowlist/allowlist.py @@ -192,13 +192,21 @@ def is_allowlisted_in_tags(check_allowlist, elem, resource, tags): # Check if there are allowlisted tags if "Tags" in check_allowlist: # Check if there are resource tags - if tags: - tags_in_resource_tags = True - for tag in check_allowlist["Tags"]: - if tag not in tags: - tags_in_resource_tags = False - if tags_in_resource_tags and re.search(elem, resource): - return True + if not tags or not re.search(elem, resource): + return False + + all_allowed_tags_in_resource_tags = True + for allowed_tag in check_allowlist["Tags"]: + found_allowed_tag = False + for resource_tag in tags: + if re.search(allowed_tag, resource_tag): + found_allowed_tag = True + break + + if not found_allowed_tag: + all_allowed_tags_in_resource_tags = False + + return all_allowed_tags_in_resource_tags else: if re.search(elem, resource): return True diff --git a/tests/providers/aws/lib/allowlist/allowlist_test.py b/tests/providers/aws/lib/allowlist/allowlist_test.py index 161f75ea..b9f0e230 100644 --- a/tests/providers/aws/lib/allowlist/allowlist_test.py +++ b/tests/providers/aws/lib/allowlist/allowlist_test.py @@ -165,15 +165,15 @@ class Test_Allowlist: } assert is_allowlisted( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", [] ) assert is_allowlisted( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler-test", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler-test", [] ) assert is_allowlisted( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "test-prowler", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "test-prowler", [] ) assert is_allowlisted( @@ -187,7 +187,7 @@ class Test_Allowlist: assert not ( is_allowlisted( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", "us-east-2", "test", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", "us-east-2", "test", [] ) ) @@ -207,20 +207,20 @@ class Test_Allowlist: } assert is_allowlisted( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", [] ) assert is_allowlisted( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler-test", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler-test", [] ) assert is_allowlisted( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "test-prowler", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "test-prowler", [] ) assert not ( is_allowlisted( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", "us-east-2", "test", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", "us-east-2", "test", [] ) ) @@ -240,20 +240,20 @@ class Test_Allowlist: } assert is_allowlisted( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", [] ) assert is_allowlisted( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler-test", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler-test", [] ) assert is_allowlisted( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "test-prowler", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "test-prowler", [] ) assert not ( is_allowlisted( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", "us-east-2", "test", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", "us-east-2", "test", [] ) ) @@ -273,20 +273,20 @@ class Test_Allowlist: } assert is_allowlisted_in_region( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", [] ) assert is_allowlisted_in_region( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler-test", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler-test", [] ) assert is_allowlisted_in_region( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "test-prowler", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "test-prowler", [] ) assert not ( is_allowlisted_in_region( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", "us-east-2", "test", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", "us-east-2", "test", [] ) ) @@ -306,20 +306,20 @@ class Test_Allowlist: } assert is_allowlisted_in_check( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", [] ) assert is_allowlisted_in_check( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler-test", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler-test", [] ) assert is_allowlisted_in_check( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "test-prowler", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "test-prowler", [] ) assert not ( is_allowlisted_in_check( - allowlist, AWS_ACCOUNT_NUMBER, "check_test", "us-east-2", "test", "" + allowlist, AWS_ACCOUNT_NUMBER, "check_test", "us-east-2", "test", [] ) ) @@ -332,7 +332,7 @@ class Test_Allowlist: "check_test": { "Regions": ["us-east-1", "eu-west-1"], "Resources": ["*"], - "Tags": ["environment=dev", "project=prowler"], + "Tags": ["environment=dev", "project=.*"], } } } @@ -345,7 +345,7 @@ class Test_Allowlist: "check_test", AWS_REGION, "prowler", - "environment=dev", + ["environment=dev"], ) assert is_allowlisted( @@ -354,7 +354,7 @@ class Test_Allowlist: "check_test", AWS_REGION, "prowler-test", - "environment=dev project=prowler", + ["environment=dev", "project=prowler"], ) assert not ( @@ -364,7 +364,7 @@ class Test_Allowlist: "check_test", "us-east-2", "test", - "environment=pro", + ["environment=pro"], ) ) @@ -380,14 +380,14 @@ class Test_Allowlist: check_allowlist, check_allowlist["Resources"][0], "prowler", - "environment=dev", + ["environment=dev"], ) assert is_allowlisted_in_tags( check_allowlist, check_allowlist["Resources"][0], "prowler-test", - "environment=dev project=prowler", + ["environment=dev", "project=prowler"], ) assert not ( @@ -395,6 +395,35 @@ class Test_Allowlist: check_allowlist, check_allowlist["Resources"][0], "test", - "environment=pro", + ["environment=pro"], ) ) + + def test_is_allowlisted_in_tags_regex(self): + # Allowlist example + check_allowlist = { + "Regions": ["us-east-1", "eu-west-1"], + "Resources": ["*"], + "Tags": ["environment=(dev|test)", ".*=prowler"], + } + + assert is_allowlisted_in_tags( + check_allowlist, + check_allowlist["Resources"][0], + "prowler-test", + ["environment=test", "proj=prowler"], + ) + + assert not is_allowlisted_in_tags( + check_allowlist, + check_allowlist["Resources"][0], + "prowler-test", + ["env=prod", "project=prowler"], + ) + + assert not is_allowlisted_in_tags( + check_allowlist, + check_allowlist["Resources"][0], + "prowler-test", + ["environment=prod", "project=myproj"], + )