From ec0eb05f4c7cfc3fd5bd1327f931e97562ee91de Mon Sep 17 00:00:00 2001 From: Nithin Chandran Rajashankar Date: Mon, 13 Jul 2026 16:54:08 +0000 Subject: [PATCH] feat(guardduty-file-modification-sfn-response-cdk): GuardDuty incident response Automated incident response for Amazon GuardDuty sensitive file modification findings. AWS Step Functions classifies severity and routes: HIGH (>=7) -> AWS Lambda isolates instance (replace SG, forensic snapshot, tag) + Amazon SNS alert. MEDIUM -> notify only. LOW -> log. 6 services composed: Amazon GuardDuty, Amazon EventBridge, AWS Step Functions, AWS Lambda, Amazon SNS, Amazon EC2. Fundamentally different from existing guardduty-malware-s3 (S3 object scanning, SAM, notify only). This pattern handles host-level runtime threats with automated orchestrated response. --- .../README.md | 136 ++++++++++++ .../cdk/.gitignore | 6 + .../cdk/bin/app.ts | 12 + .../cdk/cdk.json | 3 + ...ty-file-modification-sfn-response-stack.ts | 205 ++++++++++++++++++ .../cdk/package.json | 20 ++ .../cdk/tsconfig.json | 24 ++ .../example-pattern.json | 107 +++++++++ .../lambdas/isolate-instance/handler.py | 172 +++++++++++++++ 9 files changed, 685 insertions(+) create mode 100644 guardduty-file-modification-sfn-response-cdk/README.md create mode 100644 guardduty-file-modification-sfn-response-cdk/cdk/.gitignore create mode 100644 guardduty-file-modification-sfn-response-cdk/cdk/bin/app.ts create mode 100644 guardduty-file-modification-sfn-response-cdk/cdk/cdk.json create mode 100644 guardduty-file-modification-sfn-response-cdk/cdk/lib/guardduty-file-modification-sfn-response-stack.ts create mode 100644 guardduty-file-modification-sfn-response-cdk/cdk/package.json create mode 100644 guardduty-file-modification-sfn-response-cdk/cdk/tsconfig.json create mode 100644 guardduty-file-modification-sfn-response-cdk/example-pattern.json create mode 100644 guardduty-file-modification-sfn-response-cdk/lambdas/isolate-instance/handler.py diff --git a/guardduty-file-modification-sfn-response-cdk/README.md b/guardduty-file-modification-sfn-response-cdk/README.md new file mode 100644 index 0000000000..16b964717c --- /dev/null +++ b/guardduty-file-modification-sfn-response-cdk/README.md @@ -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 diff --git a/guardduty-file-modification-sfn-response-cdk/cdk/.gitignore b/guardduty-file-modification-sfn-response-cdk/cdk/.gitignore new file mode 100644 index 0000000000..1f0ea03523 --- /dev/null +++ b/guardduty-file-modification-sfn-response-cdk/cdk/.gitignore @@ -0,0 +1,6 @@ +node_modules +cdk.out +cdk.context.json +build +*.js +*.d.ts diff --git a/guardduty-file-modification-sfn-response-cdk/cdk/bin/app.ts b/guardduty-file-modification-sfn-response-cdk/cdk/bin/app.ts new file mode 100644 index 0000000000..167cd7ed3a --- /dev/null +++ b/guardduty-file-modification-sfn-response-cdk/cdk/bin/app.ts @@ -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)', +}); diff --git a/guardduty-file-modification-sfn-response-cdk/cdk/cdk.json b/guardduty-file-modification-sfn-response-cdk/cdk/cdk.json new file mode 100644 index 0000000000..a6700a2ff4 --- /dev/null +++ b/guardduty-file-modification-sfn-response-cdk/cdk/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/app.ts" +} diff --git a/guardduty-file-modification-sfn-response-cdk/cdk/lib/guardduty-file-modification-sfn-response-stack.ts b/guardduty-file-modification-sfn-response-cdk/cdk/lib/guardduty-file-modification-sfn-response-stack.ts new file mode 100644 index 0000000000..499a10651c --- /dev/null +++ b/guardduty-file-modification-sfn-response-cdk/cdk/lib/guardduty-file-modification-sfn-response-stack.ts @@ -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', + }); + } +} diff --git a/guardduty-file-modification-sfn-response-cdk/cdk/package.json b/guardduty-file-modification-sfn-response-cdk/cdk/package.json new file mode 100644 index 0000000000..55854484ad --- /dev/null +++ b/guardduty-file-modification-sfn-response-cdk/cdk/package.json @@ -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" + } +} diff --git a/guardduty-file-modification-sfn-response-cdk/cdk/tsconfig.json b/guardduty-file-modification-sfn-response-cdk/cdk/tsconfig.json new file mode 100644 index 0000000000..7ddcfe705e --- /dev/null +++ b/guardduty-file-modification-sfn-response-cdk/cdk/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["es2020"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "outDir": "./build", + "rootDir": "." + }, + "exclude": ["node_modules", "build"] +} diff --git a/guardduty-file-modification-sfn-response-cdk/example-pattern.json b/guardduty-file-modification-sfn-response-cdk/example-pattern.json new file mode 100644 index 0000000000..fd559616d4 --- /dev/null +++ b/guardduty-file-modification-sfn-response-cdk/example-pattern.json @@ -0,0 +1,107 @@ +{ + "title": "Amazon GuardDuty Sensitive File Modification with AWS Step Functions Incident Response", + "description": "Detect sensitive file modifications with Amazon GuardDuty, classify severity, and auto-isolate compromised instances via AWS Step Functions and AWS Lambda", + "language": "TypeScript", + "level": "300", + "framework": "AWS CDK", + "introBox": { + "headline": "How it works", + "text": [ + "Amazon GuardDuty detects sensitive file modifications or unauthorized access on Amazon EC2 instances and publishes findings to Amazon EventBridge.", + "Amazon EventBridge triggers an AWS Step Functions workflow that classifies finding severity: HIGH (≥7) isolates the instance, MEDIUM (4-6) notifies only, LOW (<4) logs only.", + "For HIGH severity findings, AWS Lambda replaces the instance security group (network isolation), creates forensic EBS snapshots, and tags the instance. Amazon SNS delivers alerts to the security team." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/guardduty-file-modification-sfn-response-cdk", + "templateURL": "serverless-patterns/guardduty-file-modification-sfn-response-cdk", + "projectFolder": "guardduty-file-modification-sfn-response-cdk", + "templateFile": "cdk/lib/guardduty-file-modification-sfn-response-stack.ts" + } + }, + "resources": { + "bullets": [ + { + "text": "Amazon GuardDuty Runtime Monitoring", + "link": "https://docs.aws.amazon.com/guardduty/latest/ug/runtime-monitoring.html" + }, + { + "text": "Automating incident response with Amazon EventBridge", + "link": "https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_findings_cloudwatch.html" + }, + { + "text": "AWS Step Functions service integrations", + "link": "https://docs.aws.amazon.com/step-functions/latest/dg/connect-supported-services.html" + } + ] + }, + "deploy": { + "text": [ + "cd guardduty-file-modification-sfn-response-cdk/cdk", + "npm install", + "npx cdk deploy" + ] + }, + "testing": { + "text": [ + "Generate sample Amazon GuardDuty findings using the create-sample-findings API.", + "Verify AWS Step Functions execution was triggered via the list-executions API.", + "For HIGH severity, confirm the instance security group was replaced and forensic snapshots were created.", + "Subscribe to the Amazon SNS topic to receive incident alerts." + ] + }, + "cleanup": { + "text": [ + "npx cdk destroy" + ] + }, + "authors": [ + { + "name": "Nithin Chandran R", + "image": "", + "bio": "Technical Account Manager at AWS", + "linkedin": "nithin-chandran-r", + "twitter": "" + } + ], + "patternArch": { + "icon1": { + "x": 10, + "y": 50, + "service": "guardduty", + "label": "Amazon GuardDuty" + }, + "icon2": { + "x": 30, + "y": 50, + "service": "eventbridge", + "label": "Amazon EventBridge" + }, + "icon3": { + "x": 50, + "y": 50, + "service": "sfn", + "label": "AWS Step Functions" + }, + "icon4": { + "x": 70, + "y": 50, + "service": "lambda", + "label": "AWS Lambda" + }, + "icon5": { + "x": 90, + "y": 25, + "service": "sns", + "label": "Amazon SNS" + }, + "icon6": { + "x": 90, + "y": 75, + "service": "ec2", + "label": "Amazon EC2" + } + }, + "patternType": "Serverless" +} diff --git a/guardduty-file-modification-sfn-response-cdk/lambdas/isolate-instance/handler.py b/guardduty-file-modification-sfn-response-cdk/lambdas/isolate-instance/handler.py new file mode 100644 index 0000000000..9296d7b641 --- /dev/null +++ b/guardduty-file-modification-sfn-response-cdk/lambdas/isolate-instance/handler.py @@ -0,0 +1,172 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 (2026) +"""Instance Isolation: Triggered by AWS Step Functions when Amazon GuardDuty +detects HIGH severity sensitive file modification. Isolates the compromised +Amazon EC2 instance by replacing its security group, creating forensic +snapshots, and tagging for investigation.""" + +import json +from datetime import datetime, timezone + +import boto3 + +EC2_CLIENT = boto3.client('ec2') + + +def lambda_handler(event, context): + """Isolate a compromised Amazon EC2 instance.""" + detail = event.get('detail', {}) + resource = detail.get('resource', {}) + instance_details = resource.get('instanceDetails', {}) + instance_id = instance_details.get('instanceId', '') + finding_type = detail.get('type', 'Unknown') + severity = detail.get('severity', 0) + + if not instance_id: + print(f'No instanceId in finding: {finding_type}') + return {'status': 'SKIPPED', 'reason': 'No instance ID in finding'} + + print(f'Isolating instance {instance_id} | Finding: {finding_type} | Severity: {severity}') + + try: + # Step 1: Get instance details + instance_info = get_instance_info(instance_id) + vpc_id = instance_info['VpcId'] + original_sgs = [sg['GroupId'] for sg in instance_info.get('SecurityGroups', [])] + + # Step 2: Create isolation security group (no ingress, no egress) + isolation_sg = create_isolation_sg(vpc_id, instance_id) + + # Step 3: Replace security groups with isolation SG + EC2_CLIENT.modify_instance_attribute( + InstanceId=instance_id, + Groups=[isolation_sg], + ) + print(f'Replaced security groups on {instance_id} with isolation SG {isolation_sg}') + + # Step 4: Create forensic snapshots of all volumes + snapshot_ids = create_forensic_snapshots(instance_id, instance_info, finding_type) + + # Step 5: Tag instance as isolated + tag_instance(instance_id, finding_type, original_sgs) + + return { + 'status': 'ISOLATED', + 'instanceId': instance_id, + 'findingType': finding_type, + 'severity': severity, + 'isolationSecurityGroup': isolation_sg, + 'originalSecurityGroups': original_sgs, + 'forensicSnapshots': snapshot_ids, + 'isolatedAt': datetime.now(timezone.utc).isoformat(), + } + + except Exception as e: + print(f'Isolation failed for {instance_id}: {e}') + return { + 'status': 'FAILED', + 'instanceId': instance_id, + 'error': str(e), + } + + +def get_instance_info(instance_id: str) -> dict: + """Get Amazon EC2 instance details.""" + try: + response = EC2_CLIENT.describe_instances(InstanceIds=[instance_id]) + return response['Reservations'][0]['Instances'][0] + except Exception as e: + raise Exception(f'Failed to describe instance {instance_id}: {e}') + + +def create_isolation_sg(vpc_id: str, instance_id: str) -> str: + """Create a security group with no ingress and no egress rules.""" + sg_name = f'guardduty-isolation-{instance_id}' + + try: + response = EC2_CLIENT.create_security_group( + GroupName=sg_name, + Description=f'Isolation SG for compromised instance {instance_id}', + VpcId=vpc_id, + ) + sg_id = response['GroupId'] + + # Remove the default egress rule (allow all outbound) + EC2_CLIENT.revoke_security_group_egress( + GroupId=sg_id, + IpPermissions=[{ + 'IpProtocol': '-1', + 'IpRanges': [{'CidrIp': '0.0.0.0/0'}], + }], + ) + + EC2_CLIENT.create_tags( + Resources=[sg_id], + Tags=[ + {'Key': 'Purpose', 'Value': 'GuardDuty-Isolation'}, + {'Key': 'CreatedBy', 'Value': 'guardduty-incident-response'}, + ], + ) + + print(f'Created isolation SG: {sg_id} (no ingress, no egress)') + return sg_id + + except EC2_CLIENT.exceptions.ClientError as e: + if 'InvalidGroup.Duplicate' in str(e): + # SG already exists from a prior isolation + sgs = EC2_CLIENT.describe_security_groups( + Filters=[ + {'Name': 'group-name', 'Values': [sg_name]}, + {'Name': 'vpc-id', 'Values': [vpc_id]}, + ] + ) + return sgs['SecurityGroups'][0]['GroupId'] + raise + + +def create_forensic_snapshots(instance_id: str, instance_info: dict, finding_type: str) -> list: + """Create snapshots of all attached volumes for forensic analysis.""" + snapshot_ids = [] + block_devices = instance_info.get('BlockDeviceMappings', []) + + for device in block_devices: + volume_id = device.get('Ebs', {}).get('VolumeId', '') + if not volume_id: + continue + + try: + response = EC2_CLIENT.create_snapshot( + VolumeId=volume_id, + Description=f'Forensic snapshot - {instance_id} - {finding_type}', + TagSpecifications=[{ + 'ResourceType': 'snapshot', + 'Tags': [ + {'Key': 'Purpose', 'Value': 'GuardDuty-Forensics'}, + {'Key': 'SourceInstance', 'Value': instance_id}, + {'Key': 'FindingType', 'Value': finding_type}, + {'Key': 'CreatedAt', 'Value': datetime.now(timezone.utc).isoformat()}, + ], + }], + ) + snapshot_ids.append(response['SnapshotId']) + print(f'Created forensic snapshot {response["SnapshotId"]} for volume {volume_id}') + except Exception as e: + print(f'Failed to snapshot volume {volume_id}: {e}') + + return snapshot_ids + + +def tag_instance(instance_id: str, finding_type: str, original_sgs: list) -> None: + """Tag the isolated instance for investigation tracking.""" + try: + EC2_CLIENT.create_tags( + Resources=[instance_id], + Tags=[ + {'Key': 'GuardDuty:Status', 'Value': 'ISOLATED'}, + {'Key': 'GuardDuty:FindingType', 'Value': finding_type}, + {'Key': 'GuardDuty:IsolatedAt', 'Value': datetime.now(timezone.utc).isoformat()}, + {'Key': 'GuardDuty:OriginalSecurityGroups', 'Value': ','.join(original_sgs)}, + ], + ) + except Exception as e: + print(f'Failed to tag instance {instance_id}: {e}')