mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 04:51:51 +00:00
chore(mutelist): create new class to encapsulate the logic (#4413)
This commit is contained in:
@@ -19,7 +19,6 @@ from prowler.lib.check.compliance_models import load_compliance_framework
|
||||
from prowler.lib.check.custom_checks_metadata import update_check_metadata
|
||||
from prowler.lib.check.models import Check, load_check_metadata
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.mutelist.mutelist import mutelist_findings
|
||||
from prowler.lib.outputs.outputs import report
|
||||
from prowler.lib.utils.utils import open_file, parse_json_file, print_boxes
|
||||
from prowler.providers.common.models import Audit_Metadata
|
||||
@@ -612,9 +611,9 @@ def execute_checks(
|
||||
else:
|
||||
# Prepare your messages
|
||||
messages = [f"Config File: {Fore.YELLOW}{config_file}{Style.RESET_ALL}"]
|
||||
if global_provider.mutelist_file_path:
|
||||
if global_provider.mutelist.mutelist_file_path:
|
||||
messages.append(
|
||||
f"Mutelist File: {Fore.YELLOW}{global_provider.mutelist_file_path}{Style.RESET_ALL}"
|
||||
f"Mutelist File: {Fore.YELLOW}{global_provider.mutelist.mutelist_file_path}{Style.RESET_ALL}"
|
||||
)
|
||||
if global_provider.type == "aws":
|
||||
messages.append(
|
||||
@@ -715,11 +714,21 @@ def execute(
|
||||
)
|
||||
|
||||
# Mutelist findings
|
||||
if hasattr(global_provider, "mutelist") and global_provider.mutelist:
|
||||
check_findings = mutelist_findings(
|
||||
global_provider,
|
||||
check_findings,
|
||||
)
|
||||
if hasattr(global_provider, "mutelist") and global_provider.mutelist.mutelist:
|
||||
# TODO: make this prettier
|
||||
is_finding_muted_args = {}
|
||||
if global_provider.type == "aws":
|
||||
is_finding_muted_args["aws_account_id"] = (
|
||||
global_provider.identity.account
|
||||
)
|
||||
elif global_provider.type == "kubernetes":
|
||||
is_finding_muted_args["cluster"] = global_provider.identity.cluster
|
||||
|
||||
for finding in check_findings:
|
||||
is_finding_muted_args["finding"] = finding
|
||||
finding.muted = global_provider.mutelist.is_finding_muted(
|
||||
**is_finding_muted_args
|
||||
)
|
||||
|
||||
# Refactor(Outputs)
|
||||
# Report the check's findings
|
||||
|
||||
+256
-337
@@ -1,373 +1,292 @@
|
||||
import re
|
||||
from typing import Any
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import yaml
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.mutelist.models import mutelist_schema
|
||||
from prowler.lib.outputs.utils import unroll_tags
|
||||
|
||||
|
||||
def get_mutelist_file_from_local_file(mutelist_path: str):
|
||||
try:
|
||||
with open(mutelist_path) as f:
|
||||
mutelist = yaml.safe_load(f)["Mutelist"]
|
||||
return mutelist
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
return {}
|
||||
class Mutelist(ABC):
|
||||
_mutelist: dict = {}
|
||||
_mutelist_file_path: str = None
|
||||
|
||||
MUTELIST_KEY = "Mutelist"
|
||||
|
||||
def validate_mutelist(mutelist: dict) -> dict:
|
||||
try:
|
||||
mutelist = mutelist_schema.validate(mutelist)
|
||||
return mutelist
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- Mutelist YAML is malformed - {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
return {}
|
||||
def __init__(
|
||||
self, mutelist_path: str = "", mutelist_content: dict = {}
|
||||
) -> "Mutelist":
|
||||
if mutelist_path:
|
||||
self._mutelist_file_path = mutelist_path
|
||||
self.get_mutelist_file_from_local_file(mutelist_path)
|
||||
else:
|
||||
self._mutelist = mutelist_content
|
||||
|
||||
if self._mutelist:
|
||||
self.validate_mutelist()
|
||||
|
||||
def mutelist_findings(
|
||||
global_provider: Any,
|
||||
check_findings: list[Any],
|
||||
) -> list[Any]:
|
||||
# Check if finding is muted
|
||||
for finding in check_findings:
|
||||
# TODO: Move this mapping to the execute_check function and pass that output to the mutelist and the report
|
||||
if global_provider.type == "aws":
|
||||
finding.muted = is_muted(
|
||||
global_provider.mutelist,
|
||||
global_provider.identity.account,
|
||||
finding.check_metadata.CheckID,
|
||||
finding.region,
|
||||
finding.resource_id,
|
||||
unroll_tags(finding.resource_tags),
|
||||
@property
|
||||
def mutelist(self) -> dict:
|
||||
return self._mutelist
|
||||
|
||||
@property
|
||||
def mutelist_file_path(self) -> dict:
|
||||
return self._mutelist_file_path
|
||||
|
||||
@abstractmethod
|
||||
def is_finding_muted(self) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_mutelist_file_from_local_file(self, mutelist_path: str):
|
||||
try:
|
||||
with open(mutelist_path) as f:
|
||||
self._mutelist = yaml.safe_load(f)[self.MUTELIST_KEY]
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
elif global_provider.type == "azure":
|
||||
finding.muted = is_muted(
|
||||
global_provider.mutelist,
|
||||
finding.subscription,
|
||||
finding.check_metadata.CheckID,
|
||||
# TODO: add region to the findings when we add Azure Locations
|
||||
# finding.region,
|
||||
"",
|
||||
finding.resource_name,
|
||||
unroll_tags(finding.resource_tags),
|
||||
|
||||
def validate_mutelist(self) -> bool:
|
||||
try:
|
||||
self._mutelist = mutelist_schema.validate(self._mutelist)
|
||||
return True
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- Mutelist YAML is malformed - {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
elif global_provider.type == "gcp":
|
||||
finding.muted = is_muted(
|
||||
global_provider.mutelist,
|
||||
finding.project_id,
|
||||
finding.check_metadata.CheckID,
|
||||
finding.location,
|
||||
finding.resource_name,
|
||||
unroll_tags(finding.resource_tags),
|
||||
self._mutelist = {}
|
||||
return False
|
||||
|
||||
def is_muted(
|
||||
self,
|
||||
audited_account: str,
|
||||
check: str,
|
||||
finding_region: str,
|
||||
finding_resource: str,
|
||||
finding_tags,
|
||||
) -> bool:
|
||||
"""
|
||||
Check if the provided finding is muted for the audited account, check, region, resource and tags.
|
||||
|
||||
Args:
|
||||
mutelist (dict): Dictionary containing information about muted checks for different accounts.
|
||||
audited_account (str): The account being audited.
|
||||
check (str): The check to be evaluated for muting.
|
||||
finding_region (str): The region where the finding occurred.
|
||||
finding_resource (str): The resource related to the finding.
|
||||
finding_tags: The tags associated with the finding.
|
||||
|
||||
Returns:
|
||||
bool: True if the finding is muted for the audited account, check, region, resource and tags., otherwise False.
|
||||
"""
|
||||
try:
|
||||
# By default is not muted
|
||||
is_finding_muted = False
|
||||
|
||||
# We always check all the accounts present in the mutelist
|
||||
# if one mutes the finding we set the finding as muted
|
||||
for account in self._mutelist.get("Accounts", []):
|
||||
if account == audited_account or account == "*":
|
||||
if self.is_muted_in_check(
|
||||
self._mutelist["Accounts"][account]["Checks"],
|
||||
audited_account,
|
||||
check,
|
||||
finding_region,
|
||||
finding_resource,
|
||||
finding_tags,
|
||||
):
|
||||
is_finding_muted = True
|
||||
break
|
||||
|
||||
return is_finding_muted
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
elif global_provider.type == "kubernetes":
|
||||
finding.muted = is_muted(
|
||||
global_provider.mutelist,
|
||||
global_provider.identity.cluster,
|
||||
finding.check_metadata.CheckID,
|
||||
finding.namespace,
|
||||
finding.resource_name,
|
||||
unroll_tags(finding.resource_tags),
|
||||
)
|
||||
return check_findings
|
||||
return False
|
||||
|
||||
def is_muted_in_check(
|
||||
self,
|
||||
muted_checks,
|
||||
audited_account,
|
||||
check,
|
||||
finding_region,
|
||||
finding_resource,
|
||||
finding_tags,
|
||||
) -> bool:
|
||||
"""
|
||||
Check if the provided check is muted.
|
||||
|
||||
def is_muted(
|
||||
mutelist: dict,
|
||||
audited_account: str,
|
||||
check: str,
|
||||
finding_region: str,
|
||||
finding_resource: str,
|
||||
finding_tags,
|
||||
) -> bool:
|
||||
"""
|
||||
Check if the provided finding is muted for the audited account, check, region, resource and tags.
|
||||
Args:
|
||||
muted_checks (dict): Dictionary containing information about muted checks.
|
||||
audited_account (str): The account to be audited.
|
||||
check (str): The check to be evaluated for muting.
|
||||
finding_region (str): The region where the finding occurred.
|
||||
finding_resource (str): The resource related to the finding.
|
||||
finding_tags (str): The tags associated with the finding.
|
||||
|
||||
Args:
|
||||
mutelist (dict): Dictionary containing information about muted checks for different accounts.
|
||||
audited_account (str): The account being audited.
|
||||
check (str): The check to be evaluated for muting.
|
||||
finding_region (str): The region where the finding occurred.
|
||||
finding_resource (str): The resource related to the finding.
|
||||
finding_tags: The tags associated with the finding.
|
||||
Returns:
|
||||
bool: True if the check is muted, otherwise False.
|
||||
"""
|
||||
try:
|
||||
# Default value is not muted
|
||||
is_check_muted = False
|
||||
|
||||
Returns:
|
||||
bool: True if the finding is muted for the audited account, check, region, resource and tags., otherwise False.
|
||||
"""
|
||||
try:
|
||||
# By default is not muted
|
||||
is_finding_muted = False
|
||||
for muted_check, muted_check_info in muted_checks.items():
|
||||
# map lambda to awslambda
|
||||
muted_check = re.sub("^lambda", "awslambda", muted_check)
|
||||
|
||||
# We always check all the accounts present in the mutelist
|
||||
# if one mutes the finding we set the finding as muted
|
||||
for account in mutelist["Accounts"]:
|
||||
if account == audited_account or account == "*":
|
||||
if is_muted_in_check(
|
||||
mutelist["Accounts"][account]["Checks"],
|
||||
audited_account,
|
||||
check,
|
||||
finding_region,
|
||||
finding_resource,
|
||||
finding_tags,
|
||||
check_match = (
|
||||
"*" == muted_check
|
||||
or check == muted_check
|
||||
or self.is_item_matched([muted_check], check)
|
||||
)
|
||||
|
||||
# Check if the finding is excepted
|
||||
exceptions = muted_check_info.get("Exceptions")
|
||||
if (
|
||||
self.is_excepted(
|
||||
exceptions,
|
||||
audited_account,
|
||||
finding_region,
|
||||
finding_resource,
|
||||
finding_tags,
|
||||
)
|
||||
and check_match
|
||||
):
|
||||
is_finding_muted = True
|
||||
# Break loop and return default value since is excepted
|
||||
break
|
||||
|
||||
return is_finding_muted
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
return False
|
||||
muted_regions = muted_check_info.get("Regions")
|
||||
muted_resources = muted_check_info.get("Resources")
|
||||
muted_tags = muted_check_info.get("Tags", "*")
|
||||
# We need to set the muted_tags if None, "" or [], so the falsy helps
|
||||
if not muted_tags:
|
||||
muted_tags = "*"
|
||||
# If there is a *, it affects to all checks
|
||||
if check_match:
|
||||
muted_in_check = True
|
||||
muted_in_region = self.is_item_matched(
|
||||
muted_regions, finding_region
|
||||
)
|
||||
muted_in_resource = self.is_item_matched(
|
||||
muted_resources, finding_resource
|
||||
)
|
||||
muted_in_tags = self.is_item_matched(muted_tags, finding_tags)
|
||||
|
||||
# For a finding to be muted requires the following set to True:
|
||||
# - muted_in_check -> True
|
||||
# - muted_in_region -> True
|
||||
# - muted_in_tags -> True
|
||||
# - muted_in_resource -> True
|
||||
# - excepted -> False
|
||||
|
||||
def is_muted_in_check(
|
||||
muted_checks,
|
||||
audited_account,
|
||||
check,
|
||||
finding_region,
|
||||
finding_resource,
|
||||
finding_tags,
|
||||
) -> bool:
|
||||
"""
|
||||
Check if the provided check is muted.
|
||||
if (
|
||||
muted_in_check
|
||||
and muted_in_region
|
||||
and muted_in_tags
|
||||
and muted_in_resource
|
||||
):
|
||||
is_check_muted = True
|
||||
|
||||
Args:
|
||||
muted_checks (dict): Dictionary containing information about muted checks.
|
||||
audited_account (str): The account to be audited.
|
||||
check (str): The check to be evaluated for muting.
|
||||
finding_region (str): The region where the finding occurred.
|
||||
finding_resource (str): The resource related to the finding.
|
||||
finding_tags (str): The tags associated with the finding.
|
||||
|
||||
Returns:
|
||||
bool: True if the check is muted, otherwise False.
|
||||
"""
|
||||
try:
|
||||
# Default value is not muted
|
||||
is_check_muted = False
|
||||
|
||||
for muted_check, muted_check_info in muted_checks.items():
|
||||
# map lambda to awslambda
|
||||
muted_check = re.sub("^lambda", "awslambda", muted_check)
|
||||
|
||||
check_match = (
|
||||
"*" == muted_check
|
||||
or check == muted_check
|
||||
or re.search(muted_check, check)
|
||||
return is_check_muted
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
# Check if the finding is excepted
|
||||
exceptions = muted_check_info.get("Exceptions")
|
||||
if (
|
||||
is_excepted(
|
||||
exceptions,
|
||||
audited_account,
|
||||
finding_region,
|
||||
finding_resource,
|
||||
finding_tags,
|
||||
)
|
||||
and check_match
|
||||
):
|
||||
# Break loop and return default value since is excepted
|
||||
break
|
||||
return False
|
||||
|
||||
muted_regions = muted_check_info.get("Regions")
|
||||
muted_resources = muted_check_info.get("Resources")
|
||||
muted_tags = muted_check_info.get("Tags", "*")
|
||||
# We need to set the muted_tags if None, "" or [], so the falsy helps
|
||||
if not muted_tags:
|
||||
muted_tags = "*"
|
||||
# If there is a *, it affects to all checks
|
||||
if check_match:
|
||||
muted_in_check = True
|
||||
muted_in_region = is_muted_in_region(muted_regions, finding_region)
|
||||
muted_in_resource = is_muted_in_resource(
|
||||
muted_resources, finding_resource
|
||||
)
|
||||
muted_in_tags = is_muted_in_tags(muted_tags, finding_tags)
|
||||
def is_excepted(
|
||||
self,
|
||||
exceptions,
|
||||
audited_account,
|
||||
finding_region,
|
||||
finding_resource,
|
||||
finding_tags,
|
||||
) -> bool:
|
||||
"""
|
||||
Check if the provided account, region, resource, and tags are excepted based on the exceptions dictionary.
|
||||
|
||||
# For a finding to be muted requires the following set to True:
|
||||
# - muted_in_check -> True
|
||||
# - muted_in_region -> True
|
||||
# - muted_in_tags -> True
|
||||
# - muted_in_resource -> True
|
||||
# - excepted -> False
|
||||
Args:
|
||||
exceptions (dict): Dictionary containing exceptions for different attributes like Accounts, Regions, Resources, and Tags.
|
||||
audited_account (str): The account to be audited.
|
||||
finding_region (str): The region where the finding occurred.
|
||||
finding_resource (str): The resource related to the finding.
|
||||
finding_tags (str): The tags associated with the finding.
|
||||
|
||||
Returns:
|
||||
bool: True if the account, region, resource, and tags are excepted based on the exceptions, otherwise False.
|
||||
"""
|
||||
try:
|
||||
excepted = False
|
||||
is_account_excepted = False
|
||||
is_region_excepted = False
|
||||
is_resource_excepted = False
|
||||
is_tag_excepted = False
|
||||
if exceptions:
|
||||
excepted_accounts = exceptions.get("Accounts", [])
|
||||
is_account_excepted = self.is_item_matched(
|
||||
excepted_accounts, audited_account
|
||||
)
|
||||
|
||||
excepted_regions = exceptions.get("Regions", [])
|
||||
is_region_excepted = self.is_item_matched(
|
||||
excepted_regions, finding_region
|
||||
)
|
||||
|
||||
excepted_resources = exceptions.get("Resources", [])
|
||||
is_resource_excepted = self.is_item_matched(
|
||||
excepted_resources, finding_resource
|
||||
)
|
||||
|
||||
excepted_tags = exceptions.get("Tags", [])
|
||||
is_tag_excepted = self.is_item_matched(excepted_tags, finding_tags)
|
||||
|
||||
if (
|
||||
muted_in_check
|
||||
and muted_in_region
|
||||
and muted_in_tags
|
||||
and muted_in_resource
|
||||
not is_account_excepted
|
||||
and not is_region_excepted
|
||||
and not is_resource_excepted
|
||||
and not is_tag_excepted
|
||||
):
|
||||
is_check_muted = True
|
||||
|
||||
return is_check_muted
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def is_muted_in_region(
|
||||
mutelist_regions,
|
||||
finding_region,
|
||||
) -> bool:
|
||||
"""
|
||||
Check if the finding_region is present in the mutelist_regions.
|
||||
|
||||
Args:
|
||||
mutelist_regions (list): List of regions in the mute list.
|
||||
finding_region (str): Region to check if it is muted.
|
||||
|
||||
Returns:
|
||||
bool: True if the finding_region is muted in any of the mutelist_regions, otherwise False.
|
||||
"""
|
||||
try:
|
||||
return __is_item_matched__(mutelist_regions, finding_region)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def is_muted_in_tags(muted_tags, finding_tags) -> bool:
|
||||
"""
|
||||
Check if any of the muted tags are present in the finding tags.
|
||||
|
||||
Args:
|
||||
muted_tags (list): List of muted tags to be checked.
|
||||
finding_tags (str): String containing tags to search for muted tags.
|
||||
|
||||
Returns:
|
||||
bool: True if any of the muted tags are present in the finding tags, otherwise False.
|
||||
"""
|
||||
try:
|
||||
return __is_item_matched__(muted_tags, finding_tags)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def is_muted_in_resource(muted_resources, finding_resource) -> bool:
|
||||
"""
|
||||
Check if any of the muted_resources are present in the finding_resource.
|
||||
|
||||
Args:
|
||||
muted_resources (list): List of muted resources to be checked.
|
||||
finding_resource (str): Resource to search for muted resources.
|
||||
|
||||
Returns:
|
||||
bool: True if any of the muted_resources are present in the finding_resource, otherwise False.
|
||||
"""
|
||||
try:
|
||||
return __is_item_matched__(muted_resources, finding_resource)
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def is_excepted(
|
||||
exceptions,
|
||||
audited_account,
|
||||
finding_region,
|
||||
finding_resource,
|
||||
finding_tags,
|
||||
) -> bool:
|
||||
"""
|
||||
Check if the provided account, region, resource, and tags are excepted based on the exceptions dictionary.
|
||||
|
||||
Args:
|
||||
exceptions (dict): Dictionary containing exceptions for different attributes like Accounts, Regions, Resources, and Tags.
|
||||
audited_account (str): The account to be audited.
|
||||
finding_region (str): The region where the finding occurred.
|
||||
finding_resource (str): The resource related to the finding.
|
||||
finding_tags (str): The tags associated with the finding.
|
||||
|
||||
Returns:
|
||||
bool: True if the account, region, resource, and tags are excepted based on the exceptions, otherwise False.
|
||||
"""
|
||||
try:
|
||||
excepted = False
|
||||
is_account_excepted = False
|
||||
is_region_excepted = False
|
||||
is_resource_excepted = False
|
||||
is_tag_excepted = False
|
||||
if exceptions:
|
||||
excepted_accounts = exceptions.get("Accounts", [])
|
||||
is_account_excepted = __is_item_matched__(
|
||||
excepted_accounts, audited_account
|
||||
excepted = False
|
||||
elif (
|
||||
(is_account_excepted or not excepted_accounts)
|
||||
and (is_region_excepted or not excepted_regions)
|
||||
and (is_resource_excepted or not excepted_resources)
|
||||
and (is_tag_excepted or not excepted_tags)
|
||||
):
|
||||
excepted = True
|
||||
return excepted
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
return False
|
||||
|
||||
excepted_regions = exceptions.get("Regions", [])
|
||||
is_region_excepted = __is_item_matched__(excepted_regions, finding_region)
|
||||
@staticmethod
|
||||
def is_item_matched(matched_items, finding_items):
|
||||
"""
|
||||
Check if any of the items in matched_items are present in finding_items.
|
||||
|
||||
excepted_resources = exceptions.get("Resources", [])
|
||||
is_resource_excepted = __is_item_matched__(
|
||||
excepted_resources, finding_resource
|
||||
Args:
|
||||
matched_items (list): List of items to be matched.
|
||||
finding_items (str): String to search for matched items.
|
||||
|
||||
Returns:
|
||||
bool: True if any of the matched_items are present in finding_items, otherwise False.
|
||||
"""
|
||||
try:
|
||||
is_item_matched = False
|
||||
if matched_items and (finding_items or finding_items == ""):
|
||||
for item in matched_items:
|
||||
if item.startswith("*"):
|
||||
item = ".*" + item[1:]
|
||||
if re.match(item, finding_items):
|
||||
is_item_matched = True
|
||||
break
|
||||
return is_item_matched
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
|
||||
excepted_tags = exceptions.get("Tags", [])
|
||||
is_tag_excepted = __is_item_matched__(excepted_tags, finding_tags)
|
||||
|
||||
if (
|
||||
not is_account_excepted
|
||||
and not is_region_excepted
|
||||
and not is_resource_excepted
|
||||
and not is_tag_excepted
|
||||
):
|
||||
excepted = False
|
||||
elif (
|
||||
(is_account_excepted or not excepted_accounts)
|
||||
and (is_region_excepted or not excepted_regions)
|
||||
and (is_resource_excepted or not excepted_resources)
|
||||
and (is_tag_excepted or not excepted_tags)
|
||||
):
|
||||
excepted = True
|
||||
return excepted
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def __is_item_matched__(matched_items, finding_items):
|
||||
"""
|
||||
Check if any of the items in matched_items are present in finding_items.
|
||||
|
||||
Args:
|
||||
matched_items (list): List of items to be matched.
|
||||
finding_items (str): String to search for matched items.
|
||||
|
||||
Returns:
|
||||
bool: True if any of the matched_items are present in finding_items, otherwise False.
|
||||
"""
|
||||
try:
|
||||
is_item_matched = False
|
||||
if matched_items and (finding_items or finding_items == ""):
|
||||
for item in matched_items:
|
||||
if item.startswith("*"):
|
||||
item = ".*" + item[1:]
|
||||
if re.search(item, finding_items):
|
||||
is_item_matched = True
|
||||
break
|
||||
return is_item_matched
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
return False
|
||||
return False
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
from argparse import Namespace
|
||||
from datetime import datetime
|
||||
@@ -22,10 +21,6 @@ from prowler.config.config import (
|
||||
)
|
||||
from prowler.lib.check.check import list_modules, recover_checks_from_service
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.mutelist.mutelist import (
|
||||
get_mutelist_file_from_local_file,
|
||||
validate_mutelist,
|
||||
)
|
||||
from prowler.lib.utils.utils import open_file, parse_json_file, print_boxes
|
||||
from prowler.providers.aws.config import (
|
||||
AWS_REGION_US_EAST_1,
|
||||
@@ -35,11 +30,7 @@ from prowler.providers.aws.config import (
|
||||
)
|
||||
from prowler.providers.aws.lib.arn.arn import parse_iam_credentials_arn
|
||||
from prowler.providers.aws.lib.arn.models import ARN
|
||||
from prowler.providers.aws.lib.mutelist.mutelist import (
|
||||
get_mutelist_file_from_dynamodb,
|
||||
get_mutelist_file_from_lambda,
|
||||
get_mutelist_file_from_s3,
|
||||
)
|
||||
from prowler.providers.aws.lib.mutelist.mutelist import AWSMutelist
|
||||
from prowler.providers.aws.lib.organizations.organizations import (
|
||||
get_organizations_metadata,
|
||||
parse_organizations_metadata,
|
||||
@@ -295,12 +286,14 @@ class AwsProvider(Provider):
|
||||
)
|
||||
|
||||
@property
|
||||
def mutelist(self):
|
||||
def mutelist(self) -> AWSMutelist:
|
||||
"""
|
||||
mutelist method returns the provider's mutelist.
|
||||
"""
|
||||
return self._mutelist
|
||||
|
||||
# TODO: this is going to be called from another place soon, since the provider
|
||||
# shouldn't hold the mutelist
|
||||
@mutelist.setter
|
||||
def mutelist(self, mutelist_path):
|
||||
"""
|
||||
@@ -309,35 +302,12 @@ class AwsProvider(Provider):
|
||||
# Set default mutelist path if none is set
|
||||
if not mutelist_path:
|
||||
mutelist_path = get_default_mute_file_path(self.type)
|
||||
if mutelist_path:
|
||||
# Mutelist from S3 URI
|
||||
if re.search("^s3://([^/]+)/(.*?([^/]+))$", mutelist_path):
|
||||
mutelist = get_mutelist_file_from_s3(
|
||||
mutelist_path, self._session.current_session
|
||||
)
|
||||
# Mutelist from Lambda Function ARN
|
||||
elif re.search(r"^arn:(\w+):lambda:", mutelist_path):
|
||||
mutelist = get_mutelist_file_from_lambda(
|
||||
mutelist_path,
|
||||
self._session.current_session,
|
||||
)
|
||||
# Mutelist from DynamoDB ARN
|
||||
elif re.search(
|
||||
r"^arn:aws(-cn|-us-gov)?:dynamodb:[a-z]{2}-[a-z-]+-[1-9]{1}:[0-9]{12}:table\/[a-zA-Z0-9._-]+$",
|
||||
mutelist_path,
|
||||
):
|
||||
mutelist = get_mutelist_file_from_dynamodb(
|
||||
mutelist_path, self._session.current_session, self._identity.account
|
||||
)
|
||||
else:
|
||||
mutelist = get_mutelist_file_from_local_file(mutelist_path)
|
||||
|
||||
mutelist = validate_mutelist(mutelist)
|
||||
else:
|
||||
mutelist = {}
|
||||
|
||||
self._mutelist = mutelist
|
||||
self._mutelist_file_path = mutelist_path
|
||||
self._mutelist = AWSMutelist(
|
||||
mutelist_path=mutelist_path,
|
||||
session=self._session.current_session,
|
||||
aws_account_id=self._identity.account,
|
||||
)
|
||||
|
||||
@property
|
||||
def get_output_mapping(self):
|
||||
|
||||
@@ -1,84 +1,137 @@
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
from boto3 import Session
|
||||
from boto3.dynamodb.conditions import Attr
|
||||
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.mutelist.mutelist import Mutelist
|
||||
from prowler.lib.outputs.utils import unroll_tags
|
||||
|
||||
|
||||
def get_mutelist_file_from_s3(mutelist_path: str, aws_session: Session = None):
|
||||
try:
|
||||
bucket = mutelist_path.split("/")[2]
|
||||
key = ("/").join(mutelist_path.split("/")[3:])
|
||||
s3_client = aws_session.client("s3")
|
||||
mutelist = yaml.safe_load(s3_client.get_object(Bucket=bucket, Key=key)["Body"])[
|
||||
"Mutelist"
|
||||
]
|
||||
return mutelist
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
class AWSMutelist(Mutelist):
|
||||
def __init__(
|
||||
self,
|
||||
mutelist_content: dict = {},
|
||||
mutelist_path: str = None,
|
||||
session: Session = None,
|
||||
aws_account_id: str = "",
|
||||
) -> "AWSMutelist":
|
||||
self._mutelist = mutelist_content
|
||||
self._mutelist_file_path = mutelist_path
|
||||
if mutelist_path:
|
||||
# Mutelist from S3 URI
|
||||
if re.search("^s3://([^/]+)/(.*?([^/]+))$", self._mutelist_file_path):
|
||||
self._mutelist = self.get_mutelist_file_from_s3(session)
|
||||
# Mutelist from Lambda Function ARN
|
||||
elif re.search(r"^arn:(\w+):lambda:", self._mutelist_file_path):
|
||||
self._mutelist = self.get_mutelist_file_from_lambda(
|
||||
session,
|
||||
)
|
||||
# Mutelist from DynamoDB ARN
|
||||
elif re.search(
|
||||
r"^arn:aws(-cn|-us-gov)?:dynamodb:[a-z]{2}-[a-z-]+-[1-9]{1}:[0-9]{12}:table\/[a-zA-Z0-9._-]+$",
|
||||
self._mutelist_file_path,
|
||||
):
|
||||
self._mutelist = self.get_mutelist_file_from_dynamodb(
|
||||
session,
|
||||
aws_account_id,
|
||||
)
|
||||
else:
|
||||
self.get_mutelist_file_from_local_file(mutelist_path)
|
||||
if self._mutelist:
|
||||
self.validate_mutelist()
|
||||
|
||||
def is_finding_muted(
|
||||
self,
|
||||
finding: Any,
|
||||
aws_account_id: str,
|
||||
) -> bool:
|
||||
return self.is_muted(
|
||||
aws_account_id,
|
||||
finding.check_metadata.CheckID,
|
||||
finding.region,
|
||||
finding.resource_id,
|
||||
unroll_tags(finding.resource_tags),
|
||||
)
|
||||
return {}
|
||||
|
||||
|
||||
def get_mutelist_file_from_lambda(mutelist_path: str, aws_session: Session = None):
|
||||
try:
|
||||
lambda_region = mutelist_path.split(":")[3]
|
||||
lambda_client = aws_session.client("lambda", region_name=lambda_region)
|
||||
lambda_response = lambda_client.invoke(
|
||||
FunctionName=mutelist_path, InvocationType="RequestResponse"
|
||||
)
|
||||
lambda_payload = lambda_response["Payload"].read()
|
||||
mutelist = yaml.safe_load(lambda_payload)["Mutelist"]
|
||||
|
||||
return mutelist
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
return {}
|
||||
|
||||
|
||||
def get_mutelist_file_from_dynamodb(
|
||||
mutelist_path: str, aws_session: Session = None, aws_account: str = None
|
||||
):
|
||||
try:
|
||||
mutelist = {"Accounts": {}}
|
||||
table_region = mutelist_path.split(":")[3]
|
||||
dynamodb_resource = aws_session.resource("dynamodb", region_name=table_region)
|
||||
dynamo_table = dynamodb_resource.Table(mutelist_path.split("/")[1])
|
||||
response = dynamo_table.scan(
|
||||
FilterExpression=Attr("Accounts").is_in([aws_account, "*"])
|
||||
)
|
||||
dynamodb_items = response["Items"]
|
||||
# Paginate through all results
|
||||
while "LastEvaluatedKey" in dynamodb_items:
|
||||
response = dynamo_table.scan(
|
||||
ExclusiveStartKey=response["LastEvaluatedKey"],
|
||||
FilterExpression=Attr("Accounts").is_in([aws_account, "*"]),
|
||||
def get_mutelist_file_from_s3(self, aws_session: Session = None):
|
||||
try:
|
||||
bucket = self._mutelist_file_path.split("/")[2]
|
||||
key = ("/").join(self._mutelist_file_path.split("/")[3:])
|
||||
s3_client = aws_session.client("s3")
|
||||
mutelist = yaml.safe_load(
|
||||
s3_client.get_object(Bucket=bucket, Key=key)["Body"]
|
||||
)["Mutelist"]
|
||||
return mutelist
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
dynamodb_items.update(response["Items"])
|
||||
for item in dynamodb_items:
|
||||
# Create mutelist for every item
|
||||
mutelist["Accounts"][item["Accounts"]] = {
|
||||
"Checks": {
|
||||
item["Checks"]: {
|
||||
"Regions": item["Regions"],
|
||||
"Resources": item["Resources"],
|
||||
return {}
|
||||
|
||||
def get_mutelist_file_from_lambda(self, aws_session: Session = None):
|
||||
try:
|
||||
lambda_region = self._mutelist_file_path.split(":")[3]
|
||||
lambda_client = aws_session.client("lambda", region_name=lambda_region)
|
||||
lambda_response = lambda_client.invoke(
|
||||
FunctionName=self._mutelist_file_path, InvocationType="RequestResponse"
|
||||
)
|
||||
lambda_payload = lambda_response["Payload"].read()
|
||||
mutelist = yaml.safe_load(lambda_payload)["Mutelist"]
|
||||
|
||||
return mutelist
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
return {}
|
||||
|
||||
def get_mutelist_file_from_dynamodb(
|
||||
self, aws_session: Session = None, aws_account: str = None
|
||||
):
|
||||
try:
|
||||
mutelist = {"Accounts": {}}
|
||||
table_region = self._mutelist_file_path.split(":")[3]
|
||||
dynamodb_resource = aws_session.resource(
|
||||
"dynamodb", region_name=table_region
|
||||
)
|
||||
dynamo_table = dynamodb_resource.Table(
|
||||
self._mutelist_file_path.split("/")[1]
|
||||
)
|
||||
response = dynamo_table.scan(
|
||||
FilterExpression=Attr("Accounts").is_in([aws_account, "*"])
|
||||
)
|
||||
dynamodb_items = response["Items"]
|
||||
# Paginate through all results
|
||||
while "LastEvaluatedKey" in dynamodb_items:
|
||||
response = dynamo_table.scan(
|
||||
ExclusiveStartKey=response["LastEvaluatedKey"],
|
||||
FilterExpression=Attr("Accounts").is_in([aws_account, "*"]),
|
||||
)
|
||||
dynamodb_items.update(response["Items"])
|
||||
for item in dynamodb_items:
|
||||
# Create mutelist for every item
|
||||
mutelist["Accounts"][item["Accounts"]] = {
|
||||
"Checks": {
|
||||
item["Checks"]: {
|
||||
"Regions": item["Regions"],
|
||||
"Resources": item["Resources"],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if "Tags" in item:
|
||||
mutelist["Accounts"][item["Accounts"]]["Checks"][item["Checks"]][
|
||||
"Tags"
|
||||
] = item["Tags"]
|
||||
if "Exceptions" in item:
|
||||
mutelist["Accounts"][item["Accounts"]]["Checks"][item["Checks"]][
|
||||
"Exceptions"
|
||||
] = item["Exceptions"]
|
||||
return mutelist
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
return {}
|
||||
if "Tags" in item:
|
||||
mutelist["Accounts"][item["Accounts"]]["Checks"][item["Checks"]][
|
||||
"Tags"
|
||||
] = item["Tags"]
|
||||
if "Exceptions" in item:
|
||||
mutelist["Accounts"][item["Accounts"]]["Checks"][item["Checks"]][
|
||||
"Exceptions"
|
||||
] = item["Exceptions"]
|
||||
return mutelist
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
return {}
|
||||
|
||||
@@ -8,9 +8,13 @@ from azure.mgmt.subscription import SubscriptionClient
|
||||
from colorama import Fore, Style
|
||||
from msgraph import GraphServiceClient
|
||||
|
||||
from prowler.config.config import load_and_validate_config_file
|
||||
from prowler.config.config import (
|
||||
get_default_mute_file_path,
|
||||
load_and_validate_config_file,
|
||||
)
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.utils.utils import print_boxes
|
||||
from prowler.providers.azure.lib.mutelist.mutelist import AzureMutelist
|
||||
from prowler.providers.azure.lib.regions.regions import get_regions_config
|
||||
from prowler.providers.azure.models import (
|
||||
AzureIdentityInfo,
|
||||
@@ -29,7 +33,7 @@ class AzureProvider(Provider):
|
||||
_region_config: AzureRegionConfig
|
||||
_locations: dict
|
||||
_output_options: AzureOutputOptions
|
||||
_mutelist: dict
|
||||
_mutelist: AzureMutelist
|
||||
# TODO: this is not optional, enforce for all providers
|
||||
audit_metadata: Audit_Metadata
|
||||
|
||||
@@ -112,6 +116,24 @@ class AzureProvider(Provider):
|
||||
arguments, bulk_checks_metadata, self._identity
|
||||
)
|
||||
|
||||
@property
|
||||
def mutelist(self) -> AzureMutelist:
|
||||
"""
|
||||
mutelist method returns the provider's mutelist.
|
||||
"""
|
||||
return self._mutelist
|
||||
|
||||
@mutelist.setter
|
||||
def mutelist(self, mutelist_path):
|
||||
"""
|
||||
mutelist.setter sets the provider's mutelist.
|
||||
"""
|
||||
# Set default mutelist path if none is set
|
||||
if not mutelist_path:
|
||||
mutelist_path = get_default_mute_file_path(self.type)
|
||||
|
||||
self._mutelist = AzureMutelist(mutelist_path)
|
||||
|
||||
@property
|
||||
def get_output_mapping(self):
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
from typing import Any
|
||||
|
||||
from prowler.lib.mutelist.mutelist import Mutelist
|
||||
from prowler.lib.outputs.utils import unroll_tags
|
||||
|
||||
|
||||
class AzureMutelist(Mutelist):
|
||||
def is_finding_muted(
|
||||
self,
|
||||
finding: Any,
|
||||
) -> bool:
|
||||
return self.is_muted(
|
||||
finding.subscription,
|
||||
finding.check_metadata.CheckID,
|
||||
finding.location,
|
||||
finding.resource_name,
|
||||
unroll_tags(finding.resource_tags),
|
||||
)
|
||||
@@ -5,12 +5,8 @@ from abc import ABC, abstractmethod
|
||||
from importlib import import_module
|
||||
from typing import Any, Optional
|
||||
|
||||
from prowler.config.config import get_default_mute_file_path
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.mutelist.mutelist import (
|
||||
get_mutelist_file_from_local_file,
|
||||
validate_mutelist,
|
||||
)
|
||||
from prowler.lib.mutelist.mutelist import Mutelist
|
||||
|
||||
providers_path = "prowler.providers"
|
||||
|
||||
@@ -30,8 +26,7 @@ providers_path = "prowler.providers"
|
||||
# TODO: enforce audit_metadata for all the providers
|
||||
class Provider(ABC):
|
||||
_global: Optional["Provider"] = None
|
||||
_mutelist: dict
|
||||
_mutelist_file_path: str
|
||||
mutelist: Mutelist
|
||||
"""
|
||||
The Provider class is an abstract base class that defines the interface for all provider classes in the auditing system.
|
||||
|
||||
@@ -158,37 +153,6 @@ class Provider(ABC):
|
||||
"""
|
||||
return set()
|
||||
|
||||
@property
|
||||
def mutelist(self):
|
||||
"""
|
||||
mutelist method returns the provider's mutelist.
|
||||
"""
|
||||
return self._mutelist
|
||||
|
||||
@property
|
||||
def mutelist_file_path(self):
|
||||
"""
|
||||
mutelist method returns the provider's mutelist file path.
|
||||
"""
|
||||
return self._mutelist_file_path
|
||||
|
||||
@mutelist.setter
|
||||
def mutelist(self, mutelist_path):
|
||||
"""
|
||||
mutelist.setter sets the provider's mutelist.
|
||||
"""
|
||||
# Set default mutelist path if none is set
|
||||
if not mutelist_path:
|
||||
mutelist_path = get_default_mute_file_path(self.type)
|
||||
if mutelist_path:
|
||||
mutelist = get_mutelist_file_from_local_file(mutelist_path)
|
||||
mutelist = validate_mutelist(mutelist)
|
||||
else:
|
||||
mutelist = {}
|
||||
|
||||
self._mutelist = mutelist
|
||||
self._mutelist_file_path = mutelist_path
|
||||
|
||||
@staticmethod
|
||||
def get_global_provider() -> "Provider":
|
||||
return Provider._global
|
||||
|
||||
@@ -9,11 +9,15 @@ from google.oauth2.credentials import Credentials
|
||||
from googleapiclient import discovery
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
from prowler.config.config import load_and_validate_config_file
|
||||
from prowler.config.config import (
|
||||
get_default_mute_file_path,
|
||||
load_and_validate_config_file,
|
||||
)
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.utils.utils import print_boxes
|
||||
from prowler.providers.common.models import Audit_Metadata
|
||||
from prowler.providers.common.provider import Provider
|
||||
from prowler.providers.gcp.lib.mutelist.mutelist import GCPMutelist
|
||||
from prowler.providers.gcp.models import (
|
||||
GCPIdentityInfo,
|
||||
GCPOrganization,
|
||||
@@ -30,7 +34,7 @@ class GcpProvider(Provider):
|
||||
_identity: GCPIdentityInfo
|
||||
_audit_config: dict
|
||||
_output_options: GCPOutputOptions
|
||||
_mutelist: dict
|
||||
_mutelist: GCPMutelist
|
||||
# TODO: this is not optional, enforce for all providers
|
||||
audit_metadata: Audit_Metadata
|
||||
|
||||
@@ -155,6 +159,24 @@ class GcpProvider(Provider):
|
||||
arguments, bulk_checks_metadata, self._identity
|
||||
)
|
||||
|
||||
@property
|
||||
def mutelist(self) -> GCPMutelist:
|
||||
"""
|
||||
mutelist method returns the provider's mutelist.
|
||||
"""
|
||||
return self._mutelist
|
||||
|
||||
@mutelist.setter
|
||||
def mutelist(self, mutelist_path):
|
||||
"""
|
||||
mutelist.setter sets the provider's mutelist.
|
||||
"""
|
||||
# Set default mutelist path if none is set
|
||||
if not mutelist_path:
|
||||
mutelist_path = get_default_mute_file_path(self.type)
|
||||
|
||||
self._mutelist = GCPMutelist(mutelist_path)
|
||||
|
||||
@property
|
||||
def get_output_mapping(self):
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
from typing import Any
|
||||
|
||||
from prowler.lib.mutelist.mutelist import Mutelist
|
||||
from prowler.lib.outputs.utils import unroll_tags
|
||||
|
||||
|
||||
class GCPMutelist(Mutelist):
|
||||
def is_finding_muted(
|
||||
self,
|
||||
finding: Any,
|
||||
) -> bool:
|
||||
return self.is_muted(
|
||||
finding.project_id,
|
||||
finding.check_metadata.CheckID,
|
||||
finding.location,
|
||||
finding.resource_name,
|
||||
unroll_tags(finding.resource_tags),
|
||||
)
|
||||
@@ -6,11 +6,15 @@ from colorama import Fore, Style
|
||||
from kubernetes.config.config_exception import ConfigException
|
||||
|
||||
from kubernetes import client, config
|
||||
from prowler.config.config import load_and_validate_config_file
|
||||
from prowler.config.config import (
|
||||
get_default_mute_file_path,
|
||||
load_and_validate_config_file,
|
||||
)
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.lib.utils.utils import print_boxes
|
||||
from prowler.providers.common.models import Audit_Metadata
|
||||
from prowler.providers.common.provider import Provider
|
||||
from prowler.providers.kubernetes.lib.mutelist.mutelist import KubernetesMutelist
|
||||
from prowler.providers.kubernetes.models import (
|
||||
KubernetesIdentityInfo,
|
||||
KubernetesOutputOptions,
|
||||
@@ -97,6 +101,24 @@ class KubernetesProvider(Provider):
|
||||
arguments, bulk_checks_metadata, self._identity
|
||||
)
|
||||
|
||||
@property
|
||||
def mutelist(self) -> KubernetesMutelist:
|
||||
"""
|
||||
mutelist method returns the provider's mutelist.
|
||||
"""
|
||||
return self._mutelist
|
||||
|
||||
@mutelist.setter
|
||||
def mutelist(self, mutelist_path):
|
||||
"""
|
||||
mutelist.setter sets the provider's mutelist.
|
||||
"""
|
||||
# Set default mutelist path if none is set
|
||||
if not mutelist_path:
|
||||
mutelist_path = get_default_mute_file_path(self.type)
|
||||
|
||||
self._mutelist = KubernetesMutelist(mutelist_path)
|
||||
|
||||
@property
|
||||
def get_output_mapping(self):
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
from typing import Any
|
||||
|
||||
from prowler.lib.mutelist.mutelist import Mutelist
|
||||
from prowler.lib.outputs.utils import unroll_tags
|
||||
|
||||
|
||||
class KubernetesMutelist(Mutelist):
|
||||
def is_finding_muted(
|
||||
self,
|
||||
finding: Any,
|
||||
cluster: str,
|
||||
) -> bool:
|
||||
return self.is_muted(
|
||||
cluster,
|
||||
finding.check_metadata.CheckID,
|
||||
finding.namespace,
|
||||
finding.resource_name,
|
||||
unroll_tags(finding.resource_tags),
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,7 @@ from prowler.providers.aws.config import (
|
||||
BOTO3_USER_AGENT_EXTRA,
|
||||
)
|
||||
from prowler.providers.aws.lib.arn.models import ARN
|
||||
from prowler.providers.aws.lib.mutelist.mutelist import AWSMutelist
|
||||
from prowler.providers.aws.models import (
|
||||
AWSAssumeRoleInfo,
|
||||
AWSCallerIdentity,
|
||||
@@ -607,7 +608,9 @@ aws:
|
||||
|
||||
os.remove(mutelist_file.name)
|
||||
|
||||
assert aws_provider.mutelist == mutelist["Mutelist"]
|
||||
assert isinstance(aws_provider.mutelist, AWSMutelist)
|
||||
assert aws_provider.mutelist.mutelist == mutelist["Mutelist"]
|
||||
assert aws_provider.mutelist.mutelist_file_path == mutelist_file.name
|
||||
|
||||
@mock_aws
|
||||
def test_aws_provider_mutelist_none(self):
|
||||
@@ -620,7 +623,9 @@ aws:
|
||||
):
|
||||
aws_provider.mutelist = None
|
||||
|
||||
assert aws_provider.mutelist == {}
|
||||
assert isinstance(aws_provider.mutelist, AWSMutelist)
|
||||
assert aws_provider.mutelist.mutelist == {}
|
||||
assert aws_provider.mutelist.mutelist_file_path is None
|
||||
|
||||
@mock_aws
|
||||
def test_aws_provider_mutelist_s3(self):
|
||||
@@ -670,7 +675,9 @@ aws:
|
||||
aws_provider.mutelist = mutelist_bucket_object_uri
|
||||
os.remove(mutelist_file.name)
|
||||
|
||||
assert aws_provider.mutelist == mutelist["Mutelist"]
|
||||
assert isinstance(aws_provider.mutelist, AWSMutelist)
|
||||
assert aws_provider.mutelist.mutelist == mutelist["Mutelist"]
|
||||
assert aws_provider.mutelist.mutelist_file_path == mutelist_bucket_object_uri
|
||||
|
||||
@mock_aws
|
||||
def test_aws_provider_mutelist_lambda(self):
|
||||
@@ -696,17 +703,19 @@ aws:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lambda_mutelist_path = f"arn:aws:lambda:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:function:lambda-mutelist"
|
||||
arguments = Namespace()
|
||||
aws_provider = AwsProvider(arguments)
|
||||
|
||||
with patch(
|
||||
"prowler.providers.aws.aws_provider.get_mutelist_file_from_lambda",
|
||||
"prowler.providers.aws.lib.mutelist.mutelist.AWSMutelist.get_mutelist_file_from_lambda",
|
||||
return_value=mutelist["Mutelist"],
|
||||
):
|
||||
aws_provider.mutelist = f"arn:aws:lambda:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:function:lambda-mutelist"
|
||||
aws_provider.mutelist = lambda_mutelist_path
|
||||
|
||||
assert aws_provider.mutelist == mutelist["Mutelist"]
|
||||
assert isinstance(aws_provider.mutelist, AWSMutelist)
|
||||
assert aws_provider.mutelist.mutelist == mutelist["Mutelist"]
|
||||
assert aws_provider.mutelist.mutelist_file_path == lambda_mutelist_path
|
||||
|
||||
@mock_aws
|
||||
def test_aws_provider_mutelist_dynamodb(self):
|
||||
@@ -732,17 +741,19 @@ aws:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dynamodb_mutelist_path = f"arn:aws:dynamodb:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:table/mutelist-dynamo"
|
||||
arguments = Namespace()
|
||||
aws_provider = AwsProvider(arguments)
|
||||
|
||||
with patch(
|
||||
"prowler.providers.aws.aws_provider.get_mutelist_file_from_dynamodb",
|
||||
"prowler.providers.aws.lib.mutelist.mutelist.AWSMutelist.get_mutelist_file_from_dynamodb",
|
||||
return_value=mutelist["Mutelist"],
|
||||
):
|
||||
aws_provider.mutelist = f"arn:aws:dynamodb:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:table/mutelist-dynamo"
|
||||
aws_provider.mutelist = dynamodb_mutelist_path
|
||||
|
||||
assert aws_provider.mutelist == mutelist["Mutelist"]
|
||||
assert isinstance(aws_provider.mutelist, AWSMutelist)
|
||||
assert aws_provider.mutelist.mutelist == mutelist["Mutelist"]
|
||||
assert aws_provider.mutelist.mutelist_file_path == dynamodb_mutelist_path
|
||||
|
||||
@mock_aws
|
||||
def test_generate_regional_clients_all_enabled_regions(self):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,68 @@
|
||||
import yaml
|
||||
from mock import MagicMock
|
||||
|
||||
from prowler.providers.azure.lib.mutelist.mutelist import AzureMutelist
|
||||
|
||||
MUTELIST_FIXTURE_PATH = (
|
||||
"tests/providers/azure/lib/mutelist/fixtures/azure_mutelist.yaml"
|
||||
)
|
||||
|
||||
|
||||
class TestAzureMutelist:
|
||||
def test_get_mutelist_file_from_local_file(self):
|
||||
mutelist = AzureMutelist(mutelist_path=MUTELIST_FIXTURE_PATH)
|
||||
|
||||
with open(MUTELIST_FIXTURE_PATH) as f:
|
||||
mutelist_fixture = yaml.safe_load(f)["Mutelist"]
|
||||
|
||||
assert mutelist.mutelist == mutelist_fixture
|
||||
assert mutelist.mutelist_file_path == MUTELIST_FIXTURE_PATH
|
||||
|
||||
def test_get_mutelist_file_from_local_file_non_existent(self):
|
||||
mutelist_path = "tests/lib/mutelist/fixtures/not_present"
|
||||
mutelist = AzureMutelist(mutelist_path=mutelist_path)
|
||||
|
||||
assert mutelist.mutelist == {}
|
||||
assert mutelist.mutelist_file_path == mutelist_path
|
||||
|
||||
def test_validate_mutelist_not_valid_key(self):
|
||||
mutelist_path = MUTELIST_FIXTURE_PATH
|
||||
with open(mutelist_path) as f:
|
||||
mutelist_fixture = yaml.safe_load(f)["Mutelist"]
|
||||
|
||||
mutelist_fixture["Accounts1"] = mutelist_fixture["Accounts"]
|
||||
del mutelist_fixture["Accounts"]
|
||||
|
||||
mutelist = AzureMutelist(mutelist_content=mutelist_fixture)
|
||||
|
||||
assert not mutelist.validate_mutelist()
|
||||
assert mutelist.mutelist == {}
|
||||
assert mutelist.mutelist_file_path is None
|
||||
|
||||
def test_is_finding_muted(self):
|
||||
# Mutelist
|
||||
mutelist_content = {
|
||||
"Accounts": {
|
||||
"subscription_1": {
|
||||
"Checks": {
|
||||
"check_test": {
|
||||
"Regions": ["*"],
|
||||
"Resources": ["test_resource"],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutelist = AzureMutelist(mutelist_content=mutelist_content)
|
||||
|
||||
finding = MagicMock
|
||||
finding.check_metadata = MagicMock
|
||||
finding.check_metadata.CheckID = "check_test"
|
||||
finding.location = "West Europe"
|
||||
finding.status = "FAIL"
|
||||
finding.resource_name = "test_resource"
|
||||
finding.resource_tags = []
|
||||
finding.subscription = "subscription_1"
|
||||
|
||||
assert mutelist.is_finding_muted(finding)
|
||||
@@ -0,0 +1,16 @@
|
||||
### Account, Check and/or Region can be * to apply for all the cases.
|
||||
### Resources and tags are lists that can have either Regex or Keywords.
|
||||
### Tags is an optional list that matches on tuples of 'key=value' and are "ANDed" together.
|
||||
### Use an alternation Regex to match one of multiple tags with "ORed" logic.
|
||||
### For each check you can except Accounts, Regions, Resources and/or Tags.
|
||||
########################### MUTELIST EXAMPLE ###########################
|
||||
Mutelist:
|
||||
Accounts:
|
||||
"subscription_1":
|
||||
Checks:
|
||||
"aks_cluster_rbac_enabled":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "resource_1"
|
||||
- "resource_2"
|
||||
@@ -0,0 +1,16 @@
|
||||
### Account, Check and/or Region can be * to apply for all the cases.
|
||||
### Resources and tags are lists that can have either Regex or Keywords.
|
||||
### Tags is an optional list that matches on tuples of 'key=value' and are "ANDed" together.
|
||||
### Use an alternation Regex to match one of multiple tags with "ORed" logic.
|
||||
### For each check you can except Accounts, Regions, Resources and/or Tags.
|
||||
########################### MUTELIST EXAMPLE ###########################
|
||||
Mutelist:
|
||||
Accounts:
|
||||
"project_1":
|
||||
Checks:
|
||||
"apikeys_api_restrictions_configured":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "resource_1"
|
||||
- "resource_2"
|
||||
@@ -0,0 +1,66 @@
|
||||
import yaml
|
||||
from mock import MagicMock
|
||||
|
||||
from prowler.providers.gcp.lib.mutelist.mutelist import GCPMutelist
|
||||
|
||||
MUTELIST_FIXTURE_PATH = "tests/providers/gcp/lib/mutelist/fixtures/gcp_mutelist.yaml"
|
||||
|
||||
|
||||
class TestGCPMutelist:
|
||||
def test_get_mutelist_file_from_local_file(self):
|
||||
mutelist = GCPMutelist(mutelist_path=MUTELIST_FIXTURE_PATH)
|
||||
|
||||
with open(MUTELIST_FIXTURE_PATH) as f:
|
||||
mutelist_fixture = yaml.safe_load(f)["Mutelist"]
|
||||
|
||||
assert mutelist.mutelist == mutelist_fixture
|
||||
assert mutelist.mutelist_file_path == MUTELIST_FIXTURE_PATH
|
||||
|
||||
def test_get_mutelist_file_from_local_file_non_existent(self):
|
||||
mutelist_path = "tests/lib/mutelist/fixtures/not_present"
|
||||
mutelist = GCPMutelist(mutelist_path=mutelist_path)
|
||||
|
||||
assert mutelist.mutelist == {}
|
||||
assert mutelist.mutelist_file_path == mutelist_path
|
||||
|
||||
def test_validate_mutelist_not_valid_key(self):
|
||||
mutelist_path = MUTELIST_FIXTURE_PATH
|
||||
with open(mutelist_path) as f:
|
||||
mutelist_fixture = yaml.safe_load(f)["Mutelist"]
|
||||
|
||||
mutelist_fixture["Accounts1"] = mutelist_fixture["Accounts"]
|
||||
del mutelist_fixture["Accounts"]
|
||||
|
||||
mutelist = GCPMutelist(mutelist_content=mutelist_fixture)
|
||||
|
||||
assert not mutelist.validate_mutelist()
|
||||
assert mutelist.mutelist == {}
|
||||
assert mutelist.mutelist_file_path is None
|
||||
|
||||
def test_is_finding_muted(self):
|
||||
# Mutelist
|
||||
mutelist_content = {
|
||||
"Accounts": {
|
||||
"project_1": {
|
||||
"Checks": {
|
||||
"check_test": {
|
||||
"Regions": ["*"],
|
||||
"Resources": ["test_resource"],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutelist = GCPMutelist(mutelist_content=mutelist_content)
|
||||
|
||||
finding = MagicMock
|
||||
finding.check_metadata = MagicMock
|
||||
finding.check_metadata.CheckID = "check_test"
|
||||
finding.status = "FAIL"
|
||||
finding.resource_name = "test_resource"
|
||||
finding.location = "test-location"
|
||||
finding.resource_tags = []
|
||||
finding.project_id = "project_1"
|
||||
|
||||
assert mutelist.is_finding_muted(finding)
|
||||
@@ -0,0 +1,16 @@
|
||||
### Account, Check and/or Region can be * to apply for all the cases.
|
||||
### Resources and tags are lists that can have either Regex or Keywords.
|
||||
### Tags is an optional list that matches on tuples of 'key=value' and are "ANDed" together.
|
||||
### Use an alternation Regex to match one of multiple tags with "ORed" logic.
|
||||
### For each check you can except Accounts, Regions, Resources and/or Tags.
|
||||
########################### MUTELIST EXAMPLE ###########################
|
||||
Mutelist:
|
||||
Accounts:
|
||||
"cluster_1":
|
||||
Checks:
|
||||
"controllermanager_bind_address":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "resource_1"
|
||||
- "resource_2"
|
||||
@@ -0,0 +1,99 @@
|
||||
import yaml
|
||||
from mock import MagicMock
|
||||
|
||||
from prowler.providers.kubernetes.lib.mutelist.mutelist import KubernetesMutelist
|
||||
|
||||
MUTELIST_FIXTURE_PATH = (
|
||||
"tests/providers/kubernetes/lib/mutelist/fixtures/kubernetes_mutelist.yaml"
|
||||
)
|
||||
|
||||
|
||||
class TestKubernetesMutelist:
|
||||
def test_get_mutelist_file_from_local_file(self):
|
||||
mutelist = KubernetesMutelist(mutelist_path=MUTELIST_FIXTURE_PATH)
|
||||
|
||||
with open(MUTELIST_FIXTURE_PATH) as f:
|
||||
mutelist_fixture = yaml.safe_load(f)["Mutelist"]
|
||||
|
||||
assert mutelist.mutelist == mutelist_fixture
|
||||
assert mutelist.mutelist_file_path == MUTELIST_FIXTURE_PATH
|
||||
|
||||
def test_get_mutelist_file_from_local_file_non_existent(self):
|
||||
mutelist_path = "tests/lib/mutelist/fixtures/not_present"
|
||||
mutelist = KubernetesMutelist(mutelist_path=mutelist_path)
|
||||
|
||||
assert mutelist.mutelist == {}
|
||||
assert mutelist.mutelist_file_path == mutelist_path
|
||||
|
||||
def test_validate_mutelist_not_valid_key(self):
|
||||
mutelist_path = MUTELIST_FIXTURE_PATH
|
||||
with open(mutelist_path) as f:
|
||||
mutelist_fixture = yaml.safe_load(f)["Mutelist"]
|
||||
|
||||
mutelist_fixture["Accounts1"] = mutelist_fixture["Accounts"]
|
||||
del mutelist_fixture["Accounts"]
|
||||
|
||||
mutelist = KubernetesMutelist(mutelist_content=mutelist_fixture)
|
||||
|
||||
assert not mutelist.validate_mutelist()
|
||||
assert mutelist.mutelist == {}
|
||||
assert mutelist.mutelist_file_path is None
|
||||
|
||||
def test_is_finding_muted(self):
|
||||
# Mutelist
|
||||
mutelist_content = {
|
||||
"Accounts": {
|
||||
"cluster_1": {
|
||||
"Checks": {
|
||||
"check_test": {
|
||||
# TODO: review this with Sergio
|
||||
"Regions": ["*"],
|
||||
"Resources": ["test_resource"],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutelist = KubernetesMutelist(mutelist_content=mutelist_content)
|
||||
|
||||
finding = MagicMock
|
||||
finding.check_metadata = MagicMock
|
||||
finding.check_metadata.CheckID = "check_test"
|
||||
finding.status = "FAIL"
|
||||
finding.resource_name = "test_resource"
|
||||
finding.namespace = "test-location"
|
||||
finding.resource_tags = []
|
||||
|
||||
assert mutelist.is_finding_muted(finding, "cluster_1")
|
||||
|
||||
def test_is_finding_muted_etcd_star_within_check_name(self):
|
||||
# Mutelist
|
||||
mutelist_content = {
|
||||
"Accounts": {
|
||||
"*": {
|
||||
"Checks": {
|
||||
"etcd_*": {
|
||||
"Regions": ["*"],
|
||||
"Resources": ["*"],
|
||||
"Exceptions": {
|
||||
"Accounts": ["k8s-cluster-2"],
|
||||
"Regions": ["namespace1", "namespace2"],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutelist = KubernetesMutelist(mutelist_content=mutelist_content)
|
||||
|
||||
finding = MagicMock
|
||||
finding.check_metadata = MagicMock
|
||||
finding.check_metadata.CheckID = "apiserver_etcd_cafile_set"
|
||||
finding.status = "FAIL"
|
||||
finding.resource_name = "test_resource"
|
||||
finding.namespace = "namespace1"
|
||||
finding.resource_tags = []
|
||||
|
||||
assert not mutelist.is_finding_muted(finding, "cluster_1")
|
||||
Reference in New Issue
Block a user