Compare commits

...

15 Commits

Author SHA1 Message Date
github-actions
3aaf2b2c27 chore(release): 3.11.1 2023-11-10 11:29:23 +00:00
Sergio Garcia
e7d098ed1e chore(regions_update): Changes in regions for AWS services. (#3020)
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
2023-11-10 11:34:44 +01:00
Sergio Garcia
21fba27355 fix(iam): do not list tags for inline policies (#3014) 2023-11-10 09:51:19 +01:00
John Mastron
74e37307f7 fix(SQS): fix invalid SQS ARNs (#3016)
Co-authored-by: John Mastron <jmastron@jpl.nasa.gov>
2023-11-10 09:33:18 +01:00
Sergio Garcia
d9d7c009a5 fix(rds): check if engines exist in region (#3012) 2023-11-10 09:20:36 +01:00
Pepe Fagoaga
2220cf9733 refactor(allowlist): Simplify and handle corner cases (#3019) 2023-11-10 09:11:52 +01:00
Pepe Fagoaga
3325b72b86 fix(iam-sqs): Handle exceptions for non-existent resources (#3010) 2023-11-08 14:06:45 +01:00
Sergio Garcia
9182d56246 chore(regions_update): Changes in regions for AWS services. (#3011)
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
2023-11-08 10:42:23 +01:00
Nacho Rivera
299ece19a8 fix(clean local output dirs): clean dirs when output to s3 (#2997) 2023-11-08 10:05:24 +01:00
Sergio Garcia
0a0732d7c0 docs(gcp): update GCP permissions (#3008) 2023-11-07 14:06:22 +01:00
Sergio Garcia
28011d97a9 chore(regions_update): Changes in regions for AWS services. (#3007)
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
2023-11-07 11:04:45 +01:00
Sergio Garcia
e71b0d1b6a chore(regions_update): Changes in regions for AWS services. (#3001)
Co-authored-by: sergargar <sergargar@users.noreply.github.com>
2023-11-07 11:04:36 +01:00
John Mastron
ec01b62a82 fix(aws): check all conditions in IAM policy parser (#3006)
Co-authored-by: John Mastron <jmastron@jpl.nasa.gov>
Co-authored-by: Sergio Garcia <sergargar1@gmail.com>
2023-11-07 10:40:34 +01:00
dependabot[bot]
12b45c6896 build(deps): bump google-api-python-client from 2.105.0 to 2.106.0 (#3005)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-07 09:45:51 +01:00
dependabot[bot]
51c60dd4ee build(deps): bump mkdocs-material from 9.4.7 to 9.4.8 (#3004)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-07 09:02:02 +01:00
20 changed files with 876 additions and 340 deletions

View File

@@ -178,11 +178,7 @@ Prowler will follow the same credentials search as [Google authentication librar
2. [User credentials set up by using the Google Cloud CLI](https://cloud.google.com/docs/authentication/application-default-credentials#personal)
3. [The attached service account, returned by the metadata server](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa)
Those credentials must be associated to a user or service account with proper permissions to do all checks. To make sure, add the following roles to the member associated with the credentials:
- Viewer
- Security Reviewer
- Stackdriver Account Viewer
Those credentials must be associated to a user or service account with proper permissions to do all checks. To make sure, add the `Viewer` role to the member associated with the credentials.
> By default, `prowler` will scan all accessible GCP Projects, use flag `--project-ids` to specify the projects to be scanned.

View File

@@ -97,10 +97,6 @@ Prowler will follow the same credentials search as [Google authentication librar
2. [User credentials set up by using the Google Cloud CLI](https://cloud.google.com/docs/authentication/application-default-credentials#personal)
3. [The attached service account, returned by the metadata server](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa)
Those credentials must be associated to a user or service account with proper permissions to do all checks. To make sure, add the following roles to the member associated with the credentials:
- Viewer
- Security Reviewer
- Stackdriver Account Viewer
Those credentials must be associated to a user or service account with proper permissions to do all checks. To make sure, add the `Viewer` role to the member associated with the credentials.
> By default, `prowler` will scan all accessible GCP Projects, use flag `--project-ids` to specify the projects to be scanned.

View File

@@ -22,8 +22,4 @@ Prowler will follow the same credentials search as [Google authentication librar
2. [User credentials set up by using the Google Cloud CLI](https://cloud.google.com/docs/authentication/application-default-credentials#personal)
3. [The attached service account, returned by the metadata server](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa)
Those credentials must be associated to a user or service account with proper permissions to do all checks. To make sure, add the following roles to the member associated with the credentials:
- Viewer
- Security Reviewer
- Stackdriver Account Viewer
Those credentials must be associated to a user or service account with proper permissions to do all checks. To make sure, add the `Viewer` role to the member associated with the credentials.

14
poetry.lock generated
View File

@@ -902,13 +902,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0dev)"]
[[package]]
name = "google-api-python-client"
version = "2.105.0"
version = "2.106.0"
description = "Google API Client Library for Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "google-api-python-client-2.105.0.tar.gz", hash = "sha256:0a8b32cfc2d9b3c1868ae6faef7ee1ab9c89a6cec30be709ea9c97f9a3e5902d"},
{file = "google_api_python_client-2.105.0-py2.py3-none-any.whl", hash = "sha256:571ce7c41e53415e385aab5a955725f71780550683ffcb71596f5809677d40b7"},
{file = "google-api-python-client-2.106.0.tar.gz", hash = "sha256:f6a3862be2f6e5e0536d7bd47b5af3f24ac0b9147c76c830cafb3329d71d5724"},
{file = "google_api_python_client-2.106.0-py2.py3-none-any.whl", hash = "sha256:c47c0dae5dd20aa43e4ea184566fe59d0c8fd0b86dd223b29040d8ea4f7ed6ea"},
]
[package.dependencies]
@@ -1365,13 +1365,13 @@ min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-imp
[[package]]
name = "mkdocs-material"
version = "9.4.7"
version = "9.4.8"
description = "Documentation that simply works"
optional = true
python-versions = ">=3.8"
files = [
{file = "mkdocs_material-9.4.7-py3-none-any.whl", hash = "sha256:4d698d52bb6a6a3c452ab854481c4cdb68453a0420956a6aee2de55fe15fe610"},
{file = "mkdocs_material-9.4.7.tar.gz", hash = "sha256:e704e001c9ef17291e1d3462c202425217601653e18f68f85d28eff4690e662b"},
{file = "mkdocs_material-9.4.8-py3-none-any.whl", hash = "sha256:8b20f6851bddeef37dced903893cd176cf13a21a482e97705a103c45f06ce9b9"},
{file = "mkdocs_material-9.4.8.tar.gz", hash = "sha256:f0c101453e8bc12b040e8b64ca39a405d950d8402609b1378cc2b98976e74b5f"},
]
[package.dependencies]
@@ -2889,4 +2889,4 @@ docs = ["mkdocs", "mkdocs-material"]
[metadata]
lock-version = "2.0"
python-versions = "^3.9"
content-hash = "6ed432b0310655c247da3b4f542b9410842fb46de838408b99b6e61fb367cf38"
content-hash = "d765b7a8c5d49f3a20316d575093430785a29ed5968d315b727455dea803331c"

View File

@@ -47,6 +47,7 @@ from prowler.providers.common.audit_info import (
set_provider_audit_info,
set_provider_execution_parameters,
)
from prowler.providers.common.clean import clean_provider_local_output_directories
from prowler.providers.common.outputs import set_provider_output_options
from prowler.providers.common.quick_inventory import run_provider_quick_inventory
@@ -301,6 +302,9 @@ def prowler():
if checks_folder:
remove_custom_checks_module(checks_folder, provider)
# clean local directories
clean_provider_local_output_directories(args)
# If there are failed findings exit code 3, except if -z is input
if not args.ignore_exit_code_3 and stats["total_fail"] > 0:
sys.exit(3)

View File

@@ -11,7 +11,7 @@ from prowler.lib.logger import logger
timestamp = datetime.today()
timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc)
prowler_version = "3.11.0"
prowler_version = "3.11.1"
html_logo_url = "https://github.com/prowler-cloud/prowler/"
html_logo_img = "https://user-images.githubusercontent.com/3985464/113734260-7ba06900-96fb-11eb-82bc-d4f68a1e2710.png"
square_logo_img = "https://user-images.githubusercontent.com/38561120/235905862-9ece5bd7-9aa3-4e48-807a-3a9035eb8bfb.png"

View File

@@ -674,10 +674,13 @@
"regions": {
"aws": [
"ap-northeast-1",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"eu-central-1",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"us-east-1",
"us-east-2",
"us-west-2"
@@ -1013,6 +1016,17 @@
]
}
},
"aws-appfabric": {
"regions": {
"aws": [
"ap-northeast-1",
"eu-west-1",
"us-east-1"
],
"aws-cn": [],
"aws-us-gov": []
}
},
"awshealthdashboard": {
"regions": {
"aws": [
@@ -2068,7 +2082,9 @@
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ca-central-1",
@@ -2079,6 +2095,7 @@
"eu-west-1",
"eu-west-2",
"eu-west-3",
"me-central-1",
"me-south-1",
"sa-east-1",
"us-east-1",
@@ -2467,6 +2484,7 @@
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"eu-central-1",
"eu-central-2",
@@ -4336,16 +4354,6 @@
"aws-us-gov": []
}
},
"gamesparks": {
"regions": {
"aws": [
"ap-northeast-1",
"us-east-1"
],
"aws-cn": [],
"aws-us-gov": []
}
},
"glacier": {
"regions": {
"aws": [
@@ -6062,6 +6070,15 @@
]
}
},
"managedblockchain-query": {
"regions": {
"aws": [
"us-east-1"
],
"aws-cn": [],
"aws-us-gov": []
}
},
"managedservices": {
"regions": {
"aws": [
@@ -6388,11 +6405,18 @@
"aws": [
"af-south-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-4",
"ca-central-1",
"eu-central-1",
"eu-north-1",
"eu-west-1",
"eu-west-3",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-2"
@@ -7152,6 +7176,41 @@
"aws-us-gov": []
}
},
"pca-connector-ad": {
"regions": {
"aws": [
"af-south-1",
"ap-east-1",
"ap-northeast-1",
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-3",
"ap-southeast-4",
"ca-central-1",
"eu-central-1",
"eu-central-2",
"eu-north-1",
"eu-south-1",
"eu-south-2",
"eu-west-1",
"eu-west-2",
"eu-west-3",
"me-central-1",
"me-south-1",
"sa-east-1",
"us-east-1",
"us-east-2",
"us-west-1",
"us-west-2"
],
"aws-cn": [],
"aws-us-gov": []
}
},
"personalize": {
"regions": {
"aws": [
@@ -9928,6 +9987,7 @@
"ap-northeast-2",
"ap-northeast-3",
"ap-south-1",
"ap-south-2",
"ap-southeast-1",
"ap-southeast-2",
"ap-southeast-4",

View File

@@ -135,7 +135,12 @@ def allowlist_findings(
def is_allowlisted(
allowlist: dict, audited_account: str, check: str, region: str, resource: str, tags
allowlist: dict,
audited_account: str,
check: str,
finding_region: str,
finding_resource: str,
finding_tags,
):
try:
allowlisted_checks = {}
@@ -150,15 +155,15 @@ def is_allowlisted(
if "*" in allowlist["Accounts"]:
checks_multi_account = allowlist["Accounts"]["*"]["Checks"]
allowlisted_checks.update(checks_multi_account)
# Test if it is allowlisted
if is_allowlisted_in_check(
allowlisted_checks,
audited_account,
audited_account,
check,
region,
resource,
tags,
finding_region,
finding_resource,
finding_tags,
):
is_finding_allowlisted = True
@@ -171,23 +176,29 @@ def is_allowlisted(
def is_allowlisted_in_check(
allowlisted_checks, audited_account, account, check, region, resource, tags
allowlisted_checks,
audited_account,
check,
finding_region,
finding_resource,
finding_tags,
):
try:
# Default value is not allowlisted
is_check_allowlisted = False
for allowlisted_check, allowlisted_check_info in allowlisted_checks.items():
# map lambda to awslambda
allowlisted_check = re.sub("^lambda", "awslambda", allowlisted_check)
# extract the exceptions
# Check if the finding is excepted
exceptions = allowlisted_check_info.get("Exceptions")
# Check if there are exceptions
if is_excepted(
exceptions,
audited_account,
region,
resource,
tags,
finding_region,
finding_resource,
finding_tags,
):
# Break loop and return default value since is excepted
break
@@ -201,13 +212,27 @@ def is_allowlisted_in_check(
or check == allowlisted_check
or re.search(allowlisted_check, check)
):
if is_allowlisted_in_region(
allowlisted_regions,
allowlisted_resources,
allowlisted_tags,
region,
resource,
tags,
allowlisted_in_check = True
allowlisted_in_region = is_allowlisted_in_region(
allowlisted_regions, finding_region
)
allowlisted_in_resource = is_allowlisted_in_resource(
allowlisted_resources, finding_resource
)
allowlisted_in_tags = is_allowlisted_in_tags(
allowlisted_tags, finding_tags
)
# For a finding to be allowlisted requires the following set to True:
# - allowlisted_in_check -> True
# - allowlisted_in_region -> True
# - allowlisted_in_tags -> True or allowlisted_in_resource -> True
# - excepted -> False
if (
allowlisted_in_check
and allowlisted_in_region
and (allowlisted_in_tags or allowlisted_in_resource)
):
is_check_allowlisted = True
@@ -220,25 +245,11 @@ def is_allowlisted_in_check(
def is_allowlisted_in_region(
allowlist_regions, allowlist_resources, allowlisted_tags, region, resource, tags
allowlisted_regions,
finding_region,
):
try:
# By default is not allowlisted
is_region_allowlisted = False
# If there is a *, it affects to all regions
if "*" in allowlist_regions or region in allowlist_regions:
for elem in allowlist_resources:
if is_allowlisted_in_tags(
allowlisted_tags,
elem,
resource,
tags,
):
is_region_allowlisted = True
# if we find the element there is no point in continuing with the loop
break
return is_region_allowlisted
return __is_item_matched__(allowlisted_regions, finding_region)
except Exception as error:
logger.critical(
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
@@ -246,25 +257,9 @@ def is_allowlisted_in_region(
sys.exit(1)
def is_allowlisted_in_tags(allowlisted_tags, elem, resource, tags):
def is_allowlisted_in_tags(allowlisted_tags, finding_tags):
try:
# By default is not allowlisted
is_tag_allowlisted = False
# Check if it is an *
if elem == "*":
elem = ".*"
# Check if there are allowlisted tags
if allowlisted_tags:
for allowlisted_tag in allowlisted_tags:
if re.search(allowlisted_tag, tags):
is_tag_allowlisted = True
break
else:
if re.search(elem, resource):
is_tag_allowlisted = True
return is_tag_allowlisted
return __is_item_matched__(allowlisted_tags, finding_tags)
except Exception as error:
logger.critical(
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
@@ -272,7 +267,25 @@ def is_allowlisted_in_tags(allowlisted_tags, elem, resource, tags):
sys.exit(1)
def is_excepted(exceptions, audited_account, region, resource, tags):
def is_allowlisted_in_resource(allowlisted_resources, finding_resource):
try:
return __is_item_matched__(allowlisted_resources, finding_resource)
except Exception as error:
logger.critical(
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
)
sys.exit(1)
def is_excepted(
exceptions,
audited_account,
finding_region,
finding_resource,
finding_tags,
):
"""is_excepted returns True if the account, region, resource and tags are excepted"""
try:
excepted = False
is_account_excepted = False
@@ -281,39 +294,50 @@ def is_excepted(exceptions, audited_account, region, resource, tags):
is_tag_excepted = False
if exceptions:
excepted_accounts = exceptions.get("Accounts", [])
is_account_excepted = __is_item_matched__(
excepted_accounts, audited_account
)
excepted_regions = exceptions.get("Regions", [])
is_region_excepted = __is_item_matched__(excepted_regions, finding_region)
excepted_resources = exceptions.get("Resources", [])
is_resource_excepted = __is_item_matched__(
excepted_resources, finding_resource
)
excepted_tags = exceptions.get("Tags", [])
if exceptions:
if audited_account in excepted_accounts:
is_account_excepted = True
if region in excepted_regions:
is_region_excepted = True
for excepted_resource in excepted_resources:
if re.search(excepted_resource, resource):
is_resource_excepted = True
for tag in excepted_tags:
if tag in tags:
is_tag_excepted = True
if (
(
(excepted_accounts and is_account_excepted)
or not excepted_accounts
)
and (
(excepted_regions and is_region_excepted)
or not excepted_regions
)
and (
(excepted_resources and is_resource_excepted)
or not excepted_resources
)
and ((excepted_tags and is_tag_excepted) or not excepted_tags)
):
excepted = True
is_tag_excepted = __is_item_matched__(excepted_tags, finding_tags)
if (
is_account_excepted
and is_region_excepted
and is_resource_excepted
and is_tag_excepted
):
excepted = True
return excepted
except Exception as error:
logger.critical(
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
)
sys.exit(1)
def __is_item_matched__(matched_items, finding_items):
"""__is_item_matched__ return True if any of the matched_items are present in the finding_items, otherwise returns False."""
try:
is_item_matched = False
if matched_items and (finding_items or finding_items == ""):
for item in matched_items:
if item == "*":
item = ".*"
if re.search(item, finding_items):
is_item_matched = True
break
return is_item_matched
except Exception as error:
logger.critical(
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
)
sys.exit(1)

View File

@@ -56,12 +56,15 @@ def is_account_only_allowed_in_condition(
):
# if there is an arn/account without the source account -> we do not consider it safe
# here by default we assume is true and look for false entries
is_condition_valid = True
is_condition_key_restrictive = True
for item in condition_statement[condition_operator][value]:
if source_account not in item:
is_condition_valid = False
is_condition_key_restrictive = False
break
if is_condition_key_restrictive:
is_condition_valid = True
# value is a string
elif isinstance(
condition_statement[condition_operator][value],

View File

@@ -139,7 +139,10 @@ class IAM(AWSService):
logger.warning(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
@@ -208,14 +211,24 @@ class IAM(AWSService):
reuse_prevention=reuse_prevention,
hard_expiry=hard_expiry,
)
except Exception as error:
if "NoSuchEntity" in str(error):
except ClientError as error:
if error.response["Error"]["Code"] == "NoSuchEntity":
# Password policy does not exist
stored_password_policy = None
logger.warning(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
finally:
return stored_password_policy
@@ -268,17 +281,22 @@ class IAM(AWSService):
logger.info("IAM - List Attached Group Policies...")
try:
for group in self.groups:
list_attached_group_policies_paginator = self.client.get_paginator(
"list_attached_group_policies"
)
attached_group_policies = []
for page in list_attached_group_policies_paginator.paginate(
GroupName=group.name
):
for attached_group_policy in page["AttachedPolicies"]:
attached_group_policies.append(attached_group_policy)
try:
list_attached_group_policies_paginator = self.client.get_paginator(
"list_attached_group_policies"
)
attached_group_policies = []
for page in list_attached_group_policies_paginator.paginate(
GroupName=group.name
):
for attached_group_policy in page["AttachedPolicies"]:
attached_group_policies.append(attached_group_policy)
group.attached_policies = attached_group_policies
group.attached_policies = attached_group_policies
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
@@ -337,18 +355,33 @@ class IAM(AWSService):
logger.info("IAM - List Attached User Policies...")
try:
for user in self.users:
attached_user_policies = []
get_user_attached_policies_paginator = self.client.get_paginator(
"list_attached_user_policies"
)
for page in get_user_attached_policies_paginator.paginate(
UserName=user.name
):
for policy in page["AttachedPolicies"]:
attached_user_policies.append(policy)
try:
attached_user_policies = []
get_user_attached_policies_paginator = self.client.get_paginator(
"list_attached_user_policies"
)
for page in get_user_attached_policies_paginator.paginate(
UserName=user.name
):
for policy in page["AttachedPolicies"]:
attached_user_policies.append(policy)
user.attached_policies = attached_user_policies
user.attached_policies = attached_user_policies
except ClientError as error:
if error.response["Error"]["Code"] == "NoSuchEntity":
logger.warning(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
@@ -371,10 +404,19 @@ class IAM(AWSService):
role.attached_policies = attached_role_policies
except ClientError as error:
if error.response["Error"]["Code"] == "NoSuchEntityException":
if error.response["Error"]["Code"] == "NoSuchEntity":
logger.warning(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
@@ -639,8 +681,16 @@ class IAM(AWSService):
response = self.client.list_role_tags(RoleName=role.name)["Tags"]
role.tags = response
except ClientError as error:
if error.response["Error"]["Code"] == "NoSuchEntityException":
if error.response["Error"]["Code"] == "NoSuchEntity":
role.tags = []
else:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
@@ -653,8 +703,12 @@ class IAM(AWSService):
response = self.client.list_user_tags(UserName=user.name)["Tags"]
user.tags = response
except ClientError as error:
if error.response["Error"]["Code"] == "NoSuchEntityException":
if error.response["Error"]["Code"] == "NoSuchEntity":
user.tags = []
else:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
@@ -664,13 +718,22 @@ class IAM(AWSService):
try:
for policy in self.policies:
try:
response = self.client.list_policy_tags(PolicyArn=policy.arn)[
"Tags"
]
policy.tags = response
if policy.type != "Inline":
response = self.client.list_policy_tags(PolicyArn=policy.arn)[
"Tags"
]
policy.tags = response
except ClientError as error:
if error.response["Error"]["Code"] == "NoSuchEntityException":
if error.response["Error"]["Code"] == "NoSuchEntity":
policy.tags = []
else:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
@@ -697,9 +760,19 @@ class IAM(AWSService):
]
except ClientError as error:
if error.response["Error"]["Code"] == "NoSuchEntity":
logger.warning(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
@@ -717,6 +790,15 @@ class IAM(AWSService):
"AccessKeyMetadata"
]
except ClientError as error:
if error.response["Error"]["Code"] == "NoSuchEntity":
logger.warning(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)

View File

@@ -13,9 +13,14 @@ class rds_instance_deprecated_engine_version(Check):
report.resource_arn = db_instance.arn
report.resource_tags = db_instance.tags
report.status_extended = f"RDS instance {db_instance.id} is using a deprecated engine {db_instance.engine} with version {db_instance.engine_version}."
if (
db_instance.engine_version
hasattr(
rds_client.db_engines.get(db_instance.region, {}).get(
db_instance.engine, {}
),
"engine_versions",
)
and db_instance.engine_version
in rds_client.db_engines[db_instance.region][
db_instance.engine
].engine_versions

View File

@@ -16,23 +16,30 @@ class SQS(AWSService):
super().__init__(__class__.__name__, audit_info)
self.queues = []
self.__threading_call__(self.__list_queues__)
self.__get_queue_attributes__(self.regional_clients)
self.__get_queue_attributes__()
self.__list_queue_tags__()
def __list_queues__(self, regional_client):
logger.info("SQS - describing queues...")
try:
list_queues_paginator = regional_client.get_paginator("list_queues")
for page in list_queues_paginator.paginate():
# The SQS API uses nonstandard pagination
# you must specify a PageSize if there are more than 1000 queues
for page in list_queues_paginator.paginate(
PaginationConfig={"PageSize": 1000}
):
if "QueueUrls" in page:
for queue in page["QueueUrls"]:
arn = f"arn:{self.audited_partition}:sqs:{regional_client.region}:{self.audited_account}:{queue}"
# the queue name is the last path segment of the url
queue_name = queue.split("/")[-1]
arn = f"arn:{self.audited_partition}:sqs:{regional_client.region}:{self.audited_account}:{queue_name}"
if not self.audit_resources or (
is_resource_filtered(arn, self.audit_resources)
):
self.queues.append(
Queue(
arn=arn,
name=queue_name,
id=queue,
region=regional_client.region,
)
@@ -42,28 +49,46 @@ class SQS(AWSService):
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def __get_queue_attributes__(self, regional_clients):
def __get_queue_attributes__(self):
try:
logger.info("SQS - describing queue attributes...")
for queue in self.queues:
regional_client = regional_clients[queue.region]
queue_attributes = regional_client.get_queue_attributes(
QueueUrl=queue.id, AttributeNames=["All"]
)
if "Attributes" in queue_attributes:
if "Policy" in queue_attributes["Attributes"]:
queue.policy = loads(queue_attributes["Attributes"]["Policy"])
if "KmsMasterKeyId" in queue_attributes["Attributes"]:
queue.kms_key_id = queue_attributes["Attributes"][
"KmsMasterKeyId"
]
if "SqsManagedSseEnabled" in queue_attributes["Attributes"]:
if (
queue_attributes["Attributes"]["SqsManagedSseEnabled"]
== "true"
):
queue.kms_key_id = "SqsManagedSseEnabled"
try:
regional_client = self.regional_clients[queue.region]
queue_attributes = regional_client.get_queue_attributes(
QueueUrl=queue.id, AttributeNames=["All"]
)
if "Attributes" in queue_attributes:
if "Policy" in queue_attributes["Attributes"]:
queue.policy = loads(
queue_attributes["Attributes"]["Policy"]
)
if "KmsMasterKeyId" in queue_attributes["Attributes"]:
queue.kms_key_id = queue_attributes["Attributes"][
"KmsMasterKeyId"
]
if "SqsManagedSseEnabled" in queue_attributes["Attributes"]:
if (
queue_attributes["Attributes"]["SqsManagedSseEnabled"]
== "true"
):
queue.kms_key_id = "SqsManagedSseEnabled"
except ClientError as error:
if (
error.response["Error"]["Code"]
== "AWS.SimpleQueueService.NonExistentQueue"
):
logger.warning(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
@@ -87,6 +112,14 @@ class SQS(AWSService):
logger.warning(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
@@ -96,6 +129,7 @@ class SQS(AWSService):
class Queue(BaseModel):
id: str
name: str
arn: str
region: str
policy: dict = None

View File

@@ -0,0 +1,32 @@
import importlib
import sys
from shutil import rmtree
from prowler.config.config import default_output_directory
from prowler.lib.logger import logger
def clean_provider_local_output_directories(args):
"""
clean_provider_local_output_directories cleans deletes local custom dirs when output is sent to remote provider storage
"""
try:
# import provider cleaning function
provider_clean_function = f"clean_{args.provider}_local_output_directories"
getattr(importlib.import_module(__name__), provider_clean_function)(args)
except AttributeError as attribute_exception:
logger.info(
f"Cleaning local output directories not initialized for provider {args.provider}: {attribute_exception}"
)
except Exception as error:
logger.critical(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
sys.exit(1)
def clean_aws_local_output_directories(args):
"""clean_aws_provider_local_output_directories deletes local custom dirs when output is sent to remote provider storage for aws provider"""
if args.output_bucket or args.output_bucket_no_assume:
if args.output_directory != default_output_directory:
rmtree(args.output_directory)

View File

@@ -22,7 +22,7 @@ packages = [
{include = "prowler"}
]
readme = "README.md"
version = "3.11.0"
version = "3.11.1"
[tool.poetry.dependencies]
alive-progress = "3.1.4"
@@ -38,10 +38,10 @@ boto3 = "1.26.165"
botocore = "1.29.165"
colorama = "0.4.6"
detect-secrets = "1.4.0"
google-api-python-client = "2.105.0"
google-api-python-client = "2.106.0"
google-auth-httplib2 = "^0.1.0"
mkdocs = {version = "1.5.3", optional = true}
mkdocs-material = {version = "9.4.7", optional = true}
mkdocs-material = {version = "9.4.8", optional = true}
msgraph-core = "0.2.2"
pydantic = "1.10.13"
python = "^3.9"

View File

@@ -8,15 +8,18 @@ from prowler.providers.aws.lib.allowlist.allowlist import (
is_allowlisted,
is_allowlisted_in_check,
is_allowlisted_in_region,
is_allowlisted_in_resource,
is_allowlisted_in_tags,
is_excepted,
parse_allowlist_file,
)
from prowler.providers.aws.lib.audit_info.models import AWS_Audit_Info
from prowler.providers.common.models import Audit_Metadata
AWS_ACCOUNT_NUMBER = "123456789012"
AWS_REGION = "us-east-1"
from tests.providers.aws.audit_info_utils import (
AWS_ACCOUNT_NUMBER,
AWS_REGION_EU_WEST_1,
AWS_REGION_US_EAST_1,
)
class Test_Allowlist:
@@ -56,7 +59,7 @@ class Test_Allowlist:
def test_s3_allowlist(self):
audit_info = self.set_mocked_audit_info()
# Create bucket and upload allowlist yaml
s3_resource = resource("s3", region_name=AWS_REGION)
s3_resource = resource("s3", region_name=AWS_REGION_US_EAST_1)
s3_resource.create_bucket(Bucket="test-allowlist")
s3_resource.Object("test-allowlist", "allowlist.yaml").put(
Body=open(
@@ -75,7 +78,7 @@ class Test_Allowlist:
def test_dynamo_allowlist(self):
audit_info = self.set_mocked_audit_info()
# Create table and put item
dynamodb_resource = resource("dynamodb", region_name=AWS_REGION)
dynamodb_resource = resource("dynamodb", region_name=AWS_REGION_US_EAST_1)
table_name = "test-allowlist"
params = {
"TableName": table_name,
@@ -97,7 +100,7 @@ class Test_Allowlist:
Item={
"Accounts": "*",
"Checks": "iam_user_hardware_mfa_enabled",
"Regions": ["eu-west-1", AWS_REGION],
"Regions": [AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1],
"Resources": ["keyword"],
}
)
@@ -107,7 +110,7 @@ class Test_Allowlist:
in parse_allowlist_file(
audit_info,
"arn:aws:dynamodb:"
+ AWS_REGION
+ AWS_REGION_US_EAST_1
+ ":"
+ str(AWS_ACCOUNT_NUMBER)
+ ":table/"
@@ -119,7 +122,7 @@ class Test_Allowlist:
def test_dynamo_allowlist_with_tags(self):
audit_info = self.set_mocked_audit_info()
# Create table and put item
dynamodb_resource = resource("dynamodb", region_name=AWS_REGION)
dynamodb_resource = resource("dynamodb", region_name=AWS_REGION_US_EAST_1)
table_name = "test-allowlist"
params = {
"TableName": table_name,
@@ -152,7 +155,7 @@ class Test_Allowlist:
in parse_allowlist_file(
audit_info,
"arn:aws:dynamodb:"
+ AWS_REGION
+ AWS_REGION_US_EAST_1
+ ":"
+ str(AWS_ACCOUNT_NUMBER)
+ ":table/"
@@ -169,7 +172,7 @@ class Test_Allowlist:
"*": {
"Checks": {
"check_test": {
"Regions": [AWS_REGION, "eu-west-1"],
"Regions": [AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1],
"Resources": ["prowler", "^test", "prowler-pro"],
}
}
@@ -183,7 +186,7 @@ class Test_Allowlist:
finding_1.check_metadata = MagicMock
finding_1.check_metadata.CheckID = "check_test"
finding_1.status = "FAIL"
finding_1.region = AWS_REGION
finding_1.region = AWS_REGION_US_EAST_1
finding_1.resource_id = "prowler"
finding_1.resource_tags = []
@@ -195,6 +198,66 @@ class Test_Allowlist:
assert len(allowlisted_findings) == 1
assert allowlisted_findings[0].status == "WARNING"
def test_is_allowlisted_with_everything_excepted(self):
allowlist = {
"Accounts": {
"*": {
"Checks": {
"athena_*": {
"Regions": "*",
"Resources": "*",
"Tags": "*",
"Exceptions": {
"Accounts": ["*"],
"Regions": ["*"],
"Resources": ["*"],
"Tags": ["*"],
},
}
}
}
}
}
assert not is_allowlisted(
allowlist,
AWS_ACCOUNT_NUMBER,
"athena_1",
AWS_REGION_US_EAST_1,
"prowler",
"",
)
def test_is_allowlisted_with_default_allowlist(self):
allowlist = {
"Accounts": {
"*": {
"Checks": {
"*": {
"Tags": ["*"],
"Regions": ["*"],
"Resources": ["*"],
"Exceptions": {
"Tags": [],
"Regions": [],
"Accounts": [],
"Resources": [],
},
}
}
}
}
}
assert is_allowlisted(
allowlist,
AWS_ACCOUNT_NUMBER,
"athena_1",
AWS_REGION_US_EAST_1,
"prowler",
"",
)
def test_is_allowlisted(self):
# Allowlist example
allowlist = {
@@ -202,7 +265,7 @@ class Test_Allowlist:
"*": {
"Checks": {
"check_test": {
"Regions": [AWS_REGION, "eu-west-1"],
"Regions": [AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1],
"Resources": ["prowler", "^test", "prowler-pro"],
}
}
@@ -211,22 +274,37 @@ class Test_Allowlist:
}
assert is_allowlisted(
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", ""
)
assert is_allowlisted(
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler-test", ""
)
assert is_allowlisted(
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "test-prowler", ""
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION_US_EAST_1,
"prowler",
"",
)
assert is_allowlisted(
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION,
AWS_REGION_US_EAST_1,
"prowler-test",
"",
)
assert is_allowlisted(
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION_US_EAST_1,
"test-prowler",
"",
)
assert is_allowlisted(
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION_US_EAST_1,
"prowler-pro-test",
"",
)
@@ -244,7 +322,7 @@ class Test_Allowlist:
"*": {
"Checks": {
"check_test": {
"Regions": [AWS_REGION, "eu-west-1"],
"Regions": [AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1],
"Resources": [".*"],
}
}
@@ -253,15 +331,30 @@ class Test_Allowlist:
}
assert is_allowlisted(
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", ""
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION_US_EAST_1,
"prowler",
"",
)
assert is_allowlisted(
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler-test", ""
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION_US_EAST_1,
"prowler-test",
"",
)
assert is_allowlisted(
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "test-prowler", ""
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION_US_EAST_1,
"test-prowler",
"",
)
assert not (
@@ -277,7 +370,7 @@ class Test_Allowlist:
"*": {
"Checks": {
"check_test": {
"Regions": [AWS_REGION, "eu-west-1"],
"Regions": [AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1],
"Resources": ["*"],
}
}
@@ -286,15 +379,30 @@ class Test_Allowlist:
}
assert is_allowlisted(
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", ""
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION_US_EAST_1,
"prowler",
"",
)
assert is_allowlisted(
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler-test", ""
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION_US_EAST_1,
"prowler-test",
"",
)
assert is_allowlisted(
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "test-prowler", ""
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION_US_EAST_1,
"test-prowler",
"",
)
assert not (
@@ -310,7 +418,7 @@ class Test_Allowlist:
"*": {
"Checks": {
"check_test_2": {
"Regions": [AWS_REGION, "eu-west-1"],
"Regions": [AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1],
"Resources": ["*"],
}
}
@@ -318,7 +426,7 @@ class Test_Allowlist:
AWS_ACCOUNT_NUMBER: {
"Checks": {
"check_test": {
"Regions": [AWS_REGION],
"Regions": [AWS_REGION_US_EAST_1],
"Resources": ["*"],
}
}
@@ -327,19 +435,39 @@ class Test_Allowlist:
}
assert is_allowlisted(
allowlist, AWS_ACCOUNT_NUMBER, "check_test_2", AWS_REGION, "prowler", ""
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test_2",
AWS_REGION_US_EAST_1,
"prowler",
"",
)
assert is_allowlisted(
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", ""
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION_US_EAST_1,
"prowler",
"",
)
assert is_allowlisted(
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler-test", ""
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION_US_EAST_1,
"prowler-test",
"",
)
assert is_allowlisted(
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "test-prowler", ""
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION_US_EAST_1,
"test-prowler",
"",
)
assert not (
@@ -354,7 +482,7 @@ class Test_Allowlist:
AWS_ACCOUNT_NUMBER: {
"Checks": {
"check_test": {
"Regions": [AWS_REGION],
"Regions": [AWS_REGION_US_EAST_1],
"Resources": ["prowler"],
}
}
@@ -363,7 +491,12 @@ class Test_Allowlist:
}
assert is_allowlisted(
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", ""
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION_US_EAST_1,
"prowler",
"",
)
assert not (
@@ -373,47 +506,27 @@ class Test_Allowlist:
)
def test_is_allowlisted_in_region(self):
# Allowlist example
allowlisted_regions = [AWS_REGION, "eu-west-1"]
allowlisted_resources = ["*"]
allowlisted_regions = [AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1]
finding_region = AWS_REGION_US_EAST_1
assert is_allowlisted_in_region(
allowlisted_regions, allowlisted_resources, None, AWS_REGION, "prowler", ""
)
assert is_allowlisted_in_region(allowlisted_regions, finding_region)
assert is_allowlisted_in_region(
allowlisted_regions,
allowlisted_resources,
None,
AWS_REGION,
"prowler-test",
"",
)
def test_is_allowlisted_in_region_wildcard(self):
allowlisted_regions = ["*"]
finding_region = AWS_REGION_US_EAST_1
assert is_allowlisted_in_region(
allowlisted_regions,
allowlisted_resources,
None,
AWS_REGION,
"test-prowler",
"",
)
assert is_allowlisted_in_region(allowlisted_regions, finding_region)
assert not (
is_allowlisted_in_region(
allowlisted_regions,
allowlisted_resources,
None,
"us-east-2",
"test",
"",
)
)
def test_is_not_allowlisted_in_region(self):
allowlisted_regions = [AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1]
finding_region = "eu-west-2"
assert not is_allowlisted_in_region(allowlisted_regions, finding_region)
def test_is_allowlisted_in_check(self):
allowlisted_checks = {
"check_test": {
"Regions": [AWS_REGION, "eu-west-1"],
"Regions": [AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1],
"Resources": ["*"],
}
}
@@ -421,9 +534,8 @@ class Test_Allowlist:
assert is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION,
AWS_REGION_US_EAST_1,
"prowler",
"",
)
@@ -431,9 +543,8 @@ class Test_Allowlist:
assert is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION,
AWS_REGION_US_EAST_1,
"prowler-test",
"",
)
@@ -441,9 +552,8 @@ class Test_Allowlist:
assert is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION,
AWS_REGION_US_EAST_1,
"test-prowler",
"",
)
@@ -452,7 +562,6 @@ class Test_Allowlist:
is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"check_test",
"us-east-2",
"test",
@@ -464,7 +573,7 @@ class Test_Allowlist:
# Allowlist example
allowlisted_checks = {
"s3_*": {
"Regions": [AWS_REGION, "eu-west-1"],
"Regions": [AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1],
"Resources": ["*"],
}
}
@@ -472,9 +581,8 @@ class Test_Allowlist:
assert is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"s3_bucket_public_access",
AWS_REGION,
AWS_REGION_US_EAST_1,
"prowler",
"",
)
@@ -482,9 +590,8 @@ class Test_Allowlist:
assert is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"s3_bucket_no_mfa_delete",
AWS_REGION,
AWS_REGION_US_EAST_1,
"prowler-test",
"",
)
@@ -492,9 +599,8 @@ class Test_Allowlist:
assert is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"s3_bucket_policy_public_write_access",
AWS_REGION,
AWS_REGION_US_EAST_1,
"test-prowler",
"",
)
@@ -503,9 +609,8 @@ class Test_Allowlist:
is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"iam_user_hardware_mfa_enabled",
AWS_REGION,
AWS_REGION_US_EAST_1,
"test",
"",
)
@@ -514,7 +619,7 @@ class Test_Allowlist:
def test_is_allowlisted_lambda_generic_check(self):
allowlisted_checks = {
"lambda_*": {
"Regions": [AWS_REGION, "eu-west-1"],
"Regions": [AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1],
"Resources": ["*"],
}
}
@@ -522,9 +627,8 @@ class Test_Allowlist:
assert is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"awslambda_function_invoke_api_operations_cloudtrail_logging_enabled",
AWS_REGION,
AWS_REGION_US_EAST_1,
"prowler",
"",
)
@@ -532,9 +636,8 @@ class Test_Allowlist:
assert is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"awslambda_function_no_secrets_in_code",
AWS_REGION,
AWS_REGION_US_EAST_1,
"prowler",
"",
)
@@ -542,9 +645,8 @@ class Test_Allowlist:
assert is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"awslambda_function_no_secrets_in_variables",
AWS_REGION,
AWS_REGION_US_EAST_1,
"prowler",
"",
)
@@ -552,9 +654,8 @@ class Test_Allowlist:
assert is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"awslambda_function_not_publicly_accessible",
AWS_REGION,
AWS_REGION_US_EAST_1,
"prowler",
"",
)
@@ -562,9 +663,8 @@ class Test_Allowlist:
assert is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"awslambda_function_url_cors_policy",
AWS_REGION,
AWS_REGION_US_EAST_1,
"prowler",
"",
)
@@ -572,9 +672,8 @@ class Test_Allowlist:
assert is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"awslambda_function_url_public",
AWS_REGION,
AWS_REGION_US_EAST_1,
"prowler",
"",
)
@@ -582,9 +681,8 @@ class Test_Allowlist:
assert is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"awslambda_function_using_supported_runtimes",
AWS_REGION,
AWS_REGION_US_EAST_1,
"prowler",
"",
)
@@ -592,7 +690,7 @@ class Test_Allowlist:
def test_is_allowlisted_lambda_concrete_check(self):
allowlisted_checks = {
"lambda_function_no_secrets_in_variables": {
"Regions": [AWS_REGION, "eu-west-1"],
"Regions": [AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1],
"Resources": ["*"],
}
}
@@ -600,9 +698,8 @@ class Test_Allowlist:
assert is_allowlisted_in_check(
allowlisted_checks,
AWS_ACCOUNT_NUMBER,
AWS_ACCOUNT_NUMBER,
"awslambda_function_no_secrets_in_variables",
AWS_REGION,
AWS_REGION_US_EAST_1,
"prowler",
"",
)
@@ -614,7 +711,7 @@ class Test_Allowlist:
"*": {
"Checks": {
"check_test": {
"Regions": [AWS_REGION, "eu-west-1"],
"Regions": [AWS_REGION_US_EAST_1, AWS_REGION_EU_WEST_1],
"Resources": ["*"],
"Tags": ["environment=dev", "project=.*"],
}
@@ -627,7 +724,7 @@ class Test_Allowlist:
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION,
AWS_REGION_US_EAST_1,
"prowler",
"environment=dev",
)
@@ -636,7 +733,7 @@ class Test_Allowlist:
allowlist,
AWS_ACCOUNT_NUMBER,
"check_test",
AWS_REGION,
AWS_REGION_US_EAST_1,
"prowler-test",
"environment=dev | project=prowler",
)
@@ -654,56 +751,45 @@ class Test_Allowlist:
def test_is_allowlisted_in_tags(self):
allowlist_tags = ["environment=dev", "project=prowler"]
allowlist_resource = "*"
assert is_allowlisted_in_tags(allowlist_tags, "environment=dev")
assert is_allowlisted_in_tags(
allowlist_tags,
"*",
"prowler",
"environment=dev",
)
assert is_allowlisted_in_tags(
allowlist_tags,
allowlist_resource,
"prowler-test",
"environment=dev | project=prowler",
)
assert not (
is_allowlisted_in_tags(
allowlist_tags,
allowlist_resource,
"test",
"environment=pro",
)
)
def test_is_allowlisted_in_tags_regex(self):
allowlist_tags = ["environment=(dev|test)", ".*=prowler"]
allowlist_resource = "*"
assert is_allowlisted_in_tags(
allowlist_tags,
allowlist_resource,
"prowler-test",
"environment=test | proj=prowler",
)
assert is_allowlisted_in_tags(
allowlist_tags,
allowlist_resource,
"prowler-test",
"env=prod | project=prowler",
)
assert not is_allowlisted_in_tags(
allowlist_tags,
allowlist_resource,
"prowler-test",
"environment=prod | project=myproj",
)
def test_is_allowlisted_in_tags_with_no_tags_in_finding(self):
allowlist_tags = ["environment=(dev|test)", ".*=prowler"]
finding_tags = ""
assert not is_allowlisted_in_tags(allowlist_tags, finding_tags)
def test_is_excepted(self):
# Allowlist example
exceptions = {
@@ -737,6 +823,28 @@ class Test_Allowlist:
"environment=test",
)
def test_is_excepted_all_wildcard(self):
exceptions = {
"Accounts": ["*"],
"Regions": ["*"],
"Resources": ["*"],
"Tags": ["*"],
}
assert is_excepted(
exceptions, AWS_ACCOUNT_NUMBER, "eu-south-2", "test", "environment=test"
)
assert not is_excepted(
exceptions, AWS_ACCOUNT_NUMBER, "eu-south-2", "test", None
)
def test_is_not_excepted(self):
exceptions = {
"Accounts": [AWS_ACCOUNT_NUMBER],
"Regions": ["eu-central-1", "eu-south-3"],
"Resources": ["test"],
"Tags": ["environment=test", "project=.*"],
}
assert not is_excepted(
exceptions,
AWS_ACCOUNT_NUMBER,
@@ -760,3 +868,11 @@ class Test_Allowlist:
"test",
"environment=pro",
)
def test_is_allowlisted_in_resource(self):
allowlist_resources = ["prowler", "^test", "prowler-pro"]
assert is_allowlisted_in_resource(allowlist_resources, "prowler")
assert is_allowlisted_in_resource(allowlist_resources, "prowler-test")
assert is_allowlisted_in_resource(allowlist_resources, "test-prowler")
assert not is_allowlisted_in_resource(allowlist_resources, "random")

View File

@@ -1282,3 +1282,75 @@ class Test_policy_condition_parser:
assert not is_account_only_allowed_in_condition(
condition_statement, TRUSTED_AWS_ACCOUNT_NUMBER
)
def test_condition_parser_two_lists_unrestrictive(self):
condition_statement = {
"StringLike": {
"AWS:ResourceAccount": [
TRUSTED_AWS_ACCOUNT_NUMBER,
NON_TRUSTED_AWS_ACCOUNT_NUMBER,
]
},
"ArnLike": {
"AWS:SourceArn": [
f"arn:aws:cloudtrail:*:{TRUSTED_AWS_ACCOUNT_NUMBER}:trail/*",
f"arn:aws:cloudtrail:*:{NON_TRUSTED_AWS_ACCOUNT_NUMBER}:trail/*",
]
},
}
assert not is_account_only_allowed_in_condition(
condition_statement, TRUSTED_AWS_ACCOUNT_NUMBER
)
def test_condition_parser_two_lists_both_restrictive(self):
condition_statement = {
"StringLike": {
"AWS:ResourceAccount": [
TRUSTED_AWS_ACCOUNT_NUMBER,
]
},
"ArnLike": {
"AWS:SourceArn": [
f"arn:aws:cloudtrail:*:{TRUSTED_AWS_ACCOUNT_NUMBER}:trail/*",
]
},
}
assert is_account_only_allowed_in_condition(
condition_statement, TRUSTED_AWS_ACCOUNT_NUMBER
)
def test_condition_parser_two_lists_first_restrictive(self):
condition_statement = {
"StringLike": {
"AWS:ResourceAccount": [
TRUSTED_AWS_ACCOUNT_NUMBER,
]
},
"ArnLike": {
"AWS:SourceArn": [
f"arn:aws:cloudtrail:*:{TRUSTED_AWS_ACCOUNT_NUMBER}:trail/*",
f"arn:aws:cloudtrail:*:{NON_TRUSTED_AWS_ACCOUNT_NUMBER}:trail/*",
]
},
}
assert is_account_only_allowed_in_condition(
condition_statement, TRUSTED_AWS_ACCOUNT_NUMBER
)
def test_condition_parser_two_lists_second_restrictive(self):
condition_statement = {
"StringLike": {
"AWS:ResourceAccount": [
TRUSTED_AWS_ACCOUNT_NUMBER,
NON_TRUSTED_AWS_ACCOUNT_NUMBER,
]
},
"ArnLike": {
"AWS:SourceArn": [
f"arn:aws:cloudtrail:*:{TRUSTED_AWS_ACCOUNT_NUMBER}:trail/*",
]
},
}
assert is_account_only_allowed_in_condition(
condition_statement, TRUSTED_AWS_ACCOUNT_NUMBER
)

View File

@@ -7,8 +7,11 @@ from prowler.providers.aws.services.sqs.sqs_service import Queue
AWS_REGION = "eu-west-1"
AWS_ACCOUNT_NUMBER = "123456789012"
queue_id = str(uuid4())
topic_arn = f"arn:aws:sqs:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:{queue_id}"
test_queue_name = str(uuid4())
test_queue_url = (
f"https://sqs.{AWS_REGION}.amazonaws.com/{AWS_ACCOUNT_NUMBER}/{test_queue_name}"
)
test_queue_arn = f"arn:aws:sqs:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:{test_queue_name}"
test_restricted_policy = {
"Version": "2012-10-17",
@@ -19,7 +22,7 @@ test_restricted_policy = {
"Effect": "Allow",
"Principal": {"AWS": {AWS_ACCOUNT_NUMBER}},
"Action": "sqs:ReceiveMessage",
"Resource": topic_arn,
"Resource": test_queue_arn,
}
],
}
@@ -33,7 +36,7 @@ test_public_policy = {
"Effect": "Allow",
"Principal": "*",
"Action": "sqs:ReceiveMessage",
"Resource": topic_arn,
"Resource": test_queue_arn,
}
],
}
@@ -47,7 +50,7 @@ test_public_policy_with_condition_same_account_not_valid = {
"Effect": "Allow",
"Principal": "*",
"Action": "sqs:ReceiveMessage",
"Resource": topic_arn,
"Resource": test_queue_arn,
"Condition": {
"DateGreaterThan": {"aws:CurrentTime": "2009-01-31T12:00Z"},
"DateLessThan": {"aws:CurrentTime": "2009-01-31T15:00Z"},
@@ -65,7 +68,7 @@ test_public_policy_with_condition_same_account = {
"Effect": "Allow",
"Principal": "*",
"Action": "sqs:ReceiveMessage",
"Resource": topic_arn,
"Resource": test_queue_arn,
"Condition": {
"StringEquals": {"aws:SourceAccount": f"{AWS_ACCOUNT_NUMBER}"}
},
@@ -82,7 +85,7 @@ test_public_policy_with_condition_diff_account = {
"Effect": "Allow",
"Principal": "*",
"Action": "sqs:ReceiveMessage",
"Resource": topic_arn,
"Resource": test_queue_arn,
"Condition": {"StringEquals": {"aws:SourceAccount": "111122223333"}},
}
],
@@ -110,10 +113,11 @@ class Test_sqs_queues_not_publicly_accessible:
sqs_client.queues = []
sqs_client.queues.append(
Queue(
id=queue_id,
id=test_queue_url,
name=test_queue_name,
region=AWS_REGION,
policy=test_restricted_policy,
arn="arn_test",
arn=test_queue_arn,
)
)
with mock.patch(
@@ -129,8 +133,8 @@ class Test_sqs_queues_not_publicly_accessible:
assert len(result) == 1
assert result[0].status == "PASS"
assert search("is not public", result[0].status_extended)
assert result[0].resource_id == queue_id
assert result[0].resource_arn == "arn_test"
assert result[0].resource_id == test_queue_url
assert result[0].resource_arn == test_queue_arn
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION
@@ -139,10 +143,11 @@ class Test_sqs_queues_not_publicly_accessible:
sqs_client.queues = []
sqs_client.queues.append(
Queue(
id=queue_id,
id=test_queue_url,
name=test_queue_name,
region=AWS_REGION,
policy=test_public_policy,
arn="arn_test",
arn=test_queue_arn,
)
)
with mock.patch(
@@ -161,8 +166,8 @@ class Test_sqs_queues_not_publicly_accessible:
"is public because its policy allows public access",
result[0].status_extended,
)
assert result[0].resource_id == queue_id
assert result[0].resource_arn == "arn_test"
assert result[0].resource_id == test_queue_url
assert result[0].resource_arn == test_queue_arn
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION
@@ -172,10 +177,11 @@ class Test_sqs_queues_not_publicly_accessible:
sqs_client.audited_account = AWS_ACCOUNT_NUMBER
sqs_client.queues.append(
Queue(
id=queue_id,
id=test_queue_url,
name=test_queue_name,
region=AWS_REGION,
policy=test_public_policy_with_condition_same_account_not_valid,
arn="arn_test",
arn=test_queue_arn,
)
)
with mock.patch(
@@ -194,8 +200,8 @@ class Test_sqs_queues_not_publicly_accessible:
"is public because its policy allows public access",
result[0].status_extended,
)
assert result[0].resource_id == queue_id
assert result[0].resource_arn == "arn_test"
assert result[0].resource_id == test_queue_url
assert result[0].resource_arn == test_queue_arn
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION
@@ -205,10 +211,11 @@ class Test_sqs_queues_not_publicly_accessible:
sqs_client.audited_account = AWS_ACCOUNT_NUMBER
sqs_client.queues.append(
Queue(
id=queue_id,
id=test_queue_url,
name=test_queue_name,
region=AWS_REGION,
policy=test_public_policy_with_condition_same_account,
arn="arn_test",
arn=test_queue_arn,
)
)
with mock.patch(
@@ -225,10 +232,10 @@ class Test_sqs_queues_not_publicly_accessible:
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"SQS queue {queue_id} is not public because its policy only allows access from the same account."
== f"SQS queue {test_queue_url} is not public because its policy only allows access from the same account."
)
assert result[0].resource_id == queue_id
assert result[0].resource_arn == "arn_test"
assert result[0].resource_id == test_queue_url
assert result[0].resource_arn == test_queue_arn
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION
@@ -238,10 +245,11 @@ class Test_sqs_queues_not_publicly_accessible:
sqs_client.audited_account = AWS_ACCOUNT_NUMBER
sqs_client.queues.append(
Queue(
id=queue_id,
id=test_queue_url,
name=test_queue_name,
region=AWS_REGION,
policy=test_public_policy_with_condition_diff_account,
arn="arn_test",
arn=test_queue_arn,
)
)
with mock.patch(
@@ -258,9 +266,9 @@ class Test_sqs_queues_not_publicly_accessible:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"SQS queue {queue_id} is public because its policy allows public access, and the condition does not limit access to resources within the same account."
== f"SQS queue {test_queue_url} is public because its policy allows public access, and the condition does not limit access to resources within the same account."
)
assert result[0].resource_id == queue_id
assert result[0].resource_arn == "arn_test"
assert result[0].resource_id == test_queue_url
assert result[0].resource_arn == test_queue_arn
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION

View File

@@ -8,8 +8,11 @@ AWS_REGION = "eu-west-1"
AWS_ACCOUNT_NUMBER = "123456789012"
test_kms_key_id = str(uuid4())
queue_id = str(uuid4())
topic_arn = f"arn:aws:sqs:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:{queue_id}"
test_queue_name = str(uuid4())
test_queue_url = (
f"https://sqs.{AWS_REGION}.amazonaws.com/{AWS_ACCOUNT_NUMBER}/{test_queue_name}"
)
test_queue_arn = f"arn:aws:sqs:{AWS_REGION}:{AWS_ACCOUNT_NUMBER}:{test_queue_name}"
class Test_sqs_queues_server_side_encryption_enabled:
@@ -33,10 +36,11 @@ class Test_sqs_queues_server_side_encryption_enabled:
sqs_client.queues = []
sqs_client.queues.append(
Queue(
id=queue_id,
id=test_queue_url,
name=test_queue_name,
region=AWS_REGION,
kms_key_id=test_kms_key_id,
arn="arn_test",
arn=test_queue_arn,
)
)
with mock.patch(
@@ -52,17 +56,18 @@ class Test_sqs_queues_server_side_encryption_enabled:
assert len(result) == 1
assert result[0].status == "PASS"
assert search("is using Server Side Encryption", result[0].status_extended)
assert result[0].resource_id == queue_id
assert result[0].resource_arn == "arn_test"
assert result[0].resource_id == test_queue_url
assert result[0].resource_arn == test_queue_arn
def test_queues_no_encryption(self):
sqs_client = mock.MagicMock
sqs_client.queues = []
sqs_client.queues.append(
Queue(
id=queue_id,
id=test_queue_url,
name=test_queue_name,
region=AWS_REGION,
arn="arn_test",
arn=test_queue_arn,
)
)
with mock.patch(
@@ -80,5 +85,5 @@ class Test_sqs_queues_server_side_encryption_enabled:
assert search(
"is not using Server Side Encryption", result[0].status_extended
)
assert result[0].resource_id == queue_id
assert result[0].resource_arn == "arn_test"
assert result[0].resource_id == test_queue_url
assert result[0].resource_arn == test_queue_arn

View File

@@ -110,9 +110,25 @@ class Test_SQS_Service:
sqs = SQS(audit_info)
assert len(sqs.queues) == 1
assert sqs.queues[0].id == queue["QueueUrl"]
assert sqs.queues[0].name == test_queue
assert sqs.queues[0].name == sqs.queues[0].arn.split(":")[-1]
assert sqs.queues[0].name == sqs.queues[0].id.split("/")[-1]
assert sqs.queues[0].arn == test_queue_arn
assert sqs.queues[0].region == AWS_REGION
assert sqs.queues[0].tags == [{"test": "test"}]
# moto does not properly mock this and is hardcoded to return 1000 queues
# so this test currently always fails
# @mock_sqs
# # Test SQS list queues for over 1000 queues
# def test__list_queues__pagination_over_a_thousand(self):
# sqs_client = client("sqs", region_name=AWS_REGION)
# for i in range(0,1050):
# sqs_client.create_queue(QueueName=f"{test_queue}-{i}", tags={"test": "test"})
# audit_info = self.set_mocked_audit_info()
# sqs = SQS(audit_info)
# assert len(sqs.queues) > 1000
@mock_sqs
# Test SQS list queues
def test__get_queue_attributes__(self):

View File

@@ -0,0 +1,87 @@
import importlib
import logging
import tempfile
from argparse import Namespace
from os import path
from mock import patch
from prowler.providers.common.clean import clean_provider_local_output_directories
class Test_Common_Clean:
def set_provider_input_args(self, provider):
set_args_function = f"set_{provider}_input_args"
args = getattr(
getattr(importlib.import_module(__name__), __class__.__name__),
set_args_function,
)(self)
return args
def set_aws_input_args(self):
args = Namespace()
args.provider = "aws"
args.output_bucket = "test-bucket"
args.output_bucket_no_assume = None
return args
def set_azure_input_args(self):
args = Namespace()
args.provider = "azure"
return args
def test_clean_provider_local_output_directories_non_initialized(self, caplog):
provider = "azure"
input_args = self.set_provider_input_args(provider)
caplog.set_level(logging.INFO)
clean_provider_local_output_directories(input_args)
assert (
f"Cleaning local output directories not initialized for provider {provider}:"
in caplog.text
)
def test_clean_aws_local_output_directories_non_default_dir_output_bucket(self):
provider = "aws"
input_args = self.set_provider_input_args(provider)
with tempfile.TemporaryDirectory() as temp_dir:
input_args.output_directory = temp_dir
clean_provider_local_output_directories(input_args)
assert not path.exists(input_args.output_directory)
def test_clean_aws_local_output_directories_non_default_dir_output_bucket_no_assume(
self,
):
provider = "aws"
input_args = self.set_provider_input_args(provider)
input_args.output_bucket = None
input_args.output_bucket_no_assume = "test"
with tempfile.TemporaryDirectory() as temp_dir:
input_args.output_directory = temp_dir
clean_provider_local_output_directories(input_args)
assert not path.exists(input_args.output_directory)
def test_clean_aws_local_output_directories_default_dir_output_bucket(self):
provider = "aws"
input_args = self.set_provider_input_args(provider)
with tempfile.TemporaryDirectory() as temp_dir:
with patch(
"prowler.providers.common.clean.default_output_directory", new=temp_dir
):
input_args.output_directory = temp_dir
clean_provider_local_output_directories(input_args)
assert path.exists(input_args.output_directory)
def test_clean_aws_local_output_directories_default_dir_output_bucket_no_assume(
self,
):
provider = "aws"
input_args = self.set_provider_input_args(provider)
input_args.output_bucket_no_assume = "test"
input_args.ouput_bucket = None
with tempfile.TemporaryDirectory() as temp_dir:
with patch(
"prowler.providers.common.clean.default_output_directory", new=temp_dir
):
input_args.output_directory = temp_dir
clean_provider_local_output_directories(input_args)
assert path.exists(input_args.output_directory)