-
Notifications
You must be signed in to change notification settings - Fork 1k
New pattern - eventbridge-cloudtrail-dataplane-cdk #3100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| # Amazon EventBridge Data Plane Logging with AWS CloudTrail | ||
|
|
||
| This pattern enables CloudTrail data plane logging for Amazon EventBridge and triggers a Lambda function when PutEvents API calls are detected, providing security and operational visibility into event bus activity. | ||
|
|
||
| Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/eventbridge-cloudtrail-dataplane-cdk | ||
|
|
||
| Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. | ||
|
|
||
| ## Requirements | ||
|
|
||
| * [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured | ||
| * [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed | ||
| * [Node.js](https://nodejs.org/en/download/) installed | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Specify Node.js 18+ or appropriate in Requirements (function uses Node.js 20.x)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated to Node.js 20+. |
||
|
|
||
| ## Deployment Instructions | ||
|
|
||
| 1. Clone and navigate to the pattern: | ||
| ``` | ||
| cd serverless-patterns/eventbridge-cloudtrail-dataplane-cdk | ||
| npm install | ||
| ``` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a |
||
| 2. Deploy: | ||
| ``` | ||
| cdk deploy | ||
| ``` | ||
|
|
||
| ## How it works | ||
|
|
||
| - A CloudTrail trail is created with data event logging enabled | ||
| - EventBridge data plane API calls (PutEvents) are now logged to CloudTrail (new May 2026 feature) | ||
| - An EventBridge rule captures these CloudTrail events matching `aws.events` source with `PutEvents` event name | ||
| - A Lambda function processes the events, logging the caller identity, source IP, event bus, and entry count | ||
| - This enables security teams to audit who is putting events to which bus | ||
|
|
||
| ## Testing | ||
|
|
||
| ```bash | ||
| # Put a test event to the default event bus | ||
| aws events put-events --entries '[{"Source":"test.app","DetailType":"TestEvent","Detail":"{\"key\":\"value\"}"}]' | ||
|
|
||
| # Check Lambda logs (allow ~5 minutes for CloudTrail delivery) | ||
| aws logs tail /aws/lambda/$(aws cloudformation describe-stacks \ | ||
| --stack-name EventbridgeCloudtrailDataplaneStack \ | ||
| --query 'Stacks[0].Outputs[?OutputKey==`ProcessorFunctionName`].OutputValue' --output text) \ | ||
| --follow | ||
| ``` | ||
|
|
||
| ## Cleanup | ||
|
|
||
| ``` | ||
| cdk destroy | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
|
|
||
| SPDX-License-Identifier: MIT-0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| #!/usr/bin/env node | ||
| import 'source-map-support/register'; | ||
| import * as cdk from 'aws-cdk-lib'; | ||
| import { EventbridgeCloudtrailDataplaneStack } from '../lib/eventbridge-cloudtrail-dataplane-stack'; | ||
|
|
||
| const app = new cdk.App(); | ||
| new EventbridgeCloudtrailDataplaneStack(app, 'EventbridgeCloudtrailDataplaneStack'); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "app": "npx ts-node --prefer-ts-exts bin/app.ts" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| { | ||
| "title": "Amazon EventBridge Data Plane Logging with AWS CloudTrail", | ||
| "description": "Monitor EventBridge PutEvents API calls using CloudTrail data plane logging with Lambda alerting for security and operational visibility.", | ||
| "language": "TypeScript", | ||
| "level": "300", | ||
| "framework": "CDK", | ||
| "introBox": { | ||
| "headline": "How it works", | ||
| "text": [ | ||
| "This pattern enables CloudTrail data plane logging for Amazon EventBridge (launched May 2026).", | ||
| "CloudTrail captures PutEvents API calls and delivers them as events to EventBridge.", | ||
| "An EventBridge rule matches these CloudTrail events and triggers a Lambda function for alerting.", | ||
| "This provides visibility into who is putting events, from where, and how many — essential for security auditing." | ||
| ] | ||
| }, | ||
| "gitHub": { | ||
| "template": { | ||
| "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/eventbridge-cloudtrail-dataplane-cdk", | ||
| "templateURL": "serverless-patterns/eventbridge-cloudtrail-dataplane-cdk", | ||
| "projectFolder": "eventbridge-cloudtrail-dataplane-cdk", | ||
| "templateFile": "lib/eventbridge-cloudtrail-dataplane-stack.ts" | ||
| } | ||
| }, | ||
| "resources": { | ||
| "bullets": [ | ||
| { "text": "EventBridge Data Plane CloudTrail Logging", "link": "https://aws.amazon.com/about-aws/whats-new/2026/05/amazon-eventbridge-data-aws-cloudtrail/" }, | ||
| { "text": "CloudTrail Data Events", "link": "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-data-events-with-cloudtrail.html" } | ||
| ] | ||
| }, | ||
| "deploy": { "text": ["cdk deploy"] }, | ||
| "testing": { "text": ["See the README for testing instructions."] }, | ||
| "cleanup": { "text": ["cdk destroy"] }, | ||
| "authors": [ | ||
| { | ||
| "name": "Nithin Chandran R", | ||
| "bio": "Technical Account Manager at AWS, passionate about serverless and AI/ML.", | ||
| "linkedin": "nithin-chandran-r" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| import * as cdk from 'aws-cdk-lib'; | ||
| import * as events from 'aws-cdk-lib/aws-events'; | ||
| import * as targets from 'aws-cdk-lib/aws-events-targets'; | ||
| import * as lambda from 'aws-cdk-lib/aws-lambda'; | ||
| import * as logs from 'aws-cdk-lib/aws-logs'; | ||
| import * as cloudtrail from 'aws-cdk-lib/aws-cloudtrail'; | ||
| import * as s3 from 'aws-cdk-lib/aws-s3'; | ||
| import { Construct } from 'constructs'; | ||
|
|
||
| export class EventbridgeCloudtrailDataplaneStack extends cdk.Stack { | ||
| constructor(scope: Construct, id: string, props?: cdk.StackProps) { | ||
| super(scope, id, props); | ||
|
|
||
| // S3 bucket for CloudTrail logs | ||
| const trailBucket = new s3.Bucket(this, 'TrailBucket', { | ||
| removalPolicy: cdk.RemovalPolicy.DESTROY, | ||
| autoDeleteObjects: true, | ||
| enforceSSL: true, | ||
|
Comment on lines
+26
to
+29
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The bucket holding CloudTrail logs has no serverAccessLogsBucket configured. For an audit-trail bucket, S3 server access logging adds a second-tier audit record of who accessed/modified the audit logs themselves. This is a defense-in-depth recommendation rather than a hard requirement.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a dedicated |
||
| }); | ||
|
Comment on lines
+26
to
+34
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Set explicit
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done — added |
||
|
|
||
| // CloudTrail trail with data events for EventBridge | ||
| const trail = new cloudtrail.Trail(this, 'EventBridgeDataPlaneTrail', { | ||
| bucket: trailBucket, | ||
| trailName: 'eventbridge-dataplane-trail', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please drop the hardcoded
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed — letting CloudFormation generate the name now. |
||
| isMultiRegionTrail: false, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair point, flipped to |
||
| }); | ||
|
|
||
| // Enable EventBridge data plane events logging | ||
| trail.addEventSelector(cloudtrail.DataResourceType.LAMBDA_FUNCTION, ['arn:aws:lambda']); | ||
|
|
||
| // Lambda function to process CloudTrail events | ||
|
Comment on lines
+37
to
+61
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrong CloudTrail data event resource type; this trail does not capture EventBridge
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd also suggest a quick smoke-test command in the README that asserts the Lambda actually logged a record.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After fixing above, you may want to drop or update the "allow ~5 minutes" note in the README test step, as CloudTrail data events would not typically take that long to reach EventBridge.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch — you're right, LAMBDA_FUNCTION doesn't capture EventBridge PutEvents at all. Switched to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea — I'll add a quick aws events put-events + aws logs tail assertion to the Testing section so folks can verify end-to-end without guessing.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right — data events via advanced selectors hit EventBridge much faster than the old management-event path. I'll update to ~30 seconds and add the smoke-test command so it's verifiable.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea -- added a CLI verification command that checks the CloudTrail log group for recent events.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated the testing section to reference the CLI check instead of the wait-and-hope approach. |
||
| const processor = new lambda.Function(this, 'EventProcessor', { | ||
| runtime: lambda.Runtime.NODEJS_20_X, | ||
| handler: 'index.handler', | ||
| code: lambda.Code.fromAsset('src'), | ||
| timeout: cdk.Duration.seconds(10), | ||
| loggingFormat: lambda.LoggingFormat.JSON, | ||
|
Comment on lines
+64
to
+67
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The processor function has no logRetention (or pre-created logGroup with retention) configured. The auto-created log group /aws/lambda/ will retain logs forever, accruing CloudWatch Logs storage cost. So either set retention or give a callout in README and wherever applicable.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added |
||
| }); | ||
|
Comment on lines
+62
to
+71
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Configure
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done — set |
||
|
|
||
| // EventBridge rule to capture EventBridge PutEvents API calls from CloudTrail | ||
| const rule = new events.Rule(this, 'DataPlaneRule', { | ||
| eventPattern: { | ||
| source: ['aws.events'], | ||
| detailType: ['AWS API Call via CloudTrail'], | ||
| detail: { | ||
| eventSource: ['events.amazonaws.com'], | ||
| eventName: ['PutEvents'], | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| rule.addTarget(new targets.LambdaFunction(processor)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a DLQ to the EventBridge target. Without
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a shared SQS DLQ for the EventBridge target. Failed deliveries go there with 14-day retention. |
||
|
|
||
| new cdk.CfnOutput(this, 'ProcessorFunctionName', { value: processor.functionName }); | ||
| new cdk.CfnOutput(this, 'TrailBucketName', { value: trailBucket.bucketName }); | ||
| new cdk.CfnOutput(this, 'RuleName', { value: rule.ruleName }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| { | ||
| "name": "eventbridge-cloudtrail-dataplane-cdk", | ||
| "version": "1.0.0", | ||
| "bin": { "app": "bin/app.ts" }, | ||
| "scripts": { "build": "tsc", "cdk": "cdk" }, | ||
| "dependencies": { | ||
| "aws-cdk-lib": "^2.180.0", | ||
| "constructs": "^10.0.0", | ||
| "source-map-support": "^0.5.21" | ||
| }, | ||
| "devDependencies": { | ||
| "typescript": "~5.4.0", | ||
| "ts-node": "^10.9.0", | ||
| "@types/node": "^20.0.0" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| exports.handler = async (event) => { | ||
| const detail = event.detail || {}; | ||
| console.log(JSON.stringify({ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Switched to |
||
| message: 'EventBridge data plane API call detected', | ||
| eventName: detail.eventName, | ||
| eventSource: detail.eventSource, | ||
| sourceIPAddress: detail.sourceIPAddress, | ||
| userAgent: detail.userAgent, | ||
| userIdentity: detail.userIdentity?.arn, | ||
| eventBusName: detail.requestParameters?.entries?.[0]?.eventBusName || 'default', | ||
| entryCount: detail.requestParameters?.entries?.length || 0, | ||
| eventTime: detail.eventTime, | ||
| })); | ||
| return { statusCode: 200 }; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "ES2020", "module": "commonjs", "lib": ["es2020"], | ||
| "declaration": true, "strict": true, "outDir": "build", | ||
| "rootDir": ".", "skipLibCheck": true, "forceConsistentCasingInFileNames": true | ||
| }, | ||
| "exclude": ["node_modules", "build"] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recommendation: Mention that CloudTrail data events are billed separately and link pricing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a callout box in the README noting CloudTrail data events are billed separately, with a link to the pricing page.