Photo by Steve Johnson on Unsplash
AWS Lambda Function: IAM User Password Expiry Notice | SES, Boto3 & Terraform
Overview
In this implementation, you'll be guided through the necessary steps to set up an AWS Lambda function to email notifications to IAM Users when their AWS Web Console passwords are expiring. The function is written in Python (boto3) and integrated with AWS SES using a verified domain. Terraform code samples are provided for all of the infrastructure configuration steps.
The purpose of this initiative is to provide proactive security measures while streamlining administrative tasks by sending an advanced notice to AWS IAM users that their passwords are expiring. By promptly alerting users before their passwords reach the expiration threshold defined in the security policy, you can facilitate regular password resets, ensuring stronger security measures are maintained. This approach reduces the administrative burden on administrators, as users can self-reset their passwords in advance of expiration. Additionally, it empowers users to take ownership of their account security, promoting a culture of proactive password management.
With this implementation, you will enhance the overall security, efficiency, and user experience within your AWS IAM environment.
Pre-requisites
Domain ownership and access to manage DNS records
- Domain
jennasrunbooks.com
will be used as an example in the below sample code output. Update this with your appropriate domain.
- Domain
Terraform installed
Install Python, boto3 and other script modules as needed to review the code locally without errors
Clone repo containing Lambda Python script
AWS IAM permissions to deploy Terraform resources
Email tags assigned to each AWS IAM User, sample Terraform code from a terraform.tfvars file:
user_names = { "user1" = { "name" = "user1", "tag" = { email = "user1@jennasrunbooks.com", role = "engineering" } } }
Procedure
Set up and verify the email domain
Register and verify your email domain in the Amazon SES console to establish your email sender identity.
If you'd like to use a custom MAIL FROM domain, check out this AWS doc and note the following:
Important
To successfully set up a custom MAIL FROM domain with Amazon SES, you must publish exactly one MX record to the DNS server of your MAIL FROM domain. If the MAIL FROM domain has multiple MX records, the custom MAIL FROM setup with Amazon SES will fail.Sample Terraform code to deploy an AWS SES domain identity using Easy DKIM settings and a custom MAIL FROM domain:
# Update the domain variable to your domain # using the SES service which is integrated into other services ie Lambda variable "domain" { type = string default = "jennasrunbooks.com" } resource "aws_ses_domain_identity" "ses_domain" { domain = var.domain } resource "aws_ses_domain_mail_from" "main" { domain = aws_ses_domain_identity.ses_domain.domain mail_from_domain = "mail.${var.domain}" } resource "aws_ses_domain_identity_verification" "email_identity_verification" { domain = aws_ses_domain_identity.ses_domain.domain }
The initial terraform apply will likely timeout on the
aws_ses_domain_identity_verification
resource depending on how quickly your DNS provider can propagate the changes to verify the domain identity with AWS. You can simply rerun an apply to update the state once the domain has been verified.Update your DNS provider records for your domain with the DKIM CNAME and MAIL FROM MX and SPF records provided by AWS as shown in this example after running
terraform apply
:You have the option to download a csv of each record set which can be useful if another team or 3rd party manages the records for your domain.
DNS propagation can take anywhere from a few minutes to several hours. If entered correctly, the SES page for the domain verification will display the identity status as Verified. Once verified, you'll also receive an email from AWS for each configuration type you've configured.
Write the Lambda function Python script
Link to the script: https://github.com/jksprattler/aws-security/blob/main/lambda/password_notification/password_notification.py
Only IAM users with valid
email
tags using the verified domain in their address will be notified.Update the SES email body contents with an appropriate message for your audience.
Include a direct link to the account's AWS console sign-in page. If you have multiple accounts used in your infrastructure, it can also be helpful to specify to your clients which account the password reset is for by providing both the friendly name and account# in the message body.
Update the Source email account in the
ses_client.send_email
method as appropriate to your cloud admins team Distribution List.
Configure the Lambda IAM policy and role
Create an IAM policy that includes the necessary permissions for the Lambda function to generate and get the credential report, send emails using SES, and access user tags (we specifically need the email tags for each user).
Give the policy permissions to create the log group, and log streams and push log events to the streams. This will be used by CloudWatch when it's triggered on the scheduled event.
Create an IAM role and attach the IAM policy to it. This role will be assumed by the Lambda function to access the required AWS services.
Sample Terraform code to create the IAM policy, role and policy attachment needed for the lambda function:
# Update Resource ARN references to region, accountID and aws lambda log-group name as needed for your environment resource "aws_iam_policy" "lambda_password_notification_policy" { name = "lambda_password_notification_policy" policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "iam:GenerateCredentialReport", "ses:SendEmail", "ses:SendRawEmail", "iam:GetCredentialReport", "iam:ListUserTags" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "logs:CreateLogGroup" ], "Resource": "arn:aws:logs:region:accountID:*" } ] }, { "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:region:accountID:log-group:/aws/lambda/password_notification:*" } ] } EOF tags = var.common_tags } resource "aws_iam_role" "lambda_password_notification_role" { name = "lambda_password_notification_role" assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF } resource "aws_iam_role_policy_attachment" "lambda_policy_attachment" { role = aws_iam_role.lambda_password_notification_role.name policy_arn = aws_iam_policy.lambda_password_notification_policy.arn }
Setup the Lambda function
Create the new Lambda function with Python (boto3) to generate SES email notifications of expired passwords by generating and parsing the IAM credentials report using Terraform to deploy the resources.
Python 3.8 will be used for the runtime
The handler will use the name of the function, ie:
password_notification.lambda_handler
The previously created IAM role will be assigned to the Lambda function
The Python script will be zipped using the Terraform
archive_file
data sourceThe
source_code_hash
argument will be applied to theaws_lambda_function
resource block to trigger updates in the terraform state when changes are made to the Python script.Sample Terraform code:
# Update below file paths according to your directory architecture data "aws_lambda_function" "password_notification" { function_name = aws_lambda_function.password_notification.function_name } data "archive_file" "password_notification_zip" { type = "zip" source_dir = "src_dir/scripts/aws/lambda/password_notification/" output_path = "dest_dir/scripts/aws/lambda/password_notification/password_notification.zip" } resource "aws_lambda_function" "password_notification" { filename = "dest_dir/scripts/aws/lambda/password_notification/password_notification.zip" source_code_hash = data.archive_file.password_notification_zip.output_base64sha256 function_name = "password_notification" description = "lambda function to send email notifications (SES) to users when passwords are expiring" role = aws_iam_role.lambda_password_notification_role.arn handler = "password_notification.lambda_handler" runtime = "python3.8" timeout = 180 memory_size = 128 depends_on = [ aws_iam_role_policy_attachment.lambda_policy_attachment, aws_cloudwatch_log_group.password_notification_log_group, ] }
Test the Lambda function
Test the Lambda function manually by triggering it with sample input data to ensure it sends the correct email notifications.
Use the existing lambda function deployed previously or create a new one using the same settings, IAM role, etc for this test.
Use the Lambda console to manually test the function.
Example updated Lambda Python to force a test email notification using custom JSON event values:
""" Jenna Sprattler | SRE Kentik | 2023-05-21 Test lambda function to send email notifications (SES) to specified test user that passwords are expiring """ import time from datetime import datetime from dateutil import parser import boto3 iam_client = boto3.client('iam') ses_client = boto3.client('ses') def lambda_handler(event, context): username = event['username'] password_last_changed = event['password_last_changed'] email = event['email'] if password_last_changed not in ('N/A', 'not_supported'): # Parse the date and time components from the timestamp password_last_changed_date = parser.parse(password_last_changed) days_since_password_change = ( datetime.now() - password_last_changed_date.replace(tzinfo=None)).days if days_since_password_change > 78: message = f''' <html> <body> <p>Hello {username},</p> <p>Your password to access the <a href="https://signin.aws.amazon.com/console">AWS web console</a> has expired or will be expiring within the next 12 days.</p> <p>If your password is still valid, please log into the web console and follow the banner instructions to reset your password now.</p> <p>If you have API access keys configured, you can use the Password Reset Self Service <a href="https://github.com/jksprattler/aws-security/blob/main/scripts/aws_iam_self_service_password_reset.py">script</a>.</p> <p>If you don't use API keys, and your password has passed the expiration date then reply to this email and we'll assist with your password reset.</p> <p>If you don't use your AWS account, reply to this email and we'll remove your account.</p> <br> <p>Thank you,</p> <p>Cloud Admins</p> </body> </html> ''' ses_client.send_email( Source='cloud-admins@jennasrunbooks.com', Destination={'ToAddresses': [email]}, Message={ 'Subject': {'Data': 'AWS Password Expiry Notification'}, 'Body': {'Html': {'Data': message}} } ) return 'Password expiry notifications sent to: ' + username
Add appropriate JSON test event values to your variables and generate a test SES email notification, for example:
{ "username": "user1", "password_last_changed": "2023-02-10T00:00:00Z", "email": "user1@jennasrunbooks.com" }
Validate that the test email was received by the test user's email account. Review the message subject and body contents for accuracy. Sample test email to my AWS IAM user1:
Feel free to use the Password Reset Self-Service script referenced in the above email body.
For users with passwords passed the expiration date, check out my Administrative user password reset script.
Setup the CloudWatch event rule
Create the CloudWatch Event Rule to trigger the desired schedule for automating the password expiration notification Lambda function which contains the SES
send_email
operation to your users with expiring passwords.Set the target of the CloudWatch Event Rule to the Lambda function you created.
(Optional) Create the log group for the CloudWatch event log streams. This is created automatically whenever a new Lambda function is deployed however, if/when you destroy your Lambda function in the future, Terraform will not automatically delete the log group that was created. If the log group resource is managed by the Terraform state then you will know to delete that resource and have cleaner infrastructure should the Lambda function be removed in the future.
There is a hidden Terraform resource called
aws_lambda_permission
that needs to be created. This is created automatically when a Lambda function is created using the AWS console. This allows the CloudWatch events permissions to access the Lambda function. This is needed for the CloudWatch event rule (cron scheduler), log streams, etc to work properly with the function.For testing purposes, you could update the
schedule_expression
of the event rule to run every 10 min while confirming that the Lambda function and Cloudwatch settings are working properly then update to a daily interval:rate(10 minutes)
Sample Terraform code:
resource "aws_lambda_permission" "allow_cloudwatch_for_password_notification" { statement_id = "AllowExecutionFromCloudWatch" action = "lambda:InvokeFunction" function_name = aws_lambda_function.password_notification.function_name principal = "events.amazonaws.com" source_arn = aws_cloudwatch_event_rule.password_notification_schedule.arn } resource "aws_cloudwatch_log_group" "password_notification_log_group" { name = "/aws/lambda/password_notification" retention_in_days = 7 } resource "aws_cloudwatch_event_rule" "password_notification_schedule" { name = "password_notification_schedule" description = "Scheduled rule for password notification" schedule_expression = "cron(0 13 * * ? *)" # Schedule for 8am CST / 1pm UTC daily (adjust as needed) } resource "aws_cloudwatch_event_target" "password_notification_target" { rule = aws_cloudwatch_event_rule.password_notification_schedule.name arn = data.aws_lambda_function.password_notification.arn }
Validate functionality
Monitor the CloudWatch logs in the designated Lambda function's log group and check for any errors or unexpected behavior. Example of a successful run:
Verify that the Lambda function is generating the credential report and sending the email notifications to the intended recipients.
You should see an IAM credentials report created shortly after the Lambda function's scheduled event, for example:
You can download this report and sort by the
password_last_changed
column to capture a list of users that should have received the SES notification. Reach out to these users to confirm whether or not they received the email notification.
Conclusion
This implementation provides a comprehensive solution for notifying AWS IAM users of their upcoming password expirations for accessing the AWS web console. With this proactive approach to security, password hygiene is prioritized while also helping reduce the risk of compromised credentials.
Empowering users to self-reset their passwords before expiration not only enhances account security but also streamlines administrative tasks. This implementation showcases the value of leveraging automation and user-centric design to create a more secure and efficient AWS IAM environment. With this solution, organizations can foster a culture of proactive password management.
Resources
Domain Identities
Verified identities in Amazon SES - Amazon Simple Email Service: https://docs.aws.amazon.com/ses/latest/dg/verify-addresses-and-domains.html
Using a custom MAIL FROM domain - Amazon Simple Email Service: https://docs.aws.amazon.com/ses/latest/dg/mail-from.html
Lambda Function
aws_lambda_function | Resources | hashicorp/aws | Terraform Registry: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function#iam-role
Lambda execution role - AWS Lambda: https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html
Getting started with Lambda - AWS Lambda: https://docs.aws.amazon.com/lambda/latest/dg/getting-started.html
Building Lambda functions with Python - AWS Lambda: https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html
Testing Lambda functions in the console - AWS Lambda: https://docs.aws.amazon.com/lambda/latest/dg/testing-functions.html
Monitoring and troubleshooting Lambda functions - AWS Lambda: https://docs.aws.amazon.com/lambda/latest/dg/lambda-monitoring.html
CloudWatch Events
aws_cloudwatch_event_rule | Resources | hashicorp/aws | Terraform Registry: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_rule
Schedule Expressions for Rules - Amazon CloudWatch Events: https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html
Boto3
generate_credential_report - Boto3 1.26.137 documentation: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/iam/client/generate_credential_report.html
get_credential_report - Boto3 1.26.137 documentation: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/iam/client/get_credential_report.html
list_user_tags - Boto3 1.26.137 documentation: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/iam/client/list_user_tags.html
send_email - Boto3 1.26.137 documentation: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ses/client/send_email.html