mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-11 05:54:17 +00:00
feat(permissions): dead-letter the real-time events EventBridge cannot deliver
Adds an SQS dead-letter queue to the real-time detection target, in both the CloudFormation and Terraform templates, so a delivery failure is auditable instead of silent. EventBridge retries for up to 24 hours and then writes the event to the queue with the error code and attempt count; responses that are never retried land there on the first attempt. EventBridge delivers to a DLQ as a service rather than through the target invoke role, so the queue carries a resource policy granting sqs:SendMessage to events.amazonaws.com, scoped to the rule ARN. The queue uses SQS-managed encryption to avoid granting KMS permissions, and keeps messages for the 14-day maximum. Prowler is granted no access to it.
This commit is contained in:
1 parent
0a39c004b1
commit
29102e7b33
5 files changed
+114
No files matched your search
@@ -680,6 +680,45 @@ Resources:
|
||||
- Key: "Name"
|
||||
Value: "ProwlerRealtimeInvoke"
|
||||
|
||||
# Captures the events EventBridge could not deliver, so a delivery failure is auditable
|
||||
ProwlerRealtimeDlq:
|
||||
Type: AWS::SQS::Queue
|
||||
Condition: RealtimeDetectionEnabled
|
||||
Properties:
|
||||
QueueName: ProwlerRealtimeDetectionDLQ
|
||||
MessageRetentionPeriod: 1209600
|
||||
SqsManagedSseEnabled: true
|
||||
Tags:
|
||||
- Key: "Service"
|
||||
Value: "https://prowler.com"
|
||||
- Key: "Support"
|
||||
Value: "support@prowler.com"
|
||||
- Key: "CloudFormation"
|
||||
Value: "true"
|
||||
- Key: "Name"
|
||||
Value: "ProwlerRealtimeDetectionDLQ"
|
||||
|
||||
# EventBridge writes to the DLQ as a service, not through the invoke role, so it needs a queue policy
|
||||
ProwlerRealtimeDlqPolicy:
|
||||
Type: AWS::SQS::QueuePolicy
|
||||
Condition: RealtimeDetectionEnabled
|
||||
Properties:
|
||||
Queues:
|
||||
- !Ref ProwlerRealtimeDlq
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Sid: AllowEventBridgeDeadLetterDelivery
|
||||
Effect: Allow
|
||||
Principal:
|
||||
Service: events.amazonaws.com
|
||||
Action: "sqs:SendMessage"
|
||||
Resource: !GetAtt ProwlerRealtimeDlq.Arn
|
||||
Condition:
|
||||
ArnEquals:
|
||||
# Built from the rule name to avoid a circular dependency with the rule
|
||||
"aws:SourceArn": !Sub "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/ProwlerRealtimeDetection"
|
||||
|
||||
ProwlerRealtimeRule:
|
||||
Type: AWS::Events::Rule
|
||||
Condition: RealtimeDetectionEnabled
|
||||
@@ -735,6 +774,12 @@ Resources:
|
||||
- Id: ProwlerCloud
|
||||
Arn: !GetAtt ProwlerRealtimeApiDestination.Arn
|
||||
RoleArn: !GetAtt ProwlerRealtimeInvokeRole.Arn
|
||||
# Retries cover an endpoint outage; whatever outlives the window is dead-lettered
|
||||
RetryPolicy:
|
||||
MaximumEventAgeInSeconds: 86400
|
||||
MaximumRetryAttempts: 185
|
||||
DeadLetterConfig:
|
||||
Arn: !GetAtt ProwlerRealtimeDlq.Arn
|
||||
Tags:
|
||||
- Key: "Service"
|
||||
Value: "https://prowler.com"
|
||||
@@ -806,3 +851,10 @@ Outputs:
|
||||
Value: !GetAtt ProwlerRealtimeApiDestination.Arn
|
||||
Export:
|
||||
Name: !Sub "${AWS::StackName}-ProwlerRealtimeApiDestinationArn"
|
||||
|
||||
ProwlerRealtimeDlqUrl:
|
||||
Condition: RealtimeDetectionEnabled
|
||||
Description: "URL of the dead-letter queue holding the events EventBridge could not deliver"
|
||||
Value: !Ref ProwlerRealtimeDlq
|
||||
Export:
|
||||
Name: !Sub "${AWS::StackName}-ProwlerRealtimeDlqUrl"
|
||||
@@ -55,6 +55,8 @@ terraform apply \
|
||||
|
||||
`prowler_webhook_url` already defaults to the Prowler Cloud ingest endpoint, so only the API key is needed. Override it for a self-hosted deployment or for testing.
|
||||
|
||||
Failed deliveries are not lost: EventBridge retries for up to 24 hours and then writes the event to the `ProwlerRealtimeDetectionDLQ` queue created in your account, together with the error code and the number of attempts. Responses that are never retried (any 4xx other than 401, 407, 409 and 429) land there on the first attempt. The queue is yours: Prowler has no permission to read it.
|
||||
|
||||
> **Note:** the EventBridge rule is regional. It forwards only the events delivered to the default event bus of the region Terraform deploys to (`us-east-1` by default, see `versions.tf`). IAM events are global and always land in `us-east-1`, but regional services (EC2 security groups, RDS, per-region Config and GuardDuty) are only covered in that region. Deploy the module in every region you want covered.
|
||||
|
||||
#### Using terraform.tfvars file (Recommended)
|
||||
@@ -78,5 +80,6 @@ After successful deployment, you'll get:
|
||||
- `realtime_detection_enabled`: Whether real-time detection is enabled
|
||||
- `prowler_realtime_rule_arn`: ARN of the EventBridge rule (null if real-time detection is disabled)
|
||||
- `prowler_realtime_api_destination_arn`: ARN of the EventBridge API destination (null if real-time detection is disabled)
|
||||
- `prowler_realtime_dlq_url`: URL of the dead-letter queue (null if real-time detection is disabled)
|
||||
|
||||
> **Note:** Terraform will use the AWS credentials of your default profile or AWS_PROFILE environment variable.
|
||||
@@ -35,3 +35,8 @@ output "prowler_realtime_api_destination_arn" {
|
||||
description = "ARN of the EventBridge API destination targeting Prowler Cloud (null if real-time detection is disabled)"
|
||||
value = try(module.realtime_detection[0].prowler_realtime_api_destination_arn, null)
|
||||
}
|
||||
|
||||
output "prowler_realtime_dlq_url" {
|
||||
description = "URL of the dead-letter queue holding the events EventBridge could not deliver (null if real-time detection is disabled)"
|
||||
value = try(module.realtime_detection[0].prowler_realtime_dlq_url, null)
|
||||
}
|
||||
@@ -68,6 +68,40 @@ resource "aws_iam_role_policy" "prowler_realtime_invoke" {
|
||||
})
|
||||
}
|
||||
|
||||
# Dead-letter queue for the events EventBridge could not deliver
|
||||
###################################
|
||||
resource "aws_sqs_queue" "prowler_realtime_dlq" {
|
||||
name = "ProwlerRealtimeDetectionDLQ"
|
||||
message_retention_seconds = 1209600
|
||||
sqs_managed_sse_enabled = true
|
||||
}
|
||||
|
||||
# EventBridge writes to the DLQ as a service, not through the invoke role, so it needs a queue policy
|
||||
data "aws_iam_policy_document" "prowler_realtime_dlq" {
|
||||
statement {
|
||||
sid = "AllowEventBridgeDeadLetterDelivery"
|
||||
effect = "Allow"
|
||||
actions = ["sqs:SendMessage"]
|
||||
resources = [aws_sqs_queue.prowler_realtime_dlq.arn]
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["events.amazonaws.com"]
|
||||
}
|
||||
|
||||
condition {
|
||||
test = "ArnEquals"
|
||||
variable = "aws:SourceArn"
|
||||
values = [aws_cloudwatch_event_rule.prowler_realtime.arn]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_sqs_queue_policy" "prowler_realtime_dlq" {
|
||||
queue_url = aws_sqs_queue.prowler_realtime_dlq.id
|
||||
policy = data.aws_iam_policy_document.prowler_realtime_dlq.json
|
||||
}
|
||||
|
||||
# Rule matching the CloudTrail management events tracked by Prowler real-time detection
|
||||
###################################
|
||||
resource "aws_cloudwatch_event_rule" "prowler_realtime" {
|
||||
@@ -129,4 +163,14 @@ resource "aws_cloudwatch_event_target" "prowler_realtime" {
|
||||
target_id = "ProwlerCloud"
|
||||
arn = aws_cloudwatch_event_api_destination.prowler_realtime.arn
|
||||
role_arn = aws_iam_role.prowler_realtime_invoke.arn
|
||||
|
||||
# Retries cover an endpoint outage; whatever outlives the window is dead-lettered
|
||||
retry_policy {
|
||||
maximum_event_age_in_seconds = 86400
|
||||
maximum_retry_attempts = 185
|
||||
}
|
||||
|
||||
dead_letter_config {
|
||||
arn = aws_sqs_queue.prowler_realtime_dlq.arn
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,16 @@ output "prowler_realtime_api_destination_arn" {
|
||||
value = aws_cloudwatch_event_api_destination.prowler_realtime.arn
|
||||
}
|
||||
|
||||
output "prowler_realtime_dlq_url" {
|
||||
description = "URL of the dead-letter queue holding the events EventBridge could not deliver"
|
||||
value = aws_sqs_queue.prowler_realtime_dlq.id
|
||||
}
|
||||
|
||||
output "prowler_realtime_dlq_arn" {
|
||||
description = "ARN of the dead-letter queue holding the events EventBridge could not deliver"
|
||||
value = aws_sqs_queue.prowler_realtime_dlq.arn
|
||||
}
|
||||
|
||||
output "prowler_realtime_invoke_role_arn" {
|
||||
description = "ARN of the IAM role assumed by EventBridge to invoke the API destination"
|
||||
value = aws_iam_role.prowler_realtime_invoke.arn
|
||||
|
||||
Reference in new issue
Block a user