From 131112398bd240d303a473e24128f0aa8ae04f97 Mon Sep 17 00:00:00 2001 From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Date: Tue, 14 Apr 2026 11:00:40 +0200 Subject: [PATCH] feat(aws): add bedrock_full_access_policy_attached security check (#10577) --- prowler/CHANGELOG.md | 8 + ...itected_framework_security_pillar_aws.json | 1 + prowler/compliance/aws/c5_aws.json | 1 + prowler/compliance/aws/iso27001_2022_aws.json | 1 + .../compliance/aws/kisa_isms_p_2023_aws.json | 1 + .../aws/kisa_isms_p_2023_korean_aws.json | 1 + prowler/compliance/aws/nist_csf_2.0_aws.json | 1 + .../compliance/aws/secnumcloud_3.2_aws.json | 9 +- prowler/compliance/aws/soc2_aws.json | 1 + .../__init__.py | 0 ..._full_access_policy_attached.metadata.json | 43 +++ .../bedrock_full_access_policy_attached.py | 38 ++ ...edrock_full_access_policy_attached_test.py | 357 ++++++++++++++++++ 13 files changed, 458 insertions(+), 4 deletions(-) create mode 100644 prowler/providers/aws/services/bedrock/bedrock_full_access_policy_attached/__init__.py create mode 100644 prowler/providers/aws/services/bedrock/bedrock_full_access_policy_attached/bedrock_full_access_policy_attached.metadata.json create mode 100644 prowler/providers/aws/services/bedrock/bedrock_full_access_policy_attached/bedrock_full_access_policy_attached.py create mode 100644 tests/providers/aws/services/bedrock/bedrock_full_access_policy_attached/bedrock_full_access_policy_attached_test.py diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index f0e394c5c9..5f27097cac 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to the **Prowler SDK** are documented in this file. +## [5.24.0] (Prowler UNRELEASED) + +### 🚀 Added + +- `bedrock_full_access_policy_attached` check for AWS provider [(#10577)](https://github.com/prowler-cloud/prowler/pull/10577) + +--- + ## [5.23.0] (Prowler v5.23.0) ### 🚀 Added diff --git a/prowler/compliance/aws/aws_well_architected_framework_security_pillar_aws.json b/prowler/compliance/aws/aws_well_architected_framework_security_pillar_aws.json index 496ef9cabd..caa6c92ad4 100644 --- a/prowler/compliance/aws/aws_well_architected_framework_security_pillar_aws.json +++ b/prowler/compliance/aws/aws_well_architected_framework_security_pillar_aws.json @@ -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", diff --git a/prowler/compliance/aws/c5_aws.json b/prowler/compliance/aws/c5_aws.json index cf553bef70..def79b6aa3 100644 --- a/prowler/compliance/aws/c5_aws.json +++ b/prowler/compliance/aws/c5_aws.json @@ -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", diff --git a/prowler/compliance/aws/iso27001_2022_aws.json b/prowler/compliance/aws/iso27001_2022_aws.json index 3dbac3482c..87fa20c965 100644 --- a/prowler/compliance/aws/iso27001_2022_aws.json +++ b/prowler/compliance/aws/iso27001_2022_aws.json @@ -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", diff --git a/prowler/compliance/aws/kisa_isms_p_2023_aws.json b/prowler/compliance/aws/kisa_isms_p_2023_aws.json index 5c7d31e82e..91271660c9 100644 --- a/prowler/compliance/aws/kisa_isms_p_2023_aws.json +++ b/prowler/compliance/aws/kisa_isms_p_2023_aws.json @@ -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", diff --git a/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json b/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json index b539fc9073..05e15f2fbc 100644 --- a/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json +++ b/prowler/compliance/aws/kisa_isms_p_2023_korean_aws.json @@ -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", diff --git a/prowler/compliance/aws/nist_csf_2.0_aws.json b/prowler/compliance/aws/nist_csf_2.0_aws.json index 591e558bb1..370b4981c4 100644 --- a/prowler/compliance/aws/nist_csf_2.0_aws.json +++ b/prowler/compliance/aws/nist_csf_2.0_aws.json @@ -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", diff --git a/prowler/compliance/aws/secnumcloud_3.2_aws.json b/prowler/compliance/aws/secnumcloud_3.2_aws.json index 1476ad87e5..b216e36397 100644 --- a/prowler/compliance/aws/secnumcloud_3.2_aws.json +++ b/prowler/compliance/aws/secnumcloud_3.2_aws.json @@ -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" ] }, { diff --git a/prowler/compliance/aws/soc2_aws.json b/prowler/compliance/aws/soc2_aws.json index 44d4038528..6b6ec6f904 100644 --- a/prowler/compliance/aws/soc2_aws.json +++ b/prowler/compliance/aws/soc2_aws.json @@ -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" diff --git a/prowler/providers/aws/services/bedrock/bedrock_full_access_policy_attached/__init__.py b/prowler/providers/aws/services/bedrock/bedrock_full_access_policy_attached/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/bedrock/bedrock_full_access_policy_attached/bedrock_full_access_policy_attached.metadata.json b/prowler/providers/aws/services/bedrock/bedrock_full_access_policy_attached/bedrock_full_access_policy_attached.metadata.json new file mode 100644 index 0000000000..5fff8ebb2d --- /dev/null +++ b/prowler/providers/aws/services/bedrock/bedrock_full_access_policy_attached/bedrock_full_access_policy_attached.metadata.json @@ -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 --policy-arn arn:aws:iam::aws:policy/AmazonBedrockFullAccess", + "NativeIaC": "```yaml\n# CloudFormation: IAM Role without AmazonBedrockFullAccess\nResources:\n :\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\" \"\" {\n assume_role_policy = < 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["PolicyArn"] + == "arn:aws:iam::aws:policy/AmazonBedrockFullAccess" + ): + report.status = "FAIL" + report.status_extended = f"IAM Role {role.name} has AmazonBedrockFullAccess policy attached." + break + findings.append(report) + return findings diff --git a/tests/providers/aws/services/bedrock/bedrock_full_access_policy_attached/bedrock_full_access_policy_attached_test.py b/tests/providers/aws/services/bedrock/bedrock_full_access_policy_attached/bedrock_full_access_policy_attached_test.py new file mode 100644 index 0000000000..1a261d96cc --- /dev/null +++ b/tests/providers/aws/services/bedrock/bedrock_full_access_policy_attached/bedrock_full_access_policy_attached_test.py @@ -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