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=[], + ) + ], ) - } + ]