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 1e93bb87fb..131631cd3f 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/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/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..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 @@ -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 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 0d0cca204d..74f82b6c66 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: [ @@ -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/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 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" + )