Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions eventbridge-cloudtrail-dataplane-cdk/README.md
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

## Deployment Instructions

1. Clone and navigate to the pattern:
```
cd serverless-patterns/eventbridge-cloudtrail-dataplane-cdk
npm install
```
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
7 changes: 7 additions & 0 deletions eventbridge-cloudtrail-dataplane-cdk/bin/app.ts
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');
3 changes: 3 additions & 0 deletions eventbridge-cloudtrail-dataplane-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "npx ts-node --prefer-ts-exts bin/app.ts"
}
40 changes: 40 additions & 0 deletions eventbridge-cloudtrail-dataplane-cdk/example-pattern.json
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,
});

// CloudTrail trail with data events for EventBridge
const trail = new cloudtrail.Trail(this, 'EventBridgeDataPlaneTrail', {
bucket: trailBucket,
trailName: 'eventbridge-dataplane-trail',
isMultiRegionTrail: false,
});

// Enable EventBridge data plane events logging
trail.addEventSelector(cloudtrail.DataResourceType.LAMBDA_FUNCTION, ['arn:aws:lambda']);

// Lambda function to process CloudTrail events
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,
});

// 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));

new cdk.CfnOutput(this, 'ProcessorFunctionName', { value: processor.functionName });
new cdk.CfnOutput(this, 'TrailBucketName', { value: trailBucket.bucketName });
new cdk.CfnOutput(this, 'RuleName', { value: rule.ruleName });
}
}
16 changes: 16 additions & 0 deletions eventbridge-cloudtrail-dataplane-cdk/package.json
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"
}
}
15 changes: 15 additions & 0 deletions eventbridge-cloudtrail-dataplane-cdk/src/index.js
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({
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 };
};
8 changes: 8 additions & 0 deletions eventbridge-cloudtrail-dataplane-cdk/tsconfig.json
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"]
}
58 changes: 58 additions & 0 deletions lambda-verified-permissions-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Amazon Verified Permissions with AWS Lambda

This pattern deploys a Lambda function that authorizes requests using Amazon Verified Permissions with Cedar policies for fine-grained access control.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-verified-permissions-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
* [Node.js 22+](https://nodejs.org/en/download/) installed
* [AWS CDK v2](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html) installed

## Architecture

```
┌──────────┐ ┌──────────────────┐ ┌─────────────────────────┐
│ Client │────▶│ AWS Lambda │────▶│ Amazon Verified │
│ │ │ (Authorizer) │ │ Permissions │
└──────────┘ └──────────────────┘ │ (Cedar Policy Store) │
└─────────────────────────┘
```

## How it works

1. Lambda receives an authorization request with user identity, action, and resource.
2. Lambda calls the Verified Permissions `IsAuthorized` API with the request context.
3. Cedar policies evaluate the request and return ALLOW or DENY.
4. The pattern includes two policies: admins can perform any action, readers can only read.

## Deployment

```bash
npm install
cdk deploy
```

## Testing

```bash
python3 -c "
import boto3, json
client = boto3.client('lambda')
# Admin can delete (ALLOW)
r = client.invoke(FunctionName='<FunctionName>', Payload=json.dumps({'body': json.dumps({'userId':'alice','role':'admin','action':'Delete','resourceId':'doc-1','classification':'confidential'})}))
print('Admin Delete:', json.loads(json.loads(r['Payload'].read())['body'])['decision'])
# Reader cannot delete (DENY)
r = client.invoke(FunctionName='<FunctionName>', Payload=json.dumps({'body': json.dumps({'userId':'bob','role':'reader','action':'Delete','resourceId':'doc-2','classification':'public'})}))
print('Reader Delete:', json.loads(json.loads(r['Payload'].read())['body'])['decision'])
"
```

## Cleanup

```bash
cdk destroy
```
6 changes: 6 additions & 0 deletions lambda-verified-permissions-cdk/bin/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { LambdaVerifiedPermissionsStack } from '../lib/lambda-verified-permissions-stack';
const app = new cdk.App();
new LambdaVerifiedPermissionsStack(app, 'LambdaVerifiedPermissionsStack');
1 change: 1 addition & 0 deletions lambda-verified-permissions-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"app":"npx ts-node --prefer-ts-exts bin/app.ts"}
1 change: 1 addition & 0 deletions lambda-verified-permissions-cdk/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"title":"Amazon Verified Permissions with AWS Lambda","description":"Deploy a Lambda function that authorizes requests using Amazon Verified Permissions Cedar policies.","language":"TypeScript","level":"300","framework":"CDK","introBox":{"headline":"How it works","text":["Lambda receives an authorization request and calls Amazon Verified Permissions IsAuthorized API with Cedar policies to make fine-grained access control decisions."]},"gitHub":{"template":{"repoURL":"https://github.com/aws-samples/serverless-patterns/tree/main/lambda-verified-permissions-cdk","templateURL":"serverless-patterns/lambda-verified-permissions-cdk","projectFolder":"lambda-verified-permissions-cdk"}},"resources":{"bullets":[{"text":"Amazon Verified Permissions","link":"https://docs.aws.amazon.com/verifiedpermissions/latest/userguide/what-is-avp.html"}]},"deploy":{"text":["cdk deploy"],"commands":["npm install","cdk deploy"]},"testing":{"text":["Invoke the function URL with authorization request"]},"cleanup":{"text":["cdk destroy"],"commands":["cdk destroy"]},"authors":[{"name":"Nithin Chandran R","bio":"Technical Account Manager at AWS","linkedin":"nithin-chandran-r"}],"services":{"from":[{"service":"lambda"}],"to":[{"service":"verifiedpermissions"}]}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as verifiedpermissions from 'aws-cdk-lib/aws-verifiedpermissions';
import { Construct } from 'constructs';

export class LambdaVerifiedPermissionsStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

// Create policy store with Cedar schema
const policyStore = new verifiedpermissions.CfnPolicyStore(this, 'PolicyStore', {
validationSettings: { mode: 'STRICT' },
schema: {
cedarJson: JSON.stringify({
'MyApp': {
entityTypes: {
User: { shape: { type: 'Record', attributes: { role: { type: 'String' } } } },
Document: { shape: { type: 'Record', attributes: { owner: { type: 'String' }, classification: { type: 'String' } } } }
},
actions: {
Read: { appliesTo: { principalTypes: ['User'], resourceTypes: ['Document'] } },
Write: { appliesTo: { principalTypes: ['User'], resourceTypes: ['Document'] } },
Delete: { appliesTo: { principalTypes: ['User'], resourceTypes: ['Document'] } }
}
}
})
}
});

// Create policies
new verifiedpermissions.CfnPolicy(this, 'AdminPolicy', {
policyStoreId: policyStore.attrPolicyStoreId,
definition: {
static: {
statement: 'permit(principal, action, resource) when { principal.role == "admin" };',
description: 'Admins can perform any action'
}
}
});

new verifiedpermissions.CfnPolicy(this, 'ReaderPolicy', {
policyStoreId: policyStore.attrPolicyStoreId,
definition: {
static: {
statement: 'permit(principal, action == MyApp::Action::"Read", resource) when { principal.role == "reader" };',
description: 'Readers can only read documents'
}
}
});

// Lambda authorizer function
const authFn = new lambda.Function(this, 'AuthorizerFn', {
runtime: lambda.Runtime.NODEJS_22_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('src'),
environment: { POLICY_STORE_ID: policyStore.attrPolicyStoreId },
timeout: cdk.Duration.seconds(10)
});

authFn.addToRolePolicy(new iam.PolicyStatement({
actions: ['verifiedpermissions:IsAuthorized'],
resources: [`arn:aws:verifiedpermissions::${this.account}:policy-store/${policyStore.attrPolicyStoreId}`]
}));

const fnUrl = authFn.addFunctionUrl({ authType: lambda.FunctionUrlAuthType.AWS_IAM });

new cdk.CfnOutput(this, 'FunctionUrl', { value: fnUrl.url });
new cdk.CfnOutput(this, 'PolicyStoreId', { value: policyStore.attrPolicyStoreId });
}
}
14 changes: 14 additions & 0 deletions lambda-verified-permissions-cdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "lambda-verified-permissions-cdk",
"version": "1.0.0",
"bin": { "app": "bin/app.js" },
"scripts": { "build": "tsc", "cdk": "cdk" },
"dependencies": {
"aws-cdk-lib": "^2.180.0",
"constructs": "^10.0.0"
},
"devDependencies": {
"typescript": "~5.4.0",
"@types/node": "^20.0.0"
}
}
Loading