From f0d82fe84e61678f19a6684923a26688fd615d37 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Fri, 11 Oct 2024 13:12:02 +0200 Subject: [PATCH 01/12] feat: add check metadata --- .../__init__.py | 0 ...acl_has_rules_or_rule_groups.metadata.json | 32 +++++++++++++++++++ .../waf_webacl_has_rules_or_rule_groups.py | 0 3 files changed, 32 insertions(+) create mode 100644 prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/__init__.py create mode 100644 prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.metadata.json create mode 100644 prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.py diff --git a/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/__init__.py b/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.metadata.json b/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.metadata.json new file mode 100644 index 0000000000..afb55decca --- /dev/null +++ b/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "waf_webacl_has_rules_or_rule_groups", + "CheckTitle": "Check if AWS WAF Classic Regional WebACL has at least one rule or rule group.", + "CheckType": [ + "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls" + ], + "ServiceName": "waf", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:wafv2:region:account-id:regional/webacl/web-acl-name/web-acl-id", + "Severity": "medium", + "ResourceType": "AwsWafRegionalWebAcl", + "Description": "Ensure that every AWS WAF Classic Regional WebACL contains at least one rule or rule group.", + "Risk": "An empty AWS WAF Classic Regional web ACL allows all web traffic to bypass inspection, potentially exposing resources to unauthorized access and attacks.", + "RelatedUrl": "https://docs.aws.amazon.com/waf/latest/developerguide/waf-rules.html", + "Remediation": { + "Code": { + "CLI": "aws waf-regional update-web-acl --web-acl-id --changes ", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/waf-controls.html#waf-4", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure that every AWS WAF Classic Regional web ACL includes at least one rule or rule group to monitor and control web traffic effectively.", + "Url": "https://docs.aws.amazon.com/waf/latest/developerguide/classic-web-acl-editing.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.py b/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.py new file mode 100644 index 0000000000..e69de29bb2 From ce3b4766e7f6f200f5eb068172f00fb3509f9ed5 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 14 Oct 2024 11:11:49 +0200 Subject: [PATCH 02/12] feat: added check logic --- .../providers/aws/services/waf/waf_service.py | 21 +++++++++++++++ ...acl_has_rules_or_rule_groups.metadata.json | 2 +- .../waf_webacl_has_rules_or_rule_groups.py | 27 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/prowler/providers/aws/services/waf/waf_service.py b/prowler/providers/aws/services/waf/waf_service.py index f43db5c4e4..f096654bc4 100644 --- a/prowler/providers/aws/services/waf/waf_service.py +++ b/prowler/providers/aws/services/waf/waf_service.py @@ -12,6 +12,7 @@ def __init__(self, provider): self.web_acls = {} self.__threading_call__(self._list_web_acls) self.__threading_call__(self._list_resources_for_web_acl) + self.__threading_call__(self._get_web_acl, self.web_acls.values()) def _list_web_acls(self, regional_client): logger.info("WAF - Listing Regional Web ACLs...") @@ -48,6 +49,24 @@ def _list_resources_for_web_acl(self, regional_client): f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def _get_web_acl(self, acl): + logger.info(f"WAF - Getting Web ACL {acl.name}...") + try: + get_web_acl = self.regional_clients[acl.region].get_web_acl(WebACLId=acl.id) + for rule in get_web_acl.get("WebACL", {}).get("Rules", []): + rule_id = rule.get("RuleGroupId", "") + if rule.get("Type", "") == "GROUP": + acl.rule_groups.append(Rule(id=rule_id)) + else: + acl.rules.append(Rule(id=rule_id)) + logger.info(f"Rule: {rule['Name']} - Priority: {rule['Priority']}") + except KeyError: + logger.error(f"Web ACL {acl.name} not found in {acl.region}.") + + +class Rule(BaseModel): + id: str + class WebAcl(BaseModel): arn: str @@ -55,3 +74,5 @@ class WebAcl(BaseModel): id: str albs: list[str] region: str + rules: list[Rule] = [] + rule_groups: list[Rule] = [] diff --git a/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.metadata.json b/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.metadata.json index afb55decca..ee3edad924 100644 --- a/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.metadata.json +++ b/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.metadata.json @@ -7,7 +7,7 @@ ], "ServiceName": "waf", "SubServiceName": "", - "ResourceIdTemplate": "arn:aws:wafv2:region:account-id:regional/webacl/web-acl-name/web-acl-id", + "ResourceIdTemplate": "arn:aws:waf-regional:region:account-id:webacl/web-acl-id", "Severity": "medium", "ResourceType": "AwsWafRegionalWebAcl", "Description": "Ensure that every AWS WAF Classic Regional WebACL contains at least one rule or rule group.", diff --git a/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.py b/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.py index e69de29bb2..0c6c279fe2 100644 --- a/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.py +++ b/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.py @@ -0,0 +1,27 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.waf.waf_client import waf_client + + +class waf_webacl_has_rules_or_rule_groups(Check): + def execute(self): + findings = [] + for acl in waf_client.web_acls.values(): + report = Check_Report_AWS(self.metadata()) + report.region = acl.region + report.resource_id = acl.id + report.resource_arn = acl.arn + # report.resource_tags = acl.tags + report.status = "FAIL" + report.status_extended = ( + f"AWS WAFv2 Web ACL {acl.id} does not have any rules or rule groups." + ) + + if acl.rules or acl.rule_groups: + report.status = "PASS" + report.status_extended = ( + f"AWS WAFv2 Web ACL {acl.id} has at least one rule or rule group." + ) + + findings.append(report) + + return findings From 3eaf02b422850dfcb33f6655ed5a53be378785ce Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 14 Oct 2024 11:12:21 +0200 Subject: [PATCH 03/12] feat: added testing --- ...af_webacl_has_rules_or_rule_groups_test.py | 287 ++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 tests/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups_test.py diff --git a/tests/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups_test.py b/tests/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups_test.py new file mode 100644 index 0000000000..efb6a4d390 --- /dev/null +++ b/tests/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups_test.py @@ -0,0 +1,287 @@ +from unittest import mock +from unittest.mock import patch + +import botocore +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +WEB_ACL_ID = "test-web-acl-id" + +# Original botocore _make_api_call function +orig = botocore.client.BaseClient._make_api_call + + +# Mocked botocore _make_api_call function +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "GetChangeToken": + return {"ChangeToken": "my-change-token"} + if operation_name == "ListWebACLs": + return { + "WebACLs": [ + {"WebACLId": WEB_ACL_ID, "Name": "my-web-acl"}, + ] + } + if operation_name == "GetWebACL": + return { + "WebACL": { + "Rules": [], + } + } + # If we don't want to patch the API call + return orig(self, operation_name, kwarg) + + +def mock_make_api_call_only_rules(self, operation_name, kwarg): + if operation_name == "GetChangeToken": + return {"ChangeToken": "my-change-token"} + if operation_name == "ListWebACLs": + return { + "WebACLs": [ + {"WebACLId": WEB_ACL_ID, "Name": "my-web-acl"}, + ] + } + if operation_name == "GetWebACL": + return { + "WebACL": { + "Rules": [ + { + "RuleId": "my-rule-id", + "Type": "BLOCK", + } + ], + } + } + # If we don't want to patch the API call + return orig(self, operation_name, kwarg) + + +def mock_make_api_call_only_rule_groups(self, operation_name, kwarg): + if operation_name == "GetChangeToken": + return {"ChangeToken": "my-change-token"} + if operation_name == "ListWebACLs": + return { + "WebACLs": [ + {"WebACLId": WEB_ACL_ID, "Name": "my-web-acl"}, + ] + } + if operation_name == "GetWebACL": + return { + "WebACL": { + "Rules": [ + { + "RuleId": "my-rulegroup-id", + "Type": "GROUP", + } + ], + } + } + # If we don't want to patch the API call + return orig(self, operation_name, kwarg) + + +def mock_make_api_call_both(self, operation_name, kwarg): + if operation_name == "GetChangeToken": + return {"ChangeToken": "my-change-token"} + if operation_name == "ListWebACLs": + return { + "WebACLs": [ + {"WebACLId": WEB_ACL_ID, "Name": "my-web-acl"}, + ] + } + if operation_name == "GetWebACL": + return { + "WebACL": { + "Rules": [ + { + "RuleId": "my-rule-id", + "Type": "BLOCK", + }, + { + "RuleId": "my-rulegroup-id", + "Type": "GROUP", + }, + ], + } + } + # If we don't want to patch the API call + return orig(self, operation_name, kwarg) + + +class Test_waf_webacl_has_rules_or_rule_groups: + @mock_aws + def test_no_waf(self): + from prowler.providers.aws.services.waf.waf_service import WAF + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.waf_client", + new=WAF(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups import ( + waf_webacl_has_rules_or_rule_groups, + ) + + check = waf_webacl_has_rules_or_rule_groups() + result = check.execute() + + assert len(result) == 0 + + @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock_aws + def test_waf_no_rules_and_no_rule_group(self): + from prowler.providers.aws.services.waf.waf_service import WAF + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.waf_client", + new=WAF(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups import ( + waf_webacl_has_rules_or_rule_groups, + ) + + check = waf_webacl_has_rules_or_rule_groups() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"AWS WAFv2 Web ACL {WEB_ACL_ID} does not have any rules or rule groups." + ) + assert result[0].resource_id == WEB_ACL_ID + assert ( + result[0].resource_arn + == f"arn:aws:waf-regional:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:webacl/{WEB_ACL_ID}" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + + @patch( + "botocore.client.BaseClient._make_api_call", new=mock_make_api_call_only_rules + ) + @mock_aws + def test_waf_rules_and_no_rule_group(self): + from prowler.providers.aws.services.waf.waf_service import WAF + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.waf_client", + new=WAF(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups import ( + waf_webacl_has_rules_or_rule_groups, + ) + + check = waf_webacl_has_rules_or_rule_groups() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"AWS WAFv2 Web ACL {WEB_ACL_ID} has at least one rule or rule group." + ) + assert result[0].resource_id == WEB_ACL_ID + assert ( + result[0].resource_arn + == f"arn:aws:waf-regional:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:webacl/{WEB_ACL_ID}" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + + @patch( + "botocore.client.BaseClient._make_api_call", + new=mock_make_api_call_only_rule_groups, + ) + @mock_aws + def test_waf_no_rules_and_rule_group(self): + from prowler.providers.aws.services.waf.waf_service import WAF + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.waf_client", + new=WAF(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups import ( + waf_webacl_has_rules_or_rule_groups, + ) + + check = waf_webacl_has_rules_or_rule_groups() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"AWS WAFv2 Web ACL {WEB_ACL_ID} has at least one rule or rule group." + ) + assert result[0].resource_id == WEB_ACL_ID + assert ( + result[0].resource_arn + == f"arn:aws:waf-regional:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:webacl/{WEB_ACL_ID}" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + + @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call_both) + @mock_aws + def test_waf_rules_and_rule_group(self): + from prowler.providers.aws.services.waf.waf_service import WAF + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.waf_client", + new=WAF(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups import ( + waf_webacl_has_rules_or_rule_groups, + ) + + check = waf_webacl_has_rules_or_rule_groups() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"AWS WAFv2 Web ACL {WEB_ACL_ID} has at least one rule or rule group." + ) + assert result[0].resource_id == WEB_ACL_ID + assert ( + result[0].resource_arn + == f"arn:aws:waf-regional:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:webacl/{WEB_ACL_ID}" + ) + assert result[0].region == AWS_REGION_US_EAST_1 From ddf73fdc806a2562d2483453ee997e10519db3b0 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 14 Oct 2024 15:14:38 +0200 Subject: [PATCH 04/12] feat: added check metadata --- .../waf/waf_rule_has_conditions/__init__.py | 0 .../waf_rule_has_conditions.py | 0 .../waf_rules_has_conditions.metadata.json | 32 +++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 prowler/providers/aws/services/waf/waf_rule_has_conditions/__init__.py create mode 100644 prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py create mode 100644 prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rules_has_conditions.metadata.json diff --git a/prowler/providers/aws/services/waf/waf_rule_has_conditions/__init__.py b/prowler/providers/aws/services/waf/waf_rule_has_conditions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py b/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rules_has_conditions.metadata.json b/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rules_has_conditions.metadata.json new file mode 100644 index 0000000000..47584ba81a --- /dev/null +++ b/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rules_has_conditions.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "waf_rule_has_conditions", + "CheckTitle": "AWS WAF Classic Regional Rules Should Have at Least One Condition.", + "CheckType": [ + "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls" + ], + "ServiceName": "waf", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:waf-regional:region:account-id:rule/rule-id", + "Severity": "medium", + "ResourceType": "AwsWafRegionalRule", + "Description": "Ensure that every AWS WAF Classic Regional Rule contains at least one condition.", + "Risk": "An AWS WAF Classic Regional rule without any conditions cannot inspect or filter traffic, potentially allowing malicious requests to pass unchecked.", + "RelatedUrl": "https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html", + "Remediation": { + "Code": { + "CLI": "aws waf-regional update-rule --rule-id --change-token --updates ", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/waf-controls.html#waf-2", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure that every AWS WAF Classic Regional rule has at least one condition to properly inspect and manage web traffic.", + "Url": "https://docs.aws.amazon.com/waf/latest/developerguide/classic-web-acl-rules-editing.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} From 7f3fd57fc684bcb100522d706fc8ecf8872a2531 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 14 Oct 2024 15:27:22 +0200 Subject: [PATCH 05/12] feat: added check logic --- .../waf_rule_has_conditions.py | 23 ++++++++++ .../providers/aws/services/waf/waf_service.py | 42 +++++++++++++++++++ .../aws/services/waf/waf_service_test.py | 4 ++ 3 files changed, 69 insertions(+) diff --git a/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py b/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py index e69de29bb2..0f538c6731 100644 --- a/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py +++ b/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py @@ -0,0 +1,23 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.waf.waf_client import waf_client + + +class waf_webacl_has_rules_or_rule_groups(Check): + def execute(self): + findings = [] + for rule in waf_client.rules.values(): + report = Check_Report_AWS(self.metadata()) + report.region = rule.region + report.resource_id = rule.id + report.resource_arn = rule.arn + # report.resource_tags = rule.tags + report.status = "FAIL" + report.status_extended = f"AWS WAF Classic Regional Rule {rule.id} does not have any rules or rule groups." + + if rule.predicates: + report.status = "PASS" + report.status_extended = f"AWS WAF Classic Regional Rule {rule.id} has at least one condition." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/waf/waf_service.py b/prowler/providers/aws/services/waf/waf_service.py index f096654bc4..74de99b801 100644 --- a/prowler/providers/aws/services/waf/waf_service.py +++ b/prowler/providers/aws/services/waf/waf_service.py @@ -10,9 +10,12 @@ def __init__(self, provider): # Call AWSService's __init__ super().__init__("waf-regional", provider) self.web_acls = {} + self.rules = {} self.__threading_call__(self._list_web_acls) self.__threading_call__(self._list_resources_for_web_acl) self.__threading_call__(self._get_web_acl, self.web_acls.values()) + self.__threading_call__(self._list_rules) + self.__threading_call__(self._get_rule, self.rules.values()) def _list_web_acls(self, regional_client): logger.info("WAF - Listing Regional Web ACLs...") @@ -63,9 +66,48 @@ def _get_web_acl(self, acl): except KeyError: logger.error(f"Web ACL {acl.name} not found in {acl.region}.") + def _list_rules(self, regional_client): + logger.info("WAF - Listing Regional Rules...") + try: + for rule in regional_client.list_rules().get("Rules", []): + arn = f"arn:aws:waf-regional:{regional_client.region}:{self.audited_account}:rule/{rule['RuleId']}" + self.rules[arn] = Rule( + arn=arn, + id=rule.get("RuleId", ""), + region=regional_client.region, + name=rule.get("Name", ""), + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _get_rule(self, rule): + logger.info(f"WAF - Getting Rule {rule.name}...") + try: + get_rule = self.regional_clients[rule.region].get_rule(RuleId=rule.id) + for predicate in get_rule.get("Rule", {}).get("Predicates", []): + rule.predicates.append( + Predicate( + negated=predicate.get("Negated", False), + data_id=predicate.get("DataId", ""), + ) + ) + except KeyError: + logger.error(f"Rule {rule.name} not found in {rule.region}.") + + +class Predicate(BaseModel): + negated: bool + data_id: str + class Rule(BaseModel): + arn: str id: str + region: str + name: str + predicates: list[str] = [] class WebAcl(BaseModel): diff --git a/tests/providers/aws/services/waf/waf_service_test.py b/tests/providers/aws/services/waf/waf_service_test.py index 3e9c2bccb6..e5ebb58049 100644 --- a/tests/providers/aws/services/waf/waf_service_test.py +++ b/tests/providers/aws/services/waf/waf_service_test.py @@ -85,3 +85,7 @@ def test_list_resources_for_web_acl(self): assert len(waf.web_acls) == 1 assert len(waf.web_acls[waf_arn].albs) == 1 assert "alb-arn" in waf.web_acls[waf_arn].albs + + # Test WAF Get Web ACL + def test_get_web_acl(self): + pass From dfec63962512893a54fc2bf7ca3059914ef68b2c Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 14 Oct 2024 16:17:00 +0200 Subject: [PATCH 06/12] feat: added testing --- ... => waf_rule_has_conditions.metadata.json} | 2 +- .../waf_rule_has_conditions.py | 6 +- .../waf_rule_has_conditions_test.py | 167 ++++++++++++++++++ 3 files changed, 172 insertions(+), 3 deletions(-) rename prowler/providers/aws/services/waf/waf_rule_has_conditions/{waf_rules_has_conditions.metadata.json => waf_rule_has_conditions.metadata.json} (92%) create mode 100644 tests/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions_test.py diff --git a/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rules_has_conditions.metadata.json b/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.metadata.json similarity index 92% rename from prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rules_has_conditions.metadata.json rename to prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.metadata.json index 47584ba81a..6c5d37ee2a 100644 --- a/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rules_has_conditions.metadata.json +++ b/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.metadata.json @@ -12,7 +12,7 @@ "ResourceType": "AwsWafRegionalRule", "Description": "Ensure that every AWS WAF Classic Regional Rule contains at least one condition.", "Risk": "An AWS WAF Classic Regional rule without any conditions cannot inspect or filter traffic, potentially allowing malicious requests to pass unchecked.", - "RelatedUrl": "https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html", + "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/waf-regional-rule-not-empty.html", "Remediation": { "Code": { "CLI": "aws waf-regional update-rule --rule-id --change-token --updates ", diff --git a/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py b/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py index 0f538c6731..ed34b2b9af 100644 --- a/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py +++ b/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py @@ -2,7 +2,7 @@ from prowler.providers.aws.services.waf.waf_client import waf_client -class waf_webacl_has_rules_or_rule_groups(Check): +class waf_rule_has_conditions(Check): def execute(self): findings = [] for rule in waf_client.rules.values(): @@ -12,7 +12,9 @@ def execute(self): report.resource_arn = rule.arn # report.resource_tags = rule.tags report.status = "FAIL" - report.status_extended = f"AWS WAF Classic Regional Rule {rule.id} does not have any rules or rule groups." + report.status_extended = ( + f"AWS WAF Classic Regional Rule {rule.id} does not have any conditions." + ) if rule.predicates: report.status = "PASS" diff --git a/tests/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions_test.py b/tests/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions_test.py new file mode 100644 index 0000000000..f010b2b1b3 --- /dev/null +++ b/tests/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions_test.py @@ -0,0 +1,167 @@ +from unittest import mock +from unittest.mock import patch + +import botocore +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +RULE_ID = "test-rule-id" + +# Original botocore _make_api_call function +orig = botocore.client.BaseClient._make_api_call + + +# Mocked botocore _make_api_call function +def mock_make_api_call_compliant_rule(self, operation_name, kwarg): + if operation_name == "ListRules": + return { + "Rules": [ + { + "RuleId": RULE_ID, + "Name": RULE_ID, + }, + ] + } + if operation_name == "GetRule": + return { + "Rule": { + "RuleId": RULE_ID, + "Predicates": [ + { + "Negated": False, + "Type": "IPMatch", + "DataId": "IPSetId", + }, + ], + } + } + return orig(self, operation_name, kwarg) + + +def mock_make_api_call_non_compliant_rule(self, operation_name, kwarg): + if operation_name == "ListRules": + return { + "Rules": [ + { + "RuleId": RULE_ID, + "Name": RULE_ID, + }, + ] + } + if operation_name == "GetRule": + return { + "Rule": { + "RuleId": RULE_ID, + "Predicates": [], + } + } + return orig(self, operation_name, kwarg) + + +class Test_waf_rule_has_conditions: + @mock_aws + def test_no_rules(self): + from prowler.providers.aws.services.waf.waf_service import WAF + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.waf.waf_rule_has_conditions.waf_rule_has_conditions.waf_client", + new=WAF(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.waf.waf_rule_has_conditions.waf_rule_has_conditions import ( + waf_rule_has_conditions, + ) + + check = waf_rule_has_conditions() + result = check.execute() + + assert len(result) == 0 + + @patch( + "botocore.client.BaseClient._make_api_call", + new=mock_make_api_call_compliant_rule, + ) + @mock_aws + def test_waf_rules_with_condition(self): + from prowler.providers.aws.services.waf.waf_service import WAF + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.waf.waf_rule_has_conditions.waf_rule_has_conditions.waf_client", + new=WAF(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.waf.waf_rule_has_conditions.waf_rule_has_conditions import ( + waf_rule_has_conditions, + ) + + check = waf_rule_has_conditions() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"AWS WAF Classic Regional Rule {RULE_ID} has at least one condition." + ) + assert result[0].resource_id == RULE_ID + assert ( + result[0].resource_arn + == f"arn:aws:waf-regional:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:rule/{RULE_ID}" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + + @patch( + "botocore.client.BaseClient._make_api_call", + new=mock_make_api_call_non_compliant_rule, + ) + @mock_aws + def test_waf_rules_without_condition(self): + from prowler.providers.aws.services.waf.waf_service import WAF + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.waf.waf_rule_has_conditions.waf_rule_has_conditions.waf_client", + new=WAF(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.waf.waf_rule_has_conditions.waf_rule_has_conditions import ( + waf_rule_has_conditions, + ) + + check = waf_rule_has_conditions() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"AWS WAF Classic Regional Rule {RULE_ID} does not have any conditions." + ) + assert result[0].resource_id == RULE_ID + assert ( + result[0].resource_arn + == f"arn:aws:waf-regional:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:rule/{RULE_ID}" + ) + assert result[0].region == AWS_REGION_US_EAST_1 From 8683cd19f9ab9d2b84366044c5d238475f2126cb Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Tue, 15 Oct 2024 08:43:07 +0200 Subject: [PATCH 07/12] feat: adapted code to new client distinction --- .../providers/aws/services/waf/waf_service.py | 21 ++++++---- .../waf_webacl_has_rules_or_rule_groups.py | 14 +++---- .../aws/services/waf/waf_service_test.py | 33 ++++++++++++++-- ...af_webacl_has_rules_or_rule_groups_test.py | 38 +++++++++---------- 4 files changed, 67 insertions(+), 39 deletions(-) diff --git a/prowler/providers/aws/services/waf/waf_service.py b/prowler/providers/aws/services/waf/waf_service.py index 64c488c221..e5e77fca84 100644 --- a/prowler/providers/aws/services/waf/waf_service.py +++ b/prowler/providers/aws/services/waf/waf_service.py @@ -1,3 +1,5 @@ +from typing import Optional + from pydantic import BaseModel from prowler.lib.logger import logger @@ -61,7 +63,7 @@ def __init__(self, provider): self.__threading_call__(self._get_web_acl, self.web_acls.values()) def _list_web_acls(self, regional_client): - logger.info("WAF - Listing Regional Web ACLs...") + logger.info("WAFRegional - Listing Regional Web ACLs...") try: for waf in regional_client.list_web_acls()["WebACLs"]: if not self.audit_resources or ( @@ -81,7 +83,7 @@ def _list_web_acls(self, regional_client): ) def _list_resources_for_web_acl(self, regional_client): - logger.info("WAF - Describing resources...") + logger.info("WAFRegional - Describing resources...") try: for acl in self.web_acls.values(): if acl.region == regional_client.region: @@ -96,21 +98,25 @@ def _list_resources_for_web_acl(self, regional_client): ) def _get_web_acl(self, acl): - logger.info(f"WAF - Getting Web ACL {acl.name}...") + logger.info(f"WAFRegional - Getting Web ACL {acl.name}...") try: get_web_acl = self.regional_clients[acl.region].get_web_acl(WebACLId=acl.id) for rule in get_web_acl.get("WebACL", {}).get("Rules", []): - rule_id = rule.get("RuleGroupId", "") + rule_id = rule.get("RuleId", "") if rule.get("Type", "") == "GROUP": acl.rule_groups.append(Rule(id=rule_id)) else: acl.rules.append(Rule(id=rule_id)) - logger.info(f"Rule: {rule['Name']} - Priority: {rule['Priority']}") - except KeyError: - logger.error(f"Web ACL {acl.name} not found in {acl.region}.") + + except Exception as error: + logger.error( + f"{acl.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) class Rule(BaseModel): + """Rule Model for WAF and WAFRegional""" + id: str @@ -124,3 +130,4 @@ class WebAcl(BaseModel): region: str rules: list[Rule] = [] rule_groups: list[Rule] = [] + tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.py b/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.py index 0c6c279fe2..b31be1113a 100644 --- a/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.py +++ b/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.py @@ -1,26 +1,22 @@ from prowler.lib.check.models import Check, Check_Report_AWS -from prowler.providers.aws.services.waf.waf_client import waf_client +from prowler.providers.aws.services.waf.wafregional_client import wafregional_client class waf_webacl_has_rules_or_rule_groups(Check): def execute(self): findings = [] - for acl in waf_client.web_acls.values(): + for acl in wafregional_client.web_acls.values(): report = Check_Report_AWS(self.metadata()) report.region = acl.region report.resource_id = acl.id report.resource_arn = acl.arn - # report.resource_tags = acl.tags + report.resource_tags = acl.tags report.status = "FAIL" - report.status_extended = ( - f"AWS WAFv2 Web ACL {acl.id} does not have any rules or rule groups." - ) + report.status_extended = f"AWS WAFRegional Web ACL {acl.id} does not have any rules or rule groups." if acl.rules or acl.rule_groups: report.status = "PASS" - report.status_extended = ( - f"AWS WAFv2 Web ACL {acl.id} has at least one rule or rule group." - ) + report.status_extended = f"AWS WAFRegional Web ACL {acl.id} has at least one rule or rule group." findings.append(report) diff --git a/tests/providers/aws/services/waf/waf_service_test.py b/tests/providers/aws/services/waf/waf_service_test.py index 410ba3673f..5270864f87 100644 --- a/tests/providers/aws/services/waf/waf_service_test.py +++ b/tests/providers/aws/services/waf/waf_service_test.py @@ -2,10 +2,10 @@ import botocore -from prowler.providers.aws.services.waf.waf_service import WAF, WAFRegional +from prowler.providers.aws.services.waf.waf_service import WAF, Rule, WAFRegional from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider -# Mocking WAF-Regional Calls +# Mocking WAF Calls make_api_call = botocore.client.BaseClient._make_api_call @@ -23,7 +23,21 @@ def mock_make_api_call(self, operation_name, kwarg): "alb-arn", ] } - + if operation_name == "GetWebACL": + return { + "WebACL": { + "Rules": [ + { + "RuleId": "my-rule-id", + "Type": "REGULAR", + }, + { + "RuleId": "my-rule-group-id", + "Type": "GROUP", + }, + ], + } + } return make_api_call(self, operation_name, kwarg) @@ -43,7 +57,6 @@ def mock_generate_regional_clients(provider, service): new=mock_generate_regional_clients, ) class Test_WAF_Service: - # Test WAF Service def test_service(self): # WAF client for this test class @@ -119,3 +132,15 @@ def test_list_web_acls_waf_regional(self): assert waf.web_acls[waf_arn].name == "my-web-acl" assert waf.web_acls[waf_arn].region == AWS_REGION_EU_WEST_1 assert waf.web_acls[waf_arn].id == "my-web-acl-id" + + # Test WAFRegional Get Web ACL + def test_get_web_acl(self): + # WAF client for this test class + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + waf = WAFRegional(aws_provider) + waf_arn = "arn:aws:waf-regional:eu-west-1:123456789012:webacl/my-web-acl-id" + assert waf.web_acls[waf_arn].name == "my-web-acl" + assert waf.web_acls[waf_arn].region == AWS_REGION_EU_WEST_1 + assert waf.web_acls[waf_arn].id == "my-web-acl-id" + assert waf.web_acls[waf_arn].rules == [Rule(id="my-rule-id")] + assert waf.web_acls[waf_arn].rule_groups == [Rule(id="my-rule-group-id")] diff --git a/tests/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups_test.py b/tests/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups_test.py index efb6a4d390..caefc8d58c 100644 --- a/tests/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups_test.py +++ b/tests/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups_test.py @@ -115,7 +115,7 @@ def mock_make_api_call_both(self, operation_name, kwarg): class Test_waf_webacl_has_rules_or_rule_groups: @mock_aws def test_no_waf(self): - from prowler.providers.aws.services.waf.waf_service import WAF + from prowler.providers.aws.services.waf.waf_service import WAFRegional aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) @@ -124,8 +124,8 @@ def test_no_waf(self): return_value=aws_provider, ): with mock.patch( - "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.waf_client", - new=WAF(aws_provider), + "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.wafregional_client", + new=WAFRegional(aws_provider), ): # Test Check from prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups import ( @@ -140,7 +140,7 @@ def test_no_waf(self): @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) @mock_aws def test_waf_no_rules_and_no_rule_group(self): - from prowler.providers.aws.services.waf.waf_service import WAF + from prowler.providers.aws.services.waf.waf_service import WAFRegional aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) @@ -149,8 +149,8 @@ def test_waf_no_rules_and_no_rule_group(self): return_value=aws_provider, ): with mock.patch( - "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.waf_client", - new=WAF(aws_provider), + "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.wafregional_client", + new=WAFRegional(aws_provider), ): # Test Check from prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups import ( @@ -164,7 +164,7 @@ def test_waf_no_rules_and_no_rule_group(self): assert result[0].status == "FAIL" assert ( result[0].status_extended - == f"AWS WAFv2 Web ACL {WEB_ACL_ID} does not have any rules or rule groups." + == f"AWS WAFRegional Web ACL {WEB_ACL_ID} does not have any rules or rule groups." ) assert result[0].resource_id == WEB_ACL_ID assert ( @@ -178,7 +178,7 @@ def test_waf_no_rules_and_no_rule_group(self): ) @mock_aws def test_waf_rules_and_no_rule_group(self): - from prowler.providers.aws.services.waf.waf_service import WAF + from prowler.providers.aws.services.waf.waf_service import WAFRegional aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) @@ -187,8 +187,8 @@ def test_waf_rules_and_no_rule_group(self): return_value=aws_provider, ): with mock.patch( - "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.waf_client", - new=WAF(aws_provider), + "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.wafregional_client", + new=WAFRegional(aws_provider), ): # Test Check from prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups import ( @@ -202,7 +202,7 @@ def test_waf_rules_and_no_rule_group(self): assert result[0].status == "PASS" assert ( result[0].status_extended - == f"AWS WAFv2 Web ACL {WEB_ACL_ID} has at least one rule or rule group." + == f"AWS WAFRegional Web ACL {WEB_ACL_ID} has at least one rule or rule group." ) assert result[0].resource_id == WEB_ACL_ID assert ( @@ -217,7 +217,7 @@ def test_waf_rules_and_no_rule_group(self): ) @mock_aws def test_waf_no_rules_and_rule_group(self): - from prowler.providers.aws.services.waf.waf_service import WAF + from prowler.providers.aws.services.waf.waf_service import WAFRegional aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) @@ -226,8 +226,8 @@ def test_waf_no_rules_and_rule_group(self): return_value=aws_provider, ): with mock.patch( - "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.waf_client", - new=WAF(aws_provider), + "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.wafregional_client", + new=WAFRegional(aws_provider), ): # Test Check from prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups import ( @@ -241,7 +241,7 @@ def test_waf_no_rules_and_rule_group(self): assert result[0].status == "PASS" assert ( result[0].status_extended - == f"AWS WAFv2 Web ACL {WEB_ACL_ID} has at least one rule or rule group." + == f"AWS WAFRegional Web ACL {WEB_ACL_ID} has at least one rule or rule group." ) assert result[0].resource_id == WEB_ACL_ID assert ( @@ -253,7 +253,7 @@ def test_waf_no_rules_and_rule_group(self): @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call_both) @mock_aws def test_waf_rules_and_rule_group(self): - from prowler.providers.aws.services.waf.waf_service import WAF + from prowler.providers.aws.services.waf.waf_service import WAFRegional aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) @@ -262,8 +262,8 @@ def test_waf_rules_and_rule_group(self): return_value=aws_provider, ): with mock.patch( - "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.waf_client", - new=WAF(aws_provider), + "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.wafregional_client", + new=WAFRegional(aws_provider), ): # Test Check from prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups import ( @@ -277,7 +277,7 @@ def test_waf_rules_and_rule_group(self): assert result[0].status == "PASS" assert ( result[0].status_extended - == f"AWS WAFv2 Web ACL {WEB_ACL_ID} has at least one rule or rule group." + == f"AWS WAFRegional Web ACL {WEB_ACL_ID} has at least one rule or rule group." ) assert result[0].resource_id == WEB_ACL_ID assert ( From c1b51c6ec011080b029bfd60c534b25829407f52 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Tue, 15 Oct 2024 13:07:00 +0200 Subject: [PATCH 08/12] feat: added testing for new service functions --- .../__init__.py | 0 ...acl_has_rules_or_rule_groups.metadata.json | 32 -- .../waf_webacl_has_rules_or_rule_groups.py | 23 -- .../aws/services/waf/waf_service_test.py | 66 +++- ...af_webacl_has_rules_or_rule_groups_test.py | 301 ------------------ 5 files changed, 65 insertions(+), 357 deletions(-) delete mode 100644 prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/__init__.py delete mode 100644 prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.metadata.json delete mode 100644 prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.py delete mode 100644 tests/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups_test.py diff --git a/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/__init__.py b/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.metadata.json b/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.metadata.json deleted file mode 100644 index ee3edad924..0000000000 --- a/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.metadata.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "Provider": "aws", - "CheckID": "waf_webacl_has_rules_or_rule_groups", - "CheckTitle": "Check if AWS WAF Classic Regional WebACL has at least one rule or rule group.", - "CheckType": [ - "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls" - ], - "ServiceName": "waf", - "SubServiceName": "", - "ResourceIdTemplate": "arn:aws:waf-regional:region:account-id:webacl/web-acl-id", - "Severity": "medium", - "ResourceType": "AwsWafRegionalWebAcl", - "Description": "Ensure that every AWS WAF Classic Regional WebACL contains at least one rule or rule group.", - "Risk": "An empty AWS WAF Classic Regional web ACL allows all web traffic to bypass inspection, potentially exposing resources to unauthorized access and attacks.", - "RelatedUrl": "https://docs.aws.amazon.com/waf/latest/developerguide/waf-rules.html", - "Remediation": { - "Code": { - "CLI": "aws waf-regional update-web-acl --web-acl-id --changes ", - "NativeIaC": "", - "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/waf-controls.html#waf-4", - "Terraform": "" - }, - "Recommendation": { - "Text": "Ensure that every AWS WAF Classic Regional web ACL includes at least one rule or rule group to monitor and control web traffic effectively.", - "Url": "https://docs.aws.amazon.com/waf/latest/developerguide/classic-web-acl-editing.html" - } - }, - "Categories": [], - "DependsOn": [], - "RelatedTo": [], - "Notes": "" -} diff --git a/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.py b/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.py deleted file mode 100644 index b31be1113a..0000000000 --- a/prowler/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups.py +++ /dev/null @@ -1,23 +0,0 @@ -from prowler.lib.check.models import Check, Check_Report_AWS -from prowler.providers.aws.services.waf.wafregional_client import wafregional_client - - -class waf_webacl_has_rules_or_rule_groups(Check): - def execute(self): - findings = [] - for acl in wafregional_client.web_acls.values(): - report = Check_Report_AWS(self.metadata()) - report.region = acl.region - report.resource_id = acl.id - report.resource_arn = acl.arn - report.resource_tags = acl.tags - report.status = "FAIL" - report.status_extended = f"AWS WAFRegional Web ACL {acl.id} does not have any rules or rule groups." - - if acl.rules or acl.rule_groups: - report.status = "PASS" - report.status_extended = f"AWS WAFRegional Web ACL {acl.id} has at least one rule or rule group." - - findings.append(report) - - return findings diff --git a/tests/providers/aws/services/waf/waf_service_test.py b/tests/providers/aws/services/waf/waf_service_test.py index 5270864f87..b85fdafab3 100644 --- a/tests/providers/aws/services/waf/waf_service_test.py +++ b/tests/providers/aws/services/waf/waf_service_test.py @@ -2,7 +2,13 @@ import botocore -from prowler.providers.aws.services.waf.waf_service import WAF, Rule, WAFRegional +from prowler.providers.aws.services.waf.waf_service import ( + WAF, + ACLRule, + Predicate, + Rule, + WAFRegional, +) from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider # Mocking WAF Calls @@ -38,6 +44,29 @@ def mock_make_api_call(self, operation_name, kwarg): ], } } + if operation_name == "ListRules": + return { + "Rules": [ + { + "RuleId": "my-rule-id", + "Name": "my-rule", + }, + ] + } + if operation_name == "GetRule": + return { + "Rule": { + "RuleId": "my-rule-id", + "Name": "my-rule", + "Predicates": [ + { + "Negated": False, + "Type": "IPMatch", + "DataId": "my-data-id", + } + ], + } + } return make_api_call(self, operation_name, kwarg) @@ -144,3 +173,38 @@ def test_get_web_acl(self): assert waf.web_acls[waf_arn].id == "my-web-acl-id" assert waf.web_acls[waf_arn].rules == [Rule(id="my-rule-id")] assert waf.web_acls[waf_arn].rule_groups == [Rule(id="my-rule-group-id")] + + # Test WAFRegional List Rules + def test_list_rules(self): + # WAF client for this test class + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + waf = WAFRegional(aws_provider) + waf_arn = "arn:aws:waf-regional:eu-west-1:123456789012:webacl/my-web-acl-id" + assert waf.web_acls[waf_arn].name == "my-web-acl" + assert waf.web_acls[waf_arn].region == AWS_REGION_EU_WEST_1 + assert waf.web_acls[waf_arn].id == "my-web-acl-id" + assert waf.web_acls[waf_arn].rules == [ACLRule(id="my-rule-id")] + assert waf.web_acls[waf_arn].rule_groups == [ACLRule(id="my-rule-group-id")] + assert waf.rules + + # Test WAFRegional Get Rule + def test_get_rule(self): + # WAF client for this test class + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + waf = WAFRegional(aws_provider) + waf_arn = "arn:aws:waf-regional:eu-west-1:123456789012:webacl/my-web-acl-id" + assert waf.web_acls[waf_arn].name == "my-web-acl" + assert waf.web_acls[waf_arn].region == AWS_REGION_EU_WEST_1 + assert waf.web_acls[waf_arn].id == "my-web-acl-id" + assert waf.web_acls[waf_arn].rules == [ACLRule(id="my-rule-id")] + assert waf.web_acls[waf_arn].rule_groups == [ACLRule(id="my-rule-group-id")] + rule_arn = "arn:aws:waf-regional:eu-west-1:123456789012:rule/my-rule-id" + assert waf.rules == { + rule_arn: Rule( + arn=rule_arn, + id="my-rule-id", + name="my-rule", + region=AWS_REGION_EU_WEST_1, + predicates=[Predicate(negated=False, data_id="my-data-id")], + ) + } diff --git a/tests/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups_test.py b/tests/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups_test.py deleted file mode 100644 index ce0a72269e..0000000000 --- a/tests/providers/aws/services/waf/waf_webacl_has_rules_or_rule_groups/waf_webacl_has_rules_or_rule_groups_test.py +++ /dev/null @@ -1,301 +0,0 @@ -from unittest import mock -from unittest.mock import patch - -import botocore -from moto import mock_aws - -from tests.providers.aws.utils import ( - AWS_ACCOUNT_NUMBER, - AWS_REGION_US_EAST_1, - set_mocked_aws_provider, -) - -WEB_ACL_ID = "test-web-acl-id" - -# Original botocore _make_api_call function -orig = botocore.client.BaseClient._make_api_call - - -# Mocked botocore _make_api_call function -def mock_make_api_call(self, operation_name, kwarg): - if operation_name == "ListRules": - return {} - if operation_name == "GetChangeToken": - return {"ChangeToken": "my-change-token"} - if operation_name == "ListWebACLs": - return { - "WebACLs": [ - {"WebACLId": WEB_ACL_ID, "Name": "my-web-acl"}, - ] - } - if operation_name == "GetWebACL": - return { - "WebACL": { - "Rules": [], - } - } - # If we don't want to patch the API call - return orig(self, operation_name, kwarg) - - -def mock_make_api_call_only_rules(self, operation_name, kwarg): - if operation_name == "ListRules": - return {} - if operation_name == "ListResourcesForWebACL": - return { - "ResourceArns": [ - "alb-arn", - ] - } - if operation_name == "GetChangeToken": - return {"ChangeToken": "my-change-token"} - if operation_name == "ListWebACLs": - return { - "WebACLs": [ - {"WebACLId": WEB_ACL_ID, "Name": "my-web-acl"}, - ] - } - if operation_name == "GetWebACL": - return { - "WebACL": { - "Rules": [ - { - "RuleId": "my-rule-id", - "Type": "BLOCK", - } - ], - } - } - # If we don't want to patch the API call - return orig(self, operation_name, kwarg) - - -def mock_make_api_call_only_rule_groups(self, operation_name, kwarg): - if operation_name == "ListRules": - return {} - if operation_name == "GetChangeToken": - return {"ChangeToken": "my-change-token"} - if operation_name == "ListWebACLs": - return { - "WebACLs": [ - {"WebACLId": WEB_ACL_ID, "Name": "my-web-acl"}, - ] - } - if operation_name == "GetWebACL": - return { - "WebACL": { - "Rules": [ - { - "RuleId": "my-rulegroup-id", - "Type": "GROUP", - } - ], - } - } - # If we don't want to patch the API call - return orig(self, operation_name, kwarg) - - -def mock_make_api_call_both(self, operation_name, kwarg): - if operation_name == "ListRules": - return {} - if operation_name == "GetChangeToken": - return {"ChangeToken": "my-change-token"} - if operation_name == "ListWebACLs": - return { - "WebACLs": [ - {"WebACLId": WEB_ACL_ID, "Name": "my-web-acl"}, - ] - } - if operation_name == "GetWebACL": - return { - "WebACL": { - "Rules": [ - { - "RuleId": "my-rule-id", - "Type": "BLOCK", - }, - { - "RuleId": "my-rulegroup-id", - "Type": "GROUP", - }, - ], - } - } - # If we don't want to patch the API call - return orig(self, operation_name, kwarg) - - -class Test_waf_webacl_has_rules_or_rule_groups: - @mock_aws - def test_no_waf(self): - from prowler.providers.aws.services.waf.waf_service import WAFRegional - - aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) - - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=aws_provider, - ): - with mock.patch( - "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.wafregional_client", - new=WAFRegional(aws_provider), - ): - # Test Check - from prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups import ( - waf_webacl_has_rules_or_rule_groups, - ) - - check = waf_webacl_has_rules_or_rule_groups() - result = check.execute() - - assert len(result) == 0 - - @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) - @mock_aws - def test_waf_no_rules_and_no_rule_group(self): - from prowler.providers.aws.services.waf.waf_service import WAFRegional - - aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) - - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=aws_provider, - ): - with mock.patch( - "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.wafregional_client", - new=WAFRegional(aws_provider), - ): - # Test Check - from prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups import ( - waf_webacl_has_rules_or_rule_groups, - ) - - check = waf_webacl_has_rules_or_rule_groups() - result = check.execute() - - assert len(result) == 1 - assert result[0].status == "FAIL" - assert ( - result[0].status_extended - == f"AWS WAFRegional Web ACL {WEB_ACL_ID} does not have any rules or rule groups." - ) - assert result[0].resource_id == WEB_ACL_ID - assert ( - result[0].resource_arn - == f"arn:aws:waf-regional:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:webacl/{WEB_ACL_ID}" - ) - assert result[0].region == AWS_REGION_US_EAST_1 - - @patch( - "botocore.client.BaseClient._make_api_call", new=mock_make_api_call_only_rules - ) - @mock_aws - def test_waf_rules_and_no_rule_group(self): - from prowler.providers.aws.services.waf.waf_service import WAFRegional - - aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) - - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=aws_provider, - ): - with mock.patch( - "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.wafregional_client", - new=WAFRegional(aws_provider), - ): - # Test Check - from prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups import ( - waf_webacl_has_rules_or_rule_groups, - ) - - check = waf_webacl_has_rules_or_rule_groups() - result = check.execute() - - assert len(result) == 1 - assert result[0].status == "PASS" - assert ( - result[0].status_extended - == f"AWS WAFRegional Web ACL {WEB_ACL_ID} has at least one rule or rule group." - ) - assert result[0].resource_id == WEB_ACL_ID - assert ( - result[0].resource_arn - == f"arn:aws:waf-regional:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:webacl/{WEB_ACL_ID}" - ) - assert result[0].region == AWS_REGION_US_EAST_1 - - @patch( - "botocore.client.BaseClient._make_api_call", - new=mock_make_api_call_only_rule_groups, - ) - @mock_aws - def test_waf_no_rules_and_rule_group(self): - from prowler.providers.aws.services.waf.waf_service import WAFRegional - - aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) - - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=aws_provider, - ): - with mock.patch( - "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.wafregional_client", - new=WAFRegional(aws_provider), - ): - # Test Check - from prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups import ( - waf_webacl_has_rules_or_rule_groups, - ) - - check = waf_webacl_has_rules_or_rule_groups() - result = check.execute() - - assert len(result) == 1 - assert result[0].status == "PASS" - assert ( - result[0].status_extended - == f"AWS WAFRegional Web ACL {WEB_ACL_ID} has at least one rule or rule group." - ) - assert result[0].resource_id == WEB_ACL_ID - assert ( - result[0].resource_arn - == f"arn:aws:waf-regional:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:webacl/{WEB_ACL_ID}" - ) - assert result[0].region == AWS_REGION_US_EAST_1 - - @patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call_both) - @mock_aws - def test_waf_rules_and_rule_group(self): - from prowler.providers.aws.services.waf.waf_service import WAFRegional - - aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) - - with mock.patch( - "prowler.providers.common.provider.Provider.get_global_provider", - return_value=aws_provider, - ): - with mock.patch( - "prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups.wafregional_client", - new=WAFRegional(aws_provider), - ): - # Test Check - from prowler.providers.aws.services.waf.waf_webacl_has_rules_or_rule_groups.waf_webacl_has_rules_or_rule_groups import ( - waf_webacl_has_rules_or_rule_groups, - ) - - check = waf_webacl_has_rules_or_rule_groups() - result = check.execute() - - assert len(result) == 1 - assert result[0].status == "PASS" - assert ( - result[0].status_extended - == f"AWS WAFRegional Web ACL {WEB_ACL_ID} has at least one rule or rule group." - ) - assert result[0].resource_id == WEB_ACL_ID - assert ( - result[0].resource_arn - == f"arn:aws:waf-regional:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:webacl/{WEB_ACL_ID}" - ) - assert result[0].region == AWS_REGION_US_EAST_1 From 077efa8c12b335810fc8b38f3e4fb3f07e0050f3 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Wed, 16 Oct 2024 11:34:58 +0200 Subject: [PATCH 09/12] feat: added full service structure Resolved comments and added total structure for WAFRegional --- .../waf_rule_has_conditions.metadata.json | 4 +- .../waf_rule_has_conditions.py | 8 +- .../providers/aws/services/waf/waf_service.py | 153 ++++++++++++------ .../waf_rule_has_conditions_test.py | 4 +- .../aws/services/waf/waf_service_test.py | 149 ++++++++++++++--- 5 files changed, 239 insertions(+), 79 deletions(-) diff --git a/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.metadata.json b/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.metadata.json index 6c5d37ee2a..80676488bd 100644 --- a/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.metadata.json +++ b/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.metadata.json @@ -1,6 +1,6 @@ { "Provider": "aws", - "CheckID": "waf_rule_has_conditions", + "CheckID": "waf_regional_rule_with_conditions", "CheckTitle": "AWS WAF Classic Regional Rules Should Have at Least One Condition.", "CheckType": [ "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls" @@ -15,7 +15,7 @@ "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/waf-regional-rule-not-empty.html", "Remediation": { "Code": { - "CLI": "aws waf-regional update-rule --rule-id --change-token --updates ", + "CLI": "aws waf-regional update-rule --rule-id --change-token --updates '[{\"Action\":\"INSERT\",\"Predicate\":{\"Negated\":false,\"Type\":\"IPMatch\",\"DataId\":\"\"}}]' --region ", "NativeIaC": "", "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/waf-controls.html#waf-2", "Terraform": "" diff --git a/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py b/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py index 890ff5d1b6..a54113a023 100644 --- a/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py +++ b/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py @@ -12,11 +12,15 @@ def execute(self): report.resource_arn = rule.arn report.resource_tags = rule.tags report.status = "FAIL" - report.status_extended = f"AWS WAFRegional Classic Regional Rule {rule.id} does not have any conditions." + report.status_extended = ( + f"AWS WAF Regional Rule {rule.id} does not have any conditions." + ) if rule.predicates: report.status = "PASS" - report.status_extended = f"AWS WAFRegional Classic Regional Rule {rule.id} has at least one condition." + report.status_extended = ( + f"AWS WAF Regional Rule {rule.id} has at least one condition." + ) findings.append(report) diff --git a/prowler/providers/aws/services/waf/waf_service.py b/prowler/providers/aws/services/waf/waf_service.py index a46c546b4e..ce60bfc1ec 100644 --- a/prowler/providers/aws/services/waf/waf_service.py +++ b/prowler/providers/aws/services/waf/waf_service.py @@ -57,13 +57,84 @@ class WAFRegional(AWSService): def __init__(self, provider): # Call AWSService's __init__ super().__init__("waf-regional", provider) - self.web_acls = {} self.rules = {} - self.__threading_call__(self._list_web_acls) - self.__threading_call__(self._list_resources_for_web_acl) - self.__threading_call__(self._get_web_acl, self.web_acls.values()) + self.rule_groups = {} + self.web_acls = {} self.__threading_call__(self._list_rules) self.__threading_call__(self._get_rule, self.rules.values()) + self.__threading_call__(self._list_rule_groups) + self.__threading_call__( + self._list_activated_rules_in_rule_group, self.rule_groups.values() + ) + self.__threading_call__(self._list_web_acls) + self.__threading_call__(self._get_web_acl, self.web_acls.values()) + self.__threading_call__(self._list_resources_for_web_acl) + + def _list_rules(self, regional_client): + logger.info("WAFRegional - Listing Regional Rules...") + try: + for rule in regional_client.list_rules().get("Rules", []): + arn = f"arn:aws:waf-regional:{regional_client.region}:{self.audited_account}:rule/{rule['RuleId']}" + self.rules[arn] = Rule( + arn=arn, + id=rule.get("RuleId", ""), + region=regional_client.region, + name=rule.get("Name", ""), + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _get_rule(self, rule): + logger.info(f"WAFRegional - Getting Rule {rule.name}...") + try: + get_rule = self.regional_clients[rule.region].get_rule(RuleId=rule.id) + for predicate in get_rule.get("Rule", {}).get("Predicates", []): + rule.predicates.append( + Predicate( + negated=predicate.get("Negated", False), + type=predicate.get("Type", "IPMatch"), + data_id=predicate.get("DataId", ""), + ) + ) + except KeyError: + logger.error(f"Rule {rule.name} not found in {rule.region}.") + + def _list_rule_groups(self, regional_client): + logger.info("WAFRegional - Listing Regional Rule Groups...") + try: + for rule_group in regional_client.list_rule_groups().get("RuleGroups", []): + arn = f"arn:aws:waf-regional:{regional_client.region}:{self.audited_account}:rulegroup/{rule_group['RuleGroupId']}" + self.rule_groups[arn] = RuleGroup( + arn=arn, + region=regional_client.region, + id=rule_group.get("RuleGroupId", ""), + name=rule_group.get("Name", ""), + ) + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _list_activated_rules_in_rule_group(self, rule_group): + logger.info( + f"WAFRegional - Listing activated rules in Rule Group {rule_group.name}..." + ) + try: + for rule in ( + self.regional_clients[rule_group.region] + .list_activated_rules_in_rule_group(RuleGroupId=rule_group.id) + .get("ActivatedRules", []) + ): + rule_arn = f"arn:aws:waf-regional:{rule_group.region}:{self.audited_account}:rule/{rule.get('RuleId', '')}" + rule_group.rules.append(self.rules[rule_arn]) + + except Exception as error: + logger.error( + f"{rule_group.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) def _list_web_acls(self, regional_client): logger.info("WAFRegional - Listing Regional Web ACLs...") @@ -80,20 +151,6 @@ def _list_web_acls(self, regional_client): albs=[], region=regional_client.region, ) - except Exception as error: - logger.error( - f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) - - def _list_resources_for_web_acl(self, regional_client): - logger.info("WAFRegional - Describing resources...") - try: - for acl in self.web_acls.values(): - if acl.region == regional_client.region: - for resource in regional_client.list_resources_for_web_acl( - WebACLId=acl.id, ResourceType="APPLICATION_LOAD_BALANCER" - )["ResourceArns"]: - acl.albs.append(resource) except Exception as error: logger.error( @@ -105,57 +162,42 @@ def _get_web_acl(self, acl): try: get_web_acl = self.regional_clients[acl.region].get_web_acl(WebACLId=acl.id) for rule in get_web_acl.get("WebACL", {}).get("Rules", []): - rule_id = rule.get("RuleId", "") if rule.get("Type", "") == "GROUP": - acl.rule_groups.append(ACLRule(id=rule_id)) + rule_group_arn = f"arn:aws:waf-regional:{acl.region}:{self.audited_account}:rulegroup/{rule.get("RuleId", "")}" + acl.rule_groups.append(self.rule_groups[rule_group_arn]) else: - acl.rules.append(ACLRule(id=rule_id)) + rule_arn = f"arn:aws:waf-regional:{acl.region}:{self.audited_account}:rule/{rule.get("RuleId", "")}" + acl.rules.append(self.rules[rule_arn]) except Exception as error: logger.error( f"{acl.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) - def _list_rules(self, regional_client): - logger.info("WAFRegional - Listing Regional Rules...") + def _list_resources_for_web_acl(self, regional_client): + logger.info("WAFRegional - Describing resources...") try: - for rule in regional_client.list_rules().get("Rules", []): - arn = f"arn:aws:waf-regional:{regional_client.region}:{self.audited_account}:rule/{rule['RuleId']}" - self.rules[arn] = Rule( - arn=arn, - id=rule.get("RuleId", ""), - region=regional_client.region, - name=rule.get("Name", ""), - ) + for acl in self.web_acls.values(): + if acl.region == regional_client.region: + for resource in regional_client.list_resources_for_web_acl( + WebACLId=acl.id, ResourceType="APPLICATION_LOAD_BALANCER" + )["ResourceArns"]: + acl.albs.append(resource) + except Exception as error: logger.error( f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) - def _get_rule(self, rule): - logger.info(f"WAFRegional - Getting Rule {rule.name}...") - try: - get_rule = self.regional_clients[rule.region].get_rule(RuleId=rule.id) - for predicate in get_rule.get("Rule", {}).get("Predicates", []): - rule.predicates.append( - Predicate( - negated=predicate.get("Negated", False), - data_id=predicate.get("DataId", ""), - ) - ) - except KeyError: - logger.error(f"Rule {rule.name} not found in {rule.region}.") - class Predicate(BaseModel): + """Conditions for WAF and WAFRegional Rules""" + negated: bool + type: str data_id: str -class ACLRule(BaseModel): - id: str - - class Rule(BaseModel): """Rule Model for WAF and WAFRegional""" @@ -167,6 +209,17 @@ class Rule(BaseModel): tags: Optional[list] = [] +class RuleGroup(BaseModel): + """RuleGroup Model for WAF and WAFRegional""" + + arn: str + id: str + region: str + name: str + rules: list[Rule] = [] + tags: Optional[list] = [] + + class WebAcl(BaseModel): """Web ACL Model for WAF and WAFRegional""" @@ -176,5 +229,5 @@ class WebAcl(BaseModel): albs: list[str] region: str rules: list[Rule] = [] - rule_groups: list[Rule] = [] + rule_groups: list[RuleGroup] = [] tags: Optional[list] = [] diff --git a/tests/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions_test.py b/tests/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions_test.py index a03f908c49..f5de9f148a 100644 --- a/tests/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions_test.py +++ b/tests/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions_test.py @@ -118,7 +118,7 @@ def test_waf_rules_with_condition(self): assert result[0].status == "PASS" assert ( result[0].status_extended - == f"AWS WAFRegional Classic Regional Rule {RULE_ID} has at least one condition." + == f"AWS WAF Regional Rule {RULE_ID} has at least one condition." ) assert result[0].resource_id == RULE_ID assert ( @@ -157,7 +157,7 @@ def test_waf_rules_without_condition(self): assert result[0].status == "FAIL" assert ( result[0].status_extended - == f"AWS WAFRegional Classic Regional Rule {RULE_ID} does not have any conditions." + == f"AWS WAF Regional Rule {RULE_ID} does not have any conditions." ) assert result[0].resource_id == RULE_ID assert ( diff --git a/tests/providers/aws/services/waf/waf_service_test.py b/tests/providers/aws/services/waf/waf_service_test.py index b85fdafab3..d86bfe503a 100644 --- a/tests/providers/aws/services/waf/waf_service_test.py +++ b/tests/providers/aws/services/waf/waf_service_test.py @@ -4,9 +4,9 @@ from prowler.providers.aws.services.waf.waf_service import ( WAF, - ACLRule, Predicate, Rule, + RuleGroup, WAFRegional, ) from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider @@ -67,6 +67,23 @@ def mock_make_api_call(self, operation_name, kwarg): ], } } + if operation_name == "ListRuleGroups": + return { + "RuleGroups": [ + { + "RuleGroupId": "my-rule-group-id", + "Name": "my-rule-group", + }, + ] + } + if operation_name == "ListActivatedRulesInRuleGroup": + return { + "ActivatedRules": [ + { + "RuleId": "my-rule-id", + }, + ] + } return make_api_call(self, operation_name, kwarg) @@ -151,19 +168,21 @@ def test__get_session___regional(self): waf = WAFRegional(aws_provider) assert waf.session.__class__.__name__ == "Session" - # Test WAFRegional Describe Web ACLs - def test_list_web_acls_waf_regional(self): + # Test WAFRegional List Rules + def test_list_rules(self): # WAF client for this test class aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) waf = WAFRegional(aws_provider) waf_arn = "arn:aws:waf-regional:eu-west-1:123456789012:webacl/my-web-acl-id" - assert len(waf.web_acls) == 1 assert waf.web_acls[waf_arn].name == "my-web-acl" assert waf.web_acls[waf_arn].region == AWS_REGION_EU_WEST_1 assert waf.web_acls[waf_arn].id == "my-web-acl-id" + assert waf.web_acls[waf_arn].rules + assert waf.web_acls[waf_arn].rule_groups + assert waf.rules - # Test WAFRegional Get Web ACL - def test_get_web_acl(self): + # Test WAFRegional Get Rule + def test_get_rule(self): # WAF client for this test class aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) waf = WAFRegional(aws_provider) @@ -171,11 +190,23 @@ def test_get_web_acl(self): assert waf.web_acls[waf_arn].name == "my-web-acl" assert waf.web_acls[waf_arn].region == AWS_REGION_EU_WEST_1 assert waf.web_acls[waf_arn].id == "my-web-acl-id" - assert waf.web_acls[waf_arn].rules == [Rule(id="my-rule-id")] - assert waf.web_acls[waf_arn].rule_groups == [Rule(id="my-rule-group-id")] + assert waf.web_acls[waf_arn].rules + assert waf.web_acls[waf_arn].rule_groups + rule_arn = "arn:aws:waf-regional:eu-west-1:123456789012:rule/my-rule-id" + assert waf.rules == { + rule_arn: Rule( + arn=rule_arn, + id="my-rule-id", + name="my-rule", + region=AWS_REGION_EU_WEST_1, + predicates=[ + Predicate(negated=False, type="IPMatch", data_id="my-data-id") + ], + ) + } - # Test WAFRegional List Rules - def test_list_rules(self): + # Test WAFRegional List Rule Groups + def test_list_rule_groups(self): # WAF client for this test class aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) waf = WAFRegional(aws_provider) @@ -183,12 +214,12 @@ def test_list_rules(self): assert waf.web_acls[waf_arn].name == "my-web-acl" assert waf.web_acls[waf_arn].region == AWS_REGION_EU_WEST_1 assert waf.web_acls[waf_arn].id == "my-web-acl-id" - assert waf.web_acls[waf_arn].rules == [ACLRule(id="my-rule-id")] - assert waf.web_acls[waf_arn].rule_groups == [ACLRule(id="my-rule-group-id")] - assert waf.rules + assert waf.web_acls[waf_arn].rules + assert waf.web_acls[waf_arn].rule_groups + assert waf.rule_groups - # Test WAFRegional Get Rule - def test_get_rule(self): + # Test WAFRegional Get Rule Groups + def test_get_rule_groups(self): # WAF client for this test class aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) waf = WAFRegional(aws_provider) @@ -196,15 +227,87 @@ def test_get_rule(self): assert waf.web_acls[waf_arn].name == "my-web-acl" assert waf.web_acls[waf_arn].region == AWS_REGION_EU_WEST_1 assert waf.web_acls[waf_arn].id == "my-web-acl-id" - assert waf.web_acls[waf_arn].rules == [ACLRule(id="my-rule-id")] - assert waf.web_acls[waf_arn].rule_groups == [ACLRule(id="my-rule-group-id")] - rule_arn = "arn:aws:waf-regional:eu-west-1:123456789012:rule/my-rule-id" - assert waf.rules == { - rule_arn: Rule( - arn=rule_arn, + assert waf.web_acls[waf_arn].rules + assert waf.web_acls[waf_arn].rule_groups + rule_arn = ( + "arn:aws:waf-regional:eu-west-1:123456789012:rulegroup/my-rule-group-id" + ) + assert waf.rule_groups == { + rule_arn: RuleGroup( + arn="arn:aws:waf-regional:eu-west-1:123456789012:rulegroup/my-rule-group-id", + id="my-rule-group-id", + region=AWS_REGION_EU_WEST_1, + name="my-rule-group", + rules=[ + Rule( + arn="arn:aws:waf-regional:eu-west-1:123456789012:rule/my-rule-id", + id="my-rule-id", + region=AWS_REGION_EU_WEST_1, + name="my-rule", + predicates=[ + Predicate( + negated=False, type="IPMatch", data_id="my-data-id" + ) + ], + tags=[], + ) + ], + ) + } + + # Test WAFRegional List Web ACLs + def test_list_web_acls_waf_regional(self): + # WAF client for this test class + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + waf = WAFRegional(aws_provider) + waf_arn = "arn:aws:waf-regional:eu-west-1:123456789012:webacl/my-web-acl-id" + assert len(waf.web_acls) == 1 + assert waf.web_acls[waf_arn].name == "my-web-acl" + assert waf.web_acls[waf_arn].region == AWS_REGION_EU_WEST_1 + assert waf.web_acls[waf_arn].id == "my-web-acl-id" + assert waf.web_acls[waf_arn].rules + assert waf.web_acls[waf_arn].rule_groups + + # Test WAFRegional Get Web ACL + def test_get_web_acl(self): + # WAF client for this test class + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + waf = WAFRegional(aws_provider) + waf_arn = "arn:aws:waf-regional:eu-west-1:123456789012:webacl/my-web-acl-id" + assert waf.web_acls[waf_arn].name == "my-web-acl" + assert waf.web_acls[waf_arn].region == AWS_REGION_EU_WEST_1 + assert waf.web_acls[waf_arn].id == "my-web-acl-id" + assert waf.web_acls[waf_arn].rules == [ + Rule( + arn="arn:aws:waf-regional:eu-west-1:123456789012:rule/my-rule-id", id="my-rule-id", + region=AWS_REGION_EU_WEST_1, name="my-rule", + predicates=[ + Predicate(negated=False, type="IPMatch", data_id="my-data-id") + ], + tags=[], + ) + ] + assert waf.web_acls[waf_arn].rule_groups == [ + RuleGroup( + arn="arn:aws:waf-regional:eu-west-1:123456789012:rulegroup/my-rule-group-id", + id="my-rule-group-id", region=AWS_REGION_EU_WEST_1, - predicates=[Predicate(negated=False, data_id="my-data-id")], + name="my-rule-group", + rules=[ + Rule( + arn="arn:aws:waf-regional:eu-west-1:123456789012:rule/my-rule-id", + id="my-rule-id", + region=AWS_REGION_EU_WEST_1, + name="my-rule", + predicates=[ + Predicate( + negated=False, type="IPMatch", data_id="my-data-id" + ) + ], + tags=[], + ) + ], ) - } + ] From f7976ee0d5d99279bc621a1a7b1676ad3d5f3581 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Wed, 16 Oct 2024 11:38:28 +0200 Subject: [PATCH 10/12] feat: changed check name --- .../__init__.py | 0 ...gional_rule_with_conditions.metadata.json} | 0 .../waf_regional_rule_with_conditions.py} | 2 +- ...waf_regional_rule_with_conditions_test.py} | 26 +++++++++---------- 4 files changed, 14 insertions(+), 14 deletions(-) rename prowler/providers/aws/services/waf/{waf_rule_has_conditions => waf_regional_rule_with_conditions}/__init__.py (100%) rename prowler/providers/aws/services/waf/{waf_rule_has_conditions/waf_rule_has_conditions.metadata.json => waf_regional_rule_with_conditions/waf_regional_rule_with_conditions.metadata.json} (100%) rename prowler/providers/aws/services/waf/{waf_rule_has_conditions/waf_rule_has_conditions.py => waf_regional_rule_with_conditions/waf_regional_rule_with_conditions.py} (95%) rename tests/providers/aws/services/waf/{waf_rule_has_conditions/waf_rule_has_conditions_test.py => waf_regional_rule_with_conditions/waf_regional_rule_with_conditions_test.py} (79%) diff --git a/prowler/providers/aws/services/waf/waf_rule_has_conditions/__init__.py b/prowler/providers/aws/services/waf/waf_regional_rule_with_conditions/__init__.py similarity index 100% rename from prowler/providers/aws/services/waf/waf_rule_has_conditions/__init__.py rename to prowler/providers/aws/services/waf/waf_regional_rule_with_conditions/__init__.py diff --git a/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.metadata.json b/prowler/providers/aws/services/waf/waf_regional_rule_with_conditions/waf_regional_rule_with_conditions.metadata.json similarity index 100% rename from prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.metadata.json rename to prowler/providers/aws/services/waf/waf_regional_rule_with_conditions/waf_regional_rule_with_conditions.metadata.json diff --git a/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py b/prowler/providers/aws/services/waf/waf_regional_rule_with_conditions/waf_regional_rule_with_conditions.py similarity index 95% rename from prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py rename to prowler/providers/aws/services/waf/waf_regional_rule_with_conditions/waf_regional_rule_with_conditions.py index a54113a023..2d7566c4c5 100644 --- a/prowler/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions.py +++ b/prowler/providers/aws/services/waf/waf_regional_rule_with_conditions/waf_regional_rule_with_conditions.py @@ -2,7 +2,7 @@ from prowler.providers.aws.services.waf.wafregional_client import wafregional_client -class waf_rule_has_conditions(Check): +class waf_regional_rule_with_conditions(Check): def execute(self): findings = [] for rule in wafregional_client.rules.values(): diff --git a/tests/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions_test.py b/tests/providers/aws/services/waf/waf_regional_rule_with_conditions/waf_regional_rule_with_conditions_test.py similarity index 79% rename from tests/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions_test.py rename to tests/providers/aws/services/waf/waf_regional_rule_with_conditions/waf_regional_rule_with_conditions_test.py index f5de9f148a..f9a54e90d3 100644 --- a/tests/providers/aws/services/waf/waf_rule_has_conditions/waf_rule_has_conditions_test.py +++ b/tests/providers/aws/services/waf/waf_regional_rule_with_conditions/waf_regional_rule_with_conditions_test.py @@ -63,7 +63,7 @@ def mock_make_api_call_non_compliant_rule(self, operation_name, kwarg): return orig(self, operation_name, kwarg) -class Test_waf_rule_has_conditions: +class Test_waf_regional_rule_with_conditions: @mock_aws def test_no_rules(self): from prowler.providers.aws.services.waf.waf_service import WAFRegional @@ -75,15 +75,15 @@ def test_no_rules(self): return_value=aws_provider, ): with mock.patch( - "prowler.providers.aws.services.waf.waf_rule_has_conditions.waf_rule_has_conditions.wafregional_client", + "prowler.providers.aws.services.waf.waf_regional_rule_with_conditions.waf_regional_rule_with_conditions.wafregional_client", new=WAFRegional(aws_provider), ): # Test Check - from prowler.providers.aws.services.waf.waf_rule_has_conditions.waf_rule_has_conditions import ( - waf_rule_has_conditions, + from prowler.providers.aws.services.waf.waf_regional_rule_with_conditions.waf_regional_rule_with_conditions import ( + waf_regional_rule_with_conditions, ) - check = waf_rule_has_conditions() + check = waf_regional_rule_with_conditions() result = check.execute() assert len(result) == 0 @@ -103,15 +103,15 @@ def test_waf_rules_with_condition(self): return_value=aws_provider, ): with mock.patch( - "prowler.providers.aws.services.waf.waf_rule_has_conditions.waf_rule_has_conditions.wafregional_client", + "prowler.providers.aws.services.waf.waf_regional_rule_with_conditions.waf_regional_rule_with_conditions.wafregional_client", new=WAFRegional(aws_provider), ): # Test Check - from prowler.providers.aws.services.waf.waf_rule_has_conditions.waf_rule_has_conditions import ( - waf_rule_has_conditions, + from prowler.providers.aws.services.waf.waf_regional_rule_with_conditions.waf_regional_rule_with_conditions import ( + waf_regional_rule_with_conditions, ) - check = waf_rule_has_conditions() + check = waf_regional_rule_with_conditions() result = check.execute() assert len(result) == 1 @@ -142,15 +142,15 @@ def test_waf_rules_without_condition(self): return_value=aws_provider, ): with mock.patch( - "prowler.providers.aws.services.waf.waf_rule_has_conditions.waf_rule_has_conditions.wafregional_client", + "prowler.providers.aws.services.waf.waf_regional_rule_with_conditions.waf_regional_rule_with_conditions.wafregional_client", new=WAFRegional(aws_provider), ): # Test Check - from prowler.providers.aws.services.waf.waf_rule_has_conditions.waf_rule_has_conditions import ( - waf_rule_has_conditions, + from prowler.providers.aws.services.waf.waf_regional_rule_with_conditions.waf_regional_rule_with_conditions import ( + waf_regional_rule_with_conditions, ) - check = waf_rule_has_conditions() + check = waf_regional_rule_with_conditions() result = check.execute() assert len(result) == 1 From 8398026e6bf3a9e2963697be265e224d1d06dd61 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Wed, 16 Oct 2024 11:48:39 +0200 Subject: [PATCH 11/12] fix: flake8 lint --- prowler/providers/aws/services/waf/waf_service.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/prowler/providers/aws/services/waf/waf_service.py b/prowler/providers/aws/services/waf/waf_service.py index ce60bfc1ec..2346dbe1bb 100644 --- a/prowler/providers/aws/services/waf/waf_service.py +++ b/prowler/providers/aws/services/waf/waf_service.py @@ -162,11 +162,12 @@ def _get_web_acl(self, acl): try: get_web_acl = self.regional_clients[acl.region].get_web_acl(WebACLId=acl.id) for rule in get_web_acl.get("WebACL", {}).get("Rules", []): + rule_id = rule.get("RuleId", "") if rule.get("Type", "") == "GROUP": - rule_group_arn = f"arn:aws:waf-regional:{acl.region}:{self.audited_account}:rulegroup/{rule.get("RuleId", "")}" + rule_group_arn = f"arn:aws:waf-regional:{acl.region}:{self.audited_account}:rulegroup/{rule_id}" acl.rule_groups.append(self.rule_groups[rule_group_arn]) else: - rule_arn = f"arn:aws:waf-regional:{acl.region}:{self.audited_account}:rule/{rule.get("RuleId", "")}" + rule_arn = f"arn:aws:waf-regional:{acl.region}:{self.audited_account}:rule/{rule_id}" acl.rules.append(self.rules[rule_arn]) except Exception as error: From 9c49a9360d46b10ea097b2dcd69a6f337a0b9cb4 Mon Sep 17 00:00:00 2001 From: Sergio Date: Wed, 16 Oct 2024 11:52:21 -0400 Subject: [PATCH 12/12] chore: revision --- .../waf_regional_rule_with_conditions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prowler/providers/aws/services/waf/waf_regional_rule_with_conditions/waf_regional_rule_with_conditions.py b/prowler/providers/aws/services/waf/waf_regional_rule_with_conditions/waf_regional_rule_with_conditions.py index 2d7566c4c5..9704dfc986 100644 --- a/prowler/providers/aws/services/waf/waf_regional_rule_with_conditions/waf_regional_rule_with_conditions.py +++ b/prowler/providers/aws/services/waf/waf_regional_rule_with_conditions/waf_regional_rule_with_conditions.py @@ -13,13 +13,13 @@ def execute(self): report.resource_tags = rule.tags report.status = "FAIL" report.status_extended = ( - f"AWS WAF Regional Rule {rule.id} does not have any conditions." + f"AWS WAF Regional Rule {rule.name} does not have any conditions." ) if rule.predicates: report.status = "PASS" report.status_extended = ( - f"AWS WAF Regional Rule {rule.id} has at least one condition." + f"AWS WAF Regional Rule {rule.name} has at least one condition." ) findings.append(report)