| """ |
| email_gatekeeper_stack.py β AWS CDK Stack for the Email Gatekeeper. |
| |
| Resources created: |
| - S3 bucket : receives raw .eml files from SES |
| - Lambda function : classifies each email using the rule-based engine |
| - DynamoDB table : stores every triage result (email_id as partition key) |
| - SNS topic : fires an alert whenever a Security Breach is detected |
| - SES receipt rule : routes inbound email β S3 bucket (requires verified domain) |
| - IAM roles/policies : least-privilege access for Lambda β S3, DynamoDB, SNS |
| |
| Deploy: |
| cd cdk |
| pip install aws-cdk-lib constructs |
| cdk bootstrap # first time only per account/region |
| cdk deploy |
| """ |
|
|
| import aws_cdk as cdk |
| from aws_cdk import ( |
| Stack, |
| Duration, |
| RemovalPolicy, |
| aws_s3 as s3, |
| aws_lambda as lambda_, |
| aws_dynamodb as dynamodb, |
| aws_sns as sns, |
| aws_sns_subscriptions as sns_subs, |
| aws_s3_notifications as s3n, |
| aws_ses as ses, |
| aws_ses_actions as ses_actions, |
| aws_iam as iam, |
| ) |
| from constructs import Construct |
|
|
|
|
| class EmailGatekeeperStack(Stack): |
| def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: |
| super().__init__(scope, construct_id, **kwargs) |
|
|
| |
| email_bucket = s3.Bucket( |
| self, "EmailBucket", |
| bucket_name=f"email-gatekeeper-inbox-{self.account}", |
| |
| block_public_access=s3.BlockPublicAccess.BLOCK_ALL, |
| encryption=s3.BucketEncryption.S3_MANAGED, |
| |
| lifecycle_rules=[ |
| s3.LifecycleRule(expiration=Duration.days(30)) |
| ], |
| removal_policy=RemovalPolicy.RETAIN, |
| ) |
|
|
| |
| results_table = dynamodb.Table( |
| self, "EmailResultsTable", |
| table_name="EmailTriageResults", |
| partition_key=dynamodb.Attribute( |
| name="email_id", |
| type=dynamodb.AttributeType.STRING, |
| ), |
| billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST, |
| removal_policy=RemovalPolicy.RETAIN, |
| ) |
|
|
| |
| security_topic = sns.Topic( |
| self, "SecurityAlertTopic", |
| topic_name="EmailGatekeeperSecurityAlerts", |
| display_name="Email Gatekeeper β Security Breach Alerts", |
| ) |
|
|
| |
| security_topic.add_subscription( |
| sns_subs.EmailSubscription("security-team@your-domain.com") |
| ) |
|
|
| |
| classifier_fn = lambda_.Function( |
| self, "EmailClassifierFn", |
| function_name="EmailGatekeeperClassifier", |
| runtime=lambda_.Runtime.PYTHON_3_12, |
| |
| code=lambda_.Code.from_asset("../lambda"), |
| handler="handler.lambda_handler", |
| timeout=Duration.seconds(30), |
| memory_size=256, |
| environment={ |
| "EMAIL_RESULTS_TABLE": results_table.table_name, |
| "SECURITY_ALERT_TOPIC_ARN": security_topic.topic_arn, |
| }, |
| ) |
|
|
| |
| email_bucket.grant_read(classifier_fn) |
| results_table.grant_write_data(classifier_fn) |
| security_topic.grant_publish(classifier_fn) |
|
|
| |
| |
| email_bucket.add_event_notification( |
| s3.EventType.OBJECT_CREATED, |
| s3n.LambdaDestination(classifier_fn), |
| ) |
|
|
| |
| |
| |
| rule_set = ses.ReceiptRuleSet( |
| self, "EmailRuleSet", |
| rule_set_name="EmailGatekeeperRuleSet", |
| ) |
|
|
| rule_set.add_rule( |
| "StoreInS3Rule", |
| recipients=["inbox@mail.your-domain.com"], |
| actions=[ |
| ses_actions.S3( |
| bucket=email_bucket, |
| object_key_prefix="incoming/", |
| ) |
| ], |
| scan_enabled=True, |
| ) |
|
|
| |
| email_bucket.add_to_resource_policy( |
| iam.PolicyStatement( |
| sid="AllowSESPuts", |
| principals=[iam.ServicePrincipal("ses.amazonaws.com")], |
| actions=["s3:PutObject"], |
| resources=[email_bucket.arn_for_objects("incoming/*")], |
| conditions={ |
| "StringEquals": {"aws:SourceAccount": self.account} |
| }, |
| ) |
| ) |
|
|
| |
| cdk.CfnOutput(self, "BucketName", value=email_bucket.bucket_name) |
| cdk.CfnOutput(self, "TableName", value=results_table.table_name) |
| cdk.CfnOutput(self, "LambdaArn", value=classifier_fn.function_arn) |
| cdk.CfnOutput(self, "SecurityTopicArn",value=security_topic.topic_arn) |
|
|