feat(waf): add new check waf_regional_rulegroup_not_empty (#5415)

Co-authored-by: Sergio <sergio@prowler.com>
This commit is contained in:
Hugo Pereira Brito
2024-10-16 21:22:24 +02:00
committed by GitHub
parent f1449b66d6
commit 9969e271ed
12 changed files with 255 additions and 5 deletions
@@ -19,7 +19,8 @@ class dynamodb_table_protected_by_backup_plan(Check):
if (
table_arn in backup_client.protected_resources
or "arn:aws:dynamodb:*:*:table/*" in backup_client.protected_resources
or f"arn:{dynamodb_client.audited_partition}:dynamodb:*:*:table/*"
in backup_client.protected_resources
or "*" in backup_client.protected_resources
):
report.status = "PASS"
@@ -18,7 +18,8 @@ class ec2_ebs_volume_protected_by_backup_plan(Check):
)
if (
volume.arn in backup_client.protected_resources
or "arn:aws:ec2:*:*:volume/*" in backup_client.protected_resources
or f"arn:{ec2_client.audited_partition}:ec2:*:*:volume/*"
in backup_client.protected_resources
or "*" in backup_client.protected_resources
):
report.status = "PASS"
@@ -22,7 +22,7 @@ class FSx(AWSService):
)
for page in describe_file_system_paginator.paginate():
for file_system in page["FileSystems"]:
file_system_arn = f"arn:aws:fsx:{regional_client.region}:{self.audited_account}:file-system/{file_system['FileSystemId']}"
file_system_arn = f"arn:{self.audited_partition}:fsx:{regional_client.region}:{self.audited_account}:file-system/{file_system['FileSystemId']}"
if not self.audit_resources or (
is_resource_filtered(file_system_arn, self.audit_resources)
):
@@ -19,7 +19,7 @@ class rds_instance_protected_by_backup_plan(Check):
if (
db_instance_arn in backup_client.protected_resources
or "arn:aws:dynamodb:*:*:instance:*"
or f"arn:{rds_client.audited_partition}:rds:*:*:instance:*"
in backup_client.protected_resources
or "*" in backup_client.protected_resources
):
@@ -0,0 +1,32 @@
{
"Provider": "aws",
"CheckID": "waf_regional_rulegroup_not_empty",
"CheckTitle": "Check if AWS WAF Classic Regional 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": "AwsWafRegionalRuleGroup",
"Description": "Ensure that every AWS WAF Classic Regional rule group contains at least one rule.",
"Risk": "A WAF Classic Regional 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-regional update-rule-group --rule-group-id <rule-group-id> --updates Action=INSERT,ActivatedRule={Priority=1,RuleId=<rule-id>,Action={Type=BLOCK}} --change-token <change-token> --region <region>",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/waf-controls.html#waf-3",
"Terraform": ""
},
"Recommendation": {
"Text": "Ensure that every AWS WAF Classic Regional 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": ""
}
@@ -0,0 +1,25 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.waf.wafregional_client import wafregional_client
class waf_regional_rulegroup_not_empty(Check):
def execute(self):
findings = []
for rule_group in wafregional_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 Regional Rule Group {rule_group.name} does not have any rules."
if rule_group.rules:
report.status = "PASS"
report.status_extended = (
f"AWS WAF Regional Rule Group {rule_group.name} is not empty."
)
findings.append(report)
return findings
@@ -32,6 +32,7 @@ class WAF(AWSService):
albs=[],
region=regional_client.region,
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
@@ -222,6 +222,7 @@ class Test_dynamodb_table_protected_by_backup_plan:
dynamodb_client = mock.MagicMock()
from prowler.providers.aws.services.dynamodb.dynamodb_service import Table
dynamodb_client.audited_partition = "aws"
dynamodb_client.tables = {
f"arn:aws:dynamodb:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:table/test1": Table(
name="test1",
@@ -210,6 +210,7 @@ class Test_ec2_ebs_volume_protected_by_backup_plan:
ec2_client = mock.MagicMock()
from prowler.providers.aws.services.ec2.ec2_service import Volume
ec2_client.audited_partition = "aws"
ec2_client.volumes = [
Volume(
id="volume-tester",
@@ -246,10 +246,11 @@ class Test_rds_instance_protected_by_backup_plan:
def test_rds_instance_with_backup_plan_via_instance_wildcard(self):
instance = mock.MagicMock()
instance.audited_partition = "aws"
from prowler.providers.aws.services.rds.rds_service import DBInstance
arn = "arn:aws:dynamodb:*:*:instance:*"
arn = "arn:aws:rds:*:*:instance:*"
instance.db_instances = {
f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-1": DBInstance(
id="db-master-1",
@@ -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_regional_rulegroup_not_empty:
@mock_aws
def test_no_rule_groups(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_regional_rulegroup_not_empty.waf_regional_rulegroup_not_empty.wafregional_client",
new=WAFRegional(aws_provider),
):
# Test Check
from prowler.providers.aws.services.waf.waf_regional_rulegroup_not_empty.waf_regional_rulegroup_not_empty import (
waf_regional_rulegroup_not_empty,
)
check = waf_regional_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 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_regional_rulegroup_not_empty.waf_regional_rulegroup_not_empty.wafregional_client",
new=WAFRegional(aws_provider),
):
# Test Check
from prowler.providers.aws.services.waf.waf_regional_rulegroup_not_empty.waf_regional_rulegroup_not_empty import (
waf_regional_rulegroup_not_empty,
)
check = waf_regional_rulegroup_not_empty()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"AWS WAF Regional 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-regional:{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 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_regional_rulegroup_not_empty.waf_regional_rulegroup_not_empty.wafregional_client",
new=WAFRegional(aws_provider),
):
# Test Check
from prowler.providers.aws.services.waf.waf_regional_rulegroup_not_empty.waf_regional_rulegroup_not_empty import (
waf_regional_rulegroup_not_empty,
)
check = waf_regional_rulegroup_not_empty()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"AWS WAF Regional 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-regional:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:rulegroup/{RULE_GROUP_ID}"
)
assert result[0].region == AWS_REGION_US_EAST_1