mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
fix(status): Recover status filtering (#4572)
Co-authored-by: Sergio <sergio@prowler.com>
This commit is contained in:
@@ -10,7 +10,7 @@ Execute Prowler in verbose mode (like in Version 2):
|
||||
prowler <provider> --verbose
|
||||
```
|
||||
## Filter findings by status
|
||||
Prowler can filter the findings by their status:
|
||||
Prowler can filter the findings by their status, so you can see only in the CLI and in the reports the findings with a specific status:
|
||||
```console
|
||||
prowler <provider> --status [PASS, FAIL, MANUAL]
|
||||
```
|
||||
|
||||
@@ -593,7 +593,6 @@ def prowler():
|
||||
aws_partition=global_provider.identity.partition,
|
||||
aws_session=global_provider.session.current_session,
|
||||
findings=asff_output.data,
|
||||
status=global_provider.output_options.status,
|
||||
send_only_fails=global_provider.output_options.send_sh_only_fails,
|
||||
aws_security_hub_available_regions=security_hub_regions,
|
||||
)
|
||||
|
||||
@@ -706,6 +706,14 @@ def execute(
|
||||
check_class, verbose, global_provider.output_options.only_logs
|
||||
)
|
||||
|
||||
# Exclude findings per status
|
||||
if global_provider.output_options.status:
|
||||
check_findings = [
|
||||
finding
|
||||
for finding in check_findings
|
||||
if finding.status in global_provider.output_options.status
|
||||
]
|
||||
|
||||
# Update Audit Status
|
||||
services_executed.add(service)
|
||||
checks_executed.add(check_name)
|
||||
|
||||
@@ -25,6 +25,7 @@ def stdout_report(finding, color, verbose, status, fix):
|
||||
)
|
||||
|
||||
|
||||
# TODO: Only pass check_findings, provider.output_options and provider.type
|
||||
def report(check_findings, provider):
|
||||
try:
|
||||
output_options = provider.output_options
|
||||
|
||||
@@ -22,7 +22,7 @@ class SecurityHub:
|
||||
|
||||
Methods:
|
||||
__init__: Initializes the SecurityHub object with necessary attributes.
|
||||
filter: Filters findings based on region and status, returning a dictionary with findings per region.
|
||||
filter: Filters findings based on region, returning a dictionary with findings per region.
|
||||
verify_enabled_per_region: Verifies and stores enabled regions with SecurityHub clients.
|
||||
batch_send_to_security_hub: Sends findings to Security Hub and returns the count of successfully sent findings.
|
||||
archive_previous_findings: Archives findings that are not present in the current execution.
|
||||
@@ -41,7 +41,6 @@ class SecurityHub:
|
||||
aws_account_id: str,
|
||||
aws_partition: str,
|
||||
findings: list[AWSSecurityFindingFormat] = [],
|
||||
status: list[str] = [],
|
||||
aws_security_hub_available_regions: list[str] = [],
|
||||
send_only_fails: bool = False,
|
||||
) -> "SecurityHub":
|
||||
@@ -50,20 +49,19 @@ class SecurityHub:
|
||||
self._aws_partition = aws_partition
|
||||
|
||||
self._enabled_regions = None
|
||||
self._findings_per_region = None
|
||||
self._findings_per_region = {}
|
||||
|
||||
if aws_security_hub_available_regions:
|
||||
self._enabled_regions = self.verify_enabled_per_region(
|
||||
aws_security_hub_available_regions
|
||||
)
|
||||
if findings and self._enabled_regions:
|
||||
self._findings_per_region = self.filter(findings, send_only_fails, status)
|
||||
self._findings_per_region = self.filter(findings, send_only_fails)
|
||||
|
||||
def filter(
|
||||
self,
|
||||
findings: list[AWSSecurityFindingFormat],
|
||||
send_only_fails: bool,
|
||||
status: list[str],
|
||||
) -> dict:
|
||||
"""
|
||||
Filters the given list of findings based on the provided criteria and returns a dictionary containing findings per region.
|
||||
@@ -71,46 +69,38 @@ class SecurityHub:
|
||||
Args:
|
||||
findings (list[AWSSecurityFindingFormat]): List of findings to filter.
|
||||
send_only_fails (bool): Flag indicating whether to send only findings with status 'FAILED'.
|
||||
status (list[str]): List of valid statuses to filter the findings.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing findings per region after applying the filtering criteria.
|
||||
"""
|
||||
|
||||
findings_per_region = {}
|
||||
try:
|
||||
# Create a key per audited region
|
||||
for region in self._enabled_regions.keys():
|
||||
findings_per_region[region] = []
|
||||
|
||||
# Create a key per audited region
|
||||
for region in self._enabled_regions.keys():
|
||||
findings_per_region[region] = []
|
||||
|
||||
for finding in findings:
|
||||
# We don't send findings to not enabled regions
|
||||
if finding.Resources[0].Region not in findings_per_region:
|
||||
continue
|
||||
|
||||
if (
|
||||
finding.Compliance.Status != "FAILED"
|
||||
or finding.Compliance.Status == "WARNING"
|
||||
) and send_only_fails:
|
||||
continue
|
||||
|
||||
# SecurityHub valid statuses are: PASSED, FAILED, WARNING
|
||||
if status:
|
||||
if finding.Compliance.Status == "PASSED" and "PASS" not in status:
|
||||
continue
|
||||
if finding.Compliance.Status == "FAILED" and "FAIL" not in status:
|
||||
continue
|
||||
# Check muted finding
|
||||
if finding.Compliance.Status == "WARNING":
|
||||
for finding in findings:
|
||||
# We don't send findings to not enabled regions
|
||||
if finding.Resources[0].Region not in findings_per_region:
|
||||
continue
|
||||
|
||||
# Get the finding region
|
||||
# We can do that since the finding always stores just one finding
|
||||
region = finding.Resources[0].Region
|
||||
if (
|
||||
finding.Compliance.Status != "FAILED"
|
||||
or finding.Compliance.Status == "WARNING"
|
||||
) and send_only_fails:
|
||||
continue
|
||||
|
||||
# Include that finding within their region
|
||||
findings_per_region[region].append(finding)
|
||||
# Get the finding region
|
||||
# We can do that since the finding always stores just one finding
|
||||
region = finding.Resources[0].Region
|
||||
|
||||
# Include that finding within their region
|
||||
findings_per_region[region].append(finding)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- [{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return findings_per_region
|
||||
|
||||
def verify_enabled_per_region(
|
||||
|
||||
@@ -6,6 +6,7 @@ from argparse import Namespace
|
||||
from importlib.machinery import FileFinder
|
||||
from logging import DEBUG, ERROR
|
||||
from pkgutil import ModuleInfo
|
||||
from unittest import mock
|
||||
|
||||
from boto3 import client
|
||||
from colorama import Fore, Style
|
||||
@@ -15,6 +16,7 @@ from moto import mock_aws
|
||||
from prowler.lib.check.check import (
|
||||
exclude_checks_to_run,
|
||||
exclude_services_to_run,
|
||||
execute,
|
||||
list_categories,
|
||||
list_checks_json,
|
||||
list_modules,
|
||||
@@ -29,8 +31,16 @@ from prowler.lib.check.check import (
|
||||
)
|
||||
from prowler.lib.check.models import load_check_metadata
|
||||
from prowler.providers.aws.aws_provider import AwsProvider
|
||||
from prowler.providers.aws.services.accessanalyzer.accessanalyzer_service import (
|
||||
Analyzer,
|
||||
)
|
||||
from tests.lib.check.fixtures.bulk_checks_metadata import test_bulk_checks_metadata
|
||||
from tests.providers.aws.utils import AWS_REGION_US_EAST_1
|
||||
from tests.providers.aws.utils import (
|
||||
AWS_ACCOUNT_ARN,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_REGION_US_EAST_1,
|
||||
set_mocked_aws_provider,
|
||||
)
|
||||
|
||||
# AWS_ACCOUNT_NUMBER = "123456789012"
|
||||
# AWS_REGION = "us-east-1"
|
||||
@@ -792,6 +802,65 @@ class TestCheck:
|
||||
== '{\n "aws": [\n "awslambda_function_invoke_api_operations_cloudtrail_logging_enabled",\n "awslambda_function_no_secrets_in_code",\n "awslambda_function_no_secrets_in_variables",\n "awslambda_function_not_publicly_accessible",\n "awslambda_function_url_cors_policy",\n "awslambda_function_url_public",\n "awslambda_function_using_supported_runtimes"\n ]\n}'
|
||||
)
|
||||
|
||||
def test_execute(self):
|
||||
accessanalyzer_client = mock.MagicMock
|
||||
accessanalyzer_client.region = AWS_REGION_US_EAST_1
|
||||
accessanalyzer_client.analyzers = [
|
||||
Analyzer(
|
||||
arn=AWS_ACCOUNT_ARN,
|
||||
name=AWS_ACCOUNT_NUMBER,
|
||||
status="NOT_AVAILABLE",
|
||||
tags=[],
|
||||
type="",
|
||||
region=AWS_REGION_US_EAST_1,
|
||||
)
|
||||
]
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.accessanalyzer.accessanalyzer_service.AccessAnalyzer",
|
||||
accessanalyzer_client,
|
||||
):
|
||||
findings = execute(
|
||||
service="accessanalyzer",
|
||||
check_name="accessanalyzer_enabled",
|
||||
global_provider=set_mocked_aws_provider(
|
||||
expected_checks=["accessanalyzer_enabled"]
|
||||
),
|
||||
services_executed={"accessanalyzer"},
|
||||
checks_executed={"accessanalyzer_enabled"},
|
||||
custom_checks_metadata=None,
|
||||
)
|
||||
assert len(findings) == 1
|
||||
|
||||
def test_execute_with_filtering_status(self):
|
||||
accessanalyzer_client = mock.MagicMock
|
||||
accessanalyzer_client.region = AWS_REGION_US_EAST_1
|
||||
accessanalyzer_client.analyzers = [
|
||||
Analyzer(
|
||||
arn=AWS_ACCOUNT_ARN,
|
||||
name=AWS_ACCOUNT_NUMBER,
|
||||
status="NOT_AVAILABLE",
|
||||
tags=[],
|
||||
type="",
|
||||
region=AWS_REGION_US_EAST_1,
|
||||
)
|
||||
]
|
||||
status = ["PASS"]
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.accessanalyzer.accessanalyzer_service.AccessAnalyzer",
|
||||
accessanalyzer_client,
|
||||
):
|
||||
findings = execute(
|
||||
service="accessanalyzer",
|
||||
check_name="accessanalyzer_enabled",
|
||||
global_provider=set_mocked_aws_provider(
|
||||
status=status, expected_checks=["accessanalyzer_enabled"]
|
||||
),
|
||||
services_executed={"accessanalyzer"},
|
||||
checks_executed={"accessanalyzer_enabled"},
|
||||
custom_checks_metadata=None,
|
||||
)
|
||||
assert len(findings) == 0
|
||||
|
||||
def test_run_check(self, caplog):
|
||||
caplog.set_level(DEBUG)
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ class TestSecurityHub:
|
||||
findings=asff.data,
|
||||
)
|
||||
|
||||
assert security_hub._findings_per_region is None
|
||||
assert security_hub._findings_per_region == {}
|
||||
|
||||
@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call)
|
||||
def test_filter_security_hub_findings_per_region_disabled_region(self):
|
||||
@@ -259,24 +259,6 @@ class TestSecurityHub:
|
||||
|
||||
assert security_hub._findings_per_region == {AWS_REGION_EU_WEST_1: []}
|
||||
|
||||
@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call)
|
||||
def test_filter_security_hub_findings_per_region_PASS_and_FAIL_statuses(self):
|
||||
findings = [generate_finding_output(status="PASS", region=AWS_REGION_EU_WEST_1)]
|
||||
asff = ASFF(findings=findings)
|
||||
|
||||
security_hub = SecurityHub(
|
||||
aws_session=session.Session(
|
||||
region_name=AWS_REGION_EU_WEST_1,
|
||||
),
|
||||
aws_account_id=AWS_ACCOUNT_NUMBER,
|
||||
aws_partition=AWS_COMMERCIAL_PARTITION,
|
||||
aws_security_hub_available_regions=[AWS_REGION_EU_WEST_1],
|
||||
findings=asff.data,
|
||||
status=["FAIL"],
|
||||
)
|
||||
|
||||
assert security_hub._findings_per_region == {AWS_REGION_EU_WEST_1: []}
|
||||
|
||||
@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call)
|
||||
def test_filter_security_hub_findings_per_region_FAIL_and_FAIL_statuses(self):
|
||||
findings = [generate_finding_output(status="FAIL", region=AWS_REGION_EU_WEST_1)]
|
||||
@@ -290,7 +272,6 @@ class TestSecurityHub:
|
||||
aws_partition=AWS_COMMERCIAL_PARTITION,
|
||||
aws_security_hub_available_regions=[AWS_REGION_EU_WEST_1],
|
||||
findings=asff.data,
|
||||
status=["FAIL"],
|
||||
)
|
||||
|
||||
assert security_hub._findings_per_region == {
|
||||
@@ -310,7 +291,6 @@ class TestSecurityHub:
|
||||
aws_partition=AWS_COMMERCIAL_PARTITION,
|
||||
aws_security_hub_available_regions=[AWS_REGION_EU_WEST_1],
|
||||
findings=asff.data,
|
||||
status=[],
|
||||
send_only_fails=True,
|
||||
)
|
||||
|
||||
@@ -329,7 +309,6 @@ class TestSecurityHub:
|
||||
aws_partition=AWS_COMMERCIAL_PARTITION,
|
||||
aws_security_hub_available_regions=[AWS_REGION_EU_WEST_1],
|
||||
findings=asff.data,
|
||||
status=[],
|
||||
send_only_fails=True,
|
||||
)
|
||||
|
||||
@@ -350,11 +329,10 @@ class TestSecurityHub:
|
||||
aws_partition=AWS_COMMERCIAL_PARTITION,
|
||||
aws_security_hub_available_regions=[],
|
||||
findings=asff.data,
|
||||
status=[],
|
||||
send_only_fails=True,
|
||||
)
|
||||
|
||||
assert security_hub._findings_per_region is None
|
||||
assert security_hub._findings_per_region == {}
|
||||
|
||||
@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call)
|
||||
def test_filter_security_hub_findings_per_region_muted_fail_with_send_sh_only_fails(
|
||||
@@ -375,7 +353,6 @@ class TestSecurityHub:
|
||||
aws_partition=AWS_COMMERCIAL_PARTITION,
|
||||
aws_security_hub_available_regions=[AWS_REGION_EU_WEST_1],
|
||||
findings=asff.data,
|
||||
status=[],
|
||||
send_only_fails=True,
|
||||
)
|
||||
|
||||
@@ -400,7 +377,6 @@ class TestSecurityHub:
|
||||
aws_partition=AWS_COMMERCIAL_PARTITION,
|
||||
aws_security_hub_available_regions=[AWS_REGION_EU_WEST_1],
|
||||
findings=asff.data,
|
||||
status=["FAIL"],
|
||||
send_only_fails=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ def set_mocked_aws_provider(
|
||||
original_session: session.Session = None,
|
||||
enabled_regions: set = None,
|
||||
arguments: Namespace = Namespace(),
|
||||
status: list[str] = [],
|
||||
create_default_organization: bool = True,
|
||||
) -> AwsProvider:
|
||||
if create_default_organization:
|
||||
@@ -114,12 +115,13 @@ def set_mocked_aws_provider(
|
||||
create_default_aws_organization()
|
||||
|
||||
# Default arguments
|
||||
arguments = set_default_provider_arguments(arguments)
|
||||
arguments = set_default_provider_arguments(arguments, status)
|
||||
|
||||
# AWS Provider
|
||||
provider = AwsProvider(arguments)
|
||||
|
||||
# Output options
|
||||
|
||||
provider.output_options = arguments, {}
|
||||
|
||||
# Mock Session
|
||||
@@ -156,8 +158,10 @@ def set_mocked_aws_provider(
|
||||
return provider
|
||||
|
||||
|
||||
def set_default_provider_arguments(arguments: Namespace) -> Namespace:
|
||||
arguments.status = []
|
||||
def set_default_provider_arguments(
|
||||
arguments: Namespace, status: list = []
|
||||
) -> Namespace:
|
||||
arguments.status = status
|
||||
arguments.output_formats = []
|
||||
arguments.output_directory = ""
|
||||
arguments.verbose = False
|
||||
|
||||
Reference in New Issue
Block a user