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
136 changes: 136 additions & 0 deletions guardduty-file-modification-sfn-response-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Amazon GuardDuty Sensitive File Modification with AWS Step Functions Incident Response

This pattern deploys an automated incident response architecture that detects sensitive file modifications on Amazon EC2 instances using Amazon GuardDuty, classifies findings by severity using AWS Step Functions, and automatically isolates compromised instances via AWS Lambda while notifying security teams through Amazon SNS.

Important: This pattern is fundamentally different from the existing `guardduty-malware-s3` pattern, which scans S3 objects for malware and sends notifications. This pattern handles **host-level runtime threat detection** with **automated incident response orchestration** — isolating compromised instances, creating forensic snapshots, and tagging for investigation.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/guardduty-file-modification-sfn-response-cdk

## Architecture

```
┌──────────────────┐ ┌─────────────────┐ ┌──────────────────────────────────────────────────────┐
│ Amazon GuardDuty │────▶│ Amazon │────▶│ AWS Step Functions (Incident Response Workflow) │
│ (Finding) │ │ Amazon EventBridge│ │ │
└──────────────────┘ └─────────────────┘ │ ┌─────────────────┐ │
│ │ Classify │ │
│ │ Severity │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────┼────────────────┐ │
│ │ HIGH │ MEDIUM │ LOW │ │
│ ▼ ▼ ▼ │ │
│ Isolate Notify Log │ │
│ + Notify Only Only │ │
└──────────────────────────────────────────────────────┘
│ │
▼ ▼
┌────────────────┐ ┌──────────────┐
│ AWS Lambda │ │ Amazon SNS │
│ (Isolate) │ │ (Alerts) │
└─────┬──────────┘ └──────────────┘
└─────┬────┘
┌──────────────────┐
│ Amazon EC2 API │
│ (Replace SG, │
│ Snapshot, │
│ Tag) │
└──────────────────┘
```

**How it works:**

1. Amazon GuardDuty detects sensitive file modifications or unauthorized access on Amazon EC2 instances
2. Amazon EventBridge captures the finding and triggers the AWS Step Functions workflow
3. AWS Step Functions classifies the finding severity:
- **HIGH (≥7):** Isolate instance + create forensic snapshot + notify
- **MEDIUM (4-6):** Notify security team only
- **LOW (<4):** Log the finding only
4. For HIGH severity: AWS Lambda replaces the instance security group (network isolation), creates EBS snapshots for forensics, and tags the instance for investigation
5. Amazon SNS delivers alerts to the security team

## Requirements

- [AWS CDK v2](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) installed and configured
- [Node.js 20+](https://nodejs.org/) with npm
- AWS account [bootstrapped for CDK](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html)
- Python 3.12 (for AWS Lambda functions)
- Amazon GuardDuty enabled in your account (the stack enables a detector)

## Deployment

```bash
cd guardduty-file-modification-sfn-response-cdk/cdk
npm install
npx cdk deploy
```

## Testing

### Simulate a GuardDuty finding (using sample findings)

```bash
# Generate sample findings to test the pipeline
DETECTOR_ID=$(aws cloudformation describe-stacks \
--stack-name GuarddutyFileModificationSfnResponseStack \
--query 'Stacks[0].Outputs[?OutputKey==`DetectorId`].OutputValue' \
--output text)

aws guardduty create-sample-findings \
--detector-id $DETECTOR_ID \
--finding-types "UnauthorizedAccess:EC2/SSHBruteForce"
```

### Verify AWS Step Functions execution

```bash
SFN_ARN=$(aws cloudformation describe-stacks \
--stack-name GuarddutyFileModificationSfnResponseStack \
--query 'Stacks[0].Outputs[?OutputKey==`StateMachineArn`].OutputValue' \
--output text)

aws stepfunctions list-executions \
--state-machine-arn $SFN_ARN \
--max-results 5 \
--query 'executions[].{Status:status,Start:startDate}'
```

### Subscribe to incident alerts

```bash
TOPIC_ARN=$(aws cloudformation describe-stacks \
--stack-name GuarddutyFileModificationSfnResponseStack \
--query 'Stacks[0].Outputs[?OutputKey==`IncidentTopicArn`].OutputValue' \
--output text)

aws sns subscribe \
--topic-arn $TOPIC_ARN \
--protocol email \
--notification-endpoint your-security-team@example.com
```

## Cleanup

> **Warning:** This will delete the Amazon GuardDuty detector. If you have other GuardDuty configurations, remove the detector resource from the stack before deploying.

```bash
cd guardduty-file-modification-sfn-response-cdk/cdk
npx cdk destroy
```

## Services Used

| Service | Role |
|---------|------|
| Amazon GuardDuty | Detects sensitive file modifications and unauthorized access on Amazon EC2 |
| Amazon EventBridge | Routes GuardDuty findings to the incident response workflow |
| AWS Step Functions | Orchestrates severity classification and response actions |
| AWS Lambda | Isolates compromised instances (replace SG, snapshot, tag) |
| Amazon SNS | Delivers incident alerts to the security team |
| Amazon EC2 | Target of isolation actions (security group replacement, snapshots) |

----
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
6 changes: 6 additions & 0 deletions guardduty-file-modification-sfn-response-cdk/cdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
cdk.out
cdk.context.json
build
*.js
*.d.ts
12 changes: 12 additions & 0 deletions guardduty-file-modification-sfn-response-cdk/cdk/bin/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env node
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0 (2026)

import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { GuarddutyFileModificationSfnResponseStack } from '../lib/guardduty-file-modification-sfn-response-stack';

const app = new cdk.App();
new GuarddutyFileModificationSfnResponseStack(app, 'GuarddutyFileModificationSfnResponseStack', {
description: 'Amazon GuardDuty sensitive file modification with AWS Step Functions incident response (uksb-1tupboc57)',
});
3 changes: 3 additions & 0 deletions guardduty-file-modification-sfn-response-cdk/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"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0 (2026)

import * as cdk from 'aws-cdk-lib';
import * as guardduty from 'aws-cdk-lib/aws-guardduty';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import * as sfn from 'aws-cdk-lib/aws-stepfunctions';
import * as sfnTasks from 'aws-cdk-lib/aws-stepfunctions-tasks';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as logs from 'aws-cdk-lib/aws-logs';
import { Construct } from 'constructs';
import * as path from 'path';

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

const region = cdk.Stack.of(this).region;
const account = cdk.Stack.of(this).account;

// =========================================================
// 1. Amazon GuardDuty Detector (use existing or create new)
// =========================================================
const detectorId = new cdk.CfnParameter(this, 'DetectorId', {
type: 'String',
description: 'Existing Amazon GuardDuty detector ID (leave empty to create new)',
default: '',
});

const createDetector = new cdk.CfnCondition(this, 'ShouldCreateDetector', {
expression: cdk.Fn.conditionEquals(detectorId.valueAsString, ''),
});

const detector = new guardduty.CfnDetector(this, 'GuardDutyDetector', {
enable: true,
findingPublishingFrequency: 'FIFTEEN_MINUTES',
});
detector.cfnOptions.condition = createDetector;

const effectiveDetectorId = cdk.Fn.conditionIf(
'ShouldCreateDetector',
detector.ref,
detectorId.valueAsString,
).toString();

// =========================================================
// 2. Amazon SNS Topic (incident notifications)
// =========================================================
const incidentTopic = new sns.Topic(this, 'IncidentTopic', {
topicName: 'guardduty-incident-alerts',
displayName: 'Amazon GuardDuty - Sensitive File Modification Alerts',
});

// =========================================================
// 3. AWS Lambda: Instance Isolation Handler
// =========================================================
const isolateFn = new lambda.Function(this, 'IsolateInstanceFn', {
runtime: lambda.Runtime.PYTHON_3_12,
handler: 'handler.lambda_handler',
code: lambda.Code.fromAsset(path.join(__dirname, '../../lambdas/isolate-instance')),
timeout: cdk.Duration.seconds(120),
memorySize: 256,
description: 'Isolates compromised Amazon EC2 instance: replaces security group, creates snapshot, tags',
});

// EC2 permissions for isolation actions
isolateFn.addToRolePolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'ec2:DescribeInstances',
'ec2:DescribeSecurityGroups',
'ec2:CreateSecurityGroup',
'ec2:RevokeSecurityGroupEgress',
'ec2:ModifyInstanceAttribute',
'ec2:CreateSnapshot',
'ec2:CreateTags',
'ec2:DescribeVolumes',
],
resources: ['*'],
conditions: {
StringEquals: { 'aws:RequestedRegion': region },
},
}));

// =========================================================
// 4. AWS Step Functions: Incident Response Workflow
// =========================================================

// Isolate instance (HIGH severity)
const isolateTask = new sfnTasks.LambdaInvoke(this, 'IsolateInstance', {
lambdaFunction: isolateFn,
outputPath: '$.Payload',
comment: 'Replace security group, create forensic snapshot, tag instance',
});

// Publish HIGH severity to SNS (after isolation)
const notifyHighTask = new sfnTasks.SnsPublish(this, 'NotifyHighSeverity', {
topic: incidentTopic,
subject: sfn.JsonPath.format(
'GuardDuty CRITICAL: {} on instance {}',
sfn.JsonPath.stringAt('$.findingType'),
sfn.JsonPath.stringAt('$.instanceId'),
),
message: sfn.TaskInput.fromJsonPathAt('$'),
resultPath: '$.notification',
});

// Publish MEDIUM severity to SNS
const notifyMediumTask = new sfnTasks.SnsPublish(this, 'NotifyMediumSeverity', {
topic: incidentTopic,
subject: sfn.JsonPath.format(
'GuardDuty WARNING: {}',
sfn.JsonPath.stringAt('$.detail.type'),
),
message: sfn.TaskInput.fromJsonPathAt('$'),
resultPath: '$.notification',
});

// Terminal states
const highDone = new sfn.Pass(this, 'HighSeverityHandled', {
result: sfn.Result.fromObject({ status: 'ISOLATED_AND_NOTIFIED' }),
});

const mediumDone = new sfn.Pass(this, 'MediumSeverityHandled', {
result: sfn.Result.fromObject({ status: 'NOTIFIED' }),
});

const lowDone = new sfn.Pass(this, 'LowSeverityLogged', {
result: sfn.Result.fromObject({ status: 'LOGGED' }),
});

// Severity routing
const definition = new sfn.Choice(this, 'ClassifySeverity')
.when(
sfn.Condition.numberGreaterThanEquals('$.detail.severity', 7),
isolateTask.next(notifyHighTask).next(highDone),
)
.when(
sfn.Condition.numberGreaterThanEquals('$.detail.severity', 4),
notifyMediumTask.next(mediumDone),
)
.otherwise(lowDone);

const stateMachine = new sfn.StateMachine(this, 'IncidentResponseWorkflow', {
definitionBody: sfn.DefinitionBody.fromChainable(definition),
timeout: cdk.Duration.minutes(10),
tracingEnabled: true,
logs: {
destination: new logs.LogGroup(this, 'SfnLogGroup', {
logGroupName: '/aws/stepfunctions/guardduty-incident-response',
retention: logs.RetentionDays.TWO_WEEKS,
removalPolicy: cdk.RemovalPolicy.DESTROY,
}),
level: sfn.LogLevel.ALL,
},
});

// =========================================================
// 5. Amazon EventBridge Rule: GuardDuty findings
// Matches sensitive file modification findings
// =========================================================
new events.Rule(this, 'GuardDutyFileModRule', {
ruleName: 'guardduty-file-modification-to-sfn',
description: 'Routes Amazon GuardDuty sensitive file modification findings to AWS Step Functions',
eventPattern: {
source: ['aws.guardduty'],
detailType: ['GuardDuty Finding'],
detail: {
type: [
{ prefix: 'Execution:Runtime/SensitiveFileAccess' },
{ prefix: 'Execution:Runtime/SuspiciousFileModification' },
{ prefix: 'UnauthorizedAccess:EC2' },
],
},
},
targets: [new targets.SfnStateMachine(stateMachine)],
});

// =========================================================
// Outputs
// =========================================================
new cdk.CfnOutput(this, 'DetectorIdOutput', {
value: effectiveDetectorId,
description: 'Amazon GuardDuty detector ID',
});

new cdk.CfnOutput(this, 'StateMachineArn', {
value: stateMachine.stateMachineArn,
description: 'AWS Step Functions incident response workflow ARN',
});

new cdk.CfnOutput(this, 'IncidentTopicArn', {
value: incidentTopic.topicArn,
description: 'Amazon SNS incident notification topic ARN',
});

new cdk.CfnOutput(this, 'IsolationFunctionArn', {
value: isolateFn.functionArn,
description: 'AWS Lambda isolation function ARN',
});
}
}
20 changes: 20 additions & 0 deletions guardduty-file-modification-sfn-response-cdk/cdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "guardduty-file-modification-sfn-response-cdk",
"version": "1.0.0",
"bin": { "app": "bin/app.ts" },
"scripts": {
"build": "tsc",
"synth": "cdk synth",
"deploy": "cdk deploy",
"destroy": "cdk destroy"
},
"dependencies": {
"aws-cdk-lib": "2.185.0",
"constructs": "^10.0.0",
"source-map-support": "^0.5.21"
},
"devDependencies": {
"typescript": "~5.4.0",
"ts-node": "^10.9.0"
}
}
Loading