mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 06:45:08 +00:00
feat(allowlist): add exceptions to allowlist (#2527)
This commit is contained in:
@@ -11,6 +11,7 @@ You can use `-w`/`--allowlist-file` with the path of your allowlist yaml file, b
|
||||
### 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.
|
||||
########################### ALLOWLIST EXAMPLE ###########################
|
||||
Allowlist:
|
||||
Accounts:
|
||||
@@ -54,6 +55,33 @@ You can use `-w`/`--allowlist-file` with the path of your allowlist yaml file, b
|
||||
Tags:
|
||||
- "environment=dev" # Will ignore every resource containing the tag 'environment=dev' in every account and region
|
||||
|
||||
"*":
|
||||
Checks:
|
||||
"ecs_task_definitions_no_environment_secrets":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "*"
|
||||
Exceptions:
|
||||
Accounts:
|
||||
- "0123456789012"
|
||||
Regions:
|
||||
- "eu-west-1"
|
||||
- "eu-south-2" # Will ignore every resource in check ecs_task_definitions_no_environment_secrets except the ones in account 0123456789012 located in eu-south-2 or eu-west-1
|
||||
|
||||
"123456789012":
|
||||
Checks:
|
||||
"*":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "*"
|
||||
Exceptions:
|
||||
Resources:
|
||||
- "test"
|
||||
Tags:
|
||||
- "environment=prod" # Will ignore every resource except in account 123456789012 except the ones containing the string "test" and tag environment=prod
|
||||
|
||||
|
||||
## Supported Allowlist Locations
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
### 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.
|
||||
########################### ALLOWLIST EXAMPLE ###########################
|
||||
Allowlist:
|
||||
Accounts:
|
||||
@@ -45,6 +46,34 @@ Allowlist:
|
||||
Tags:
|
||||
- "environment=dev" # Will ignore every resource containing the tag 'environment=dev' in every account and region
|
||||
|
||||
"*":
|
||||
Checks:
|
||||
"ecs_task_definitions_no_environment_secrets":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "*"
|
||||
Exceptions:
|
||||
Accounts:
|
||||
- "0123456789012"
|
||||
Regions:
|
||||
- "eu-west-1"
|
||||
- "eu-south-2" # Will ignore every resource in check ecs_task_definitions_no_environment_secrets except the ones in account 0123456789012 located in eu-south-2 or eu-west-1
|
||||
|
||||
"123456789012":
|
||||
Checks:
|
||||
"*":
|
||||
Regions:
|
||||
- "*"
|
||||
Resources:
|
||||
- "*"
|
||||
Exceptions:
|
||||
Resources:
|
||||
- "test"
|
||||
Tags:
|
||||
- "environment=prod" # Will ignore every resource except in account 123456789012 except the ones containing the string "test" and tag environment=prod
|
||||
|
||||
|
||||
|
||||
# EXAMPLE: CONTROL TOWER (to migrate)
|
||||
# When using Control Tower, guardrails prevent access to certain protected resources. The allowlist
|
||||
|
||||
@@ -12,7 +12,17 @@ allowlist_schema = Schema(
|
||||
"Accounts": {
|
||||
str: {
|
||||
"Checks": {
|
||||
str: {"Regions": list, "Resources": list, Optional("Tags"): list}
|
||||
str: {
|
||||
"Regions": list,
|
||||
"Resources": list,
|
||||
Optional("Tags"): list,
|
||||
Optional("Exceptions"): {
|
||||
Optional("Accounts"): list,
|
||||
Optional("Regions"): list,
|
||||
Optional("Resources"): list,
|
||||
Optional("Tags"): list,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,25 +79,22 @@ def parse_allowlist_file(audit_info, allowlist_file):
|
||||
dynamodb_items.update(response["Items"])
|
||||
for item in dynamodb_items:
|
||||
# Create allowlist for every item
|
||||
allowlist["Accounts"][item["Accounts"]] = {
|
||||
"Checks": {
|
||||
item["Checks"]: {
|
||||
"Regions": item["Regions"],
|
||||
"Resources": item["Resources"],
|
||||
}
|
||||
}
|
||||
}
|
||||
if "Tags" in item:
|
||||
allowlist["Accounts"][item["Accounts"]] = {
|
||||
"Checks": {
|
||||
item["Checks"]: {
|
||||
"Regions": item["Regions"],
|
||||
"Resources": item["Resources"],
|
||||
"Tags": item["Tags"],
|
||||
}
|
||||
}
|
||||
}
|
||||
else:
|
||||
allowlist["Accounts"][item["Accounts"]] = {
|
||||
"Checks": {
|
||||
item["Checks"]: {
|
||||
"Regions": item["Regions"],
|
||||
"Resources": item["Resources"],
|
||||
}
|
||||
}
|
||||
}
|
||||
allowlist["Accounts"][item["Accounts"]]["Checks"][item["Checks"]][
|
||||
"Tags"
|
||||
] = item["Tags"]
|
||||
if "Exceptions" in item:
|
||||
allowlist["Accounts"][item["Accounts"]]["Checks"][item["Checks"]][
|
||||
"Exceptions"
|
||||
] = item["Exceptions"]
|
||||
else:
|
||||
with open(allowlist_file) as f:
|
||||
allowlist = yaml.safe_load(f)["Allowlist"]
|
||||
@@ -109,15 +116,16 @@ def parse_allowlist_file(audit_info, allowlist_file):
|
||||
def is_allowlisted(allowlist, audited_account, check, region, resource, tags):
|
||||
try:
|
||||
if audited_account in allowlist["Accounts"]:
|
||||
account = audited_account
|
||||
if is_allowlisted_in_check(
|
||||
allowlist, audited_account, check, region, resource, tags
|
||||
allowlist, audited_account, account, check, region, resource, tags
|
||||
):
|
||||
return True
|
||||
# If there is a *, it affects to all accounts
|
||||
if "*" in allowlist["Accounts"]:
|
||||
audited_account = "*"
|
||||
account = "*"
|
||||
if is_allowlisted_in_check(
|
||||
allowlist, audited_account, check, region, resource, tags
|
||||
allowlist, audited_account, account, check, region, resource, tags
|
||||
):
|
||||
return True
|
||||
return False
|
||||
@@ -128,29 +136,40 @@ def is_allowlisted(allowlist, audited_account, check, region, resource, tags):
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def is_allowlisted_in_check(allowlist, audited_account, check, region, resource, tags):
|
||||
def is_allowlisted_in_check(
|
||||
allowlist, audited_account, account, check, region, resource, tags
|
||||
):
|
||||
try:
|
||||
for allowlisted_check in allowlist["Accounts"][audited_account][
|
||||
"Checks"
|
||||
].keys():
|
||||
allowlisted_checks = allowlist["Accounts"][account]["Checks"]
|
||||
for allowlisted_check in allowlisted_checks.keys():
|
||||
# Check if there are exceptions
|
||||
if is_excepted(
|
||||
allowlisted_checks,
|
||||
allowlisted_check,
|
||||
audited_account,
|
||||
region,
|
||||
resource,
|
||||
tags,
|
||||
):
|
||||
return False
|
||||
# If there is a *, it affects to all checks
|
||||
if "*" == allowlisted_check:
|
||||
check = "*"
|
||||
if is_allowlisted_in_region(
|
||||
allowlist, audited_account, check, region, resource, tags
|
||||
allowlist, account, check, region, resource, tags
|
||||
):
|
||||
return True
|
||||
# Check if there is the specific check
|
||||
elif check == allowlisted_check:
|
||||
if is_allowlisted_in_region(
|
||||
allowlist, audited_account, check, region, resource, tags
|
||||
allowlist, account, check, region, resource, tags
|
||||
):
|
||||
return True
|
||||
# Check if check is a regex
|
||||
elif re.search(allowlisted_check, check):
|
||||
if is_allowlisted_in_region(
|
||||
allowlist,
|
||||
audited_account,
|
||||
account,
|
||||
allowlisted_check,
|
||||
region,
|
||||
resource,
|
||||
@@ -165,27 +184,23 @@ def is_allowlisted_in_check(allowlist, audited_account, check, region, resource,
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def is_allowlisted_in_region(allowlist, audited_account, check, region, resource, tags):
|
||||
def is_allowlisted_in_region(allowlist, account, check, region, resource, tags):
|
||||
try:
|
||||
# If there is a *, it affects to all regions
|
||||
if "*" in allowlist["Accounts"][audited_account]["Checks"][check]["Regions"]:
|
||||
for elem in allowlist["Accounts"][audited_account]["Checks"][check][
|
||||
"Resources"
|
||||
]:
|
||||
if "*" in allowlist["Accounts"][account]["Checks"][check]["Regions"]:
|
||||
for elem in allowlist["Accounts"][account]["Checks"][check]["Resources"]:
|
||||
if is_allowlisted_in_tags(
|
||||
allowlist["Accounts"][audited_account]["Checks"][check],
|
||||
allowlist["Accounts"][account]["Checks"][check],
|
||||
elem,
|
||||
resource,
|
||||
tags,
|
||||
):
|
||||
return True
|
||||
# Check if there is the specific region
|
||||
if region in allowlist["Accounts"][audited_account]["Checks"][check]["Regions"]:
|
||||
for elem in allowlist["Accounts"][audited_account]["Checks"][check][
|
||||
"Resources"
|
||||
]:
|
||||
if region in allowlist["Accounts"][account]["Checks"][check]["Regions"]:
|
||||
for elem in allowlist["Accounts"][account]["Checks"][check]["Resources"]:
|
||||
if is_allowlisted_in_tags(
|
||||
allowlist["Accounts"][audited_account]["Checks"][check],
|
||||
allowlist["Accounts"][account]["Checks"][check],
|
||||
elem,
|
||||
resource,
|
||||
tags,
|
||||
@@ -228,3 +243,52 @@ def is_allowlisted_in_tags(check_allowlist, elem, resource, tags):
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def is_excepted(
|
||||
allowlisted_checks, allowlisted_check, audited_account, region, resource, tags
|
||||
):
|
||||
try:
|
||||
excepted = False
|
||||
is_account_excepted = False
|
||||
is_region_excepted = False
|
||||
is_resource_excepted = False
|
||||
is_tag_excepted = False
|
||||
exceptions = allowlisted_checks[allowlisted_check].get("Exceptions")
|
||||
if exceptions:
|
||||
excepted_accounts = exceptions.get("Accounts", [])
|
||||
excepted_regions = exceptions.get("Regions", [])
|
||||
excepted_resources = exceptions.get("Resources", [])
|
||||
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
|
||||
if tags in excepted_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
|
||||
return excepted
|
||||
except Exception as error:
|
||||
logger.critical(
|
||||
f"{error.__class__.__name__} -- {error}[{error.__traceback__.tb_lineno}]"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -7,6 +7,7 @@ from prowler.providers.aws.lib.allowlist.allowlist import (
|
||||
is_allowlisted_in_check,
|
||||
is_allowlisted_in_region,
|
||||
is_allowlisted_in_tags,
|
||||
is_excepted,
|
||||
parse_allowlist_file,
|
||||
)
|
||||
from prowler.providers.aws.lib.audit_info.models import AWS_Audit_Info
|
||||
@@ -308,20 +309,44 @@ class Test_Allowlist:
|
||||
}
|
||||
|
||||
assert is_allowlisted_in_check(
|
||||
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler", ""
|
||||
allowlist,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"check_test",
|
||||
AWS_REGION,
|
||||
"prowler",
|
||||
"",
|
||||
)
|
||||
|
||||
assert is_allowlisted_in_check(
|
||||
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "prowler-test", ""
|
||||
allowlist,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"check_test",
|
||||
AWS_REGION,
|
||||
"prowler-test",
|
||||
"",
|
||||
)
|
||||
|
||||
assert is_allowlisted_in_check(
|
||||
allowlist, AWS_ACCOUNT_NUMBER, "check_test", AWS_REGION, "test-prowler", ""
|
||||
allowlist,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"check_test",
|
||||
AWS_REGION,
|
||||
"test-prowler",
|
||||
"",
|
||||
)
|
||||
|
||||
assert not (
|
||||
is_allowlisted_in_check(
|
||||
allowlist, AWS_ACCOUNT_NUMBER, "check_test", "us-east-2", "test", ""
|
||||
allowlist,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"check_test",
|
||||
"us-east-2",
|
||||
"test",
|
||||
"",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -343,6 +368,7 @@ class Test_Allowlist:
|
||||
assert is_allowlisted_in_check(
|
||||
allowlist,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"s3_bucket_public_access",
|
||||
AWS_REGION,
|
||||
"prowler",
|
||||
@@ -352,6 +378,7 @@ class Test_Allowlist:
|
||||
assert is_allowlisted_in_check(
|
||||
allowlist,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"s3_bucket_public_access",
|
||||
AWS_REGION,
|
||||
"prowler-test",
|
||||
@@ -361,6 +388,7 @@ class Test_Allowlist:
|
||||
assert is_allowlisted_in_check(
|
||||
allowlist,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"s3_bucket_public_access",
|
||||
AWS_REGION,
|
||||
"test-prowler",
|
||||
@@ -371,6 +399,7 @@ class Test_Allowlist:
|
||||
is_allowlisted_in_check(
|
||||
allowlist,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"iam_user_hardware_mfa_enabled",
|
||||
AWS_REGION,
|
||||
"test",
|
||||
@@ -482,3 +511,73 @@ class Test_Allowlist:
|
||||
"prowler-test",
|
||||
"environment=prod | project=myproj",
|
||||
)
|
||||
|
||||
def test_is_excepted(self):
|
||||
# Allowlist example
|
||||
check_allowlist = {
|
||||
"check_test": {
|
||||
"Regions": ["us-east-1", "eu-west-1"],
|
||||
"Resources": ["*"],
|
||||
"Tags": ["environment=dev"],
|
||||
"Exceptions": {
|
||||
"Accounts": [AWS_ACCOUNT_NUMBER],
|
||||
"Regions": ["eu-central-1", "eu-south-3"],
|
||||
"Resources": ["test"],
|
||||
"Tags": ["environment=test", "project=.*"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
assert is_excepted(
|
||||
check_allowlist,
|
||||
"check_test",
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"eu-central-1",
|
||||
"test",
|
||||
"environment=test",
|
||||
)
|
||||
|
||||
assert is_excepted(
|
||||
check_allowlist,
|
||||
"check_test",
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"eu-south-3",
|
||||
"test",
|
||||
"environment=test",
|
||||
)
|
||||
|
||||
assert is_excepted(
|
||||
check_allowlist,
|
||||
"check_test",
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"eu-south-3",
|
||||
"test123",
|
||||
"environment=test",
|
||||
)
|
||||
|
||||
assert not is_excepted(
|
||||
check_allowlist,
|
||||
"check_test",
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"eu-south-2",
|
||||
"test",
|
||||
"environment=test",
|
||||
)
|
||||
|
||||
assert not is_excepted(
|
||||
check_allowlist,
|
||||
"check_test",
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"eu-south-3",
|
||||
"prowler",
|
||||
"environment=test",
|
||||
)
|
||||
|
||||
assert not is_excepted(
|
||||
check_allowlist,
|
||||
"check_test",
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
"eu-south-3",
|
||||
"test",
|
||||
"environment=pro",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user