From ffa29f2f6ecc2b8eb7afce21fc9ff61d6b768c2f Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Mon, 21 Oct 2024 21:53:06 +0200 Subject: [PATCH] feat(waf): add new check `waf_global_rulegroup_not_empty` (#5467) Co-authored-by: Sergio --- .../__init__.py | 0 ...f_global_rulegroup_not_empty.metadata.json | 32 +++ .../waf_global_rulegroup_not_empty.py | 27 +++ .../waf_global_rulegroup_not_empty_test.py | 187 ++++++++++++++++++ 4 files changed, 246 insertions(+) create mode 100644 prowler/providers/aws/services/waf/waf_global_rulegroup_not_empty/__init__.py create mode 100644 prowler/providers/aws/services/waf/waf_global_rulegroup_not_empty/waf_global_rulegroup_not_empty.metadata.json create mode 100644 prowler/providers/aws/services/waf/waf_global_rulegroup_not_empty/waf_global_rulegroup_not_empty.py create mode 100644 tests/providers/aws/services/waf/waf_global_rulegroup_not_empty/waf_global_rulegroup_not_empty_test.py diff --git a/prowler/providers/aws/services/waf/waf_global_rulegroup_not_empty/__init__.py b/prowler/providers/aws/services/waf/waf_global_rulegroup_not_empty/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/waf/waf_global_rulegroup_not_empty/waf_global_rulegroup_not_empty.metadata.json b/prowler/providers/aws/services/waf/waf_global_rulegroup_not_empty/waf_global_rulegroup_not_empty.metadata.json new file mode 100644 index 0000000000..822043186c --- /dev/null +++ b/prowler/providers/aws/services/waf/waf_global_rulegroup_not_empty/waf_global_rulegroup_not_empty.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "waf_global_rulegroup_not_empty", + "CheckTitle": "Check if AWS WAF Classic Global rule group has at least one rule.", + "CheckType": [ + "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls" + ], + "ServiceName": "waf", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:waf::account-id:rulegroup/rule-group-name/rule-group-id", + "Severity": "medium", + "ResourceType": "AwsWafRuleGroup", + "Description": "Ensure that every AWS WAF Classic Global rule group contains at least one rule.", + "Risk": "A WAF Classic Global rule group without any rules allows all incoming traffic to bypass inspection, increasing the risk of unauthorized access and potential attacks on resources.", + "RelatedUrl": "https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-groups.html", + "Remediation": { + "Code": { + "CLI": "aws waf update-rule-group --rule-group-id --updates Action=INSERT,ActivatedRule={Priority=1,RuleId=,Action={Type=BLOCK}} --change-token --region ", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/waf-controls.html#waf-7", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure that every AWS WAF Classic Global rule group contains at least one rule to enforce traffic inspection and defined actions such as allow, block, or count.", + "Url": "https://docs.aws.amazon.com/waf/latest/developerguide/classic-rule-group-editing.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/waf/waf_global_rulegroup_not_empty/waf_global_rulegroup_not_empty.py b/prowler/providers/aws/services/waf/waf_global_rulegroup_not_empty/waf_global_rulegroup_not_empty.py new file mode 100644 index 0000000000..df4727ced8 --- /dev/null +++ b/prowler/providers/aws/services/waf/waf_global_rulegroup_not_empty/waf_global_rulegroup_not_empty.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_global_rulegroup_not_empty(Check): + def execute(self): + findings = [] + for rule_group in waf_client.rule_groups.values(): + report = Check_Report_AWS(self.metadata()) + report.region = rule_group.region + report.resource_id = rule_group.id + report.resource_arn = rule_group.arn + report.resource_tags = rule_group.tags + report.status = "FAIL" + report.status_extended = ( + f"AWS WAF Global Rule Group {rule_group.name} does not have any rules." + ) + + if rule_group.rules: + report.status = "PASS" + report.status_extended = ( + f"AWS WAF Global Rule Group {rule_group.name} is not empty." + ) + + findings.append(report) + + return findings diff --git a/tests/providers/aws/services/waf/waf_global_rulegroup_not_empty/waf_global_rulegroup_not_empty_test.py b/tests/providers/aws/services/waf/waf_global_rulegroup_not_empty/waf_global_rulegroup_not_empty_test.py new file mode 100644 index 0000000000..5fee95c972 --- /dev/null +++ b/tests/providers/aws/services/waf/waf_global_rulegroup_not_empty/waf_global_rulegroup_not_empty_test.py @@ -0,0 +1,187 @@ +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_GROUP_ID = "test-rulegroup-id" +RULE_ID = "my-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_group(self, operation_name, kwarg): + unused_operations = ["ListWebACLs", "GetRule"] + if operation_name in unused_operations: + return {} + if operation_name == "ListRules": + return { + "Rules": [ + { + "RuleId": RULE_ID, + "Name": "my-rule", + }, + ] + } + if operation_name == "GetRule": + return { + "Rule": { + "RuleId": RULE_ID, + "Name": "my-rule", + "Predicates": [ + { + "Negated": False, + "Type": "IPMatch", + "DataId": "my-data-id", + } + ], + } + } + if operation_name == "ListRuleGroups": + return { + "RuleGroups": [ + { + "RuleGroupId": RULE_GROUP_ID, + "Name": RULE_GROUP_ID, + }, + ] + } + if operation_name == "ListActivatedRulesInRuleGroup": + return { + "ActivatedRules": [ + { + "RuleId": RULE_ID, + }, + ] + } + return orig(self, operation_name, kwarg) + + +def mock_make_api_call_non_compliant_rule_group(self, operation_name, kwarg): + unused_operations = ["ListRules", "GetRule", "ListWebACLs", "GetRule"] + if operation_name in unused_operations: + return {} + if operation_name == "ListRuleGroups": + return { + "RuleGroups": [ + { + "RuleGroupId": RULE_GROUP_ID, + "Name": RULE_GROUP_ID, + }, + ] + } + if operation_name == "ListActivatedRulesInRuleGroup": + return {"Rules": []} + return orig(self, operation_name, kwarg) + + +class Test_waf_global_rulegroup_not_empty: + @mock_aws + def test_no_rule_groups(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_global_rulegroup_not_empty.waf_global_rulegroup_not_empty.waf_client", + new=WAF(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.waf.waf_global_rulegroup_not_empty.waf_global_rulegroup_not_empty import ( + waf_global_rulegroup_not_empty, + ) + + check = waf_global_rulegroup_not_empty() + result = check.execute() + + assert len(result) == 0 + + @patch( + "botocore.client.BaseClient._make_api_call", + new=mock_make_api_call_compliant_rule_group, + ) + @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_global_rulegroup_not_empty.waf_global_rulegroup_not_empty.waf_client", + new=WAF(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.waf.waf_global_rulegroup_not_empty.waf_global_rulegroup_not_empty import ( + waf_global_rulegroup_not_empty, + ) + + check = waf_global_rulegroup_not_empty() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"AWS WAF Global Rule Group {RULE_GROUP_ID} is not empty." + ) + assert result[0].resource_id == RULE_GROUP_ID + assert ( + result[0].resource_arn + == f"arn:aws:waf:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:rulegroup/{RULE_GROUP_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_group, + ) + @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_global_rulegroup_not_empty.waf_global_rulegroup_not_empty.waf_client", + new=WAF(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.waf.waf_global_rulegroup_not_empty.waf_global_rulegroup_not_empty import ( + waf_global_rulegroup_not_empty, + ) + + check = waf_global_rulegroup_not_empty() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"AWS WAF Global Rule Group {RULE_GROUP_ID} does not have any rules." + ) + assert result[0].resource_id == RULE_GROUP_ID + assert ( + result[0].resource_arn + == f"arn:aws:waf:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:rulegroup/{RULE_GROUP_ID}" + ) + assert result[0].region == AWS_REGION_US_EAST_1