From eea9d1027e88ddd8f4053d833e0b40ea08b94522 Mon Sep 17 00:00:00 2001 From: Nithin Chandran Rajashankar Date: Tue, 14 Jul 2026 05:57:44 +0000 Subject: [PATCH] feat(securityhub-finding-sfn-remediation-cdk): Auto-remediate Security Hub findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First AWS Security Hub pattern in the repo. Routes HIGH/CRITICAL findings via Amazon EventBridge to AWS Step Functions which classifies and remediates: closes open security groups (revokes 0.0.0.0/0), blocks public Amazon S3 access, updates finding status to RESOLVED, notifies security team via Amazon SNS. 5 services: AWS Security Hub, Amazon EventBridge, AWS Step Functions, AWS Lambda, Amazon SNS. Zero competition — major enterprise gap filled. --- .../README.md | 128 ++++++++++++ .../cdk/.gitignore | 6 + .../cdk/bin/app.ts | 11 ++ .../cdk/cdk.json | 1 + ...curityhub-finding-sfn-remediation-stack.ts | 186 ++++++++++++++++++ .../cdk/package.json | 12 ++ .../cdk/tsconfig.json | 1 + .../example-pattern.json | 71 +++++++ .../lambdas/remediate/handler.py | 151 ++++++++++++++ 9 files changed, 567 insertions(+) create mode 100644 securityhub-finding-sfn-remediation-cdk/README.md create mode 100644 securityhub-finding-sfn-remediation-cdk/cdk/.gitignore create mode 100644 securityhub-finding-sfn-remediation-cdk/cdk/bin/app.ts create mode 100644 securityhub-finding-sfn-remediation-cdk/cdk/cdk.json create mode 100644 securityhub-finding-sfn-remediation-cdk/cdk/lib/securityhub-finding-sfn-remediation-stack.ts create mode 100644 securityhub-finding-sfn-remediation-cdk/cdk/package.json create mode 100644 securityhub-finding-sfn-remediation-cdk/cdk/tsconfig.json create mode 100644 securityhub-finding-sfn-remediation-cdk/example-pattern.json create mode 100644 securityhub-finding-sfn-remediation-cdk/lambdas/remediate/handler.py diff --git a/securityhub-finding-sfn-remediation-cdk/README.md b/securityhub-finding-sfn-remediation-cdk/README.md new file mode 100644 index 0000000000..9eae1f4b24 --- /dev/null +++ b/securityhub-finding-sfn-remediation-cdk/README.md @@ -0,0 +1,128 @@ +# AWS Security Hub Auto-Remediation with AWS Step Functions + +This pattern deploys an automated remediation pipeline for AWS Security Hub findings. When HIGH or CRITICAL findings are detected, Amazon EventBridge routes them to an AWS Step Functions workflow that classifies the finding type and executes targeted remediation via AWS Lambda — closing open security groups, blocking public S3 bucket access, and marking findings as resolved. + +Important: This is the first AWS Security Hub pattern in the serverless-patterns repo. Every AWS account with Security Hub enabled accumulates findings that require manual triage. This pattern automates the response for common CIS benchmark violations. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/securityhub-finding-sfn-remediation-cdk + +## Architecture + +``` +┌───────────────────┐ ┌────────────────────┐ ┌────────────────────────────────────┐ +│ AWS Security Hub │────▶│ Amazon EventBridge │────▶│ AWS Step Functions │ +│ (HIGH/CRITICAL) │ │ (Finding Rule) │ │ (Classify + Route) │ +└───────────────────┘ └────────────────────┘ └─────────────┬──────────────────────┘ + │ + ┌──────────────┼──────────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌──────────┐ ┌────────────┐ + │ Open SG │ │ Public │ │ Unsupported│ + │ → Revoke │ │ Bucket │ │ → Skip │ + │ ingress │ │ → Block │ └────────────┘ + └────┬─────┘ └────┬─────┘ + │ │ + ▼ ▼ + ┌──────────────────────────┐ + │ Amazon SNS (Alert) │ + │ + Update Finding Status │ + └──────────────────────────┘ +``` + +**How it works:** + +1. AWS Security Hub detects a HIGH or CRITICAL finding (e.g., open security group, public Amazon S3 bucket) +2. Amazon EventBridge matches the finding and triggers the AWS Step Functions workflow +3. AWS Step Functions classifies the finding type and routes to the appropriate remediation path +4. AWS Lambda executes the remediation: + - **Open Security Group:** Revokes 0.0.0.0/0 ingress rules and tags the resource + - **Public Amazon S3 Bucket:** Enables full public access block +5. AWS Lambda updates the finding status to RESOLVED in AWS Security Hub +6. Amazon SNS delivers a notification to the security team with remediation details + +## 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) +- AWS Security Hub enabled in your account +- Python 3.12 (for AWS Lambda functions) + +## Deployment + +```bash +cd securityhub-finding-sfn-remediation-cdk/cdk +npm install +npx cdk deploy +``` + +## Testing + +### Generate a sample Security Hub finding + +```bash +# Create a deliberately open security group to trigger a finding +SG_ID=$(aws ec2 create-security-group \ + --group-name test-open-sg \ + --description "Test open SG for remediation" \ + --query 'GroupId' --output text) + +aws ec2 authorize-security-group-ingress \ + --group-id $SG_ID \ + --protocol tcp --port 22 --cidr 0.0.0.0/0 + +echo "Created open SG: $SG_ID — Security Hub will detect this within 15 minutes" +``` + +### Verify remediation after Security Hub detects the finding + +```bash +# Check Step Functions executions +SFN_ARN=$(aws cloudformation describe-stacks \ + --stack-name SecurityhubFindingSfnRemediationStack \ + --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}' + +# Verify the security group was closed +aws ec2 describe-security-groups --group-ids $SG_ID \ + --query 'SecurityGroups[0].IpPermissions' +``` + +### Subscribe to alerts + +```bash +TOPIC_ARN=$(aws cloudformation describe-stacks \ + --stack-name SecurityhubFindingSfnRemediationStack \ + --query 'Stacks[0].Outputs[?OutputKey==`AlertTopicArn`].OutputValue' \ + --output text) + +aws sns subscribe --topic-arn $TOPIC_ARN --protocol email \ + --notification-endpoint security-team@example.com +``` + +## Cleanup + +> **Warning:** After destroying this stack, Security Hub findings will no longer be auto-remediated. + +```bash +cd securityhub-finding-sfn-remediation-cdk/cdk +npx cdk destroy +``` + +## Services Used + +| Service | Role | +|---------|------| +| AWS Security Hub | Detects misconfigurations and compliance violations | +| Amazon EventBridge | Routes HIGH/CRITICAL findings to the remediation workflow | +| AWS Step Functions | Classifies finding type and orchestrates remediation | +| AWS Lambda | Executes remediation actions (revoke SG rules, block S3 access) | +| Amazon SNS | Delivers remediation alerts to the security team | + +---- +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: MIT-0 diff --git a/securityhub-finding-sfn-remediation-cdk/cdk/.gitignore b/securityhub-finding-sfn-remediation-cdk/cdk/.gitignore new file mode 100644 index 0000000000..1f0ea03523 --- /dev/null +++ b/securityhub-finding-sfn-remediation-cdk/cdk/.gitignore @@ -0,0 +1,6 @@ +node_modules +cdk.out +cdk.context.json +build +*.js +*.d.ts diff --git a/securityhub-finding-sfn-remediation-cdk/cdk/bin/app.ts b/securityhub-finding-sfn-remediation-cdk/cdk/bin/app.ts new file mode 100644 index 0000000000..3962e99c2d --- /dev/null +++ b/securityhub-finding-sfn-remediation-cdk/cdk/bin/app.ts @@ -0,0 +1,11 @@ +#!/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 { SecurityhubFindingSfnRemediationStack } from '../lib/securityhub-finding-sfn-remediation-stack'; + +const app = new cdk.App(); +new SecurityhubFindingSfnRemediationStack(app, 'SecurityhubFindingSfnRemediationStack', { + description: 'AWS Security Hub auto-remediation with AWS Step Functions (uksb-1tupboc57)', +}); diff --git a/securityhub-finding-sfn-remediation-cdk/cdk/cdk.json b/securityhub-finding-sfn-remediation-cdk/cdk/cdk.json new file mode 100644 index 0000000000..bebf368387 --- /dev/null +++ b/securityhub-finding-sfn-remediation-cdk/cdk/cdk.json @@ -0,0 +1 @@ +{"app":"npx ts-node --prefer-ts-exts bin/app.ts"} diff --git a/securityhub-finding-sfn-remediation-cdk/cdk/lib/securityhub-finding-sfn-remediation-stack.ts b/securityhub-finding-sfn-remediation-cdk/cdk/lib/securityhub-finding-sfn-remediation-stack.ts new file mode 100644 index 0000000000..5704c697e7 --- /dev/null +++ b/securityhub-finding-sfn-remediation-cdk/cdk/lib/securityhub-finding-sfn-remediation-stack.ts @@ -0,0 +1,186 @@ +// 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 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 SecurityhubFindingSfnRemediationStack 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 SNS Topic (security team notifications) + // ========================================================= + const alertTopic = new sns.Topic(this, 'SecurityAlertTopic', { + topicName: 'securityhub-remediation-alerts', + displayName: 'AWS Security Hub - Auto-Remediation Alerts', + }); + + // ========================================================= + // 2. AWS Lambda: Remediation Handler + // ========================================================= + const remediateFn = new lambda.Function(this, 'RemediateFunction', { + runtime: lambda.Runtime.PYTHON_3_12, + handler: 'handler.lambda_handler', + code: lambda.Code.fromAsset(path.join(__dirname, '../../lambdas/remediate')), + timeout: cdk.Duration.seconds(120), + memorySize: 256, + description: 'Remediates AWS Security Hub findings: closes open SGs, enables encryption, enforces MFA', + }); + + // EC2/S3/IAM permissions for remediation actions + remediateFn.addToRolePolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + 'ec2:RevokeSecurityGroupIngress', + 'ec2:DescribeSecurityGroups', + 'ec2:CreateTags', + ], + resources: [`arn:aws:ec2:${region}:${account}:security-group/*`], + })); + + remediateFn.addToRolePolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: [ + 's3:PutBucketPublicAccessBlock', + 's3:PutBucketEncryption', + ], + resources: [`arn:aws:s3:::*`], + conditions: { + StringEquals: { 'aws:ResourceAccount': account }, + }, + })); + + remediateFn.addToRolePolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['securityhub:BatchUpdateFindings'], + resources: [`arn:aws:securityhub:${region}:${account}:hub/default`], + })); + + // ========================================================= + // 3. AWS Step Functions: Remediation Workflow + // ========================================================= + + // Remediate via Lambda + const remediateTask = new sfnTasks.LambdaInvoke(this, 'RemediateFinding', { + lambdaFunction: remediateFn, + outputPath: '$.Payload', + comment: 'Execute auto-remediation for the finding type', + }); + + // Notify security team + const notifyTask = new sfnTasks.SnsPublish(this, 'NotifySecurityTeam', { + topic: alertTopic, + subject: sfn.JsonPath.format( + 'SecurityHub: {} remediated ({})', + sfn.JsonPath.stringAt('$.remediationType'), + sfn.JsonPath.stringAt('$.status'), + ), + message: sfn.TaskInput.fromJsonPathAt('$'), + resultPath: '$.notification', + }); + + // Skip state for unsupported findings + const skipUnsupported = new sfn.Pass(this, 'UnsupportedFinding', { + result: sfn.Result.fromObject({ status: 'SKIPPED', reason: 'Finding type not supported for auto-remediation' }), + }); + + // Done + const done = new sfn.Pass(this, 'RemediationComplete', { + result: sfn.Result.fromObject({ status: 'COMPLETE' }), + }); + + // Route by finding type + const definition = new sfn.Choice(this, 'ClassifyFinding') + .when( + sfn.Condition.or( + sfn.Condition.stringMatches('$.detail.findings[0].Type', '*EC2*SecurityGroup*'), + sfn.Condition.stringMatches('$.detail.findings[0].Type', '*Software and Configuration*'), + ), + remediateTask.next(notifyTask).next(done), + ) + .when( + sfn.Condition.stringMatches('$.detail.findings[0].Type', '*S3*PublicAccess*'), + new sfnTasks.LambdaInvoke(this, 'RemediateS3Finding', { + lambdaFunction: remediateFn, + outputPath: '$.Payload', + }).next(new sfnTasks.SnsPublish(this, 'NotifyS3Remediation', { + topic: alertTopic, + subject: sfn.JsonPath.format( + 'SecurityHub: {} remediated ({})', + sfn.JsonPath.stringAt('$.remediationType'), + sfn.JsonPath.stringAt('$.status'), + ), + message: sfn.TaskInput.fromJsonPathAt('$'), + resultPath: '$.notification', + })).next(new sfn.Pass(this, 'S3Done', { + result: sfn.Result.fromObject({ status: 'COMPLETE' }), + })), + ) + .otherwise(skipUnsupported); + + const stateMachine = new sfn.StateMachine(this, 'RemediationWorkflow', { + definitionBody: sfn.DefinitionBody.fromChainable(definition), + timeout: cdk.Duration.minutes(10), + tracingEnabled: true, + logs: { + destination: new logs.LogGroup(this, 'SfnLogGroup', { + logGroupName: '/aws/stepfunctions/securityhub-remediation', + retention: logs.RetentionDays.TWO_WEEKS, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }), + level: sfn.LogLevel.ALL, + }, + }); + + // ========================================================= + // 4. Amazon EventBridge Rule: Security Hub findings + // ========================================================= + new events.Rule(this, 'SecurityHubFindingRule', { + ruleName: 'securityhub-high-findings-to-sfn', + description: 'Routes HIGH/CRITICAL AWS Security Hub findings to AWS Step Functions for auto-remediation', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], + detail: { + findings: { + Severity: { Label: ['HIGH', 'CRITICAL'] }, + Workflow: { Status: ['NEW'] }, + RecordState: ['ACTIVE'], + }, + }, + }, + targets: [new targets.SfnStateMachine(stateMachine)], + }); + + // ========================================================= + // Outputs + // ========================================================= + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'AWS Step Functions remediation workflow ARN', + }); + + new cdk.CfnOutput(this, 'AlertTopicArn', { + value: alertTopic.topicArn, + description: 'Amazon SNS security alert topic ARN', + }); + + new cdk.CfnOutput(this, 'RemediateFunctionArn', { + value: remediateFn.functionArn, + description: 'AWS Lambda remediation function ARN', + }); + } +} diff --git a/securityhub-finding-sfn-remediation-cdk/cdk/package.json b/securityhub-finding-sfn-remediation-cdk/cdk/package.json new file mode 100644 index 0000000000..03dcbe95a0 --- /dev/null +++ b/securityhub-finding-sfn-remediation-cdk/cdk/package.json @@ -0,0 +1,12 @@ +{ + "name": "securityhub-finding-sfn-remediation-cdk", + "version": "1.0.0", + "bin": { "app": "bin/app.ts" }, + "scripts": { "build": "tsc", "synth": "cdk synth" }, + "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/securityhub-finding-sfn-remediation-cdk/cdk/tsconfig.json b/securityhub-finding-sfn-remediation-cdk/cdk/tsconfig.json new file mode 100644 index 0000000000..2f639624c7 --- /dev/null +++ b/securityhub-finding-sfn-remediation-cdk/cdk/tsconfig.json @@ -0,0 +1 @@ +{"compilerOptions":{"target":"ES2020","module":"commonjs","lib":["es2020"],"declaration":true,"strict":true,"noImplicitAny":true,"strictNullChecks":true,"noImplicitReturns":true,"inlineSourceMap":true,"inlineSources":true,"experimentalDecorators":true,"strictPropertyInitialization":false,"outDir":"./build","rootDir":"."},"exclude":["node_modules","build"]} diff --git a/securityhub-finding-sfn-remediation-cdk/example-pattern.json b/securityhub-finding-sfn-remediation-cdk/example-pattern.json new file mode 100644 index 0000000000..4106f6cbbf --- /dev/null +++ b/securityhub-finding-sfn-remediation-cdk/example-pattern.json @@ -0,0 +1,71 @@ +{ + "title": "AWS Security Hub Auto-Remediation with AWS Step Functions", + "description": "Auto-remediate HIGH/CRITICAL Security Hub findings using AWS Step Functions and AWS Lambda to close open security groups and block public S3 access", + "language": "TypeScript", + "level": "300", + "framework": "AWS CDK", + "introBox": { + "headline": "How it works", + "text": [ + "AWS Security Hub detects HIGH or CRITICAL findings such as open security groups or public Amazon S3 buckets.", + "Amazon EventBridge captures the finding and triggers an AWS Step Functions workflow that classifies the finding type.", + "AWS Lambda executes targeted remediation (revoke open ingress rules, enable S3 public access block) and Amazon SNS notifies the security team." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/securityhub-finding-sfn-remediation-cdk", + "templateURL": "serverless-patterns/securityhub-finding-sfn-remediation-cdk", + "projectFolder": "securityhub-finding-sfn-remediation-cdk", + "templateFile": "cdk/lib/securityhub-finding-sfn-remediation-stack.ts" + } + }, + "resources": { + "bullets": [ + { + "text": "AWS Security Hub automated response and remediation", + "link": "https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cloudwatch-events.html" + }, + { + "text": "AWS Step Functions service integrations", + "link": "https://docs.aws.amazon.com/step-functions/latest/dg/connect-supported-services.html" + } + ] + }, + "deploy": { + "text": [ + "cd securityhub-finding-sfn-remediation-cdk/cdk", + "npm install", + "npx cdk deploy" + ] + }, + "testing": { + "text": [ + "Create an intentionally open security group (0.0.0.0/0 on port 22).", + "Wait for AWS Security Hub to detect the finding (~15 minutes).", + "Verify the AWS Step Functions execution completed and the security group was closed.", + "Subscribe to the Amazon SNS topic to receive remediation 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": "securityhub", "label": "AWS Security Hub" }, + "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 / Amazon S3" } + }, + "patternType": "Serverless" +} diff --git a/securityhub-finding-sfn-remediation-cdk/lambdas/remediate/handler.py b/securityhub-finding-sfn-remediation-cdk/lambdas/remediate/handler.py new file mode 100644 index 0000000000..be13d33ba0 --- /dev/null +++ b/securityhub-finding-sfn-remediation-cdk/lambdas/remediate/handler.py @@ -0,0 +1,151 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 (2026) +"""SecurityHub Auto-Remediation: Triggered by AWS Step Functions when +AWS Security Hub reports HIGH/CRITICAL findings. Remediates common +misconfigurations: open security groups, public S3 buckets, missing encryption.""" + +import json +from datetime import datetime, timezone + +import boto3 + +EC2_CLIENT = boto3.client('ec2') +S3_CLIENT = boto3.client('s3') +SECURITYHUB_CLIENT = boto3.client('securityhub') + + +def lambda_handler(event, context): + """Remediate AWS Security Hub finding based on type.""" + detail = event.get('detail', {}) + findings = detail.get('findings', []) + + if not findings: + return {'status': 'SKIPPED', 'reason': 'No findings in event'} + + finding = findings[0] + finding_type = finding.get('Type', '') + finding_id = finding.get('Id', '') + resources = finding.get('Resources', []) + severity = finding.get('Severity', {}).get('Label', 'UNKNOWN') + + print(f'Remediating: {finding_type} | Severity: {severity} | ID: {finding_id}') + + try: + if 'SecurityGroup' in finding_type or 'EC2' in finding_type: + result = remediate_security_group(resources) + elif 'S3' in finding_type and 'Public' in finding_type: + result = remediate_s3_public_access(resources) + else: + result = {'action': 'NONE', 'reason': f'No handler for type: {finding_type}'} + + # Update finding workflow status to RESOLVED + update_finding_status(finding_id, finding.get('ProductArn', '')) + + return { + 'status': 'REMEDIATED', + 'remediationType': finding_type, + 'findingId': finding_id, + 'severity': severity, + 'action': result.get('action', 'UNKNOWN'), + 'detail': result, + 'remediatedAt': datetime.now(timezone.utc).isoformat(), + } + + except Exception as e: + print(f'Remediation failed: {e}') + return { + 'status': 'FAILED', + 'remediationType': finding_type, + 'findingId': finding_id, + 'error': str(e), + } + + +def remediate_security_group(resources: list) -> dict: + """Close open ingress rules (0.0.0.0/0) on security groups.""" + for resource in resources: + resource_id = resource.get('Id', '') + # Extract SG ID from ARN or direct ID + sg_id = resource_id.split('/')[-1] if '/' in resource_id else resource_id + + if not sg_id.startswith('sg-'): + continue + + try: + sg = EC2_CLIENT.describe_security_groups(GroupIds=[sg_id]) + for permission in sg['SecurityGroups'][0].get('IpPermissions', []): + open_ranges = [ + r for r in permission.get('IpRanges', []) + if r.get('CidrIp') == '0.0.0.0/0' + ] + if open_ranges: + EC2_CLIENT.revoke_security_group_ingress( + GroupId=sg_id, + IpPermissions=[{ + 'IpProtocol': permission['IpProtocol'], + 'FromPort': permission.get('FromPort', -1), + 'ToPort': permission.get('ToPort', -1), + 'IpRanges': open_ranges, + }], + ) + print(f'Revoked 0.0.0.0/0 on {sg_id} port {permission.get("FromPort")}') + + EC2_CLIENT.create_tags( + Resources=[sg_id], + Tags=[ + {'Key': 'SecurityHub:Remediated', 'Value': 'true'}, + {'Key': 'SecurityHub:RemediatedAt', 'Value': datetime.now(timezone.utc).isoformat()}, + ], + ) + + return {'action': 'REVOKED_OPEN_INGRESS', 'securityGroupId': sg_id} + + except Exception as e: + print(f'Failed to remediate SG {sg_id}: {e}') + raise + + return {'action': 'NO_SG_FOUND'} + + +def remediate_s3_public_access(resources: list) -> dict: + """Enable public access block on S3 buckets.""" + for resource in resources: + resource_id = resource.get('Id', '') + bucket_name = resource_id.split(':::')[-1] if ':::' in resource_id else resource_id + + try: + S3_CLIENT.put_public_access_block( + Bucket=bucket_name, + PublicAccessBlockConfiguration={ + 'BlockPublicAcls': True, + 'IgnorePublicAcls': True, + 'BlockPublicPolicy': True, + 'RestrictPublicBuckets': True, + }, + ) + print(f'Enabled public access block on {bucket_name}') + return {'action': 'ENABLED_PUBLIC_ACCESS_BLOCK', 'bucket': bucket_name} + + except Exception as e: + print(f'Failed to remediate S3 bucket {bucket_name}: {e}') + raise + + return {'action': 'NO_BUCKET_FOUND'} + + +def update_finding_status(finding_id: str, product_arn: str) -> None: + """Mark finding as RESOLVED in AWS Security Hub.""" + try: + SECURITYHUB_CLIENT.batch_update_findings( + FindingIdentifiers=[{ + 'Id': finding_id, + 'ProductArn': product_arn, + }], + Workflow={'Status': 'RESOLVED'}, + Note={ + 'Text': 'Auto-remediated by serverless pattern', + 'UpdatedBy': 'securityhub-finding-sfn-remediation', + }, + ) + except Exception as e: + print(f'Failed to update finding status: {e}')