@@ -88,10 +88,6 @@ resource "aws_iam_user_policy" "smtp" {
8888 })
8989}
9090
91- resource "aws_iam_access_key" "smtp" {
92- user = aws_iam_user. smtp . name
93- }
94-
9591resource "aws_secretsmanager_secret" "smtp" {
9692 name = " ${ local . prefix } -ses-smtp-credentials"
9793 description = " SES SMTP credentials for ${ local . prefix } ."
@@ -101,11 +97,132 @@ resource "aws_secretsmanager_secret" "smtp" {
10197 }
10298}
10399
104- resource "aws_secretsmanager_secret_version" "smtp" {
105- secret_id = aws_secretsmanager_secret. smtp . id
100+ # Credential rotation Lambda and supporting resources.
101+ # Based on the AWS sample at:
102+ # https://github.com/aws-samples/serverless-mail/tree/ses-credential-rotation/ses-credential-rotation
103+
104+ resource "aws_iam_role" "rotation_lambda" {
105+ name = " ${ local . prefix } -ses-smtp-rotation"
106+ path = " /system/"
107+
108+ assume_role_policy = jsonencode ({
109+ Version = " 2012-10-17"
110+ Statement = [
111+ {
112+ Effect = " Allow"
113+ Principal = {
114+ Service = " lambda.amazonaws.com"
115+ }
116+ Action = " sts:AssumeRole"
117+ }
118+ ]
119+ })
120+
121+ tags = {
122+ Name = " ${ local . prefix } -ses-smtp-rotation"
123+ }
124+ }
125+
126+ resource "aws_iam_role_policy" "rotation_lambda" {
127+ name = " ses-smtp-rotation"
128+ role = aws_iam_role. rotation_lambda . id
106129
107- secret_string = jsonencode ({
108- username = aws_iam_access_key.smtp.id
109- password = aws_iam_access_key.smtp.ses_smtp_password_v4
130+ policy = jsonencode ({
131+ Version = " 2012-10-17"
132+ Statement = [
133+ {
134+ Sid = " ManageIamKeys"
135+ Effect = " Allow"
136+ Action = [
137+ " iam:CreateAccessKey" ,
138+ " iam:DeleteAccessKey" ,
139+ " iam:ListAccessKeys" ,
140+ ]
141+ Resource = aws_iam_user.smtp.arn
142+ },
143+ {
144+ Sid = " ManageSecret"
145+ Effect = " Allow"
146+ Action = [
147+ " secretsmanager:DescribeSecret" ,
148+ " secretsmanager:GetSecretValue" ,
149+ " secretsmanager:PutSecretValue" ,
150+ " secretsmanager:UpdateSecretVersionStage" ,
151+ ]
152+ Resource = aws_secretsmanager_secret.smtp.arn
153+ },
154+ {
155+ Sid = " RedeployEcsService"
156+ Effect = " Allow"
157+ Action = " ecs:UpdateService"
158+ Resource = " arn:${ data . aws_partition . current . partition } :ecs:${ data . aws_region . current . name } :${ data . aws_caller_identity . current . account_id } :service/${ var . ecs_cluster_name } /${ var . ecs_service_name } "
159+ },
160+ {
161+ Sid = " WriteLogs"
162+ Effect = " Allow"
163+ Action = [
164+ " logs:CreateLogStream" ,
165+ " logs:PutLogEvents" ,
166+ ]
167+ Resource = " ${ aws_cloudwatch_log_group . rotation_lambda . arn } :*"
168+ },
169+ ]
110170 })
111171}
172+
173+ resource "aws_cloudwatch_log_group" "rotation_lambda" {
174+ name = " /aws/lambda/${ local . prefix } -ses-smtp-rotation"
175+ retention_in_days = 30
176+
177+ tags = {
178+ Name = " ${ local . prefix } -ses-smtp-rotation"
179+ }
180+ }
181+
182+ resource "aws_lambda_function" "rotation" {
183+ function_name = " ${ local . prefix } -ses-smtp-rotation"
184+ description = " Rotates SES SMTP credentials for ${ local . prefix } ."
185+ role = aws_iam_role. rotation_lambda . arn
186+ handler = " rotate_smtp_credentials.handler"
187+ runtime = " python3.12"
188+ timeout = 75
189+ architectures = [" arm64" ]
190+ filename = data. archive_file . rotation_lambda . output_path
191+ source_code_hash = data. archive_file . rotation_lambda . output_base64sha256
192+
193+ environment {
194+ variables = {
195+ IAM_USERNAME = aws_iam_user.smtp.name
196+ SMTP_ENDPOINT = local.smtp_server
197+ ECS_CLUSTER = var.ecs_cluster_name
198+ ECS_SERVICE = var.ecs_service_name
199+ }
200+ }
201+
202+ depends_on = [aws_cloudwatch_log_group . rotation_lambda ]
203+
204+ tags = {
205+ Name = " ${ local . prefix } -ses-smtp-rotation"
206+ }
207+ }
208+
209+ resource "aws_lambda_permission" "secrets_manager" {
210+ statement_id = " AllowSecretsManagerInvocation"
211+ action = " lambda:InvokeFunction"
212+ function_name = aws_lambda_function. rotation . function_name
213+ principal = " secretsmanager.amazonaws.com"
214+ source_arn = aws_secretsmanager_secret. smtp . arn
215+ }
216+
217+ resource "aws_secretsmanager_secret_rotation" "smtp" {
218+ secret_id = aws_secretsmanager_secret. smtp . id
219+ rotation_lambda_arn = aws_lambda_function. rotation . arn
220+
221+ rotation_rules {
222+ automatically_after_days = var. rotation_interval_days
223+ }
224+
225+ # The existing SMTP credentials are still valid — just enable the schedule
226+ # without forcing an immediate rotation on first deploy.
227+ rotate_immediately = false
228+ }
0 commit comments