mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-31 15:54:24 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
580a0436ae | ||
|
|
b5a23a2764 | ||
|
|
56d33fe252 | ||
|
|
29102e7b33 | ||
|
|
0a39c004b1 | ||
|
|
ae1592dfa8 |
No files matched your search
@@ -134,6 +134,7 @@ ai-config.json
|
||||
.terraform*
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
permissions/templates/terraform/**/*.zip
|
||||
|
||||
# .env
|
||||
ui/.env*
|
||||
|
||||
@@ -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. Defaults to the Prowler Cloud ingest endpoint;
|
||||
change it only for a self-hosted deployment or for testing. Required if EnableRealtimeDetection is true.
|
||||
Type: String
|
||||
Default: "https://api.prowler.com/api/v1/realtime/events"
|
||||
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,351 @@ 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 names to avoid a circular dependency with the rules
|
||||
"aws:SourceArn":
|
||||
- !Sub "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/ProwlerRealtimeDetection"
|
||||
- !Sub "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/ProwlerRealtimeDetectionHello"
|
||||
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"
|
||||
|
||||
# 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 names to avoid a circular dependency with the rules
|
||||
"aws:SourceArn":
|
||||
- !Sub "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/ProwlerRealtimeDetection"
|
||||
- !Sub "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/ProwlerRealtimeDetectionHello"
|
||||
|
||||
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:
|
||||
# PutEvents rejects the reserved aws. prefix, so this keeps a caller in the
|
||||
# account from forging a CloudTrail-shaped event
|
||||
source:
|
||||
- prefix: "aws."
|
||||
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
|
||||
# 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"
|
||||
- Key: "Support"
|
||||
Value: "support@prowler.com"
|
||||
- Key: "CloudFormation"
|
||||
Value: "true"
|
||||
- Key: "Name"
|
||||
Value: "ProwlerRealtimeDetection"
|
||||
|
||||
# Carries the synthetic hello event, which no CloudTrail pattern would match
|
||||
ProwlerRealtimeHelloRule:
|
||||
Type: AWS::Events::Rule
|
||||
Condition: RealtimeDetectionEnabled
|
||||
Properties:
|
||||
Name: ProwlerRealtimeDetectionHello
|
||||
Description: Forwards the Prowler real-time detection hello event used to verify the connection
|
||||
State: ENABLED
|
||||
EventPattern:
|
||||
source:
|
||||
- prowler.simulation
|
||||
detail-type:
|
||||
- test_connection
|
||||
Targets:
|
||||
- Id: ProwlerCloud
|
||||
Arn: !GetAtt ProwlerRealtimeApiDestination.Arn
|
||||
RoleArn: !GetAtt ProwlerRealtimeInvokeRole.Arn
|
||||
RetryPolicy:
|
||||
MaximumEventAgeInSeconds: 86400
|
||||
MaximumRetryAttempts: 185
|
||||
DeadLetterConfig:
|
||||
Arn: !GetAtt ProwlerRealtimeDlq.Arn
|
||||
Tags:
|
||||
- Key: "Service"
|
||||
Value: "https://prowler.com"
|
||||
- Key: "Support"
|
||||
Value: "support@prowler.com"
|
||||
- Key: "CloudFormation"
|
||||
Value: "true"
|
||||
- Key: "Name"
|
||||
Value: "ProwlerRealtimeDetectionHello"
|
||||
|
||||
# Explicit log group so the function does not leave one with unlimited retention behind
|
||||
ProwlerRealtimeHelloLogGroup:
|
||||
Type: AWS::Logs::LogGroup
|
||||
Condition: RealtimeDetectionEnabled
|
||||
Properties:
|
||||
LogGroupName: /aws/lambda/ProwlerRealtimeHello
|
||||
RetentionInDays: 30
|
||||
|
||||
ProwlerRealtimeHelloRole:
|
||||
Type: AWS::IAM::Role
|
||||
Condition: RealtimeDetectionEnabled
|
||||
Properties:
|
||||
RoleName: ProwlerRealtimeHello
|
||||
AssumeRolePolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service: lambda.amazonaws.com
|
||||
Action: "sts:AssumeRole"
|
||||
Policies:
|
||||
- PolicyName: PutHelloEvent
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action: "events:PutEvents"
|
||||
Resource: !Sub "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/default"
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- "logs:CreateLogStream"
|
||||
- "logs:PutLogEvents"
|
||||
Resource: !GetAtt ProwlerRealtimeHelloLogGroup.Arn
|
||||
Tags:
|
||||
- Key: "Service"
|
||||
Value: "https://prowler.com"
|
||||
- Key: "Support"
|
||||
Value: "support@prowler.com"
|
||||
- Key: "CloudFormation"
|
||||
Value: "true"
|
||||
- Key: "Name"
|
||||
Value: "ProwlerRealtimeHello"
|
||||
|
||||
ProwlerRealtimeHelloFunction:
|
||||
Type: AWS::Lambda::Function
|
||||
Condition: RealtimeDetectionEnabled
|
||||
Properties:
|
||||
FunctionName: ProwlerRealtimeHello
|
||||
Description: Emits the Prowler real-time detection hello event when the stack is created or updated
|
||||
Runtime: python3.13
|
||||
Handler: index.handler
|
||||
Timeout: 30
|
||||
Role: !GetAtt ProwlerRealtimeHelloRole.Arn
|
||||
Code:
|
||||
ZipFile: |
|
||||
import json
|
||||
import urllib.request
|
||||
|
||||
import boto3
|
||||
|
||||
|
||||
def respond(event, context, data):
|
||||
"""Answer CloudFormation. Always SUCCESS: see handler()."""
|
||||
body = json.dumps(
|
||||
{
|
||||
"Status": "SUCCESS",
|
||||
"Reason": f"See CloudWatch log stream {context.log_stream_name}",
|
||||
"PhysicalResourceId": "ProwlerRealtimeHelloEvent",
|
||||
"StackId": event["StackId"],
|
||||
"RequestId": event["RequestId"],
|
||||
"LogicalResourceId": event["LogicalResourceId"],
|
||||
"Data": data,
|
||||
}
|
||||
).encode()
|
||||
request = urllib.request.Request(
|
||||
event["ResponseURL"],
|
||||
data=body,
|
||||
method="PUT",
|
||||
headers={"content-type": "", "content-length": str(len(body))},
|
||||
)
|
||||
for attempt in range(3):
|
||||
try:
|
||||
urllib.request.urlopen(request, timeout=10)
|
||||
return
|
||||
except Exception as error: # noqa: BLE001
|
||||
print(f"response attempt {attempt + 1} failed: {error}")
|
||||
|
||||
|
||||
def handler(event, context):
|
||||
"""Publish the hello event, then always report SUCCESS.
|
||||
|
||||
A failed verification must never roll back the scan role, so any
|
||||
error is reported in the response data instead of failing the stack.
|
||||
"""
|
||||
data = {"Status": "Skipped"}
|
||||
try:
|
||||
if event["RequestType"] in ("Create", "Update"):
|
||||
_, _, _, region, account = context.invoked_function_arn.split(":")[:5]
|
||||
entry = boto3.client("events").put_events(
|
||||
Entries=[
|
||||
{
|
||||
"Source": "prowler.simulation",
|
||||
"DetailType": "test_connection",
|
||||
"Detail": json.dumps(
|
||||
{
|
||||
"account_id": account,
|
||||
"region": region,
|
||||
"stack_id": event["StackId"],
|
||||
"request_type": event["RequestType"],
|
||||
}
|
||||
),
|
||||
"EventBusName": "default",
|
||||
}
|
||||
]
|
||||
)["Entries"][0]
|
||||
if entry.get("ErrorCode"):
|
||||
data = {"Status": "Failed", "Error": entry["ErrorCode"]}
|
||||
else:
|
||||
data = {"Status": "Published", "EventId": entry["EventId"]}
|
||||
except Exception as error: # noqa: BLE001
|
||||
data = {"Status": "Failed", "Error": str(error)[:200]}
|
||||
print(json.dumps(data))
|
||||
respond(event, context, data)
|
||||
|
||||
# Emitting the event is the last step: the rule, destination and DLQ must already exist
|
||||
ProwlerRealtimeHelloTrigger:
|
||||
Type: Custom::ProwlerHelloEvent
|
||||
Condition: RealtimeDetectionEnabled
|
||||
DependsOn:
|
||||
- ProwlerRealtimeHelloRule
|
||||
- ProwlerRealtimeDlqPolicy
|
||||
Properties:
|
||||
ServiceToken: !GetAtt ProwlerRealtimeHelloFunction.Arn
|
||||
# Re-emits the event when the endpoint changes; the API key is deliberately not passed in
|
||||
WebhookUrl: !Ref ProwlerWebhookUrl
|
||||
|
||||
Metadata:
|
||||
AWS::CloudFormation::Interface:
|
||||
ParameterGroups:
|
||||
@@ -599,11 +982,17 @@ Metadata:
|
||||
- IAMPrincipal
|
||||
- EnableOrganizations
|
||||
- EnableS3Integration
|
||||
- EnableRealtimeDetection
|
||||
- Label:
|
||||
default: Optional S3 Integration
|
||||
Parameters:
|
||||
- S3IntegrationBucketName
|
||||
- S3IntegrationBucketAccountId
|
||||
- Label:
|
||||
default: Optional Real-Time Detection (ProwlerApiKey required if EnableRealtimeDetection is true)
|
||||
Parameters:
|
||||
- ProwlerWebhookUrl
|
||||
- ProwlerApiKey
|
||||
- Label:
|
||||
default: StackSet Configuration (Required if DeployStackSet is true)
|
||||
Parameters:
|
||||
@@ -624,3 +1013,24 @@ 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"
|
||||
|
||||
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"
|
||||
@@ -20,11 +20,15 @@ This Terraform configuration creates the necessary IAM role and policies to allo
|
||||
### Variables
|
||||
|
||||
- `external_id` (required): External ID for role assumption security
|
||||
- `region` (optional): AWS region to deploy to (default: `us-east-1`). The EventBridge rules are regional, so deploy once per region you want covered
|
||||
- `account_id` (optional): AWS Account ID that will assume the role (defaults to Prowler Cloud: "232136659152")
|
||||
- `iam_principal` (optional): IAM principal pattern allowed to assume the role (defaults to Prowler Cloud: "role/prowler*")
|
||||
- `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` (optional): Prowler Cloud endpoint that receives the events (defaults to Prowler Cloud: `https://api.prowler.com/api/v1/realtime/events`; change it only for a self-hosted deployment or for testing)
|
||||
- `prowler_api_key` (conditional): Prowler Cloud API key used to authenticate the events (required if `enable_realtime_detection` is true)
|
||||
|
||||
### Usage Examples
|
||||
|
||||
@@ -42,6 +46,34 @@ 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_api_key=your-prowler-api-key-here"
|
||||
```
|
||||
|
||||
`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.
|
||||
|
||||
The apply publishes a hello event to verify the connection, without touching any real resource. It travels the same connection, API destination, API key and endpoint as a real event, and Prowler Cloud marks the provider as connected once it arrives, without running a scan.
|
||||
|
||||
The `prowler_realtime_hello_status` output reports only that EventBridge accepted the event, which is not the same as the endpoint receiving it: delivery is asynchronous. Prowler Cloud is what confirms the connection, and a delivery that fails every retry lands in the dead-letter queue below. The event is published again whenever `prowler_webhook_url` changes.
|
||||
|
||||
To re-check the connection at any point, publish it yourself. Use the same region the template deploys to, since the rule only exists on that region's event bus:
|
||||
|
||||
```bash
|
||||
aws events put-events --region us-east-1 --entries '[{
|
||||
"Source": "prowler.simulation",
|
||||
"DetailType": "test_connection",
|
||||
"Detail": "{}"
|
||||
}]'
|
||||
```
|
||||
|
||||
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 rules are regional. They forward only the events delivered to the default event bus of the region set in `region` (`us-east-1` by default). 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 the region you deploy to. Run the template once per region you want covered, changing `region` each time.
|
||||
|
||||
#### Using terraform.tfvars file (Recommended)
|
||||
```bash
|
||||
cp terraform.tfvars.example terraform.tfvars
|
||||
@@ -60,5 +92,19 @@ 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)
|
||||
- `prowler_realtime_dlq_url`: URL of the dead-letter queue (null if real-time detection is disabled)
|
||||
- `prowler_realtime_hello_status`: whether EventBridge accepted the hello event on apply, `Published` or `Failed` with the error
|
||||
|
||||
### Handling the API key
|
||||
|
||||
Terraform writes every variable it is given to state, including `prowler_api_key`, and marking it `sensitive` only hides it from the CLI output. Before enabling real-time detection:
|
||||
|
||||
- Configure an encrypted, access-controlled remote backend (S3 with SSE and a restrictive bucket policy, Terraform Cloud, or equivalent). The default local state is a plaintext file in your working directory.
|
||||
- Pass the key from your secret manager instead of typing it into a file, for example `export TF_VAR_prowler_api_key="$(your-secret-tool read prowler/api-key)"`.
|
||||
- Do not commit a populated `terraform.tfvars`, and treat plan files as secrets too.
|
||||
- Revoking the key in Prowler Cloud stops all ingestion, so rotate it there if a state file is ever exposed.
|
||||
|
||||
> **Note:** Terraform will use the AWS credentials of your default profile or AWS_PROFILE environment variable.
|
||||
@@ -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
|
||||
}
|
||||
@@ -20,3 +20,28 @@ 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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
output "prowler_realtime_hello_status" {
|
||||
description = "Whether the hello event was accepted by EventBridge on apply (null if real-time detection is disabled)"
|
||||
value = try(module.realtime_detection[0].prowler_realtime_hello_status, null)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
data "aws_partition" "current" {}
|
||||
data "aws_caller_identity" "current" {}
|
||||
data "aws_region" "current" {}
|
||||
@@ -0,0 +1,45 @@
|
||||
"""Publishes the Prowler real-time detection hello event.
|
||||
|
||||
Invoked once by Terraform when real-time detection is enabled, mirroring the
|
||||
CloudFormation custom resource. It never raises: a failed verification must not
|
||||
fail the apply that created the scan role.
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
|
||||
import boto3
|
||||
|
||||
ATTEMPTS = 3
|
||||
BACKOFF_SECONDS = 2
|
||||
|
||||
|
||||
def handler(_event, context):
|
||||
_, _, _, region, account = context.invoked_function_arn.split(":")[:5]
|
||||
entries = [
|
||||
{
|
||||
"Source": "prowler.simulation",
|
||||
"DetailType": "test_connection",
|
||||
"Detail": json.dumps(
|
||||
{"account_id": account, "region": region, "trigger": "terraform"}
|
||||
),
|
||||
"EventBusName": "default",
|
||||
}
|
||||
]
|
||||
|
||||
client = boto3.client("events")
|
||||
error = "not attempted"
|
||||
# The role policy may not be in effect yet on the first apply
|
||||
for attempt in range(ATTEMPTS):
|
||||
try:
|
||||
entry = client.put_events(Entries=entries)["Entries"][0]
|
||||
if not entry.get("ErrorCode"):
|
||||
return {"status": "Published", "event_id": entry["EventId"]}
|
||||
error = entry["ErrorCode"]
|
||||
except Exception as failure: # noqa: BLE001
|
||||
error = str(failure)[:200]
|
||||
if attempt + 1 < ATTEMPTS:
|
||||
time.sleep(BACKOFF_SECONDS)
|
||||
|
||||
print(f"hello event not published: {error}")
|
||||
return {"status": "Failed", "error": error}
|
||||
@@ -0,0 +1,297 @@
|
||||
# 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]
|
||||
}
|
||||
|
||||
condition {
|
||||
test = "ArnLike"
|
||||
variable = "aws:SourceArn"
|
||||
values = [
|
||||
aws_cloudwatch_event_rule.prowler_realtime.arn,
|
||||
aws_cloudwatch_event_rule.prowler_realtime_hello.arn,
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
# 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,
|
||||
aws_cloudwatch_event_rule.prowler_realtime_hello.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" {
|
||||
name = "ProwlerRealtimeDetection"
|
||||
description = "Forwards the CloudTrail management events tracked by Prowler real-time detection to Prowler Cloud"
|
||||
state = "ENABLED"
|
||||
|
||||
event_pattern = jsonencode({
|
||||
# PutEvents rejects the reserved aws. prefix, so this keeps a caller in the
|
||||
# account from forging a CloudTrail-shaped event
|
||||
source = [{ prefix = "aws." }]
|
||||
"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
|
||||
|
||||
# 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
|
||||
}
|
||||
}
|
||||
|
||||
# Rule carrying the synthetic hello event, which no CloudTrail pattern would match
|
||||
###################################
|
||||
resource "aws_cloudwatch_event_rule" "prowler_realtime_hello" {
|
||||
name = "ProwlerRealtimeDetectionHello"
|
||||
description = "Forwards the Prowler real-time detection hello event used to verify the connection"
|
||||
state = "ENABLED"
|
||||
|
||||
event_pattern = jsonencode({
|
||||
source = ["prowler.simulation"]
|
||||
"detail-type" = ["test_connection"]
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_event_target" "prowler_realtime_hello" {
|
||||
rule = aws_cloudwatch_event_rule.prowler_realtime_hello.name
|
||||
target_id = "ProwlerCloud"
|
||||
arn = aws_cloudwatch_event_api_destination.prowler_realtime.arn
|
||||
role_arn = aws_iam_role.prowler_realtime_invoke.arn
|
||||
|
||||
retry_policy {
|
||||
maximum_event_age_in_seconds = 86400
|
||||
maximum_retry_attempts = 185
|
||||
}
|
||||
|
||||
dead_letter_config {
|
||||
arn = aws_sqs_queue.prowler_realtime_dlq.arn
|
||||
}
|
||||
}
|
||||
|
||||
# Hello event emitter, the same function the CloudFormation template runs, so both
|
||||
# onboarding paths leave the same footprint in the account
|
||||
###################################
|
||||
data "archive_file" "prowler_realtime_hello" {
|
||||
type = "zip"
|
||||
source_file = "${path.module}/hello.py"
|
||||
output_path = "${path.module}/hello.zip"
|
||||
}
|
||||
|
||||
# Declared explicitly so the function does not leave a log group with unlimited retention behind
|
||||
resource "aws_cloudwatch_log_group" "prowler_realtime_hello" {
|
||||
name = "/aws/lambda/ProwlerRealtimeHello"
|
||||
retention_in_days = 30
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "prowler_realtime_hello_assume_role" {
|
||||
statement {
|
||||
actions = ["sts:AssumeRole"]
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["lambda.amazonaws.com"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "prowler_realtime_hello" {
|
||||
name = "ProwlerRealtimeHello"
|
||||
assume_role_policy = data.aws_iam_policy_document.prowler_realtime_hello_assume_role.json
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "prowler_realtime_hello" {
|
||||
name = "PutHelloEvent"
|
||||
role = aws_iam_role.prowler_realtime_hello.id
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Effect = "Allow"
|
||||
Action = "events:PutEvents"
|
||||
Resource = "arn:${data.aws_partition.current.partition}:events:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:event-bus/default"
|
||||
},
|
||||
{
|
||||
Effect = "Allow"
|
||||
Action = ["logs:CreateLogStream", "logs:PutLogEvents"]
|
||||
Resource = "${aws_cloudwatch_log_group.prowler_realtime_hello.arn}:*"
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_lambda_function" "prowler_realtime_hello" {
|
||||
function_name = "ProwlerRealtimeHello"
|
||||
description = "Emits the Prowler real-time detection hello event"
|
||||
role = aws_iam_role.prowler_realtime_hello.arn
|
||||
runtime = "python3.13"
|
||||
handler = "hello.handler"
|
||||
timeout = 30
|
||||
filename = data.archive_file.prowler_realtime_hello.output_path
|
||||
source_code_hash = data.archive_file.prowler_realtime_hello.output_base64sha256
|
||||
|
||||
depends_on = [aws_cloudwatch_log_group.prowler_realtime_hello]
|
||||
}
|
||||
|
||||
# Emitting the event is the last step: the rule, the target and every policy it
|
||||
# depends on must already exist, and none of them is referenced here
|
||||
resource "aws_lambda_invocation" "prowler_realtime_hello" {
|
||||
function_name = aws_lambda_function.prowler_realtime_hello.function_name
|
||||
input = "{}"
|
||||
|
||||
# Re-emits the event when the endpoint changes, like the CloudFormation custom resource
|
||||
triggers = {
|
||||
webhook_url = var.prowler_webhook_url
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_cloudwatch_event_target.prowler_realtime_hello,
|
||||
aws_iam_role_policy.prowler_realtime_hello,
|
||||
aws_iam_role_policy.prowler_realtime_invoke,
|
||||
aws_sqs_queue_policy.prowler_realtime_dlq,
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
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_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_hello_status" {
|
||||
description = "Whether the hello event was accepted by EventBridge on apply. Prowler Cloud confirms the connection when the event reaches the endpoint"
|
||||
value = try(jsondecode(aws_lambda_invocation.prowler_realtime_hello.result), null)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
terraform {
|
||||
required_version = ">= 1.5"
|
||||
required_providers {
|
||||
archive = {
|
||||
source = "hashicorp/archive"
|
||||
version = "~> 2.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@ external_id = "your-unique-external-id-here"
|
||||
# IAM Principal Pattern (leave default unless using self-hosted)
|
||||
# iam_principal = "role/prowler*"
|
||||
|
||||
# AWS region to deploy to. The real-time detection rules are regional:
|
||||
# run the template once per region you want covered.
|
||||
# region = "us-east-1"
|
||||
|
||||
# =============================================================================
|
||||
# S3 Integration Configuration
|
||||
# =============================================================================
|
||||
@@ -29,3 +33,20 @@ 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 (leave default unless
|
||||
# self-hosted or testing against another endpoint)
|
||||
# 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"
|
||||
@@ -27,6 +27,12 @@ variable "iam_principal" {
|
||||
default = "role/prowler*"
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
type = string
|
||||
description = "AWS region to deploy to. Only relevant for real-time detection: the EventBridge rules are regional, so deploy once per region you want covered."
|
||||
default = "us-east-1"
|
||||
}
|
||||
|
||||
variable "enable_organizations" {
|
||||
type = bool
|
||||
description = "Enable AWS Organizations discovery permissions. Set to true only when deploying this role in the management account."
|
||||
@@ -60,3 +66,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. Defaults to the Prowler Cloud ingest endpoint; change it only for a self-hosted deployment or for testing."
|
||||
default = "https://api.prowler.com/api/v1/realtime/events"
|
||||
|
||||
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
|
||||
}
|
||||
@@ -11,7 +11,7 @@ terraform {
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
region = "us-east-1"
|
||||
region = var.region
|
||||
default_tags {
|
||||
tags = {
|
||||
"Name" = "ProwlerScan",
|
||||
|
||||
Reference in new issue
Block a user