mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-04 19:21:51 +00:00
chore(aws): skip unattached IAM policies unless --scan-unused-services (#11150)
This commit is contained in:
committed by
GitHub
parent
0abbb7fc59
commit
739be07077
@@ -56,6 +56,18 @@ Prowler scans only attached security groups to report vulnerabilities in activel
|
||||
|
||||
- `ec2_networkacl_allow_ingress_X_port`
|
||||
|
||||
#### AWS Identity and Access Management (IAM)
|
||||
|
||||
Customer-managed IAM policies that are not attached to any user, group, or role grant no effective permissions until a principal is bound to them. Prowler treats such policies as dormant by default and skips the content-evaluation checks below when `--scan-unused-services` is not set. Enable the flag to surface findings on unattached policies as well.
|
||||
|
||||
- `iam_policy_allows_privilege_escalation`
|
||||
- `iam_policy_no_full_access_to_cloudtrail`
|
||||
- `iam_policy_no_full_access_to_kms`
|
||||
- `iam_policy_no_wildcard_marketplace_subscribe`
|
||||
- `iam_no_custom_policy_permissive_role_assumption`
|
||||
|
||||
The dedicated `iam_customer_unattached_policy_no_administrative_privileges` check still inspects unattached policies regardless of the flag, since its purpose is to highlight dormant administrator privileges.
|
||||
|
||||
#### AWS Glue
|
||||
|
||||
AWS Glue best practices recommend encrypting metadata and connection passwords in Data Catalogs.
|
||||
|
||||
@@ -14,6 +14,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
### 🔄 Changed
|
||||
|
||||
- `entra_emergency_access_exclusion` check for M365 provider now scopes the exclusion requirement to enabled Conditional Access policies with a `Block` grant control instead of every enabled policy, focusing on the lockout-relevant policy set [(#10849)](https://github.com/prowler-cloud/prowler/pull/10849)
|
||||
- AWS IAM customer-managed policy checks no longer emit `FAIL` on unattached policies unless `--scan-unused-services` is enabled [(#11150)](https://github.com/prowler-cloud/prowler/pull/11150)
|
||||
|
||||
---
|
||||
|
||||
|
||||
+2
@@ -16,6 +16,8 @@ class iam_no_custom_policy_permissive_role_assumption(Check):
|
||||
for policy in iam_client.policies.values():
|
||||
# Check only custom policies
|
||||
if policy.type == "Custom":
|
||||
if not policy.attached and not iam_client.provider.scan_unused_services:
|
||||
continue
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=policy)
|
||||
report.region = iam_client.region
|
||||
report.status = "PASS"
|
||||
|
||||
+2
@@ -11,6 +11,8 @@ class iam_policy_allows_privilege_escalation(Check):
|
||||
|
||||
for policy in iam_client.policies.values():
|
||||
if policy.type == "Custom":
|
||||
if not policy.attached and not iam_client.provider.scan_unused_services:
|
||||
continue
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=policy)
|
||||
report.region = iam_client.region
|
||||
report.status = "PASS"
|
||||
|
||||
+2
@@ -11,6 +11,8 @@ class iam_policy_no_full_access_to_cloudtrail(Check):
|
||||
for policy in iam_client.policies.values():
|
||||
# Check only custom policies
|
||||
if policy.type == "Custom":
|
||||
if not policy.attached and not iam_client.provider.scan_unused_services:
|
||||
continue
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=policy)
|
||||
report.region = iam_client.region
|
||||
report.status = "PASS"
|
||||
|
||||
+2
@@ -11,6 +11,8 @@ class iam_policy_no_full_access_to_kms(Check):
|
||||
for policy in iam_client.policies.values():
|
||||
# Check only custom policies
|
||||
if policy.type == "Custom":
|
||||
if not policy.attached and not iam_client.provider.scan_unused_services:
|
||||
continue
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=policy)
|
||||
report.region = iam_client.region
|
||||
report.status = "PASS"
|
||||
|
||||
+2
@@ -10,6 +10,8 @@ class iam_policy_no_wildcard_marketplace_subscribe(Check):
|
||||
findings = []
|
||||
for policy in iam_client.policies.values():
|
||||
if policy.type == "Custom":
|
||||
if not policy.attached and not iam_client.provider.scan_unused_services:
|
||||
continue
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=policy)
|
||||
report.region = iam_client.region
|
||||
report.status = "PASS"
|
||||
|
||||
+80
@@ -408,3 +408,83 @@ class Test_iam_no_custom_policy_permissive_role_assumption:
|
||||
assert search(
|
||||
"allows permissive STS Role assumption", result[0].status_extended
|
||||
)
|
||||
|
||||
@mock_aws
|
||||
def test_unattached_policy_skipped_when_scan_unused_services_disabled(self):
|
||||
iam_client = client("iam")
|
||||
policy_name = "unattached_permissive_assume_role"
|
||||
policy_document = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "*"},
|
||||
],
|
||||
}
|
||||
iam_client.create_policy(
|
||||
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
|
||||
)
|
||||
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
aws_provider = set_mocked_aws_provider(
|
||||
[AWS_REGION_US_EAST_1], scan_unused_services=False
|
||||
)
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption import (
|
||||
iam_no_custom_policy_permissive_role_assumption,
|
||||
)
|
||||
|
||||
check = iam_no_custom_policy_permissive_role_assumption()
|
||||
result = check.execute()
|
||||
assert result == []
|
||||
|
||||
@mock_aws
|
||||
def test_attached_policy_fails_when_scan_unused_services_disabled(self):
|
||||
iam_client = client("iam")
|
||||
user_name = "test_user_assume_role"
|
||||
policy_name = "attached_permissive_assume_role"
|
||||
policy_document = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "*"},
|
||||
],
|
||||
}
|
||||
arn = iam_client.create_policy(
|
||||
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
|
||||
)["Policy"]["Arn"]
|
||||
iam_client.create_user(UserName=user_name)
|
||||
iam_client.attach_user_policy(UserName=user_name, PolicyArn=arn)
|
||||
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
aws_provider = set_mocked_aws_provider(
|
||||
[AWS_REGION_US_EAST_1], scan_unused_services=False
|
||||
)
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption import (
|
||||
iam_no_custom_policy_permissive_role_assumption,
|
||||
)
|
||||
|
||||
check = iam_no_custom_policy_permissive_role_assumption()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].resource_arn == arn
|
||||
assert search(
|
||||
"allows permissive STS Role assumption", result[0].status_extended
|
||||
)
|
||||
|
||||
+83
@@ -1261,3 +1261,86 @@ class Test_iam_policy_allows_privilege_escalation:
|
||||
permissions
|
||||
]:
|
||||
assert search(permission, finding.status_extended)
|
||||
|
||||
@mock_aws
|
||||
def test_unattached_policy_skipped_when_scan_unused_services_disabled(self):
|
||||
iam_client = client("iam", region_name=AWS_REGION_US_EAST_1)
|
||||
policy_name = "unattached_privilege_escalation"
|
||||
policy_document = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Effect": "Allow", "Action": "iam:CreateAccessKey", "Resource": "*"},
|
||||
],
|
||||
}
|
||||
iam_client.create_policy(
|
||||
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
|
||||
)
|
||||
|
||||
aws_provider = set_mocked_aws_provider(
|
||||
[AWS_REGION_US_EAST_1], scan_unused_services=False
|
||||
)
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation import (
|
||||
iam_policy_allows_privilege_escalation,
|
||||
)
|
||||
|
||||
check = iam_policy_allows_privilege_escalation()
|
||||
result = check.execute()
|
||||
assert result == []
|
||||
|
||||
@mock_aws
|
||||
def test_attached_policy_fails_when_scan_unused_services_disabled(self):
|
||||
iam_client = client("iam", region_name=AWS_REGION_US_EAST_1)
|
||||
user_name = "test_user_privesc"
|
||||
policy_name = "attached_privilege_escalation"
|
||||
policy_document = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Effect": "Allow", "Action": "iam:CreateAccessKey", "Resource": "*"},
|
||||
],
|
||||
}
|
||||
policy_arn = iam_client.create_policy(
|
||||
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
|
||||
)["Policy"]["Arn"]
|
||||
iam_client.create_user(UserName=user_name)
|
||||
iam_client.attach_user_policy(UserName=user_name, PolicyArn=policy_arn)
|
||||
|
||||
aws_provider = set_mocked_aws_provider(
|
||||
[AWS_REGION_US_EAST_1], scan_unused_services=False
|
||||
)
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation import (
|
||||
iam_policy_allows_privilege_escalation,
|
||||
)
|
||||
|
||||
check = iam_policy_allows_privilege_escalation()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].resource_arn == policy_arn
|
||||
assert search(
|
||||
f"Custom Policy {policy_arn} allows privilege escalation",
|
||||
result[0].status_extended,
|
||||
)
|
||||
|
||||
+75
@@ -207,3 +207,78 @@ class Test_iam_policy_no_full_access_to_cloudtrail:
|
||||
assert result[0].resource_id == "policy_no_cloudtrail_full_no_actions"
|
||||
assert result[0].resource_arn == arn
|
||||
assert result[0].region == "us-east-1"
|
||||
|
||||
@mock_aws
|
||||
def test_unattached_policy_skipped_when_scan_unused_services_disabled(self):
|
||||
aws_provider = set_mocked_aws_provider(
|
||||
[AWS_REGION_US_EAST_1], scan_unused_services=False
|
||||
)
|
||||
iam_client = client("iam", region_name=AWS_REGION_US_EAST_1)
|
||||
policy_name = "unattached_cloudtrail_full"
|
||||
policy_document_full_access = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Effect": "Allow", "Action": "cloudtrail:*", "Resource": "*"},
|
||||
],
|
||||
}
|
||||
iam_client.create_policy(
|
||||
PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access)
|
||||
)
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_policy_no_full_access_to_cloudtrail.iam_policy_no_full_access_to_cloudtrail.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_cloudtrail.iam_policy_no_full_access_to_cloudtrail import (
|
||||
iam_policy_no_full_access_to_cloudtrail,
|
||||
)
|
||||
|
||||
check = iam_policy_no_full_access_to_cloudtrail()
|
||||
result = check.execute()
|
||||
assert result == []
|
||||
|
||||
@mock_aws
|
||||
def test_attached_policy_fails_when_scan_unused_services_disabled(self):
|
||||
aws_provider = set_mocked_aws_provider(
|
||||
[AWS_REGION_US_EAST_1], scan_unused_services=False
|
||||
)
|
||||
iam_client = client("iam", region_name=AWS_REGION_US_EAST_1)
|
||||
user_name = "test_user_cloudtrail"
|
||||
policy_name = "attached_cloudtrail_full"
|
||||
policy_document_full_access = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Effect": "Allow", "Action": "cloudtrail:*", "Resource": "*"},
|
||||
],
|
||||
}
|
||||
arn = iam_client.create_policy(
|
||||
PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access)
|
||||
)["Policy"]["Arn"]
|
||||
iam_client.create_user(UserName=user_name)
|
||||
iam_client.attach_user_policy(UserName=user_name, PolicyArn=arn)
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_policy_no_full_access_to_cloudtrail.iam_policy_no_full_access_to_cloudtrail.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_cloudtrail.iam_policy_no_full_access_to_cloudtrail import (
|
||||
iam_policy_no_full_access_to_cloudtrail,
|
||||
)
|
||||
|
||||
check = iam_policy_no_full_access_to_cloudtrail()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Custom Policy {policy_name} allows 'cloudtrail:*' privileges."
|
||||
)
|
||||
assert result[0].resource_arn == arn
|
||||
|
||||
+75
@@ -329,6 +329,81 @@ class Test_iam_policy_no_full_access_to_kms_with_unicode:
|
||||
assert result[0].resource_arn == arn
|
||||
assert result[0].region == "us-east-1"
|
||||
|
||||
@mock_aws
|
||||
def test_unattached_policy_skipped_when_scan_unused_services_disabled(self):
|
||||
aws_provider = set_mocked_aws_provider(
|
||||
[AWS_REGION_US_EAST_1], scan_unused_services=False
|
||||
)
|
||||
iam_client = client("iam")
|
||||
policy_name = "unattached_kms_full"
|
||||
policy_document_full_access = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Effect": "Allow", "Action": "kms:*", "Resource": "*"},
|
||||
],
|
||||
}
|
||||
iam_client.create_policy(
|
||||
PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access)
|
||||
)
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms import (
|
||||
iam_policy_no_full_access_to_kms,
|
||||
)
|
||||
|
||||
check = iam_policy_no_full_access_to_kms()
|
||||
result = check.execute()
|
||||
assert result == []
|
||||
|
||||
@mock_aws
|
||||
def test_attached_policy_fails_when_scan_unused_services_disabled(self):
|
||||
aws_provider = set_mocked_aws_provider(
|
||||
[AWS_REGION_US_EAST_1], scan_unused_services=False
|
||||
)
|
||||
iam_client = client("iam")
|
||||
user_name = "test_user_kms"
|
||||
policy_name = "attached_kms_full"
|
||||
policy_document_full_access = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{"Effect": "Allow", "Action": "kms:*", "Resource": "*"},
|
||||
],
|
||||
}
|
||||
arn = iam_client.create_policy(
|
||||
PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access)
|
||||
)["Policy"]["Arn"]
|
||||
iam_client.create_user(UserName=user_name)
|
||||
iam_client.attach_user_policy(UserName=user_name, PolicyArn=arn)
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
"prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms import (
|
||||
iam_policy_no_full_access_to_kms,
|
||||
)
|
||||
|
||||
check = iam_policy_no_full_access_to_kms()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"Custom Policy {policy_name} allows 'kms:*' privileges."
|
||||
)
|
||||
assert result[0].resource_arn == arn
|
||||
|
||||
@mock_aws
|
||||
def test_policy_full_access_and_full_deny_to_kms(self):
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
|
||||
+81
@@ -507,3 +507,84 @@ class Test_iam_policy_no_wildcard_marketplace_subscribe:
|
||||
check = iam_policy_no_wildcard_marketplace_subscribe()
|
||||
result = check.execute()
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_unattached_policy_skipped_when_scan_unused_services_disabled(self):
|
||||
"""No FAIL for an unattached risky policy when --scan-unused-services is off."""
|
||||
aws_provider = set_mocked_aws_provider(
|
||||
[AWS_REGION_US_EAST_1], scan_unused_services=False
|
||||
)
|
||||
iam_client = client("iam")
|
||||
policy_name = "unattached_marketplace_subscribe"
|
||||
policy_document = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "aws-marketplace:Subscribe",
|
||||
"Resource": "*",
|
||||
},
|
||||
],
|
||||
}
|
||||
iam_client.create_policy(
|
||||
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
|
||||
)
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
f"{CHECK_MODULE_PATH}.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import (
|
||||
iam_policy_no_wildcard_marketplace_subscribe,
|
||||
)
|
||||
|
||||
check = iam_policy_no_wildcard_marketplace_subscribe()
|
||||
result = check.execute()
|
||||
assert result == []
|
||||
|
||||
@mock_aws
|
||||
def test_attached_policy_fails_when_scan_unused_services_disabled(self):
|
||||
"""Attached risky policy still FAILs when --scan-unused-services is off."""
|
||||
aws_provider = set_mocked_aws_provider(
|
||||
[AWS_REGION_US_EAST_1], scan_unused_services=False
|
||||
)
|
||||
iam_client = client("iam")
|
||||
user_name = "test_user_marketplace"
|
||||
policy_name = "attached_marketplace_subscribe"
|
||||
policy_document = {
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "aws-marketplace:Subscribe",
|
||||
"Resource": "*",
|
||||
},
|
||||
],
|
||||
}
|
||||
arn = iam_client.create_policy(
|
||||
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
|
||||
)["Policy"]["Arn"]
|
||||
iam_client.create_user(UserName=user_name)
|
||||
iam_client.attach_user_policy(UserName=user_name, PolicyArn=arn)
|
||||
|
||||
with mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
):
|
||||
with mock.patch(
|
||||
f"{CHECK_MODULE_PATH}.iam_client",
|
||||
new=IAM(aws_provider),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import (
|
||||
iam_policy_no_wildcard_marketplace_subscribe,
|
||||
)
|
||||
|
||||
check = iam_policy_no_wildcard_marketplace_subscribe()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].resource_arn == arn
|
||||
|
||||
Reference in New Issue
Block a user