code / cdk /email_gatekeeper_stack.py
24122168-collab
Add application file
6ba100e
"""
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)
# ── 1. S3 bucket β€” stores raw .eml files delivered by SES ─────────────
email_bucket = s3.Bucket(
self, "EmailBucket",
bucket_name=f"email-gatekeeper-inbox-{self.account}",
# Block all public access β€” emails are private
block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
encryption=s3.BucketEncryption.S3_MANAGED,
# Auto-delete raw emails after 30 days to control storage costs
lifecycle_rules=[
s3.LifecycleRule(expiration=Duration.days(30))
],
removal_policy=RemovalPolicy.RETAIN, # keep emails if stack is deleted
)
# ── 2. DynamoDB table β€” persists every triage decision ─────────────────
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, # serverless billing
removal_policy=RemovalPolicy.RETAIN,
)
# ── 3. SNS topic β€” security breach alerts ─────────────────────────────
security_topic = sns.Topic(
self, "SecurityAlertTopic",
topic_name="EmailGatekeeperSecurityAlerts",
display_name="Email Gatekeeper β€” Security Breach Alerts",
)
# Add your alert email here β€” replace with a real address
security_topic.add_subscription(
sns_subs.EmailSubscription("security-team@your-domain.com")
)
# ── 4. Lambda function ─────────────────────────────────────────────────
classifier_fn = lambda_.Function(
self, "EmailClassifierFn",
function_name="EmailGatekeeperClassifier",
runtime=lambda_.Runtime.PYTHON_3_12,
# Points to the ../lambda/ directory β€” CDK zips it automatically
code=lambda_.Code.from_asset("../lambda"),
handler="handler.lambda_handler",
timeout=Duration.seconds(30),
memory_size=256, # classifier is CPU-light
environment={
"EMAIL_RESULTS_TABLE": results_table.table_name,
"SECURITY_ALERT_TOPIC_ARN": security_topic.topic_arn,
},
)
# Grant Lambda least-privilege access to each resource
email_bucket.grant_read(classifier_fn)
results_table.grant_write_data(classifier_fn)
security_topic.grant_publish(classifier_fn)
# ── 5. S3 β†’ Lambda trigger ─────────────────────────────────────────────
# Fires whenever SES drops a new .eml into the bucket
email_bucket.add_event_notification(
s3.EventType.OBJECT_CREATED,
s3n.LambdaDestination(classifier_fn),
)
# ── 6. SES receipt rule β€” routes inbound email to S3 ──────────────────
# IMPORTANT: your domain must be verified in SES before this works.
# Replace "mail.your-domain.com" with your actual verified domain.
rule_set = ses.ReceiptRuleSet(
self, "EmailRuleSet",
rule_set_name="EmailGatekeeperRuleSet",
)
rule_set.add_rule(
"StoreInS3Rule",
recipients=["inbox@mail.your-domain.com"], # ← replace with your address
actions=[
ses_actions.S3(
bucket=email_bucket,
object_key_prefix="incoming/", # all emails land under incoming/
)
],
scan_enabled=True, # enable SES spam/virus scanning
)
# ── 7. Allow SES to write to the S3 bucket ────────────────────────────
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}
},
)
)
# ── 8. CloudFormation outputs β€” useful after deploy ────────────────────
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)