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 += '| Key | Value |
'
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'| {key} | {value_str} |
'
related_info_table += '
'
# 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