Compare commits

...

3 Commits

Author SHA1 Message Date
Daniel Barranquero
2e3b99f29e chore: update changelog 2026-04-06 17:50:25 +02:00
Daniel Barranquero
0051a75919 chore: remove check from some compliance requirements 2026-04-06 17:43:05 +02:00
Daniel Barranquero
7972d61fd8 feat(aws): add bedrock_full_access_policy_attached security check
Add new security check bedrock_full_access_policy_attached for aws provider.
Includes check implementation, metadata, and unit tests.
2026-04-06 13:07:09 +02:00
13 changed files with 450 additions and 4 deletions

View File

@@ -19,6 +19,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `entra_conditional_access_policy_device_registration_mfa_required` check and `entra_intune_enrollment_sign_in_frequency_every_time` enhancement for M365 provider [(#10222)](https://github.com/prowler-cloud/prowler/pull/10222)
- `entra_conditional_access_policy_block_elevated_insider_risk` check for M365 provider [(#10234)](https://github.com/prowler-cloud/prowler/pull/10234)
- `Vercel` provider support with 30 checks [(#10189)](https://github.com/prowler-cloud/prowler/pull/10189)
- `bedrock_full_access_policy_attached` check for AWS provider [(#10577)](https://github.com/prowler-cloud/prowler/pull/10577)
### 🔄 Changed

View File

@@ -344,6 +344,7 @@
}
],
"Checks": [
"bedrock_full_access_policy_attached",
"ec2_instance_profile_attached",
"iam_aws_attached_policy_no_administrative_privileges",
"iam_customer_attached_policy_no_administrative_privileges",

View File

@@ -5568,6 +5568,7 @@
}
],
"Checks": [
"bedrock_full_access_policy_attached",
"iam_policy_allows_privilege_escalation",
"iam_role_administratoraccess_policy",
"iam_policy_cloudshell_admin_not_attached",

View File

@@ -970,6 +970,7 @@
}
],
"Checks": [
"bedrock_full_access_policy_attached",
"ec2_instance_profile_attached",
"iam_aws_attached_policy_no_administrative_privileges",
"iam_customer_attached_policy_no_administrative_privileges",

View File

@@ -1389,6 +1389,7 @@
"Checks": [
"accessanalyzer_enabled",
"accessanalyzer_enabled_without_findings",
"bedrock_full_access_policy_attached",
"iam_administrator_access_with_mfa",
"iam_avoid_root_usage",
"iam_aws_attached_policy_no_administrative_privileges",

View File

@@ -1389,6 +1389,7 @@
"Checks": [
"accessanalyzer_enabled",
"accessanalyzer_enabled_without_findings",
"bedrock_full_access_policy_attached",
"iam_administrator_access_with_mfa",
"iam_avoid_root_usage",
"iam_aws_attached_policy_no_administrative_privileges",

View File

@@ -784,6 +784,7 @@
"iam_customer_attached_policy_no_administrative_privileges",
"iam_customer_unattached_policy_no_administrative_privileges",
"accessanalyzer_enabled_without_findings",
"bedrock_full_access_policy_attached",
"eventbridge_bus_cross_account_access",
"eventbridge_bus_exposed",
"iam_policy_no_full_access_to_cloudtrail",

View File

@@ -311,14 +311,15 @@
}
],
"Checks": [
"iam_policy_allows_privilege_escalation",
"accessanalyzer_enabled",
"bedrock_full_access_policy_attached",
"iam_group_administrator_access_policy",
"iam_inline_policy_allows_privilege_escalation",
"iam_no_custom_policy_permissive_role_assumption",
"iam_user_two_active_access_key",
"accessanalyzer_enabled",
"iam_policy_allows_privilege_escalation",
"iam_role_administratoraccess_policy",
"iam_user_administrator_access_policy",
"iam_group_administrator_access_policy"
"iam_user_two_active_access_key"
]
},
{

View File

@@ -205,6 +205,7 @@
}
],
"Checks": [
"bedrock_full_access_policy_attached",
"iam_aws_attached_policy_no_administrative_privileges",
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges"

View File

@@ -0,0 +1,43 @@
{
"Provider": "aws",
"CheckID": "bedrock_full_access_policy_attached",
"CheckTitle": "IAM role does not have AmazonBedrockFullAccess managed policy attached",
"CheckType": [
"Software and Configuration Checks/AWS Security Best Practices",
"Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices"
],
"ServiceName": "bedrock",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "AwsIamRole",
"ResourceGroup": "IAM",
"Description": "**IAM roles** (excluding service roles) are evaluated for attachment of the AWS-managed `AmazonBedrockFullAccess` policy.\n\nThis policy grants unrestricted access to all Amazon Bedrock actions and resources.",
"Risk": "The `AmazonBedrockFullAccess` policy grants broad permissions across all Bedrock resources. If a role with this policy is compromised, an attacker could:\n- Invoke any model to exfiltrate data or generate harmful content\n- Modify guardrails, logging, and security configurations\n- Incur significant costs through unrestricted model invocations",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.aws.amazon.com/bedrock/latest/userguide/security-iam.html",
"https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege"
],
"Remediation": {
"Code": {
"CLI": "aws iam detach-role-policy --role-name <ROLE_NAME> --policy-arn arn:aws:iam::aws:policy/AmazonBedrockFullAccess",
"NativeIaC": "```yaml\n# CloudFormation: IAM Role without AmazonBedrockFullAccess\nResources:\n <example_resource_name>:\n Type: AWS::IAM::Role\n Properties:\n AssumeRolePolicyDocument:\n Version: '2012-10-17'\n Statement:\n - Effect: Allow\n Principal:\n Service: ec2.amazonaws.com\n Action: sts:AssumeRole\n ManagedPolicyArns: [] # Critical: ensure AmazonBedrockFullAccess is NOT attached\n```",
"Other": "1. Open the AWS Console and go to IAM > Roles\n2. Select the role flagged by the check\n3. On the Permissions tab, find \"AmazonBedrockFullAccess\" under Attached policies\n4. Click Detach next to \"AmazonBedrockFullAccess\"\n5. Confirm the detach\n6. Attach a scoped policy granting only required Bedrock actions",
"Terraform": "```hcl\n# IAM Role without AmazonBedrockFullAccess\nresource \"aws_iam_role\" \"<example_resource_name>\" {\n assume_role_policy = <<POLICY\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [{\n \"Effect\": \"Allow\",\n \"Principal\": {\"Service\": \"ec2.amazonaws.com\"},\n \"Action\": \"sts:AssumeRole\"\n }]\n}\nPOLICY\n\n managed_policy_arns = [] # Critical: ensures AmazonBedrockFullAccess is NOT attached\n}\n```"
},
"Recommendation": {
"Text": "Apply **least privilege**: replace `AmazonBedrockFullAccess` with a custom policy granting only the specific Bedrock actions required.\n\nUse **permissions boundaries** and **SCPs** to limit the scope of Bedrock permissions. Regularly review access with **IAM Access Analyzer** to identify and remove unused privileges.",
"Url": "https://hub.prowler.com/check/bedrock_full_access_policy_attached"
}
},
"Categories": [
"gen-ai",
"identity-access"
],
"DependsOn": [],
"RelatedTo": [
"iam_role_administratoraccess_policy"
],
"Notes": ""
}

View File

@@ -0,0 +1,37 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.iam.iam_client import iam_client
class bedrock_full_access_policy_attached(Check):
"""Ensure that IAM roles do not have the AmazonBedrockFullAccess managed policy attached.
This check evaluates whether IAM roles (excluding service roles) have the
AmazonBedrockFullAccess AWS-managed policy attached, which grants excessive
permissions and violates the principle of least privilege.
- PASS: The IAM role does not have the AmazonBedrockFullAccess policy attached.
- FAIL: The IAM role has the AmazonBedrockFullAccess policy attached.
"""
def execute(self) -> list[Check_Report_AWS]:
"""Execute the check logic.
Returns:
A list of reports containing the result of the check.
"""
findings = []
if iam_client.roles:
for role in iam_client.roles:
if not role.is_service_role:
report = Check_Report_AWS(
metadata=self.metadata(), resource=role
)
report.region = iam_client.region
report.status = "PASS"
report.status_extended = f"IAM Role {role.name} does not have AmazonBedrockFullAccess policy attached."
for policy in role.attached_policies:
if policy["PolicyName"] == "AmazonBedrockFullAccess":
report.status = "FAIL"
report.status_extended = f"IAM Role {role.name} has AmazonBedrockFullAccess policy attached."
break
findings.append(report)
return findings

View File

@@ -0,0 +1,357 @@
from json import dumps
from unittest import mock
from boto3 import client
from moto import mock_aws
from prowler.providers.aws.services.iam.iam_service import Role
from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider
AWS_ACCOUNT_ID = "123456789012"
ASSUME_ROLE_POLICY_DOCUMENT = {
"Version": "2012-10-17",
"Statement": {
"Sid": "test",
"Effect": "Allow",
"Principal": {"AWS": f"arn:aws:iam::{AWS_ACCOUNT_ID}:root"},
"Action": "sts:AssumeRole",
},
}
class Test_bedrock_full_access_policy_attached:
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_no_roles(self):
from prowler.providers.aws.services.iam.iam_service import IAM
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 0
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_role_without_bedrock_full_access_policy(self):
iam = client("iam")
role_name = "test"
response = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(ASSUME_ROLE_POLICY_DOCUMENT),
)
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
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.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "IAM Role test does not have AmazonBedrockFullAccess policy attached."
)
assert result[0].resource_id == "test"
assert result[0].resource_arn == response["Role"]["Arn"]
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_US_EAST_1
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_role_with_other_policy(self):
iam = client("iam")
role_name = "test"
response = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(ASSUME_ROLE_POLICY_DOCUMENT),
)
iam.attach_role_policy(
RoleName=role_name,
PolicyArn="arn:aws:iam::aws:policy/SecurityAudit",
)
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
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.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "IAM Role test does not have AmazonBedrockFullAccess policy attached."
)
assert result[0].resource_id == "test"
assert result[0].resource_arn == response["Role"]["Arn"]
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_US_EAST_1
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_role_with_bedrock_full_access_policy(self):
iam = client("iam")
role_name = "test"
response = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(ASSUME_ROLE_POLICY_DOCUMENT),
)
iam.attach_role_policy(
RoleName=role_name,
PolicyArn="arn:aws:iam::aws:policy/AmazonBedrockFullAccess",
)
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
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.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "IAM Role test has AmazonBedrockFullAccess policy attached."
)
assert result[0].resource_id == "test"
assert result[0].resource_arn == response["Role"]["Arn"]
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_US_EAST_1
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_asterisk_principal_role_with_bedrock_full_access_policy(self):
iam = client("iam")
role_name = "test"
assume_role_policy_document = {
"Version": "2012-10-17",
"Statement": {
"Sid": "test",
"Effect": "Allow",
"Principal": {"AWS": "*"},
"Action": "sts:AssumeRole",
},
}
response = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(assume_role_policy_document),
)
iam.attach_role_policy(
RoleName=role_name,
PolicyArn="arn:aws:iam::aws:policy/AmazonBedrockFullAccess",
)
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
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.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "IAM Role test has AmazonBedrockFullAccess policy attached."
)
assert result[0].resource_id == "test"
assert result[0].resource_arn == response["Role"]["Arn"]
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_US_EAST_1
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_multiple_roles_mixed_policies(self):
iam = client("iam")
# Create a compliant role (no AmazonBedrockFullAccess)
compliant_response = iam.create_role(
RoleName="compliant-role",
AssumeRolePolicyDocument=dumps(ASSUME_ROLE_POLICY_DOCUMENT),
)
iam.attach_role_policy(
RoleName="compliant-role",
PolicyArn="arn:aws:iam::aws:policy/SecurityAudit",
)
# Create a non-compliant role (with AmazonBedrockFullAccess)
non_compliant_response = iam.create_role(
RoleName="non-compliant-role",
AssumeRolePolicyDocument=dumps(ASSUME_ROLE_POLICY_DOCUMENT),
)
iam.attach_role_policy(
RoleName="non-compliant-role",
PolicyArn="arn:aws:iam::aws:policy/AmazonBedrockFullAccess",
)
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
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.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 2
# Sort results by resource_id for deterministic assertions
result = sorted(result, key=lambda r: r.resource_id)
# Compliant role
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "IAM Role compliant-role does not have AmazonBedrockFullAccess policy attached."
)
assert result[0].resource_id == "compliant-role"
assert result[0].resource_arn == compliant_response["Role"]["Arn"]
assert result[0].region == AWS_REGION_US_EAST_1
# Non-compliant role
assert result[1].status == "FAIL"
assert (
result[1].status_extended
== "IAM Role non-compliant-role has AmazonBedrockFullAccess policy attached."
)
assert result[1].resource_id == "non-compliant-role"
assert result[1].resource_arn == non_compliant_response["Role"]["Arn"]
assert result[1].region == AWS_REGION_US_EAST_1
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_only_aws_service_linked_roles(self):
iam_client = mock.MagicMock
iam_client.roles = []
iam_client.roles.append(
Role(
name="AWSServiceRoleForAmazonGuardDuty",
arn="arn:aws:iam::106908755756:role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty",
assume_role_policy={
"Version": "2008-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole",
}
],
},
is_service_role=True,
)
)
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=iam_client,
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 0
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_access_denied(self):
iam_client = mock.MagicMock
iam_client.roles = None
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=iam_client,
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 0