From 2e47e7dfc7aec0219ea1a9693ba2702fac8b3e86 Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 7 Oct 2024 13:46:07 -0400 Subject: [PATCH 01/11] feat(beadrock): add new check bedrock_model_invocation_logging_enabled --- .../aws/services/bedrock/__init__.py | 0 .../aws/services/bedrock/bedrock_client.py | 4 + .../__init__.py | 0 ...l_invocation_logging_enabled.metadata.json | 33 ++++ ...edrock_model_invocation_logging_enabled.py | 29 +++ .../aws/services/bedrock/bedrock_service.py | 47 +++++ ...k_model_invocation_logging_enabled_test.py | 183 ++++++++++++++++++ .../services/bedrock/bedrock_service_test.py | 79 ++++++++ 8 files changed, 375 insertions(+) create mode 100644 prowler/providers/aws/services/bedrock/__init__.py create mode 100644 prowler/providers/aws/services/bedrock/bedrock_client.py create mode 100644 prowler/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/__init__.py create mode 100644 prowler/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled.metadata.json create mode 100644 prowler/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled.py create mode 100644 prowler/providers/aws/services/bedrock/bedrock_service.py create mode 100644 tests/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled_test.py create mode 100644 tests/providers/aws/services/bedrock/bedrock_service_test.py diff --git a/prowler/providers/aws/services/bedrock/__init__.py b/prowler/providers/aws/services/bedrock/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/bedrock/bedrock_client.py b/prowler/providers/aws/services/bedrock/bedrock_client.py new file mode 100644 index 0000000000..a82e872d03 --- /dev/null +++ b/prowler/providers/aws/services/bedrock/bedrock_client.py @@ -0,0 +1,4 @@ +from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock +from prowler.providers.common.provider import Provider + +bedrock_client = Bedrock(Provider.get_global_provider()) diff --git a/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/__init__.py b/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled.metadata.json b/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled.metadata.json new file mode 100644 index 0000000000..2f54911daf --- /dev/null +++ b/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled.metadata.json @@ -0,0 +1,33 @@ +{ + "Provider": "aws", + "CheckID": "bedrock_model_invocation_logging_enabled", + "CheckTitle": "Ensure that model invocation logging is enabled for Amazon Bedrock.", + "CheckType": [], + "ServiceName": "bedrock", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:bedrock:region:account-id:model/resource-id", + "Severity": "medium", + "ResourceType": "Other", + "Description": "Ensure that model invocation logging is enabled for Amazon Bedrock service in order to collect metadata, requests, and responses for all model invocations in your AWS cloud account.", + "Risk": "In Amazon Bedrock, model invocation logging enables you to collect the invocation request and response data, along with metadata, for all 'Converse', 'ConverseStream', 'InvokeModel', and 'InvokeModelWithResponseStream' API calls in your AWS account. Each log entry includes important details such as the timestamp, request ID, model ID, and token usage. Invocation logs can be utilized for troubleshooting, performance enhancements, abuse detection, and security auditing. By default, model invocation logging is disabled.", + "RelatedUrl": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html", + "Remediation": { + "Code": { + "CLI": "aws bedrock put-model-invocation-logging-configuration --logging-config 's3Config={bucketName='tm-bedrock-logging-data',keyPrefix='invocation-logs'},textDataDeliveryEnabled=true,imageDataDeliveryEnabled=true,embeddingDataDeliveryEnabled=true'", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Bedrock/enable-model-invocation-logging.html", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable model invocation logging for Amazon Bedrock service in order to collect metadata, requests, and responses for all model invocations in your AWS cloud account.", + "Url": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html#model-invocation-logging-console" + } + }, + "Categories": [ + "logging", + "forensics-ready" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled.py b/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled.py new file mode 100644 index 0000000000..7edabcf3ce --- /dev/null +++ b/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled.py @@ -0,0 +1,29 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.bedrock.bedrock_client import bedrock_client + + +class bedrock_model_invocation_logging_enabled(Check): + def execute(self): + findings = [] + for region, logging in bedrock_client.logging_configurations.items(): + report = Check_Report_AWS(self.metadata()) + report.region = region + report.resource_id = bedrock_client.audited_account + report.resource_arn = bedrock_client.audited_account_arn + report.status = "FAIL" + report.status_extended = "Bedrock Model Invocation Logging is disabled." + if logging.enabled: + report.status = "PASS" + report.status_extended = "Bedrock Model Invocation Logging is enabled" + if logging.cloudwatch_log_group and logging.s3_bucket: + report.status_extended += f" in CloudWatch Log Group: {logging.cloudwatch_log_group} and S3 Bucket: {logging.s3_bucket}." + elif logging.cloudwatch_log_group: + report.status_extended += ( + f" in CloudWatch Log Group: {logging.cloudwatch_log_group}." + ) + elif logging.s3_bucket: + report.status_extended += f" in S3 Bucket: {logging.s3_bucket}." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/bedrock/bedrock_service.py b/prowler/providers/aws/services/bedrock/bedrock_service.py new file mode 100644 index 0000000000..fd180b12ec --- /dev/null +++ b/prowler/providers/aws/services/bedrock/bedrock_service.py @@ -0,0 +1,47 @@ +from typing import Optional + +from pydantic import BaseModel + +from prowler.lib.logger import logger +from prowler.providers.aws.lib.service.service import AWSService + + +class Bedrock(AWSService): + def __init__(self, provider): + # Call AWSService's __init__ + super().__init__(__class__.__name__, provider) + self.logging_configurations = {} + self.__threading_call__(self._get_model_invocation_logging_configuration) + + def _get_model_invocation_logging_configuration(self, regional_client): + logger.info("Bedrock - Getting Model Invocation Logging Configuration...") + try: + logging_config = ( + regional_client.get_model_invocation_logging_configuration().get( + "loggingConfig", {} + ) + ) + if logging_config: + self.logging_configurations[regional_client.region] = ( + LoggingConfiguration( + cloudwatch_log_group=logging_config.get( + "cloudWatchConfig", {} + ).get("logGroupName"), + s3_bucket=logging_config.get("s3Config", {}).get("bucketName"), + enabled=True, + ) + ) + else: + self.logging_configurations[regional_client.region] = ( + LoggingConfiguration(enabled=False) + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class LoggingConfiguration(BaseModel): + enabled: bool = False + cloudwatch_log_group: Optional[str] = None + s3_bucket: Optional[str] = None diff --git a/tests/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled_test.py b/tests/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled_test.py new file mode 100644 index 0000000000..464cac1ddb --- /dev/null +++ b/tests/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled_test.py @@ -0,0 +1,183 @@ +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_ARN, + AWS_ACCOUNT_NUMBER, + AWS_REGION_EU_WEST_1, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + + +class Test_bedrock_model_invocation_logging_enabled: + @mock_aws + def test_no_loggings(self): + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_EU_WEST_1, 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_model_invocation_logging_enabled.bedrock_model_invocation_logging_enabled.bedrock_client", + new=Bedrock(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logging_enabled.bedrock_model_invocation_logging_enabled import ( + bedrock_model_invocation_logging_enabled, + ) + + check = bedrock_model_invocation_logging_enabled() + result = check.execute() + + assert len(result) == 2 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Bedrock Model Invocation Logging is disabled." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].resource_arn == AWS_ACCOUNT_ARN + assert result[0].resource_tags == [] + assert result[1].status == "FAIL" + assert ( + result[1].status_extended + == "Bedrock Model Invocation Logging is disabled." + ) + assert result[1].resource_id == AWS_ACCOUNT_NUMBER + assert result[1].resource_arn == AWS_ACCOUNT_ARN + assert result[1].resource_tags == [] + + @mock_aws + def test_s3_and_cloudwatch_logging(self): + bedrock = client("bedrock", region_name=AWS_REGION_US_EAST_1) + + logging_config = { + "cloudWatchConfig": { + "logGroupName": "Test", + "roleArn": "testrole", + "largeDataDeliveryS3Config": { + "bucketName": "testbucket", + }, + }, + "s3Config": { + "bucketName": "testconfigbucket", + }, + } + bedrock.put_model_invocation_logging_configuration(loggingConfig=logging_config) + + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + + 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_model_invocation_logging_enabled.bedrock_model_invocation_logging_enabled.bedrock_client", + new=Bedrock(aws_provider), + ): + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logging_enabled.bedrock_model_invocation_logging_enabled import ( + bedrock_model_invocation_logging_enabled, + ) + + check = bedrock_model_invocation_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Bedrock Model Invocation Logging is enabled in CloudWatch Log Group: Test and S3 Bucket: testconfigbucket." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].resource_arn == AWS_ACCOUNT_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock_aws + def test_s3_logging(self): + bedrock = client("bedrock", region_name=AWS_REGION_US_EAST_1) + + logging_config = { + "s3Config": { + "bucketName": "testconfigbucket", + }, + } + bedrock.put_model_invocation_logging_configuration(loggingConfig=logging_config) + + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + + 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_model_invocation_logging_enabled.bedrock_model_invocation_logging_enabled.bedrock_client", + new=Bedrock(aws_provider), + ): + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logging_enabled.bedrock_model_invocation_logging_enabled import ( + bedrock_model_invocation_logging_enabled, + ) + + check = bedrock_model_invocation_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Bedrock Model Invocation Logging is enabled in S3 Bucket: testconfigbucket." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].resource_arn == AWS_ACCOUNT_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock_aws + def test_cloudwatch_logging(self): + bedrock = client("bedrock", region_name=AWS_REGION_US_EAST_1) + + logging_config = { + "cloudWatchConfig": { + "logGroupName": "Test", + "roleArn": "testrole", + }, + } + bedrock.put_model_invocation_logging_configuration(loggingConfig=logging_config) + + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + + 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_model_invocation_logging_enabled.bedrock_model_invocation_logging_enabled.bedrock_client", + new=Bedrock(aws_provider), + ): + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logging_enabled.bedrock_model_invocation_logging_enabled import ( + bedrock_model_invocation_logging_enabled, + ) + + check = bedrock_model_invocation_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Bedrock Model Invocation Logging is enabled in CloudWatch Log Group: Test." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].resource_arn == AWS_ACCOUNT_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/bedrock/bedrock_service_test.py b/tests/providers/aws/services/bedrock/bedrock_service_test.py new file mode 100644 index 0000000000..96caf3d859 --- /dev/null +++ b/tests/providers/aws/services/bedrock/bedrock_service_test.py @@ -0,0 +1,79 @@ +from boto3 import client +from moto import mock_aws + +from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_EU_WEST_1, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + + +class Test_Bedrock_Service: + @mock_aws + def test_service(self): + aws_provider = set_mocked_aws_provider( + audited_regions=[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + bedrock = Bedrock(aws_provider) + assert bedrock.service == "bedrock" + + @mock_aws + def test_client(self): + aws_provider = set_mocked_aws_provider( + audited_regions=[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + bedrock = Bedrock(aws_provider) + for regional_client in bedrock.regional_clients.values(): + assert regional_client.__class__.__name__ == "Bedrock" + + @mock_aws + def test__get_session__(self): + aws_provider = set_mocked_aws_provider( + audited_regions=[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + bedrock = Bedrock(aws_provider) + assert bedrock.session.__class__.__name__ == "Session" + + @mock_aws + def test_audited_account(self): + aws_provider = set_mocked_aws_provider( + audited_regions=[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + bedrock = Bedrock(aws_provider) + assert bedrock.audited_account == AWS_ACCOUNT_NUMBER + + @mock_aws + def test_get_model_invocation_logging_configuration(self): + aws_provider = set_mocked_aws_provider( + audited_regions=[AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + bedrock_client_eu_west_1 = client("bedrock", region_name="eu-west-1") + logging_config = { + "cloudWatchConfig": { + "logGroupName": "Test", + "roleArn": "testrole", + "largeDataDeliveryS3Config": { + "bucketName": "testbucket", + }, + }, + "s3Config": { + "bucketName": "testconfigbucket", + }, + } + bedrock_client_eu_west_1.put_model_invocation_logging_configuration( + loggingConfig=logging_config + ) + bedrock = Bedrock(aws_provider) + assert len(bedrock.logging_configurations) == 2 + assert bedrock.logging_configurations[AWS_REGION_EU_WEST_1].enabled + assert ( + bedrock.logging_configurations[AWS_REGION_EU_WEST_1].cloudwatch_log_group + == "Test" + ) + assert ( + bedrock.logging_configurations[AWS_REGION_EU_WEST_1].s3_bucket + == "testconfigbucket" + ) + assert not bedrock.logging_configurations[AWS_REGION_US_EAST_1].enabled From b5b92f6f67cdb6993951696a8d052caeac6520aa Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 7 Oct 2024 16:51:33 -0400 Subject: [PATCH 02/11] feat(cloudtrail): add new check cloudtrail_threat_detection_llm_jacking --- docs/tutorials/aws/threat-detection.md | 18 +- docs/tutorials/configuration_file.md | 22 +- prowler/config/config.yaml | 22 +- ...cloudtrail_threat_detection_enumeration.py | 3 +- .../__init__.py | 0 ...threat_detection_llm_jacking.metadata.json | 32 +++ ...cloudtrail_threat_detection_llm_jacking.py | 87 ++++++ ...l_threat_detection_privilege_escalation.py | 2 +- tests/config/config_test.py | 20 +- tests/config/fixtures/config.yaml | 18 ++ ...trail_threat_detection_llm_jacking_test.py | 272 ++++++++++++++++++ 11 files changed, 481 insertions(+), 15 deletions(-) create mode 100644 prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/__init__.py create mode 100644 prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.metadata.json create mode 100644 prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.py create mode 100644 tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking_test.py diff --git a/docs/tutorials/aws/threat-detection.md b/docs/tutorials/aws/threat-detection.md index 4f793eeee1..0705e4b81b 100644 --- a/docs/tutorials/aws/threat-detection.md +++ b/docs/tutorials/aws/threat-detection.md @@ -4,21 +4,25 @@ Prowler allows you to do threat detection in AWS based on the CloudTrail log rec ``` prowler aws --category threat-detection ``` -This comand will run these checks: +This command will run these checks: -* `cloudtrail_threat_detection_privilege_escalation` -* `cloudtrail_threat_detection_enumeration` +* `cloudtrail_threat_detection_privilege_escalation` -> Detects privilege escalation attacks. +* `cloudtrail_threat_detection_enumeration` -> Detects enumeration attacks. +* `cloudtrail_threat_detection_llm_jacking` -> Detects LLM Jacking attacks. ???+ note - Threat Detection checks will be only executed using `--category threat-detection` flag due to preformance. + Threat Detection checks will be only executed using `--category threat-detection` flag due to performance. ## Config File If you want to manage the behavior of the Threat Detection checks you can edit `config.yaml` file from `/prowler/config`. In this file you can edit the following attributes related with Threat Detection: -* `threat_detection_privilege_escalation_threshold`: determines the percentage of actions found to decide if it is an privilege_scalation attack event, by default is 0.1 (10%) +* `threat_detection_privilege_escalation_threshold`: determines the percentage of actions found to decide if it is an privilege_scalation attack event, by default is 0.2 (20%) * `threat_detection_privilege_escalation_minutes`: it is the past minutes to search from now for privilege_escalation attacks, by default is 1440 minutes (24 hours) -* `threat_detection_privilege_escalation_actions`: these are the default actions related with priviledge scalation. -* `threat_detection_enumeration_threshold`: determines the percentage of actions found to decide if it is an enumeration attack event, by default is 0.1 (10%) +* `threat_detection_privilege_escalation_actions`: these are the default actions related with privilege escalation. +* `threat_detection_enumeration_threshold`: determines the percentage of actions found to decide if it is an enumeration attack event, by default is 0.3 (30%) * `threat_detection_enumeration_minutes`: it is the past minutes to search from now for enumeration attacks, by default is 1440 minutes (24 hours) * `threat_detection_enumeration_actions`: these are the default actions related with enumeration attacks. +* `threat_detection_llm_jacking_threshold`: determines the percentage of actions found to decide if it is an LLM Jacking attack event, by default is 0.4 (40%) +* `threat_detection_llm_jacking_minutes`: it is the past minutes to search from now for LLM Jacking attacks, by default is 1440 minutes (24 hours) +* `threat_detection_llm_jacking_actions`: these are the default actions related with LLM Jacking attacks. diff --git a/docs/tutorials/configuration_file.md b/docs/tutorials/configuration_file.md index 63fea6dd95..ea4cd8dc02 100644 --- a/docs/tutorials/configuration_file.md +++ b/docs/tutorials/configuration_file.md @@ -226,7 +226,7 @@ aws: # AWS CloudTrail Configuration # aws.cloudtrail_threat_detection_privilege_escalation - threat_detection_privilege_escalation_threshold: 0.1 # Percentage of actions found to decide if it is an privilege_escalation attack event, by default is 0.1 (10%) + threat_detection_privilege_escalation_threshold: 0.2 # Percentage of actions found to decide if it is an privilege_escalation attack event, by default is 0.2 (20%) threat_detection_privilege_escalation_minutes: 1440 # Past minutes to search from now for privilege_escalation attacks, by default is 1440 minutes (24 hours) threat_detection_privilege_escalation_actions: [ @@ -283,7 +283,7 @@ aws: "UpdateLoginProfile", ] # aws.cloudtrail_threat_detection_enumeration - threat_detection_enumeration_threshold: 0.1 # Percentage of actions found to decide if it is an enumeration attack event, by default is 0.1 (10%) + threat_detection_enumeration_threshold: 0.3 # Percentage of actions found to decide if it is an enumeration attack event, by default is 0.3 (30%) threat_detection_enumeration_minutes: 1440 # Past minutes to search from now for enumeration attacks, by default is 1440 minutes (24 hours) threat_detection_enumeration_actions: [ @@ -378,6 +378,24 @@ aws: "LookupEvents", "Search", ] + # aws.cloudtrail_threat_detection_llm_jacking + threat_detection_llm_jacking_threshold: 0.4 # Percentage of actions found to decide if it is an LLM Jacking attack event, by default is 0.4 (40%) + threat_detection_llm_jacking_minutes: 1440 # Past minutes to search from now for LLM Jacking attacks, by default is 1440 minutes (24 hours) + threat_detection_llm_jacking_actions: + [ + "PutUseCaseForModelAccess", # Submits a use case for model access, providing justification (Write). + "PutFoundationModelEntitlement", # Grants entitlement for accessing a foundation model (Write). + "PutModelInvocationLoggingConfiguration", # Configures logging for model invocations (Write). + "CreateFoundationModelAgreement", # Creates a new agreement to use a foundation model (Write). + "InvokeModel", # Invokes a specified Bedrock model for inference using provided prompt and parameters (Read). + "InvokeModelWithResponseStream", # Invokes a Bedrock model for inference with real-time token streaming (Read). + "GetUseCaseForModelAccess", # Retrieves an existing use case for model access (Read). + "GetModelInvocationLoggingConfiguration", # Fetches the logging configuration for model invocations (Read). + "GetFoundationModelAvailability", # Checks the availability of a foundation model for use (Read). + "ListFoundationModelAgreementOffers", # Lists available agreement offers for accessing foundation models (List). + "ListFoundationModels", # Lists the available foundation models in Bedrock (List). + "ListProvisionedModelThroughputs", # Lists the provisioned throughput for previously created models (List). + ] # AWS RDS Configuration # aws.rds_instance_backup_enabled diff --git a/prowler/config/config.yaml b/prowler/config/config.yaml index 5572a958c8..3902c5ec25 100644 --- a/prowler/config/config.yaml +++ b/prowler/config/config.yaml @@ -133,7 +133,7 @@ aws: # AWS CloudTrail Configuration # aws.cloudtrail_threat_detection_privilege_escalation - threat_detection_privilege_escalation_threshold: 0.1 # Percentage of actions found to decide if it is an privilege_escalation attack event, by default is 0.1 (10%) + threat_detection_privilege_escalation_threshold: 0.2 # Percentage of actions found to decide if it is an privilege_escalation attack event, by default is 0.2 (20%) threat_detection_privilege_escalation_minutes: 1440 # Past minutes to search from now for privilege_escalation attacks, by default is 1440 minutes (24 hours) threat_detection_privilege_escalation_actions: [ @@ -190,7 +190,7 @@ aws: "UpdateLoginProfile", ] # aws.cloudtrail_threat_detection_enumeration - threat_detection_enumeration_threshold: 0.1 # Percentage of actions found to decide if it is an enumeration attack event, by default is 0.1 (10%) + threat_detection_enumeration_threshold: 0.3 # Percentage of actions found to decide if it is an enumeration attack event, by default is 0.3 (30%) threat_detection_enumeration_minutes: 1440 # Past minutes to search from now for enumeration attacks, by default is 1440 minutes (24 hours) threat_detection_enumeration_actions: [ @@ -285,6 +285,24 @@ aws: "LookupEvents", "Search", ] + # aws.cloudtrail_threat_detection_llm_jacking + threat_detection_llm_jacking_threshold: 0.4 # Percentage of actions found to decide if it is an LLM Jacking attack event, by default is 0.4 (40%) + threat_detection_llm_jacking_minutes: 1440 # Past minutes to search from now for LLM Jacking attacks, by default is 1440 minutes (24 hours) + threat_detection_llm_jacking_actions: + [ + "PutUseCaseForModelAccess", # Submits a use case for model access, providing justification (Write). + "PutFoundationModelEntitlement", # Grants entitlement for accessing a foundation model (Write). + "PutModelInvocationLoggingConfiguration", # Configures logging for model invocations (Write). + "CreateFoundationModelAgreement", # Creates a new agreement to use a foundation model (Write). + "InvokeModel", # Invokes a specified Bedrock model for inference using provided prompt and parameters (Read). + "InvokeModelWithResponseStream", # Invokes a Bedrock model for inference with real-time token streaming (Read). + "GetUseCaseForModelAccess", # Retrieves an existing use case for model access (Read). + "GetModelInvocationLoggingConfiguration", # Fetches the logging configuration for model invocations (Read). + "GetFoundationModelAvailability", # Checks the availability of a foundation model for use (Read). + "ListFoundationModelAgreementOffers", # Lists available agreement offers for accessing foundation models (List). + "ListFoundationModels", # Lists the available foundation models in Bedrock (List). + "ListProvisionedModelThroughputs", # Lists the provisioned throughput for previously created models (List). + ] # AWS RDS Configuration # aws.rds_instance_backup_enabled diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.py index be32be559f..96b1eb5729 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.py @@ -10,7 +10,7 @@ class cloudtrail_threat_detection_enumeration(Check): def execute(self): findings = [] threshold = cloudtrail_client.audit_config.get( - "threat_detection_enumeration_threshold", 0.1 + "threat_detection_enumeration_threshold", 0.3 ) threat_detection_minutes = cloudtrail_client.audit_config.get( "threat_detection_enumeration_minutes", 1440 @@ -64,6 +64,7 @@ def execute(self): aws_identity_type = aws_identity[1] aws_identity_arn = aws_identity[0] if len(actions) / len(enumeration_actions) > threshold: + print(actions) found_potential_enumeration = True report = Check_Report_AWS(self.metadata()) report.region = cloudtrail_client.region diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/__init__.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.metadata.json b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.metadata.json new file mode 100644 index 0000000000..9b24373bcc --- /dev/null +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "cloudtrail_threat_detection_llm_jacking", + "CheckTitle": "Ensure there are no potential LLM Jacking threats in CloudTrail.", + "CheckType": [], + "ServiceName": "cloudtrail", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "critical", + "ResourceType": "AwsCloudTrailTrail", + "Description": "This check ensures that there are no potential LLM Jacking threats in CloudTrail. LLM Jacking attacks involve unauthorized access to cloud-hosted large language model (LLM) services, such as AWS Bedrock, by exploiting exposed credentials or vulnerabilities. These attacks can lead to resource hijacking, unauthorized model invocations, and high operational costs for the victim organization.", + "Risk": "Potential LLM Jacking threats in CloudTrail can lead to unauthorized access to sensitive AI models, stolen credentials, resource hijacking, or running costly workloads. Attackers may use reverse proxies or malicious credentials to sell access to models, exfiltrate sensitive data, or disrupt business operations.", + "RelatedUrl": "https://sysdig.com/blog/llmjacking-stolen-cloud-credentials-used-in-new-ai-attack/", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "To remediate this issue, enable detailed CloudTrail logging for Bedrock API calls, monitor suspicious activities, and secure sensitive credentials. Enable logging of model invocation inputs and outputs, and restrict access using IAM policies. Review CloudTrail logs regularly for suspicious `InvokeModel` actions or unauthorized access to models.", + "Url": "https://permiso.io/blog/exploiting-hosted-models" + } + }, + "Categories": [ + "threat-detection" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.py new file mode 100644 index 0000000000..8686a9a2ed --- /dev/null +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking.py @@ -0,0 +1,87 @@ +import json + +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.cloudtrail.cloudtrail_client import ( + cloudtrail_client, +) + + +class cloudtrail_threat_detection_llm_jacking(Check): + def execute(self): + findings = [] + threshold = cloudtrail_client.audit_config.get( + "threat_detection_llm_jacking_threshold", 0.4 + ) + threat_detection_minutes = cloudtrail_client.audit_config.get( + "threat_detection_llm_jacking_minutes", 1440 + ) + llm_jacking_actions = cloudtrail_client.audit_config.get( + "threat_detection_llm_jacking_actions", [] + ) + potential_llm_jacking = {} + found_potential_llm_jacking = False + multiregion_trail = None + # Check if any trail is multi-region so we only need to check once + for trail in cloudtrail_client.trails.values(): + if trail.is_multiregion: + multiregion_trail = trail + break + trails_to_scan = ( + cloudtrail_client.trails.values() + if not multiregion_trail + else [multiregion_trail] + ) + for trail in trails_to_scan: + for event_name in llm_jacking_actions: + for event_log in cloudtrail_client._lookup_events( + trail=trail, + event_name=event_name, + minutes=threat_detection_minutes, + ): + event_log = json.loads(event_log["CloudTrailEvent"]) + if ( + "arn" in event_log["userIdentity"] + ): # Ignore event logs without ARN since they are AWS services + if ( + event_log["userIdentity"]["arn"], + event_log["userIdentity"]["type"], + ) not in potential_llm_jacking: + potential_llm_jacking[ + ( + event_log["userIdentity"]["arn"], + event_log["userIdentity"]["type"], + ) + ] = set() + potential_llm_jacking[ + ( + event_log["userIdentity"]["arn"], + event_log["userIdentity"]["type"], + ) + ].add(event_name) + + for aws_identity, actions in potential_llm_jacking.items(): + identity_threshold = round(len(actions) / len(llm_jacking_actions), 2) + aws_identity_type = aws_identity[1] + aws_identity_arn = aws_identity[0] + if len(actions) / len(llm_jacking_actions) > threshold: + found_potential_llm_jacking = True + report = Check_Report_AWS(self.metadata()) + report.region = cloudtrail_client.region + report.resource_id = cloudtrail_client.audited_account + report.resource_arn = cloudtrail_client._get_trail_arn_template( + cloudtrail_client.region + ) + report.status = "FAIL" + report.status_extended = f"Potential LLM Jacking attack detected from AWS {aws_identity_type} {aws_identity_arn.split('/')[-1]} with an threshold of {identity_threshold}." + findings.append(report) + if not found_potential_llm_jacking: + report = Check_Report_AWS(self.metadata()) + report.region = cloudtrail_client.region + report.resource_id = cloudtrail_client.audited_account + report.resource_arn = cloudtrail_client._get_trail_arn_template( + cloudtrail_client.region + ) + report.status = "PASS" + report.status_extended = "No potential LLM Jacking attack detected." + findings.append(report) + return findings diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation.py index a87184c0f1..2992400912 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_privilege_escalation/cloudtrail_threat_detection_privilege_escalation.py @@ -10,7 +10,7 @@ class cloudtrail_threat_detection_privilege_escalation(Check): def execute(self): findings = [] threshold = cloudtrail_client.audit_config.get( - "threat_detection_privilege_escalation_threshold", 0.1 + "threat_detection_privilege_escalation_threshold", 0.2 ) threat_detection_minutes = cloudtrail_client.audit_config.get( "threat_detection_privilege_escalation_minutes", 1440 diff --git a/tests/config/config_test.py b/tests/config/config_test.py index d02e34b79c..d1519878df 100644 --- a/tests/config/config_test.py +++ b/tests/config/config_test.py @@ -128,7 +128,7 @@ def mock_prowler_get_latest_release(_, **kwargs): "organizations_trusted_delegated_administrators": [], "ecr_repository_vulnerability_minimum_severity": "MEDIUM", "verify_premium_support_plans": True, - "threat_detection_privilege_escalation_threshold": 0.1, + "threat_detection_privilege_escalation_threshold": 0.2, "threat_detection_privilege_escalation_minutes": 1440, "threat_detection_privilege_escalation_actions": [ "AddPermission", @@ -183,7 +183,7 @@ def mock_prowler_get_latest_release(_, **kwargs): "UpdateJob", "UpdateLoginProfile", ], - "threat_detection_enumeration_threshold": 0.1, + "threat_detection_enumeration_threshold": 0.3, "threat_detection_enumeration_minutes": 1440, "threat_detection_enumeration_actions": [ "DescribeAccessEntry", @@ -277,6 +277,22 @@ def mock_prowler_get_latest_release(_, **kwargs): "LookupEvents", "Search", ], + "threat_detection_llm_jacking_threshold": 0.4, + "threat_detection_llm_jacking_minutes": 1440, + "threat_detection_llm_jacking_actions": [ + "PutUseCaseForModelAccess", + "PutFoundationModelEntitlement", + "PutModelInvocationLoggingConfiguration", + "CreateFoundationModelAgreement", + "InvokeModel", + "InvokeModelWithResponseStream", + "GetUseCaseForModelAccess", + "GetModelInvocationLoggingConfiguration", + "GetFoundationModelAvailability", + "ListFoundationModelAgreementOffers", + "ListFoundationModels", + "ListProvisionedModelThroughputs", + ], "check_rds_instance_replicas": False, "days_to_expire_threshold": 7, "insecure_key_algorithms": [ diff --git a/tests/config/fixtures/config.yaml b/tests/config/fixtures/config.yaml index d2abd5a01e..ef1e7754bb 100644 --- a/tests/config/fixtures/config.yaml +++ b/tests/config/fixtures/config.yaml @@ -285,6 +285,24 @@ aws: "LookupEvents", "Search", ] + # aws.cloudtrail_threat_detection_llm_jacking + threat_detection_llm_jacking_threshold: 0.4 # Percentage of actions found to decide if it is an LLM Jacking attack event, by default is 0.4 (40%) + threat_detection_llm_jacking_minutes: 1440 # Past minutes to search from now for LLM Jacking attacks, by default is 1440 minutes (24 hours) + threat_detection_llm_jacking_actions: + [ + "PutUseCaseForModelAccess", # Submits a use case for model access, providing justification (Write). + "PutFoundationModelEntitlement", # Grants entitlement for accessing a foundation model (Write). + "PutModelInvocationLoggingConfiguration", # Configures logging for model invocations (Write). + "CreateFoundationModelAgreement", # Creates a new agreement to use a foundation model (Write). + "InvokeModel", # Invokes a specified Bedrock model for inference using provided prompt and parameters (Read). + "InvokeModelWithResponseStream", # Invokes a Bedrock model for inference with real-time token streaming (Read). + "GetUseCaseForModelAccess", # Retrieves an existing use case for model access (Read). + "GetModelInvocationLoggingConfiguration", # Fetches the logging configuration for model invocations (Read). + "GetFoundationModelAvailability", # Checks the availability of a foundation model for use (Read). + "ListFoundationModelAgreementOffers", # Lists available agreement offers for accessing foundation models (List). + "ListFoundationModels", # Lists the available foundation models in Bedrock (List). + "ListProvisionedModelThroughputs", # Lists the provisioned throughput for previously created models (List). + ] # AWS RDS Configuration # aws.rds_instance_backup_enabled diff --git a/tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking_test.py b/tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking_test.py new file mode 100644 index 0000000000..06ad5e33cb --- /dev/null +++ b/tests/providers/aws/services/cloudtrail/cloudtrail_threat_detection_llm_jacking/cloudtrail_threat_detection_llm_jacking_test.py @@ -0,0 +1,272 @@ +from unittest import mock + +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + + +def mock_get_trail_arn_template(region=None, *_) -> str: + if region: + return f"arn:aws:cloudtrail:{region}:{AWS_ACCOUNT_NUMBER}:trail" + else: + return f"arn:aws:cloudtrail:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:trail" + + +def mock__get_lookup_events__(trail=None, event_name=None, minutes=None, *_) -> list: + return [ + { + "CloudTrailEvent": '{"eventName": "InvokeModel", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Mateo", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Mateo", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' + }, + { + "CloudTrailEvent": '{"eventName": "InvokeModelWithResponseStream", "userIdentity": {"type": "IAMUser", "principalId": "EXAMPLE6E4XEGITWATV6R", "arn": "arn:aws:iam::123456789012:user/Mateo", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "userName": "Mateo", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' + }, + ] + + +def mock__get_lookup_events_aws_service__( + trail=None, event_name=None, minutes=None, *_ +) -> list: + return [ + { + "CloudTrailEvent": '{"eventName": "InvokeModel", "userIdentity": {"type": "AWSService", "principalId": "EXAMPLE6E4XEGITWATV6R", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' + }, + { + "CloudTrailEvent": '{"eventName": "InvokeModelWithResponseStream", "userIdentity": {"type": "AWSService", "principalId": "EXAMPLE6E4XEGITWATV6R", "accountId": "123456789012", "accessKeyId": "AKIAIOSFODNN7EXAMPLE", "sessionContext": {"sessionIssuer": {}, "webIdFederationData": {}, "attributes": {"creationDate": "2023-07-19T21:11:57Z", "mfaAuthenticated": "false"}}}}' + }, + ] + + +class Test_cloudtrail_threat_detection_llm_jacking: + @mock_aws + def test_no_trails(self): + cloudtrail_client = mock.MagicMock() + cloudtrail_client.trails = {} + cloudtrail_client._lookup_events = mock__get_lookup_events__ + cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template + cloudtrail_client.audited_account = AWS_ACCOUNT_NUMBER + cloudtrail_client.region = AWS_REGION_US_EAST_1 + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client", + new=cloudtrail_client, + ): + # Test Check + from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking import ( + cloudtrail_threat_detection_llm_jacking, + ) + + check = cloudtrail_threat_detection_llm_jacking() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended == "No potential LLM Jacking attack detected." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:cloudtrail:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:trail" + ) + + @mock_aws + def test_no_potential_llm_jacking(self): + cloudtrail_client = mock.MagicMock() + cloudtrail_client.trails = {"us-east-1": mock.MagicMock()} + cloudtrail_client.trails["us-east-1"].is_multiregion = False + cloudtrail_client.trails["us-east-1"].name = "trail_test_us" + cloudtrail_client.trails["us-east-1"].s3_bucket_name = "bucket_test_us" + cloudtrail_client.trails["us-east-1"].region = "us-east-1" + cloudtrail_client.audited_account = AWS_ACCOUNT_NUMBER + cloudtrail_client.region = AWS_REGION_US_EAST_1 + cloudtrail_client.audit_config = { + "threat_detection_llm_jacking_actions": [], + "threat_detection_llm_jacking_threshold": 0.1, + "threat_detection_llm_jacking_minutes": 1440, + } + + cloudtrail_client._lookup_events = mock__get_lookup_events__ + cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client", + new=cloudtrail_client, + ): + # Test Check + from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking import ( + cloudtrail_threat_detection_llm_jacking, + ) + + check = cloudtrail_threat_detection_llm_jacking() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended == "No potential LLM Jacking attack detected." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:cloudtrail:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:trail" + ) + + @mock_aws + def test_potential_priviledge_escalation(self): + cloudtrail_client = mock.MagicMock() + cloudtrail_client.trails = {"us-east-1": mock.MagicMock()} + cloudtrail_client.trails["us-east-1"].is_multiregion = False + cloudtrail_client.trails["us-east-1"].name = "trail_test_us" + cloudtrail_client.trails["us-east-1"].s3_bucket_name = "bucket_test_us" + cloudtrail_client.trails["us-east-1"].region = "us-east-1" + cloudtrail_client.audited_account = AWS_ACCOUNT_NUMBER + cloudtrail_client.region = AWS_REGION_US_EAST_1 + cloudtrail_client.audit_config = { + "threat_detection_llm_jacking_actions": [ + "InvokeModel", + "InvokeModelWithResponseStream", + ], + "threat_detection_llm_jacking_threshold": 0.1, + "threat_detection_llm_jacking_minutes": 1440, + } + + cloudtrail_client._lookup_events = mock__get_lookup_events__ + cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client", + new=cloudtrail_client, + ): + # Test Check + from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking import ( + cloudtrail_threat_detection_llm_jacking, + ) + + check = cloudtrail_threat_detection_llm_jacking() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Potential LLM Jacking attack detected from AWS IAMUser Mateo with an threshold of 1.0." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:cloudtrail:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:trail" + ) + + @mock_aws + def test_bigger_threshold(self): + cloudtrail_client = mock.MagicMock() + cloudtrail_client.trails = {"us-east-1": mock.MagicMock()} + cloudtrail_client.trails["us-east-1"].is_multiregion = False + cloudtrail_client.trails["us-east-1"].name = "trail_test_us" + cloudtrail_client.trails["us-east-1"].s3_bucket_name = "bucket_test_us" + cloudtrail_client.trails["us-east-1"].region = "us-east-1" + cloudtrail_client.audited_account = AWS_ACCOUNT_NUMBER + cloudtrail_client.region = AWS_REGION_US_EAST_1 + cloudtrail_client.audit_config = { + "threat_detection_llm_jacking_actions": [ + "InvokeModel", + "InvokeModelWithResponseStream", + ], + "threat_detection_llm_jacking_threshold": 2.0, + "threat_detection_llm_jacking_minutes": 1440, + } + + cloudtrail_client._lookup_events = mock__get_lookup_events__ + cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client", + new=cloudtrail_client, + ): + # Test Check + from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking import ( + cloudtrail_threat_detection_llm_jacking, + ) + + check = cloudtrail_threat_detection_llm_jacking() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended == "No potential LLM Jacking attack detected." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:cloudtrail:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:trail" + ) + + @mock_aws + def test_potential_enumeration_from_aws_service(self): + cloudtrail_client = mock.MagicMock() + cloudtrail_client.trails = {"us-east-1": mock.MagicMock()} + cloudtrail_client.trails["us-east-1"].is_multiregion = False + cloudtrail_client.trails["us-east-1"].name = "trail_test_us" + cloudtrail_client.trails["us-east-1"].s3_bucket_name = "bucket_test_us" + cloudtrail_client.trails["us-east-1"].region = "us-east-1" + cloudtrail_client.audited_account = AWS_ACCOUNT_NUMBER + cloudtrail_client.region = AWS_REGION_US_EAST_1 + cloudtrail_client.audit_config = { + "threat_detection_llm_jacking_actions": [ + "InvokeModel", + "InvokeModelWithResponseStream", + ], + "threat_detection_llm_jacking_threshold": 2.0, + "threat_detection_llm_jacking_minutes": 1440, + } + + cloudtrail_client._lookup_events = mock__get_lookup_events_aws_service__ + cloudtrail_client._get_trail_arn_template = mock_get_trail_arn_template + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), mock.patch( + "prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking.cloudtrail_client", + new=cloudtrail_client, + ): + # Test Check + from prowler.providers.aws.services.cloudtrail.cloudtrail_threat_detection_llm_jacking.cloudtrail_threat_detection_llm_jacking import ( + cloudtrail_threat_detection_llm_jacking, + ) + + check = cloudtrail_threat_detection_llm_jacking() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended == "No potential LLM Jacking attack detected." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:cloudtrail:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:trail" + ) From 56e2acf196444ebf30f5cb4c4f49b6bed8c6c1d9 Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 7 Oct 2024 18:29:24 -0400 Subject: [PATCH 03/11] fix: test --- tests/config/fixtures/config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/config/fixtures/config.yaml b/tests/config/fixtures/config.yaml index ef1e7754bb..791b7a6382 100644 --- a/tests/config/fixtures/config.yaml +++ b/tests/config/fixtures/config.yaml @@ -133,7 +133,7 @@ aws: # AWS CloudTrail Configuration # aws.cloudtrail_threat_detection_privilege_escalation - threat_detection_privilege_escalation_threshold: 0.1 # Percentage of actions found to decide if it is an privilege_escalation attack event, by default is 0.1 (10%) + threat_detection_privilege_escalation_threshold: 0.2 # Percentage of actions found to decide if it is an privilege_escalation attack event, by default is 0.2 (20%) threat_detection_privilege_escalation_minutes: 1440 # Past minutes to search from now for privilege_escalation attacks, by default is 1440 minutes (24 hours) threat_detection_privilege_escalation_actions: [ @@ -190,7 +190,7 @@ aws: "UpdateLoginProfile", ] # aws.cloudtrail_threat_detection_enumeration - threat_detection_enumeration_threshold: 0.1 # Percentage of actions found to decide if it is an enumeration attack event, by default is 0.1 (10%) + threat_detection_enumeration_threshold: 0.3 # Percentage of actions found to decide if it is an enumeration attack event, by default is 0.3 (30%) threat_detection_enumeration_minutes: 1440 # Past minutes to search from now for enumeration attacks, by default is 1440 minutes (24 hours) threat_detection_enumeration_actions: [ From e4ecd45dcff0e630376769bb702f55560d6697e4 Mon Sep 17 00:00:00 2001 From: Sergio Garcia <38561120+sergargar@users.noreply.github.com> Date: Tue, 8 Oct 2024 08:02:06 -0400 Subject: [PATCH 04/11] fix: typo --- .../cloudtrail_threat_detection_enumeration.py | 1 - 1 file changed, 1 deletion(-) diff --git a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.py b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.py index 96b1eb5729..f1ff06da85 100644 --- a/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.py +++ b/prowler/providers/aws/services/cloudtrail/cloudtrail_threat_detection_enumeration/cloudtrail_threat_detection_enumeration.py @@ -64,7 +64,6 @@ def execute(self): aws_identity_type = aws_identity[1] aws_identity_arn = aws_identity[0] if len(actions) / len(enumeration_actions) > threshold: - print(actions) found_potential_enumeration = True report = Check_Report_AWS(self.metadata()) report.region = cloudtrail_client.region From 9f111355fbb538fa3f66dfcf014c2127e341f561 Mon Sep 17 00:00:00 2001 From: Sergio Date: Thu, 10 Oct 2024 17:29:59 -0400 Subject: [PATCH 05/11] feat(bedrock): add new check bedrock_model_invocation_logs_encrypted --- .../__init__.py | 0 ...el_invocation_logs_encrypted.metadata.json | 34 ++ ...bedrock_model_invocation_logs_encrypted.py | 48 +++ ...dwatch_log_group_kms_encryption_enabled.py | 2 +- ...cloudwatch_log_group_no_secrets_in_logs.py | 2 +- ..._retention_policy_specific_days_enabled.py | 2 +- .../services/cloudwatch/cloudwatch_service.py | 28 +- ...k_model_invocation_logging_enabled_test.py | 1 - ...ck_model_invocation_logs_encrypted_test.py | 391 ++++++++++++++++++ .../cloudwatch/cloudwatch_service_test.py | 36 +- 10 files changed, 506 insertions(+), 38 deletions(-) create mode 100644 prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/__init__.py create mode 100644 prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.metadata.json create mode 100644 prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.py create mode 100644 tests/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted_test.py diff --git a/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/__init__.py b/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.metadata.json b/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.metadata.json new file mode 100644 index 0000000000..5d57066598 --- /dev/null +++ b/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "bedrock_model_invocation_logs_encrypted", + "CheckTitle": "Ensure that Amazon Bedrock model invocation logs are encrypted with KMS.", + "CheckType": [], + "ServiceName": "bedrock", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:bedrock:region:account-id:model/resource-id", + "Severity": "high", + "ResourceType": "Other", + "Description": "Ensure that Amazon Bedrock model invocation logs are encrypted using AWS KMS to protect sensitive data in the request and response logs for all model invocations.", + "Risk": "If Amazon Bedrock model invocation logs are not encrypted, sensitive data such as prompts, responses, and token usage could be exposed to unauthorized parties. This may lead to data breaches, security vulnerabilities, or unintended use of sensitive information.", + "RelatedUrl": "https://docs.aws.amazon.com/bedrock/latest/userguide/data-protection.html", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure that model invocation logs for Amazon Bedrock are encrypted using AWS KMS to prevent unauthorized access to sensitive log data.", + "Url": "hhttps://docs.aws.amazon.com/bedrock/latest/userguide/data-protection.html" + } + }, + "Categories": [ + "encryption", + "logging", + "security" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.py b/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.py new file mode 100644 index 0000000000..f06c9e1c9d --- /dev/null +++ b/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.py @@ -0,0 +1,48 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.bedrock.bedrock_client import bedrock_client +from prowler.providers.aws.services.cloudwatch.logs_client import logs_client +from prowler.providers.aws.services.s3.s3_client import s3_client + + +class bedrock_model_invocation_logs_encrypted(Check): + def execute(self): + findings = [] + for region, logging in bedrock_client.logging_configurations.items(): + if logging.enabled: + s3_encryption = True + cloudwatch_encryption = True + report = Check_Report_AWS(self.metadata()) + report.region = region + report.resource_id = bedrock_client.audited_account + report.resource_arn = bedrock_client.audited_account_arn + report.status = "PASS" + report.status_extended = "Bedrock Model Invocation logs are encrypted." + if logging.s3_bucket: + bucket_arn = ( + f"arn:{s3_client.audited_partition}:s3:::{logging.s3_bucket}" + ) + if ( + bucket_arn in s3_client.buckets + and not s3_client.buckets[bucket_arn].encryption + ): + s3_encryption = False + if logging.cloudwatch_log_group: + log_group_arn = f"arn:{logs_client.audited_partition}:logs:{region}:{logs_client.audited_account}:log-group:{logging.cloudwatch_log_group}" + if ( + log_group_arn in logs_client.log_groups + and not logs_client.log_groups[log_group_arn].kms_id + ): + cloudwatch_encryption = False + if not s3_encryption and not cloudwatch_encryption: + report.status = "FAIL" + report.status_extended = f"Bedrock Model Invocation logs are not encrypted in S3 bucket: {logging.s3_bucket} and CloudWatch Log Group: {logging.cloudwatch_log_group}." + elif not s3_encryption: + report.status = "FAIL" + report.status_extended = f"Bedrock Model Invocation logs are not encrypted in S3 bucket: {logging.s3_bucket}." + elif not cloudwatch_encryption: + report.status = "FAIL" + report.status_extended = f"Bedrock Model Invocation logs are not encrypted in CloudWatch Log Group: {logging.cloudwatch_log_group}." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_kms_encryption_enabled/cloudwatch_log_group_kms_encryption_enabled.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_kms_encryption_enabled/cloudwatch_log_group_kms_encryption_enabled.py index 83b8bf30c0..e405a7e5d4 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_kms_encryption_enabled/cloudwatch_log_group_kms_encryption_enabled.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_kms_encryption_enabled/cloudwatch_log_group_kms_encryption_enabled.py @@ -6,7 +6,7 @@ class cloudwatch_log_group_kms_encryption_enabled(Check): def execute(self): findings = [] if logs_client.log_groups: - for log_group in logs_client.log_groups: + for log_group in logs_client.log_groups.values(): report = Check_Report_AWS(self.metadata()) report.region = log_group.region report.resource_id = log_group.name diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_no_secrets_in_logs/cloudwatch_log_group_no_secrets_in_logs.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_no_secrets_in_logs/cloudwatch_log_group_no_secrets_in_logs.py index cb583ad35b..0e350a31f7 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_no_secrets_in_logs/cloudwatch_log_group_no_secrets_in_logs.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_no_secrets_in_logs/cloudwatch_log_group_no_secrets_in_logs.py @@ -15,7 +15,7 @@ def execute(self): secrets_ignore_patterns = logs_client.audit_config.get( "secrets_ignore_patterns", [] ) - for log_group in logs_client.log_groups: + for log_group in logs_client.log_groups.values(): report = Check_Report_AWS(self.metadata()) report.status = "PASS" report.status_extended = ( diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py index 342770dc8a..b01ba2363c 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py @@ -11,7 +11,7 @@ def execute(self): "log_group_retention_days", 365 ) if logs_client.log_groups: - for log_group in logs_client.log_groups: + for log_group in logs_client.log_groups.values(): report = Check_Report_AWS(self.metadata()) report.region = log_group.region report.resource_id = log_group.name diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py index d7c9d3fd9e..124b612351 100644 --- a/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py +++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py @@ -82,7 +82,7 @@ def __init__(self, provider): # Call AWSService's __init__ super().__init__(__class__.__name__, provider) self.log_group_arn_template = f"arn:{self.audited_partition}:logs:{self.region}:{self.audited_account}:log-group" - self.log_groups = [] + self.log_groups = {} self.__threading_call__(self._describe_log_groups) self.metric_filters = [] self.__threading_call__(self._describe_metric_filters) @@ -95,7 +95,9 @@ def __init__(self, provider): 1000 # The threshold for number of events to return per log group. ) self.__threading_call__(self._get_log_events) - self.__threading_call__(self._list_tags_for_resource, self.log_groups) + self.__threading_call__( + self._list_tags_for_resource, self.log_groups.values() + ) def _describe_metric_filters(self, regional_client): logger.info("CloudWatch Logs - Describing metric filters...") @@ -113,7 +115,7 @@ def _describe_metric_filters(self, regional_client): self.metric_filters = [] log_group = None - for lg in self.log_groups: + for lg in self.log_groups.values(): if lg.name == filter["logGroupName"]: log_group = lg break @@ -162,16 +164,14 @@ def _describe_log_groups(self, regional_client): never_expire = True retention_days = 9999 if self.log_groups is None: - self.log_groups = [] - self.log_groups.append( - LogGroup( - arn=log_group["arn"], - name=log_group["logGroupName"], - retention_days=retention_days, - never_expire=never_expire, - kms_id=kms, - region=regional_client.region, - ) + self.log_groups = {} + self.log_groups[log_group["arn"]] = LogGroup( + arn=log_group["arn"], + name=log_group["logGroupName"], + retention_days=retention_days, + never_expire=never_expire, + kms_id=kms, + region=regional_client.region, ) except ClientError as error: if error.response["Error"]["Code"] == "AccessDeniedException": @@ -192,7 +192,7 @@ def _describe_log_groups(self, regional_client): def _get_log_events(self, regional_client): regional_log_groups = [ log_group - for log_group in self.log_groups + for log_group in self.log_groups.values() if log_group.region == regional_client.region ] total_log_groups = len(regional_log_groups) diff --git a/tests/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled_test.py b/tests/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled_test.py index 464cac1ddb..9d934f69bc 100644 --- a/tests/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled_test.py +++ b/tests/providers/aws/services/bedrock/bedrock_model_invocation_logging_enabled/bedrock_model_invocation_logging_enabled_test.py @@ -28,7 +28,6 @@ def test_no_loggings(self): "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logging_enabled.bedrock_model_invocation_logging_enabled.bedrock_client", new=Bedrock(aws_provider), ): - # Test Check from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logging_enabled.bedrock_model_invocation_logging_enabled import ( bedrock_model_invocation_logging_enabled, ) diff --git a/tests/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted_test.py b/tests/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted_test.py new file mode 100644 index 0000000000..fba7c02aea --- /dev/null +++ b/tests/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted_test.py @@ -0,0 +1,391 @@ +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_ARN, + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + + +class Test_bedrock_model_invocation_logs_encrypted: + @mock_aws + def test_no_logging(self): + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + from prowler.providers.aws.services.cloudwatch.cloudwatch_service import Logs + from prowler.providers.aws.services.s3.s3_service import S3 + + 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_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.bedrock_client", + new=Bedrock(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.logs_client", + new=Logs(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.s3_client", + new=S3(aws_provider), + ): + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted import ( + bedrock_model_invocation_logs_encrypted, + ) + + check = bedrock_model_invocation_logs_encrypted() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_s3_and_cloudwatch_logging_not_encrypted(self): + logs_client = client("logs", region_name=AWS_REGION_US_EAST_1) + logs_client.create_log_group(logGroupName="Test") + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + s3_client.create_bucket(Bucket="testconfigbucket") + bedrock = client("bedrock", region_name=AWS_REGION_US_EAST_1) + + logging_config = { + "cloudWatchConfig": { + "logGroupName": "Test", + "roleArn": "testrole", + "largeDataDeliveryS3Config": { + "bucketName": "testbucket", + }, + }, + "s3Config": { + "bucketName": "testconfigbucket", + }, + } + bedrock.put_model_invocation_logging_configuration(loggingConfig=logging_config) + + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + from prowler.providers.aws.services.cloudwatch.cloudwatch_service import Logs + from prowler.providers.aws.services.s3.s3_service import S3 + + 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_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.bedrock_client", + new=Bedrock(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.logs_client", + new=Logs(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.s3_client", + new=S3(aws_provider), + ): + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted import ( + bedrock_model_invocation_logs_encrypted, + ) + + check = bedrock_model_invocation_logs_encrypted() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Bedrock Model Invocation logs are not encrypted in S3 bucket: testconfigbucket and CloudWatch Log Group: Test." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].resource_arn == AWS_ACCOUNT_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock_aws + def test_s3_logging_not_encrypted(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + s3_client.create_bucket(Bucket="testconfigbucket") + bedrock = client("bedrock", region_name=AWS_REGION_US_EAST_1) + + logging_config = { + "s3Config": { + "bucketName": "testconfigbucket", + }, + } + bedrock.put_model_invocation_logging_configuration(loggingConfig=logging_config) + + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + from prowler.providers.aws.services.cloudwatch.cloudwatch_service import Logs + from prowler.providers.aws.services.s3.s3_service import S3 + + 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_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.bedrock_client", + new=Bedrock(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.logs_client", + new=Logs(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.s3_client", + new=S3(aws_provider), + ): + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted import ( + bedrock_model_invocation_logs_encrypted, + ) + + check = bedrock_model_invocation_logs_encrypted() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Bedrock Model Invocation logs are not encrypted in S3 bucket: testconfigbucket." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].resource_arn == AWS_ACCOUNT_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock_aws + def test_cloudwatch_logging_not_encrypted(self): + logs_client = client("logs", region_name=AWS_REGION_US_EAST_1) + logs_client.create_log_group(logGroupName="Test") + bedrock = client("bedrock", region_name=AWS_REGION_US_EAST_1) + + logging_config = { + "cloudWatchConfig": { + "logGroupName": "Test", + "roleArn": "testrole", + }, + } + bedrock.put_model_invocation_logging_configuration(loggingConfig=logging_config) + + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + from prowler.providers.aws.services.cloudwatch.cloudwatch_service import Logs + from prowler.providers.aws.services.s3.s3_service import S3 + + 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_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.bedrock_client", + new=Bedrock(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.logs_client", + new=Logs(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.s3_client", + new=S3(aws_provider), + ): + + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted import ( + bedrock_model_invocation_logs_encrypted, + ) + + check = bedrock_model_invocation_logs_encrypted() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Bedrock Model Invocation logs are not encrypted in CloudWatch Log Group: Test." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].resource_arn == AWS_ACCOUNT_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock_aws + def test_s3_and_cloudwatch_logging_encrypted(self): + logs_client = client("logs", region_name=AWS_REGION_US_EAST_1) + logs_client.create_log_group(logGroupName="Test", kmsKeyId="testkey") + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + s3_client.create_bucket(Bucket="testconfigbucket") + sse_config = { + "Rules": [ + { + "ApplyServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256", + } + } + ] + } + + s3_client.put_bucket_encryption( + Bucket="testconfigbucket", ServerSideEncryptionConfiguration=sse_config + ) + bedrock = client("bedrock", region_name=AWS_REGION_US_EAST_1) + + logging_config = { + "cloudWatchConfig": { + "logGroupName": "Test", + "roleArn": "testrole", + "largeDataDeliveryS3Config": { + "bucketName": "testbucket", + }, + }, + "s3Config": { + "bucketName": "testconfigbucket", + }, + } + bedrock.put_model_invocation_logging_configuration(loggingConfig=logging_config) + + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + from prowler.providers.aws.services.cloudwatch.cloudwatch_service import Logs + from prowler.providers.aws.services.s3.s3_service import S3 + + 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_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.bedrock_client", + new=Bedrock(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.logs_client", + new=Logs(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.s3_client", + new=S3(aws_provider), + ): + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted import ( + bedrock_model_invocation_logs_encrypted, + ) + + check = bedrock_model_invocation_logs_encrypted() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Bedrock Model Invocation logs are encrypted." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].resource_arn == AWS_ACCOUNT_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock_aws + def test_s3_logging_encrypted(self): + s3_client = client("s3", region_name=AWS_REGION_US_EAST_1) + s3_client.create_bucket(Bucket="testconfigbucket") + sse_config = { + "Rules": [ + { + "ApplyServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256", + } + } + ] + } + + s3_client.put_bucket_encryption( + Bucket="testconfigbucket", ServerSideEncryptionConfiguration=sse_config + ) + bedrock = client("bedrock", region_name=AWS_REGION_US_EAST_1) + + logging_config = { + "s3Config": { + "bucketName": "testconfigbucket", + }, + } + bedrock.put_model_invocation_logging_configuration(loggingConfig=logging_config) + + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + from prowler.providers.aws.services.cloudwatch.cloudwatch_service import Logs + from prowler.providers.aws.services.s3.s3_service import S3 + + 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_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.bedrock_client", + new=Bedrock(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.logs_client", + new=Logs(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.s3_client", + new=S3(aws_provider), + ): + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted import ( + bedrock_model_invocation_logs_encrypted, + ) + + check = bedrock_model_invocation_logs_encrypted() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Bedrock Model Invocation logs are encrypted." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].resource_arn == AWS_ACCOUNT_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock_aws + def test_cloudwatch_logging_encrypted(self): + logs_client = client("logs", region_name=AWS_REGION_US_EAST_1) + logs_client.create_log_group(logGroupName="Test", kmsKeyId="testkey") + bedrock = client("bedrock", region_name=AWS_REGION_US_EAST_1) + + logging_config = { + "cloudWatchConfig": { + "logGroupName": "Test", + "roleArn": "testrole", + }, + } + bedrock.put_model_invocation_logging_configuration(loggingConfig=logging_config) + + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + from prowler.providers.aws.services.cloudwatch.cloudwatch_service import Logs + from prowler.providers.aws.services.s3.s3_service import S3 + + 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_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.bedrock_client", + new=Bedrock(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.logs_client", + new=Logs(aws_provider), + ), mock.patch( + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.s3_client", + new=S3(aws_provider), + ): + + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted import ( + bedrock_model_invocation_logs_encrypted, + ) + + check = bedrock_model_invocation_logs_encrypted() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Bedrock Model Invocation logs are encrypted." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert result[0].resource_arn == AWS_ACCOUNT_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/cloudwatch/cloudwatch_service_test.py b/tests/providers/aws/services/cloudwatch/cloudwatch_service_test.py index e175dfd65e..d857102f43 100644 --- a/tests/providers/aws/services/cloudwatch/cloudwatch_service_test.py +++ b/tests/providers/aws/services/cloudwatch/cloudwatch_service_test.py @@ -178,18 +178,16 @@ def test_describe_log_groups(self): aws_provider = set_mocked_aws_provider( expected_checks=["cloudwatch_log_group_no_secrets_in_logs"] ) + arn = f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" logs = Logs(aws_provider) assert len(logs.log_groups) == 1 - assert ( - logs.log_groups[0].arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" - ) - assert logs.log_groups[0].name == "/log-group/test" - assert logs.log_groups[0].retention_days == 400 - assert logs.log_groups[0].kms_id == "test_kms_id" - assert not logs.log_groups[0].never_expire - assert logs.log_groups[0].region == AWS_REGION_US_EAST_1 - assert logs.log_groups[0].tags == [ + assert arn in logs.log_groups + assert logs.log_groups[arn].name == "/log-group/test" + assert logs.log_groups[arn].retention_days == 400 + assert logs.log_groups[arn].kms_id == "test_kms_id" + assert not logs.log_groups[arn].never_expire + assert logs.log_groups[arn].region == AWS_REGION_US_EAST_1 + assert logs.log_groups[arn].tags == [ {"tag_key_1": "tag_value_1", "tag_key_2": "tag_value_2"} ] @@ -206,18 +204,16 @@ def test_describe_log_groupsnever_expire(self): aws_provider = set_mocked_aws_provider( expected_checks=["cloudwatch_log_group_no_secrets_in_logs"] ) + arn = f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" logs = Logs(aws_provider) assert len(logs.log_groups) == 1 - assert ( - logs.log_groups[0].arn - == f"arn:aws:logs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:log-group:/log-group/test" - ) - assert logs.log_groups[0].name == "/log-group/test" - assert logs.log_groups[0].never_expire + assert arn in logs.log_groups + assert logs.log_groups[arn].name == "/log-group/test" + assert logs.log_groups[arn].never_expire # Since it never expires we don't use the retention_days - assert logs.log_groups[0].retention_days == 9999 - assert logs.log_groups[0].kms_id == "test_kms_id" - assert logs.log_groups[0].region == AWS_REGION_US_EAST_1 - assert logs.log_groups[0].tags == [ + assert logs.log_groups[arn].retention_days == 9999 + assert logs.log_groups[arn].kms_id == "test_kms_id" + assert logs.log_groups[arn].region == AWS_REGION_US_EAST_1 + assert logs.log_groups[arn].tags == [ {"tag_key_1": "tag_value_1", "tag_key_2": "tag_value_2"} ] From d66e338af1b2320789d4952afda5dc00ef57ab1c Mon Sep 17 00:00:00 2001 From: Sergio Date: Fri, 11 Oct 2024 09:15:43 -0400 Subject: [PATCH 06/11] chore: change check name --- .../__init__.py | 0 ...ion_logs_encryption_enabled.metadata.json} | 2 +- ...del_invocation_logs_encryption_enabled.py} | 2 +- ...nvocation_logs_encryption_enabled_test.py} | 86 +++++++++---------- 4 files changed, 45 insertions(+), 45 deletions(-) rename prowler/providers/aws/services/bedrock/{bedrock_model_invocation_logs_encrypted => bedrock_model_invocation_logs_encryption_enabled}/__init__.py (100%) rename prowler/providers/aws/services/bedrock/{bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.metadata.json => bedrock_model_invocation_logs_encryption_enabled/bedrock_model_invocation_logs_encryption_enabled.metadata.json} (95%) rename prowler/providers/aws/services/bedrock/{bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.py => bedrock_model_invocation_logs_encryption_enabled/bedrock_model_invocation_logs_encryption_enabled.py} (97%) rename tests/providers/aws/services/bedrock/{bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted_test.py => bedrock_model_invocation_logs_encryption_enabled/bedrock_model_invocation_logs_encryption_enabled_test.py} (78%) diff --git a/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/__init__.py b/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encryption_enabled/__init__.py similarity index 100% rename from prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/__init__.py rename to prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encryption_enabled/__init__.py diff --git a/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.metadata.json b/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encryption_enabled/bedrock_model_invocation_logs_encryption_enabled.metadata.json similarity index 95% rename from prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.metadata.json rename to prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encryption_enabled/bedrock_model_invocation_logs_encryption_enabled.metadata.json index 5d57066598..7e5c14221b 100644 --- a/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.metadata.json +++ b/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encryption_enabled/bedrock_model_invocation_logs_encryption_enabled.metadata.json @@ -1,6 +1,6 @@ { "Provider": "aws", - "CheckID": "bedrock_model_invocation_logs_encrypted", + "CheckID": "bedrock_model_invocation_logs_encryption_enabled", "CheckTitle": "Ensure that Amazon Bedrock model invocation logs are encrypted with KMS.", "CheckType": [], "ServiceName": "bedrock", diff --git a/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.py b/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encryption_enabled/bedrock_model_invocation_logs_encryption_enabled.py similarity index 97% rename from prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.py rename to prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encryption_enabled/bedrock_model_invocation_logs_encryption_enabled.py index f06c9e1c9d..7c66a80ce1 100644 --- a/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted.py +++ b/prowler/providers/aws/services/bedrock/bedrock_model_invocation_logs_encryption_enabled/bedrock_model_invocation_logs_encryption_enabled.py @@ -4,7 +4,7 @@ from prowler.providers.aws.services.s3.s3_client import s3_client -class bedrock_model_invocation_logs_encrypted(Check): +class bedrock_model_invocation_logs_encryption_enabled(Check): def execute(self): findings = [] for region, logging in bedrock_client.logging_configurations.items(): diff --git a/tests/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted_test.py b/tests/providers/aws/services/bedrock/bedrock_model_invocation_logs_encryption_enabled/bedrock_model_invocation_logs_encryption_enabled_test.py similarity index 78% rename from tests/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted_test.py rename to tests/providers/aws/services/bedrock/bedrock_model_invocation_logs_encryption_enabled/bedrock_model_invocation_logs_encryption_enabled_test.py index fba7c02aea..d215b62b9a 100644 --- a/tests/providers/aws/services/bedrock/bedrock_model_invocation_logs_encrypted/bedrock_model_invocation_logs_encrypted_test.py +++ b/tests/providers/aws/services/bedrock/bedrock_model_invocation_logs_encryption_enabled/bedrock_model_invocation_logs_encryption_enabled_test.py @@ -11,7 +11,7 @@ ) -class Test_bedrock_model_invocation_logs_encrypted: +class Test_bedrock_model_invocation_logs_encryption_enabled: @mock_aws def test_no_logging(self): from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock @@ -24,20 +24,20 @@ def test_no_logging(self): "prowler.providers.common.provider.Provider.get_global_provider", return_value=aws_provider, ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.bedrock_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.bedrock_client", new=Bedrock(aws_provider), ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.logs_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.logs_client", new=Logs(aws_provider), ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.s3_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.s3_client", new=S3(aws_provider), ): - from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted import ( - bedrock_model_invocation_logs_encrypted, + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled import ( + bedrock_model_invocation_logs_encryption_enabled, ) - check = bedrock_model_invocation_logs_encrypted() + check = bedrock_model_invocation_logs_encryption_enabled() result = check.execute() assert len(result) == 0 @@ -74,20 +74,20 @@ def test_s3_and_cloudwatch_logging_not_encrypted(self): "prowler.providers.common.provider.Provider.get_global_provider", return_value=aws_provider, ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.bedrock_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.bedrock_client", new=Bedrock(aws_provider), ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.logs_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.logs_client", new=Logs(aws_provider), ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.s3_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.s3_client", new=S3(aws_provider), ): - from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted import ( - bedrock_model_invocation_logs_encrypted, + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled import ( + bedrock_model_invocation_logs_encryption_enabled, ) - check = bedrock_model_invocation_logs_encrypted() + check = bedrock_model_invocation_logs_encryption_enabled() result = check.execute() assert len(result) == 1 @@ -124,20 +124,20 @@ def test_s3_logging_not_encrypted(self): "prowler.providers.common.provider.Provider.get_global_provider", return_value=aws_provider, ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.bedrock_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.bedrock_client", new=Bedrock(aws_provider), ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.logs_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.logs_client", new=Logs(aws_provider), ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.s3_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.s3_client", new=S3(aws_provider), ): - from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted import ( - bedrock_model_invocation_logs_encrypted, + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled import ( + bedrock_model_invocation_logs_encryption_enabled, ) - check = bedrock_model_invocation_logs_encrypted() + check = bedrock_model_invocation_logs_encryption_enabled() result = check.execute() assert len(result) == 1 @@ -175,21 +175,21 @@ def test_cloudwatch_logging_not_encrypted(self): "prowler.providers.common.provider.Provider.get_global_provider", return_value=aws_provider, ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.bedrock_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.bedrock_client", new=Bedrock(aws_provider), ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.logs_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.logs_client", new=Logs(aws_provider), ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.s3_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.s3_client", new=S3(aws_provider), ): - from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted import ( - bedrock_model_invocation_logs_encrypted, + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled import ( + bedrock_model_invocation_logs_encryption_enabled, ) - check = bedrock_model_invocation_logs_encrypted() + check = bedrock_model_invocation_logs_encryption_enabled() result = check.execute() assert len(result) == 1 @@ -248,20 +248,20 @@ def test_s3_and_cloudwatch_logging_encrypted(self): "prowler.providers.common.provider.Provider.get_global_provider", return_value=aws_provider, ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.bedrock_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.bedrock_client", new=Bedrock(aws_provider), ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.logs_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.logs_client", new=Logs(aws_provider), ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.s3_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.s3_client", new=S3(aws_provider), ): - from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted import ( - bedrock_model_invocation_logs_encrypted, + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled import ( + bedrock_model_invocation_logs_encryption_enabled, ) - check = bedrock_model_invocation_logs_encrypted() + check = bedrock_model_invocation_logs_encryption_enabled() result = check.execute() assert len(result) == 1 @@ -311,20 +311,20 @@ def test_s3_logging_encrypted(self): "prowler.providers.common.provider.Provider.get_global_provider", return_value=aws_provider, ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.bedrock_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.bedrock_client", new=Bedrock(aws_provider), ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.logs_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.logs_client", new=Logs(aws_provider), ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.s3_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.s3_client", new=S3(aws_provider), ): - from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted import ( - bedrock_model_invocation_logs_encrypted, + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled import ( + bedrock_model_invocation_logs_encryption_enabled, ) - check = bedrock_model_invocation_logs_encrypted() + check = bedrock_model_invocation_logs_encryption_enabled() result = check.execute() assert len(result) == 1 @@ -362,21 +362,21 @@ def test_cloudwatch_logging_encrypted(self): "prowler.providers.common.provider.Provider.get_global_provider", return_value=aws_provider, ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.bedrock_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.bedrock_client", new=Bedrock(aws_provider), ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.logs_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.logs_client", new=Logs(aws_provider), ), mock.patch( - "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted.s3_client", + "prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled.s3_client", new=S3(aws_provider), ): - from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encrypted.bedrock_model_invocation_logs_encrypted import ( - bedrock_model_invocation_logs_encrypted, + from prowler.providers.aws.services.bedrock.bedrock_model_invocation_logs_encryption_enabled.bedrock_model_invocation_logs_encryption_enabled import ( + bedrock_model_invocation_logs_encryption_enabled, ) - check = bedrock_model_invocation_logs_encrypted() + check = bedrock_model_invocation_logs_encryption_enabled() result = check.execute() assert len(result) == 1 From c59678b4997f206aea988d5f0c0c4492d5b85627 Mon Sep 17 00:00:00 2001 From: Sergio Date: Fri, 11 Oct 2024 12:54:37 -0400 Subject: [PATCH 07/11] feat(bedrock): add guardrail checks --- .../__init__.py | 0 ...prompt_attack_filter_enabled.metadata.json | 32 +++++++++++++ ..._guardrail_prompt_attack_filter_enabled.py | 23 ++++++++++ .../__init__.py | 0 ...e_information_filter_enabled.metadata.json | 32 +++++++++++++ ...il_sensitive_information_filter_enabled.py | 20 ++++++++ .../aws/services/bedrock/bedrock_service.py | 46 +++++++++++++++++++ 7 files changed, 153 insertions(+) create mode 100644 prowler/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/__init__.py create mode 100644 prowler/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled.metadata.json create mode 100644 prowler/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled.py create mode 100644 prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/__init__.py create mode 100644 prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.metadata.json create mode 100644 prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.py diff --git a/prowler/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/__init__.py b/prowler/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled.metadata.json b/prowler/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled.metadata.json new file mode 100644 index 0000000000..e6b40d35dc --- /dev/null +++ b/prowler/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "bedrock_guardrail_prompt_attack_filter_enabled", + "CheckTitle": "Configure Prompt Attack Filter with the highest strength for Amazon Bedrock Guardrails.", + "CheckType": [], + "ServiceName": "bedrock", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:bedrock:region:account-id:guardrails/resource-id", + "Severity": "high", + "ResourceType": "Other", + "Description": "Ensure that prompt attack filter strength is set to HIGH for Amazon Bedrock guardrails to mitigate prompt injection and bypass techniques.", + "Risk": "If prompt attack filter strength is not set to HIGH, Bedrock models may be more vulnerable to prompt injection attacks or jailbreak attempts, which could allow harmful or sensitive content to bypass filters and reach end users.", + "RelatedUrl": "https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html", + "Remediation": { + "Code": { + "CLI": "aws bedrock put-guardrails-configuration --guardrails-config 'promptAttackStrength=HIGH'", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Bedrock/prompt-attack-strength.html", + "Terraform": "" + }, + "Recommendation": { + "Text": "Set the prompt attack filter strength to HIGH for Amazon Bedrock guardrails to prevent prompt injection attacks and ensure robust protection against content manipulation.", + "Url": "https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-injection.html" + } + }, + "Categories": [ + "security" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Ensure that prompt attack protection is set to the highest strength to minimize the risk of prompt injection attacks." +} diff --git a/prowler/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled.py b/prowler/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled.py new file mode 100644 index 0000000000..88236ed790 --- /dev/null +++ b/prowler/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled.py @@ -0,0 +1,23 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.bedrock.bedrock_client import bedrock_client + + +class bedrock_guardrail_prompt_attack_filter_enabled(Check): + def execute(self): + findings = [] + for guardrail in bedrock_client.guardrails.values(): + report = Check_Report_AWS(self.metadata()) + report.region = guardrail.region + report.resource_id = guardrail.id + report.resource_arn = guardrail.arn + report.status = "PASS" + report.status_extended = f"Bedrock Guardrail {guardrail.name} is configured with a prompt attacks filter with a HIGH strength." + if not guardrail.prompt_attack_filter_strength: + report.status = "FAIL" + report.status_extended = f"Bedrock Guardrail {guardrail.name} is not configured with a prompt attacks filter." + elif guardrail.prompt_attack_filter_strength != "HIGH": + report.status = "FAIL" + report.status_extended = f"Bedrock Guardrail {guardrail.name} is not configured with a prompt attacks filter with a HIGH strength ({guardrail.prompt_attack_filter_strength})." + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/__init__.py b/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.metadata.json b/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.metadata.json new file mode 100644 index 0000000000..9def1128fe --- /dev/null +++ b/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "bedrock_guardrail_sensitive_information_filter_enabled", + "CheckTitle": "Configure Sensitive Information Filters for Amazon Bedrock Guardrails.", + "CheckType": [], + "ServiceName": "bedrock", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:bedrock:region:account-id:guardrails/resource-id", + "Severity": "high", + "ResourceType": "Other", + "Description": "Ensure that sensitive information filters are enabled for Amazon Bedrock guardrails to prevent the leakage of sensitive data such as personally identifiable information (PII), financial data, or confidential corporate information.", + "Risk": "If sensitive information filters are not enabled, Bedrock models may inadvertently generate or expose confidential or sensitive information in responses, leading to data breaches, regulatory violations, or reputational damage.", + "RelatedUrl": "https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html", + "Remediation": { + "Code": { + "CLI": "aws bedrock put-guardrails-configuration --guardrails-config 'sensitiveInformationFilter=true'", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Bedrock/guardrails-with-pii-mask-block.html", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable sensitive information filters for Amazon Bedrock guardrails to prevent the exposure of sensitive or confidential information.", + "Url": "https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-sensitive-filters.html" + } + }, + "Categories": [ + "security" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.py b/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.py new file mode 100644 index 0000000000..57a47b2eb4 --- /dev/null +++ b/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.py @@ -0,0 +1,20 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.bedrock.bedrock_client import bedrock_client + + +class bedrock_guardrail_sensitive_information_filter_enabled(Check): + def execute(self): + findings = [] + for guardrail in bedrock_client.guardrails.values(): + report = Check_Report_AWS(self.metadata()) + report.region = guardrail.region + report.resource_id = guardrail.id + report.resource_arn = guardrail.arn + report.status = "PASS" + report.status_extended = f"Bedrock Guardrail {guardrail.name} block or mask sensitive information." + if not guardrail.sensitive_information_filter: + report.status = "FAIL" + report.status_extended = f"Bedrock Guardrail {guardrail.name} is not configured to block or mask sensitive information." + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/bedrock/bedrock_service.py b/prowler/providers/aws/services/bedrock/bedrock_service.py index fd180b12ec..0adcc39937 100644 --- a/prowler/providers/aws/services/bedrock/bedrock_service.py +++ b/prowler/providers/aws/services/bedrock/bedrock_service.py @@ -11,7 +11,10 @@ def __init__(self, provider): # Call AWSService's __init__ super().__init__(__class__.__name__, provider) self.logging_configurations = {} + self.guardrails = {} self.__threading_call__(self._get_model_invocation_logging_configuration) + self.__threading_call__(self._list_guardrails) + self.__threading_call__(self._get_guardrail, self.guardrails.values()) def _get_model_invocation_logging_configuration(self, regional_client): logger.info("Bedrock - Getting Model Invocation Logging Configuration...") @@ -40,8 +43,51 @@ def _get_model_invocation_logging_configuration(self, regional_client): f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def _list_guardrails(self, regional_client): + logger.info("Bedrock - Listing Guardrails...") + try: + for guardrail in regional_client.list_guardrails().get("guardrails", []): + self.guardrails[guardrail["arn"]] = Guardrail( + id=guardrail["id"], + name=guardrail["name"], + arn=guardrail["arn"], + region=regional_client.region, + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _get_guardrail(self, guardrail): + logger.info("Bedrock - Getting Guardrail...") + try: + guardrail_info = self.regional_clients[guardrail.region].get_guardrail( + guardrailIdentifier=guardrail.id + ) + guardrail.sensitive_information_filter = ( + "sensitiveInformationPolicy" in guardrail_info + ) + for filter in guardrail_info.get("contentPolicy", {}).get("filters", []): + if filter.get("type") == "PROMPT_ATTACK": + guardrail.prompt_attack_filter_strength = filter.get( + "inputStrength", None + ) + except Exception as error: + logger.error( + f"{guardrail.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + class LoggingConfiguration(BaseModel): enabled: bool = False cloudwatch_log_group: Optional[str] = None s3_bucket: Optional[str] = None + + +class Guardrail(BaseModel): + id: str + name: str + arn: str + region: str + sensitive_information_filter: bool = False + prompt_attack_filter_strength: str = None From ae9b34f454b45d22bd4d532ca1b115807909eb76 Mon Sep 17 00:00:00 2001 From: Sergio Date: Fri, 11 Oct 2024 15:00:53 -0400 Subject: [PATCH 08/11] chore: add tags --- ...ck_guardrail_prompt_attack_filter_enabled.py | 7 ++++--- ...rail_sensitive_information_filter_enabled.py | 1 + .../aws/services/bedrock/bedrock_service.py | 17 ++++++++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/prowler/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled.py b/prowler/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled.py index 88236ed790..6f2ba89507 100644 --- a/prowler/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled.py +++ b/prowler/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled.py @@ -10,14 +10,15 @@ def execute(self): report.region = guardrail.region report.resource_id = guardrail.id report.resource_arn = guardrail.arn + report.resource_tags = guardrail.tags report.status = "PASS" - report.status_extended = f"Bedrock Guardrail {guardrail.name} is configured with a prompt attacks filter with a HIGH strength." + report.status_extended = f"Bedrock Guardrail {guardrail.name} is configured to detect and block prompt attacks with a HIGH strength." if not guardrail.prompt_attack_filter_strength: report.status = "FAIL" - report.status_extended = f"Bedrock Guardrail {guardrail.name} is not configured with a prompt attacks filter." + report.status_extended = f"Bedrock Guardrail {guardrail.name} is not configured to block prompt attacks." elif guardrail.prompt_attack_filter_strength != "HIGH": report.status = "FAIL" - report.status_extended = f"Bedrock Guardrail {guardrail.name} is not configured with a prompt attacks filter with a HIGH strength ({guardrail.prompt_attack_filter_strength})." + report.status_extended = f"Bedrock Guardrail {guardrail.name} is configured to block prompt attacks but with a filter strength of {guardrail.prompt_attack_filter_strength}, not HIGH." findings.append(report) return findings diff --git a/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.py b/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.py index 57a47b2eb4..07e0388241 100644 --- a/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.py +++ b/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.py @@ -10,6 +10,7 @@ def execute(self): report.region = guardrail.region report.resource_id = guardrail.id report.resource_arn = guardrail.arn + report.resource_tags = guardrail.tags report.status = "PASS" report.status_extended = f"Bedrock Guardrail {guardrail.name} block or mask sensitive information." if not guardrail.sensitive_information_filter: diff --git a/prowler/providers/aws/services/bedrock/bedrock_service.py b/prowler/providers/aws/services/bedrock/bedrock_service.py index 0adcc39937..a75287fabd 100644 --- a/prowler/providers/aws/services/bedrock/bedrock_service.py +++ b/prowler/providers/aws/services/bedrock/bedrock_service.py @@ -15,6 +15,7 @@ def __init__(self, provider): self.__threading_call__(self._get_model_invocation_logging_configuration) self.__threading_call__(self._list_guardrails) self.__threading_call__(self._get_guardrail, self.guardrails.values()) + self.__threading_call__(self._list_tags_for_resource, self.guardrails.values()) def _get_model_invocation_logging_configuration(self, regional_client): logger.info("Bedrock - Getting Model Invocation Logging Configuration...") @@ -77,6 +78,19 @@ def _get_guardrail(self, guardrail): f"{guardrail.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def _list_tags_for_resource(self, guardrail): + logger.info("Bedrock - Listing Tags for Resource...") + try: + guardrail.tags = ( + self.regional_clients[guardrail.region] + .list_tags_for_resource(resourceARN=guardrail.arn) + .get("tags", []) + ) + except Exception as error: + logger.error( + f"{guardrail.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + class LoggingConfiguration(BaseModel): enabled: bool = False @@ -89,5 +103,6 @@ class Guardrail(BaseModel): name: str arn: str region: str + tags: Optional[list] = [] sensitive_information_filter: bool = False - prompt_attack_filter_strength: str = None + prompt_attack_filter_strength: Optional[str] From 12181abe114fc03292b3fed64710c11b6fc8cdc7 Mon Sep 17 00:00:00 2001 From: Sergio Date: Fri, 11 Oct 2024 16:32:39 -0400 Subject: [PATCH 09/11] add test --- ...drail_prompt_attack_filter_enabled_test.py | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 tests/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled_test.py diff --git a/tests/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled_test.py b/tests/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled_test.py new file mode 100644 index 0000000000..52af65fbe3 --- /dev/null +++ b/tests/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled_test.py @@ -0,0 +1,230 @@ +from unittest import mock + +import botocore +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_EU_WEST_1, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +make_api_call = botocore.client.BaseClient._make_api_call + +GUARDRAIL_ARN = ( + f"arn:aws:bedrock:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:guardrail/test-id" +) + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "ListGuardrails": + return { + "guardrails": [ + { + "id": "test-id", + "arn": GUARDRAIL_ARN, + "status": "READY", + "name": "test", + } + ] + } + elif operation_name == "GetGuardrail": + return { + "name": "test", + "guardrailId": "test-id", + "guardrailArn": GUARDRAIL_ARN, + "status": "READY", + "contentPolicy": { + "filters": [ + { + "type": "PROMPT_ATTACK", + "inputStrength": "HIGH", + "outputStrength": "NONE", + }, + ] + }, + "blockedInputMessaging": "Sorry, the model cannot answer this question.", + "blockedOutputsMessaging": "Sorry, the model cannot answer this question.", + } + return make_api_call(self, operation_name, kwarg) + + +def mock_make_api_call_v2(self, operation_name, kwarg): + if operation_name == "ListGuardrails": + return { + "guardrails": [ + { + "id": "test-id", + "arn": GUARDRAIL_ARN, + "status": "READY", + "name": "test", + } + ] + } + elif operation_name == "GetGuardrail": + return { + "name": "test", + "guardrailId": "test-id", + "guardrailArn": GUARDRAIL_ARN, + "status": "READY", + "contentPolicy": {"filters": []}, + "blockedInputMessaging": "Sorry, the model cannot answer this question.", + "blockedOutputsMessaging": "Sorry, the model cannot answer this question.", + } + return make_api_call(self, operation_name, kwarg) + + +def mock_make_api_call_v3(self, operation_name, kwarg): + if operation_name == "ListGuardrails": + return { + "guardrails": [ + { + "id": "test-id", + "arn": GUARDRAIL_ARN, + "status": "READY", + "name": "test", + } + ] + } + elif operation_name == "GetGuardrail": + return { + "name": "test", + "guardrailId": "test-id", + "guardrailArn": GUARDRAIL_ARN, + "status": "READY", + "contentPolicy": { + "filters": [ + { + "type": "PROMPT_ATTACK", + "inputStrength": "LOW", + "outputStrength": "NONE", + }, + ] + }, + "blockedInputMessaging": "Sorry, the model cannot answer this question.", + "blockedOutputsMessaging": "Sorry, the model cannot answer this question.", + } + return make_api_call(self, operation_name, kwarg) + + +class Test_bedrock_guardrail_prompt_attack_filter_enabled: + @mock_aws + def test_no_guardrails(self): + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_EU_WEST_1, 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_guardrail_prompt_attack_filter_enabled.bedrock_guardrail_prompt_attack_filter_enabled.bedrock_client", + new=Bedrock(aws_provider), + ): + from prowler.providers.aws.services.bedrock.bedrock_guardrail_prompt_attack_filter_enabled.bedrock_guardrail_prompt_attack_filter_enabled import ( + bedrock_guardrail_prompt_attack_filter_enabled, + ) + + check = bedrock_guardrail_prompt_attack_filter_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock_aws + def test_guardrail_high_filter(self): + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + + 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_guardrail_prompt_attack_filter_enabled.bedrock_guardrail_prompt_attack_filter_enabled.bedrock_client", + new=Bedrock(aws_provider), + ): + from prowler.providers.aws.services.bedrock.bedrock_guardrail_prompt_attack_filter_enabled.bedrock_guardrail_prompt_attack_filter_enabled import ( + bedrock_guardrail_prompt_attack_filter_enabled, + ) + + check = bedrock_guardrail_prompt_attack_filter_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Bedrock Guardrail test is configured to detect and block prompt attacks with a HIGH strength." + ) + assert result[0].resource_id == "test-id" + assert result[0].resource_arn == GUARDRAIL_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call_v2) + @mock_aws + def test_guardrail_no_filter(self): + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + + 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_guardrail_prompt_attack_filter_enabled.bedrock_guardrail_prompt_attack_filter_enabled.bedrock_client", + new=Bedrock(aws_provider), + ): + from prowler.providers.aws.services.bedrock.bedrock_guardrail_prompt_attack_filter_enabled.bedrock_guardrail_prompt_attack_filter_enabled import ( + bedrock_guardrail_prompt_attack_filter_enabled, + ) + + check = bedrock_guardrail_prompt_attack_filter_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Bedrock Guardrail test is not configured to block prompt attacks." + ) + assert result[0].resource_id == "test-id" + assert result[0].resource_arn == GUARDRAIL_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call_v3) + @mock_aws + def test_guardrail_low_filter(self): + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + + 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_guardrail_prompt_attack_filter_enabled.bedrock_guardrail_prompt_attack_filter_enabled.bedrock_client", + new=Bedrock(aws_provider), + ): + from prowler.providers.aws.services.bedrock.bedrock_guardrail_prompt_attack_filter_enabled.bedrock_guardrail_prompt_attack_filter_enabled import ( + bedrock_guardrail_prompt_attack_filter_enabled, + ) + + check = bedrock_guardrail_prompt_attack_filter_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Bedrock Guardrail test is configured to block prompt attacks but with a filter strength of LOW, not HIGH." + ) + assert result[0].resource_id == "test-id" + assert result[0].resource_arn == GUARDRAIL_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] From 16579b4ed967d53dccba57706737d1f39df7e065 Mon Sep 17 00:00:00 2001 From: Sergio Date: Fri, 11 Oct 2024 16:40:46 -0400 Subject: [PATCH 10/11] add tests --- ...il_sensitive_information_filter_enabled.py | 2 +- ...nsitive_information_filter_enabled_test.py | 157 ++++++++++++++++++ .../services/bedrock/bedrock_service_test.py | 77 +++++++++ 3 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 tests/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled_test.py diff --git a/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.py b/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.py index 07e0388241..a758358c60 100644 --- a/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.py +++ b/prowler/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled.py @@ -12,7 +12,7 @@ def execute(self): report.resource_arn = guardrail.arn report.resource_tags = guardrail.tags report.status = "PASS" - report.status_extended = f"Bedrock Guardrail {guardrail.name} block or mask sensitive information." + report.status_extended = f"Bedrock Guardrail {guardrail.name} is blocking or masking sensitive information." if not guardrail.sensitive_information_filter: report.status = "FAIL" report.status_extended = f"Bedrock Guardrail {guardrail.name} is not configured to block or mask sensitive information." diff --git a/tests/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled_test.py b/tests/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled_test.py new file mode 100644 index 0000000000..1c7743052f --- /dev/null +++ b/tests/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled_test.py @@ -0,0 +1,157 @@ +from unittest import mock + +import botocore +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_EU_WEST_1, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +make_api_call = botocore.client.BaseClient._make_api_call + +GUARDRAIL_ARN = ( + f"arn:aws:bedrock:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:guardrail/test-id" +) + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "ListGuardrails": + return { + "guardrails": [ + { + "id": "test-id", + "arn": GUARDRAIL_ARN, + "status": "READY", + "name": "test", + } + ] + } + elif operation_name == "GetGuardrail": + return { + "name": "test", + "guardrailId": "test-id", + "guardrailArn": GUARDRAIL_ARN, + "status": "READY", + "blockedInputMessaging": "Sorry, the model cannot answer this question.", + "blockedOutputsMessaging": "Sorry, the model cannot answer this question.", + } + return make_api_call(self, operation_name, kwarg) + + +def mock_make_api_call_v2(self, operation_name, kwarg): + if operation_name == "ListGuardrails": + return { + "guardrails": [ + { + "id": "test-id", + "arn": GUARDRAIL_ARN, + "status": "READY", + "name": "test", + } + ] + } + elif operation_name == "GetGuardrail": + return { + "name": "test", + "guardrailId": "test-id", + "guardrailArn": GUARDRAIL_ARN, + "status": "READY", + "sensitiveInformationPolicy": True, + "contentPolicy": {"filters": []}, + "blockedInputMessaging": "Sorry, the model cannot answer this question.", + "blockedOutputsMessaging": "Sorry, the model cannot answer this question.", + } + return make_api_call(self, operation_name, kwarg) + + +class Test_bedrock_guardrail_sensitive_information_filter_enabled: + @mock_aws + def test_no_guardrails(self): + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_EU_WEST_1, 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_guardrail_sensitive_information_filter_enabled.bedrock_guardrail_sensitive_information_filter_enabled.bedrock_client", + new=Bedrock(aws_provider), + ): + from prowler.providers.aws.services.bedrock.bedrock_guardrail_sensitive_information_filter_enabled.bedrock_guardrail_sensitive_information_filter_enabled import ( + bedrock_guardrail_sensitive_information_filter_enabled, + ) + + check = bedrock_guardrail_sensitive_information_filter_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock_aws + def test_guardrail_no_filter(self): + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + + 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_guardrail_sensitive_information_filter_enabled.bedrock_guardrail_sensitive_information_filter_enabled.bedrock_client", + new=Bedrock(aws_provider), + ): + from prowler.providers.aws.services.bedrock.bedrock_guardrail_sensitive_information_filter_enabled.bedrock_guardrail_sensitive_information_filter_enabled import ( + bedrock_guardrail_sensitive_information_filter_enabled, + ) + + check = bedrock_guardrail_sensitive_information_filter_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Bedrock Guardrail test is not configured to block or mask sensitive information." + ) + assert result[0].resource_id == "test-id" + assert result[0].resource_arn == GUARDRAIL_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call_v2) + @mock_aws + def test_guardrail_with_filter(self): + from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock + + 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_guardrail_sensitive_information_filter_enabled.bedrock_guardrail_sensitive_information_filter_enabled.bedrock_client", + new=Bedrock(aws_provider), + ): + from prowler.providers.aws.services.bedrock.bedrock_guardrail_sensitive_information_filter_enabled.bedrock_guardrail_sensitive_information_filter_enabled import ( + bedrock_guardrail_sensitive_information_filter_enabled, + ) + + check = bedrock_guardrail_sensitive_information_filter_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Bedrock Guardrail test is blocking or masking sensitive information." + ) + assert result[0].resource_id == "test-id" + assert result[0].resource_arn == GUARDRAIL_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/bedrock/bedrock_service_test.py b/tests/providers/aws/services/bedrock/bedrock_service_test.py index 96caf3d859..fceccd9e7a 100644 --- a/tests/providers/aws/services/bedrock/bedrock_service_test.py +++ b/tests/providers/aws/services/bedrock/bedrock_service_test.py @@ -1,3 +1,6 @@ +from unittest import mock + +import botocore from boto3 import client from moto import mock_aws @@ -9,6 +12,52 @@ set_mocked_aws_provider, ) +make_api_call = botocore.client.BaseClient._make_api_call + +GUARDRAIL_ARN = ( + f"arn:aws:bedrock:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:guardrail/test-id" +) + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "ListGuardrails": + return { + "guardrails": [ + { + "id": "test-id", + "arn": GUARDRAIL_ARN, + "status": "READY", + "name": "test", + } + ] + } + elif operation_name == "GetGuardrail": + return { + "name": "test", + "guardrailId": "test-id", + "guardrailArn": GUARDRAIL_ARN, + "status": "READY", + "contentPolicy": { + "filters": [ + { + "type": "PROMPT_ATTACK", + "inputStrength": "HIGH", + "outputStrength": "NONE", + }, + ] + }, + "sensitiveInformationPolicy": True, + "blockedInputMessaging": "Sorry, the model cannot answer this question.", + "blockedOutputsMessaging": "Sorry, the model cannot answer this question.", + } + elif operation_name == "ListTagsForResource": + return { + "tags": [ + {"Key": "Name", "Value": "test"}, + ] + } + return make_api_call(self, operation_name, kwarg) + class Test_Bedrock_Service: @mock_aws @@ -77,3 +126,31 @@ def test_get_model_invocation_logging_configuration(self): == "testconfigbucket" ) assert not bedrock.logging_configurations[AWS_REGION_US_EAST_1].enabled + + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock_aws + def test_list_guardrails(self): + aws_provider = set_mocked_aws_provider(audited_regions=[AWS_REGION_US_EAST_1]) + bedrock = Bedrock(aws_provider) + assert len(bedrock.guardrails) == 1 + assert GUARDRAIL_ARN in bedrock.guardrails + assert bedrock.guardrails[GUARDRAIL_ARN].id == "test-id" + assert bedrock.guardrails[GUARDRAIL_ARN].name == "test" + assert bedrock.guardrails[GUARDRAIL_ARN].region == AWS_REGION_US_EAST_1 + + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock_aws + def test_get_guardrail(self): + aws_provider = set_mocked_aws_provider(audited_regions=[AWS_REGION_US_EAST_1]) + bedrock = Bedrock(aws_provider) + assert bedrock.guardrails[GUARDRAIL_ARN].sensitive_information_filter + assert bedrock.guardrails[GUARDRAIL_ARN].prompt_attack_filter_strength == "HIGH" + + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock_aws + def test_list_tags_for_resource(self): + aws_provider = set_mocked_aws_provider(audited_regions=[AWS_REGION_US_EAST_1]) + bedrock = Bedrock(aws_provider) + assert bedrock.guardrails[GUARDRAIL_ARN].tags == [ + {"Key": "Name", "Value": "test"} + ] From a50114f41424d69ff72472849b896ccd8ba27d89 Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 14 Oct 2024 09:21:39 -0400 Subject: [PATCH 11/11] chore: solve comments --- .../aws/services/bedrock/bedrock_service.py | 2 +- ...rdrail_prompt_attack_filter_enabled_test.py | 18 ++++++++++++------ ...ensitive_information_filter_enabled_test.py | 12 ++++++++---- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/prowler/providers/aws/services/bedrock/bedrock_service.py b/prowler/providers/aws/services/bedrock/bedrock_service.py index a75287fabd..3cf9f9dcfb 100644 --- a/prowler/providers/aws/services/bedrock/bedrock_service.py +++ b/prowler/providers/aws/services/bedrock/bedrock_service.py @@ -71,7 +71,7 @@ def _get_guardrail(self, guardrail): for filter in guardrail_info.get("contentPolicy", {}).get("filters", []): if filter.get("type") == "PROMPT_ATTACK": guardrail.prompt_attack_filter_strength = filter.get( - "inputStrength", None + "inputStrength", "NONE" ) except Exception as error: logger.error( diff --git a/tests/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled_test.py b/tests/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled_test.py index 52af65fbe3..4f5ed8a2a9 100644 --- a/tests/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled_test.py +++ b/tests/providers/aws/services/bedrock/bedrock_guardrail_prompt_attack_filter_enabled/bedrock_guardrail_prompt_attack_filter_enabled_test.py @@ -17,7 +17,7 @@ ) -def mock_make_api_call(self, operation_name, kwarg): +def mock_make_api_call_high_filter(self, operation_name, kwarg): if operation_name == "ListGuardrails": return { "guardrails": [ @@ -50,7 +50,7 @@ def mock_make_api_call(self, operation_name, kwarg): return make_api_call(self, operation_name, kwarg) -def mock_make_api_call_v2(self, operation_name, kwarg): +def mock_make_api_call_no_filter(self, operation_name, kwarg): if operation_name == "ListGuardrails": return { "guardrails": [ @@ -75,7 +75,7 @@ def mock_make_api_call_v2(self, operation_name, kwarg): return make_api_call(self, operation_name, kwarg) -def mock_make_api_call_v3(self, operation_name, kwarg): +def mock_make_api_call_low_filter(self, operation_name, kwarg): if operation_name == "ListGuardrails": return { "guardrails": [ @@ -133,7 +133,9 @@ def test_no_guardrails(self): assert len(result) == 0 - @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock.patch( + "botocore.client.BaseClient._make_api_call", new=mock_make_api_call_high_filter + ) @mock_aws def test_guardrail_high_filter(self): from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock @@ -165,7 +167,9 @@ def test_guardrail_high_filter(self): assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_tags == [] - @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call_v2) + @mock.patch( + "botocore.client.BaseClient._make_api_call", new=mock_make_api_call_no_filter + ) @mock_aws def test_guardrail_no_filter(self): from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock @@ -197,7 +201,9 @@ def test_guardrail_no_filter(self): assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_tags == [] - @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call_v3) + @mock.patch( + "botocore.client.BaseClient._make_api_call", new=mock_make_api_call_low_filter + ) @mock_aws def test_guardrail_low_filter(self): from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock diff --git a/tests/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled_test.py b/tests/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled_test.py index 1c7743052f..b33eb6ffbc 100644 --- a/tests/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled_test.py +++ b/tests/providers/aws/services/bedrock/bedrock_guardrail_sensitive_information_filter_enabled/bedrock_guardrail_sensitive_information_filter_enabled_test.py @@ -17,7 +17,7 @@ ) -def mock_make_api_call(self, operation_name, kwarg): +def mock_make_api_call_no_filter(self, operation_name, kwarg): if operation_name == "ListGuardrails": return { "guardrails": [ @@ -41,7 +41,7 @@ def mock_make_api_call(self, operation_name, kwarg): return make_api_call(self, operation_name, kwarg) -def mock_make_api_call_v2(self, operation_name, kwarg): +def mock_make_api_call_with_filter(self, operation_name, kwarg): if operation_name == "ListGuardrails": return { "guardrails": [ @@ -92,7 +92,9 @@ def test_no_guardrails(self): assert len(result) == 0 - @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + @mock.patch( + "botocore.client.BaseClient._make_api_call", new=mock_make_api_call_no_filter + ) @mock_aws def test_guardrail_no_filter(self): from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock @@ -124,7 +126,9 @@ def test_guardrail_no_filter(self): assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_tags == [] - @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call_v2) + @mock.patch( + "botocore.client.BaseClient._make_api_call", new=mock_make_api_call_with_filter + ) @mock_aws def test_guardrail_with_filter(self): from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock