diff --git a/permissions/templates/cloudformation/prowler-scan-role.yml b/permissions/templates/cloudformation/prowler-scan-role.yml index 6cabb30664..08aacbca68 100644 --- a/permissions/templates/cloudformation/prowler-scan-role.yml +++ b/permissions/templates/cloudformation/prowler-scan-role.yml @@ -66,6 +66,35 @@ Parameters: Type: String Default: "" + # Real-Time Detection Parameters + EnableRealtimeDetection: + Description: | + Enable real-time detection. Forwards the tracked CloudTrail management events to Prowler Cloud + through an EventBridge API destination, so a change is scanned within minutes instead of waiting + for the next scheduled scan. Adds no new read permissions to the ProwlerScan role. + Type: String + Default: false + AllowedValues: + - true + - false + + ProwlerWebhookUrl: + Description: | + Prowler Cloud endpoint that receives the events. Provided during Prowler Cloud onboarding. + Required if EnableRealtimeDetection is true. + Type: String + Default: "" + AllowedPattern: '^(https://.+)?$' + ConstraintDescription: "ProwlerWebhookUrl must be an HTTPS URL." + + ProwlerApiKey: + Description: | + Prowler Cloud API key used to authenticate the events sent to the endpoint above. + Shown once during Prowler Cloud onboarding. Required if EnableRealtimeDetection is true. + Type: String + Default: "" + NoEcho: true + # Deployment Control Parameters DeployStackSet: Description: | @@ -134,6 +163,7 @@ Conditions: DeployStackSetEnabled: !Equals [!Ref DeployStackSet, true] DeployLocalRoleEnabled: !Equals [!Ref DeployLocalRole, true] UseDelegatedAdmin: !Equals [!Ref DeployFromDelegatedAdmin, true] + RealtimeDetectionEnabled: !Equals [!Ref EnableRealtimeDetection, true] Rules: S3IntegrationRequiresParams: @@ -144,6 +174,14 @@ Rules: - Assert: !Not [!Equals [!Ref S3IntegrationBucketAccountId, ""]] AssertDescription: "S3IntegrationBucketAccountId is required when EnableS3Integration is true." + RealtimeDetectionRequiresParams: + RuleCondition: !Equals [!Ref EnableRealtimeDetection, "true"] + Assertions: + - Assert: !Not [!Equals [!Ref ProwlerWebhookUrl, ""]] + AssertDescription: "ProwlerWebhookUrl is required when EnableRealtimeDetection is true." + - Assert: !Not [!Equals [!Ref ProwlerApiKey, ""]] + AssertDescription: "ProwlerApiKey is required when EnableRealtimeDetection is true." + Resources: # Local ProwlerScan Role (deployed in this account) ProwlerScan: @@ -583,6 +621,130 @@ Resources: Export: Name: !Sub "${AWS::StackName}-ProwlerScanRoleArn" + # Real-Time Detection: forwards the tracked CloudTrail events to Prowler Cloud + ProwlerRealtimeConnection: + Type: AWS::Events::Connection + Condition: RealtimeDetectionEnabled + Properties: + Name: ProwlerRealtimeDetection + Description: Holds the Prowler Cloud API key used to authenticate forwarded events + AuthorizationType: API_KEY + AuthParameters: + ApiKeyAuthParameters: + ApiKeyName: x-api-key + ApiKeyValue: !Ref ProwlerApiKey + + ProwlerRealtimeApiDestination: + Type: AWS::Events::ApiDestination + Condition: RealtimeDetectionEnabled + Properties: + Name: ProwlerRealtimeDetection + Description: Prowler Cloud endpoint that receives the forwarded CloudTrail events + ConnectionArn: !GetAtt ProwlerRealtimeConnection.Arn + InvocationEndpoint: !Ref ProwlerWebhookUrl + HttpMethod: POST + + ProwlerRealtimeInvokeRole: + Type: AWS::IAM::Role + Condition: RealtimeDetectionEnabled + Properties: + RoleName: ProwlerRealtimeInvoke + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: events.amazonaws.com + Action: "sts:AssumeRole" + Condition: + StringEquals: + "aws:SourceAccount": !Ref AWS::AccountId + ArnLike: + # 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" + Policies: + - PolicyName: InvokeApiDestination + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: "events:InvokeApiDestination" + Resource: !GetAtt ProwlerRealtimeApiDestination.Arn + Tags: + - Key: "Service" + Value: "https://prowler.com" + - Key: "Support" + Value: "support@prowler.com" + - Key: "CloudFormation" + Value: "true" + - Key: "Name" + Value: "ProwlerRealtimeInvoke" + + ProwlerRealtimeRule: + Type: AWS::Events::Rule + Condition: RealtimeDetectionEnabled + Properties: + Name: ProwlerRealtimeDetection + Description: Forwards the CloudTrail management events tracked by Prowler real-time detection to Prowler Cloud + State: ENABLED + EventPattern: + detail-type: + - "AWS API Call via CloudTrail" + detail: + eventSource: + - ec2.amazonaws.com + - rds.amazonaws.com + - iam.amazonaws.com + - s3.amazonaws.com + - s3-control.amazonaws.com + - cloudtrail.amazonaws.com + - config.amazonaws.com + - guardduty.amazonaws.com + eventName: + # Security group opened to 0.0.0.0/0 + - AuthorizeSecurityGroupIngress + - ModifySecurityGroupRules + # RDS instance made publicly accessible + - CreateDBInstance + - ModifyDBInstance + # Administrator privileges attached to a principal + - AttachUserPolicy + - AttachRolePolicy + - AttachGroupPolicy + - PutUserPolicy + - PutRolePolicy + # S3 bucket policy or ACL grants public access + - PutBucketPolicy + - PutBucketAcl + # S3 Block Public Access weakened + - PutAccountPublicAccessBlock + - DeleteAccountPublicAccessBlock + - PutBucketPublicAccessBlock + - DeleteBucketPublicAccessBlock + # CloudTrail logging stopped or trail deleted + - StopLogging + - DeleteTrail + - UpdateTrail + # AWS Config recorder stopped or deleted + - StopConfigurationRecorder + - DeleteConfigurationRecorder + # GuardDuty detector disabled or deleted + - UpdateDetector + - DeleteDetector + Targets: + - Id: ProwlerCloud + Arn: !GetAtt ProwlerRealtimeApiDestination.Arn + RoleArn: !GetAtt ProwlerRealtimeInvokeRole.Arn + Tags: + - Key: "Service" + Value: "https://prowler.com" + - Key: "Support" + Value: "support@prowler.com" + - Key: "CloudFormation" + Value: "true" + - Key: "Name" + Value: "ProwlerRealtimeDetection" + Metadata: AWS::CloudFormation::Interface: ParameterGroups: @@ -599,11 +761,17 @@ Metadata: - IAMPrincipal - EnableOrganizations - EnableS3Integration + - EnableRealtimeDetection - Label: default: Optional S3 Integration Parameters: - S3IntegrationBucketName - S3IntegrationBucketAccountId + - Label: + default: Optional Real-Time Detection (Required if EnableRealtimeDetection is true) + Parameters: + - ProwlerWebhookUrl + - ProwlerApiKey - Label: default: StackSet Configuration (Required if DeployStackSet is true) Parameters: @@ -624,3 +792,17 @@ Outputs: Condition: DeployStackSetEnabled Description: "StackSet ID for the ProwlerScan role deployment across accounts" Value: !Ref ProwlerScanStackSet + + ProwlerRealtimeRuleArn: + Condition: RealtimeDetectionEnabled + Description: "ARN of the EventBridge rule forwarding the tracked CloudTrail events to Prowler Cloud" + Value: !GetAtt ProwlerRealtimeRule.Arn + Export: + Name: !Sub "${AWS::StackName}-ProwlerRealtimeRuleArn" + + ProwlerRealtimeApiDestinationArn: + Condition: RealtimeDetectionEnabled + Description: "ARN of the EventBridge API destination targeting Prowler Cloud" + Value: !GetAtt ProwlerRealtimeApiDestination.Arn + Export: + Name: !Sub "${AWS::StackName}-ProwlerRealtimeApiDestinationArn" diff --git a/permissions/templates/terraform/README.md b/permissions/templates/terraform/README.md index 1bb037daa2..af0d16c313 100644 --- a/permissions/templates/terraform/README.md +++ b/permissions/templates/terraform/README.md @@ -25,6 +25,9 @@ This Terraform configuration creates the necessary IAM role and policies to allo - `enable_s3_integration` (optional): Enable S3 integration for storing scan reports (default: false) - `s3_integration_bucket_name` (conditional): S3 bucket name for reports (required if `enable_s3_integration` is true) - `s3_integration_bucket_account_id` (conditional): S3 bucket owner account ID (required if `enable_s3_integration` is true) +- `enable_realtime_detection` (optional): Forward the tracked CloudTrail management events to Prowler Cloud through an EventBridge API destination (default: false) +- `prowler_webhook_url` (conditional): Prowler Cloud endpoint that receives the events (required if `enable_realtime_detection` is true) +- `prowler_api_key` (conditional): Prowler Cloud API key used to authenticate the events (required if `enable_realtime_detection` is true) ### Usage Examples @@ -42,6 +45,17 @@ terraform apply \ -var="s3_integration_bucket_account_id=123456789012" ``` +#### With real-time detection enabled +```bash +terraform apply \ + -var="external_id=your-external-id-here" \ + -var="enable_realtime_detection=true" \ + -var="prowler_webhook_url=https://api.prowler.com/api/v1/realtime/events" \ + -var="prowler_api_key=your-prowler-api-key-here" +``` + +> **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) ```bash cp terraform.tfvars.example terraform.tfvars @@ -60,5 +74,8 @@ After successful deployment, you'll get: - `prowler_role_arn`: The ARN of the created IAM role (use this in Prowler App) - `prowler_role_name`: The name of the IAM role - `s3_integration_enabled`: Whether S3 integration is enabled +- `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) > **Note:** Terraform will use the AWS credentials of your default profile or AWS_PROFILE environment variable. diff --git a/permissions/templates/terraform/main.tf b/permissions/templates/terraform/main.tf index 1f1306c4ce..742388f4f7 100644 --- a/permissions/templates/terraform/main.tf +++ b/permissions/templates/terraform/main.tf @@ -15,6 +15,13 @@ check "s3_integration_requirements" { } } +check "realtime_detection_requirements" { + assert { + condition = !var.enable_realtime_detection || (var.prowler_webhook_url != "" && var.prowler_api_key != "") + error_message = "When enable_realtime_detection is true, both prowler_webhook_url and prowler_api_key must be provided and non-empty." + } +} + # IAM Role ################################### data "aws_iam_policy_document" "prowler_assume_role_policy" { @@ -113,8 +120,19 @@ module "s3_integration" { source = "./s3-integration" - s3_integration_bucket_name = var.s3_integration_bucket_name + s3_integration_bucket_name = var.s3_integration_bucket_name s3_integration_bucket_account_id = var.s3_integration_bucket_account_id prowler_role_name = aws_iam_role.prowler_scan.name } + +# Real-Time Detection Module +################################### +module "realtime_detection" { + count = var.enable_realtime_detection ? 1 : 0 + + source = "./realtime-detection" + + prowler_webhook_url = var.prowler_webhook_url + prowler_api_key = var.prowler_api_key +} diff --git a/permissions/templates/terraform/outputs.tf b/permissions/templates/terraform/outputs.tf index e1a971dcde..6d6aa38649 100644 --- a/permissions/templates/terraform/outputs.tf +++ b/permissions/templates/terraform/outputs.tf @@ -20,3 +20,18 @@ output "s3_integration_enabled" { description = "Whether S3 integration is enabled" value = var.enable_s3_integration } + +output "realtime_detection_enabled" { + description = "Whether real-time detection is enabled" + value = var.enable_realtime_detection +} + +output "prowler_realtime_rule_arn" { + description = "ARN of the EventBridge rule forwarding the tracked CloudTrail events to Prowler Cloud (null if real-time detection is disabled)" + value = try(module.realtime_detection[0].prowler_realtime_rule_arn, null) +} + +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) +} diff --git a/permissions/templates/terraform/realtime-detection/data.tf b/permissions/templates/terraform/realtime-detection/data.tf new file mode 100644 index 0000000000..7281c036f7 --- /dev/null +++ b/permissions/templates/terraform/realtime-detection/data.tf @@ -0,0 +1,3 @@ +data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} diff --git a/permissions/templates/terraform/realtime-detection/main.tf b/permissions/templates/terraform/realtime-detection/main.tf new file mode 100644 index 0000000000..acb1fa86da --- /dev/null +++ b/permissions/templates/terraform/realtime-detection/main.tf @@ -0,0 +1,132 @@ +# EventBridge Connection holding the Prowler Cloud API key +################################### +resource "aws_cloudwatch_event_connection" "prowler_realtime" { + name = "ProwlerRealtimeDetection" + description = "Holds the Prowler Cloud API key used to authenticate forwarded events" + authorization_type = "API_KEY" + + auth_parameters { + api_key { + key = "x-api-key" + value = var.prowler_api_key + } + } +} + +# EventBridge API Destination pointing at the Prowler Cloud endpoint +################################### +resource "aws_cloudwatch_event_api_destination" "prowler_realtime" { + name = "ProwlerRealtimeDetection" + description = "Prowler Cloud endpoint that receives the forwarded CloudTrail events" + invocation_endpoint = var.prowler_webhook_url + http_method = "POST" + connection_arn = aws_cloudwatch_event_connection.prowler_realtime.arn +} + +# IAM Role assumed by EventBridge to invoke the API Destination +################################### +data "aws_iam_policy_document" "prowler_realtime_assume_role" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["events.amazonaws.com"] + } + + condition { + test = "StringEquals" + variable = "aws:SourceAccount" + values = [data.aws_caller_identity.current.account_id] + } + + # Built from the rule name to avoid a circular dependency with the rule + condition { + test = "ArnLike" + variable = "aws:SourceArn" + values = ["arn:${data.aws_partition.current.partition}:events:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:rule/ProwlerRealtimeDetection"] + } + } +} + +resource "aws_iam_role" "prowler_realtime_invoke" { + name = "ProwlerRealtimeInvoke" + assume_role_policy = data.aws_iam_policy_document.prowler_realtime_assume_role.json +} + +resource "aws_iam_role_policy" "prowler_realtime_invoke" { + name = "InvokeApiDestination" + role = aws_iam_role.prowler_realtime_invoke.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Action = "events:InvokeApiDestination" + Resource = aws_cloudwatch_event_api_destination.prowler_realtime.arn + }] + }) +} + +# Rule matching the CloudTrail management events tracked by Prowler real-time detection +################################### +resource "aws_cloudwatch_event_rule" "prowler_realtime" { + name = "ProwlerRealtimeDetection" + description = "Forwards the CloudTrail management events tracked by Prowler real-time detection to Prowler Cloud" + state = "ENABLED" + + event_pattern = jsonencode({ + "detail-type" = ["AWS API Call via CloudTrail"] + detail = { + eventSource = [ + "ec2.amazonaws.com", + "rds.amazonaws.com", + "iam.amazonaws.com", + "s3.amazonaws.com", + "s3-control.amazonaws.com", + "cloudtrail.amazonaws.com", + "config.amazonaws.com", + "guardduty.amazonaws.com", + ] + eventName = [ + # Security group opened to 0.0.0.0/0 + "AuthorizeSecurityGroupIngress", + "ModifySecurityGroupRules", + # RDS instance made publicly accessible + "CreateDBInstance", + "ModifyDBInstance", + # Administrator privileges attached to a principal + "AttachUserPolicy", + "AttachRolePolicy", + "AttachGroupPolicy", + "PutUserPolicy", + "PutRolePolicy", + # S3 bucket policy or ACL grants public access + "PutBucketPolicy", + "PutBucketAcl", + # S3 Block Public Access weakened + "PutAccountPublicAccessBlock", + "DeleteAccountPublicAccessBlock", + "PutBucketPublicAccessBlock", + "DeleteBucketPublicAccessBlock", + # CloudTrail logging stopped or trail deleted + "StopLogging", + "DeleteTrail", + "UpdateTrail", + # AWS Config recorder stopped or deleted + "StopConfigurationRecorder", + "DeleteConfigurationRecorder", + # GuardDuty detector disabled or deleted + "UpdateDetector", + "DeleteDetector", + ] + } + }) +} + +resource "aws_cloudwatch_event_target" "prowler_realtime" { + rule = aws_cloudwatch_event_rule.prowler_realtime.name + target_id = "ProwlerCloud" + arn = aws_cloudwatch_event_api_destination.prowler_realtime.arn + role_arn = aws_iam_role.prowler_realtime_invoke.arn +} diff --git a/permissions/templates/terraform/realtime-detection/outputs.tf b/permissions/templates/terraform/realtime-detection/outputs.tf new file mode 100644 index 0000000000..a32d605341 --- /dev/null +++ b/permissions/templates/terraform/realtime-detection/outputs.tf @@ -0,0 +1,14 @@ +output "prowler_realtime_rule_arn" { + description = "ARN of the EventBridge rule forwarding the tracked CloudTrail events to Prowler Cloud" + value = aws_cloudwatch_event_rule.prowler_realtime.arn +} + +output "prowler_realtime_api_destination_arn" { + description = "ARN of the EventBridge API destination targeting Prowler Cloud" + value = aws_cloudwatch_event_api_destination.prowler_realtime.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 +} diff --git a/permissions/templates/terraform/realtime-detection/variables.tf b/permissions/templates/terraform/realtime-detection/variables.tf new file mode 100644 index 0000000000..e57cbd7f6f --- /dev/null +++ b/permissions/templates/terraform/realtime-detection/variables.tf @@ -0,0 +1,20 @@ +variable "prowler_webhook_url" { + type = string + description = "Prowler Cloud endpoint that receives the events. Provided during Prowler Cloud onboarding." + + validation { + condition = can(regex("^https://", var.prowler_webhook_url)) + error_message = "prowler_webhook_url must be an HTTPS URL." + } +} + +variable "prowler_api_key" { + type = string + description = "Prowler Cloud API key used to authenticate the events sent to the endpoint above." + sensitive = true + + validation { + condition = length(var.prowler_api_key) > 0 + error_message = "prowler_api_key must not be empty." + } +} diff --git a/permissions/templates/terraform/realtime-detection/versions.tf b/permissions/templates/terraform/realtime-detection/versions.tf new file mode 100644 index 0000000000..f5a0ad7ccb --- /dev/null +++ b/permissions/templates/terraform/realtime-detection/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 1.5" +} diff --git a/permissions/templates/terraform/terraform.tfvars.example b/permissions/templates/terraform/terraform.tfvars.example index 71561f5574..32f3ad898e 100644 --- a/permissions/templates/terraform/terraform.tfvars.example +++ b/permissions/templates/terraform/terraform.tfvars.example @@ -29,3 +29,19 @@ external_id = "your-unique-external-id-here" # AWS Account ID that owns the S3 bucket (usually your account) # s3_integration_bucket_account_id = "123456789012" + +# ============================================================================= +# Real-Time Detection Configuration +# ============================================================================= +# Uncomment the following lines to forward the tracked CloudTrail management +# events to Prowler Cloud through an EventBridge API destination, so a change +# is scanned within minutes instead of waiting for the next scheduled scan. + +# Enable real-time detection +# enable_realtime_detection = true + +# Prowler Cloud endpoint that receives the events (provided during onboarding) +# prowler_webhook_url = "https://api.prowler.com/api/v1/realtime/events" + +# Prowler Cloud API key used to authenticate the events (shown once during onboarding) +# prowler_api_key = "your-prowler-api-key-here" diff --git a/permissions/templates/terraform/variables.tf b/permissions/templates/terraform/variables.tf index 832171b62a..0a0b495832 100644 --- a/permissions/templates/terraform/variables.tf +++ b/permissions/templates/terraform/variables.tf @@ -60,3 +60,27 @@ variable "s3_integration_bucket_account_id" { error_message = "s3_integration_bucket_account_id must be a valid 12-digit AWS Account ID or empty." } } + +variable "enable_realtime_detection" { + type = bool + description = "Enable real-time detection. Forwards the tracked CloudTrail management events to Prowler Cloud through an EventBridge API destination. Adds no new read permissions to the ProwlerScan role." + default = false +} + +variable "prowler_webhook_url" { + type = string + description = "Prowler Cloud endpoint that receives the events. Provided during Prowler Cloud onboarding. Required if enable_realtime_detection is true." + default = "" + + validation { + condition = var.prowler_webhook_url == "" || can(regex("^https://", var.prowler_webhook_url)) + error_message = "prowler_webhook_url must be an HTTPS URL or empty." + } +} + +variable "prowler_api_key" { + type = string + description = "Prowler Cloud API key used to authenticate the events sent to the endpoint above. Shown once during Prowler Cloud onboarding. Required if enable_realtime_detection is true." + default = "" + sensitive = true +}