AWSTemplateFormatVersion: '2010-09-09' Description: Stack to automate AWS Health security event reports (access key exposed) with EventBridge, Step Functions, and email notification. Parameters: ReEmailAddress: Type: String Description: Email address to receive security notifications. SendEmailAddress: Type: String Description: Email address to send security notifications (must be verified in SES). Resources: SOALambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: AllowAWSHealthAndSES PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - health:DescribeEvents - health:DescribeEventDetails - health:DescribeAffectedEntities Resource: '*' - Effect: Allow Action: ses:SendEmail Resource: '*' SOALambdaGenerateReport: Type: AWS::Lambda::Function Properties: FunctionName: SOA-Lambda-GenerateSecurityReport Handler: index.lambda_handler Role: !GetAtt SOALambdaExecutionRole.Arn Runtime: python3.12 Environment: Variables: REC_EMAIL_ADDRESS: !Ref ReEmailAddress SEND_EMAIL_ADDRESS: !Ref SendEmailAddress Code: ZipFile: | import boto3 import os import datetime import json from botocore.exceptions import ClientError ses = boto3.client('ses') def lambda_handler(event, context): # Get event details from input directly (no API call to avoid subscription shit) detail = event.get('detail', {}) event_arn = detail.get('eventArn', 'Unknown ARN') event_type_code = detail.get('eventTypeCode', 'Unknown') account_id = event.get('account', 'Unknown') event_time = event.get('time', datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) # Extract description event_descriptions = detail.get('eventDescription', []) reason = 'Access key exposed on internet (no description available)' if event_descriptions: for desc in event_descriptions: if desc.get('language', '') == 'en_US': reason = desc.get('latestDescription', reason) break # Extract affected entities affected_entities = detail.get('affectedEntities', []) exposed_info = [] for entity in affected_entities: entity_value = entity.get('entityValue', 'Unknown') exposed_info.append(entity_value) # IAM user/role/access key ID # Generate table for related info from event dict related_info_table = '' related_info_table += '' for key, value in event.items(): value_str = json.dumps(value, indent=2) if isinstance(value, (dict, list)) else str(value) related_info_table += f'' related_info_table += '
KeyValue
{key}{value_str}
' # Generate HTML report with red/orange warning html = f'''

Cảnh báo bảo mật nghiêm trọng từ AWS

Lý do: {reason}

Account: {account_id}

IAM user/role/access key id bị rò rỉ: {', '.join(exposed_info)}

Thời gian: {event_time}

Các thông tin liên quan:

{related_info_table}
''' try: # Send email sender_email = os.environ['SEND_EMAIL_ADDRESS'] recipient_email = os.environ['REC_EMAIL_ADDRESS'] ses.send_email( Source=sender_email, Destination={'ToAddresses': [recipient_email]}, Message={ 'Subject': {'Data': 'Cảnh báo bảo mật nghiêm trọng từ AWS'}, 'Body': { 'Html': {'Data': html}, 'Text': {'Data': 'Cảnh báo: Access key bị lộ. Kiểm tra ngay!'} } } ) return {'status': 'sent'} except ClientError as e: return {'status': 'error', 'message': str(e)} SOAStepFunctionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: states.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: AllowInvokeLambda PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: lambda:InvokeFunction Resource: !GetAtt SOALambdaGenerateReport.Arn SOAStepFunctionSecurityReport: Type: AWS::StepFunctions::StateMachine Properties: StateMachineName: SOA-StepFunction-SecurityReport RoleArn: !GetAtt SOAStepFunctionRole.Arn DefinitionString: !Sub | { "Comment": "Workflow for AWS Health security event report", "StartAt": "GenerateReport", "States": { "GenerateReport": { "Type": "Task", "Resource": "${SOALambdaGenerateReport.Arn}", "End": true } } } SOAEventBridgeRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: AllowStartExecution PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: states:StartExecution Resource: !Ref SOAStepFunctionSecurityReport SOAEventBridgeRuleSecurityEvent: Type: AWS::Events::Rule Properties: Name: SOA-EventBridge-SecurityEvent EventPattern: source: - aws.health detail-type: - AWS Health Event detail: eventTypeCategory: - issue eventTypeCode: - AWS_RISK_CREDENTIALS_EXPOSED # Filter for access key exposed State: ENABLED Targets: - Arn: !Ref SOAStepFunctionSecurityReport Id: StepFunctionTarget RoleArn: !GetAtt SOAEventBridgeRole.Arn Outputs: LambdaGenerateReportArn: Value: !GetAtt SOALambdaGenerateReport.Arn Description: ARN của Lambda Generate Security Report StepFunctionArn: Value: !Ref SOAStepFunctionSecurityReport Description: ARN của Step Function EventBridgeRuleArn: Value: !GetAtt SOAEventBridgeRuleSecurityEvent.Arn Description: ARN của EventBridge Rule LambdaExecutionRoleArn: Value: !GetAtt SOALambdaExecutionRole.Arn Description: ARN của IAM Role cho Lambda StepFunctionRoleArn: Value: !GetAtt SOAStepFunctionRole.Arn Description: ARN của IAM Role cho Step Function EventBridgeRoleArn: Value: !GetAtt SOAEventBridgeRole.Arn Description: ARN của IAM Role cho EventBridge