feat(aws): add checks for Bedrock logging configuration and CloudTrail LLM Jacking detection (#5314)

This commit is contained in:
Sergio Garcia
2024-10-11 15:01:45 -04:00
committed by GitHub
parent 444d820f98
commit 3c929bd68f
19 changed files with 857 additions and 17 deletions
+11 -7
View File
@@ -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.
+20 -2
View File
@@ -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
+20 -2
View File
@@ -135,7 +135,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:
[
@@ -192,7 +192,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:
[
@@ -287,6 +287,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
@@ -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())
@@ -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": ""
}
@@ -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
@@ -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
@@ -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
@@ -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": ""
}
@@ -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
@@ -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
+18 -2
View File
@@ -128,7 +128,7 @@ config_aws = {
"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 @@ config_aws = {
"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 @@ config_aws = {
"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": [
+20 -2
View File
@@ -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
@@ -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 == []
@@ -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
@@ -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"
)