AWS IAM Access keys rotation using Lambda function

Syedusmanahmad
6 min readJan 13, 2022

As per AWS security best practice, we should regularly rotate our IAM access keys to improve our AWS accounts security.

In this article I will teach you how we automate the process of IAM access keys rotation according to the access key ages using Lambda function.

Use this link to rotate the keys manually using AWS document https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_RotateAccessKey

AWS IAM (Identity and access management) provides find-grained access control across all of AWS.

AWS Access keys are long term credentials for an IAM user. You can use access keys to sign programmatic requests to the AWS CLI or AWS API (directly or using the AWS SDK).

AWS Lambda is a serverless, event-driven compute service that lets you run code for virtually any type of application or backend service without provisioning or managing servers. You can trigger Lambda from over 200 AWS services and software as a service (SaaS) applications, and only pay for what you use.

Architecture Diagram

IAM Access Keys Rotation Using AWS Lambda Function and Send Notifications Using AWS SES

The diagram shows the following workflow:

1- IAM users will login programmatically using IAM access keys

2- CloudWatch event initiates a Lambda function every 24 hours

3- The Lambda function initiates a Lambda function for each AWS account ID and passes it the metadata for additional processing. It will check all users access key ages and initiate the email to the account’s owner

4- In my case I have setup the three conditions to send emails using AWS SES.

Python script in Lambda Function:

I have written the access keys rotation script in python. This python script will first get the each user information/metadata from AWS IAM. As you know AWS IAM user could be human or could be machines, so I have properly tag each user with the following information:

  • Name
  • UserType (employee or machine)
  • Email (abc@xyz.com)
IAM Access key age
IAM User tags

Lambda function will get this above information from the IAM user tags and if the UserType is “employee” and the key age 70th, 80th or 90th day then it will send the notification email using AWS SES and inactive the access key. If the UserType is “machine” and the key age 70th, 80th or 90th day then it will just send the notification email to the concerned team (like IT, Ops etc.).

Send alerts and inactivate access keys conditions:

First condition if IAM access key age of the user is 70th day then send the alert email “Today is the 70th day of your access keys. Please rotate your access key before it turns 90 days”.

Second condition if IAM access key age of the user is 80th day then send the alert email “Today is the 80th day of your access keys. Please rotate your access key before it turns 90 days”.

Third condition if IAM access key age of the user is 90th day then send the alert email “Today is the 90th day of your access keys and your key has been inactivate. Please generate new key or reach IT support team”.

Lambda function:

import boto3, os, time, datetime, sys, jsonfrom datetime import datefrom botocore.exceptions import ClientErroriam = boto3.client('iam')def lambda_handler(event, context):email_70_list = []email_80_list = []email_90_list = []# print("All IAM user emails that have AccessKeys 70 days or older")unique_user_list = (iam.list_users()['Users'])for userlist in unique_user_list:userKeys = iam.list_access_keys(UserName=userlist['UserName'])for keyValue in userKeys['AccessKeyMetadata']:UserAccessKeyID = keyValue['AccessKeyId']IAMUserName = keyValue['UserName']#print(f"IAMUserName IAM Users:{len(IAMUserName)}: {IAMUserName}")if keyValue['Status'] == 'Active':currentdate = date.today()active_days = currentdate - keyValue['CreateDate'].date()#print ("The active days details are: ", active_days)#print ("datetime details are: ", datetime.timedelta(days=15))# if Access key age is greater then or equal to 70 days, send warningif active_days == datetime.timedelta(days=int(os.environ['days_70'])):userTags = iam.list_user_tags(UserName=keyValue['UserName'])email_tag = list(filter(lambda tag: tag['Key'] == 'email', userTags['Tags']))if(len(email_tag) == 1):email = email_tag[0]['Value']email_70_list.append(email)print("This User: ", IAMUserName, ", with the email: ", email, ", is having access key age is 70 days")email_unique = list(set(email_70_list))print("Email list: ", email_unique)RECIPIENTS = email_uniqueSENDER = os.environ['sender_email']AWS_REGION = os.environ['region']SUBJECT_70 = os.environ['SUBJECT_70']BODY_TEXT_70 = os.environ['BODY_TEXT_70']BODY_HTML_70 = os.environ['BODY_HTML_70']CHARSET = "UTF-8"client = boto3.client('ses',region_name=AWS_REGION)try:response = client.send_email(Destination={'ToAddresses': RECIPIENTS,},Message={'Body': {'Html': {'Charset': CHARSET,'Data': BODY_HTML_70,},'Text': {'Charset': CHARSET,'Data': BODY_TEXT_70,},},'Subject': {'Charset': CHARSET,'Data': SUBJECT_70,},},Source=SENDER,)except ClientError as e:print(e.response['Error']['Message'])else:print("Email sent! Message ID:"),print(response['MessageId'])# if Access Key Age is greater then 80 days, send email alertif active_days == datetime.timedelta(days=int(os.environ['days_80'])):userTags = iam.list_user_tags(UserName=keyValue['UserName'])email_tag = list(filter(lambda tag: tag['Key'] == 'email', userTags['Tags']))if(len(email_tag) == 1):email = email_tag[0]['Value']email_80_list.append(email)print("The User: ", IAMUserName, ", with the email: ", email, ", is having access key age is 80 days")email_unique = list(set(email_80_list))print("Email list: ", email_unique)RECIPIENTS = email_uniqueSENDER = os.environ['sender_email']print("Sender: ", SENDER)AWS_REGION = os.environ['region']SUBJECT_80 = os.environ['SUBJECT_80']BODY_TEXT_80 = os.environ['BODY_TEXT_80']BODY_HTML_80 = os.environ['BODY_HTML_80']CHARSET = "UTF-8"client = boto3.client('ses',region_name=AWS_REGION)try:response = client.send_email(Destination={'ToAddresses': RECIPIENTS,},Message={'Body': {'Html': {'Charset': CHARSET,'Data': BODY_HTML_80,},'Text': {'Charset': CHARSET,'Data': BODY_TEXT_80,},},'Subject': {'Charset': CHARSET,'Data': SUBJECT_80,},},Source=SENDER,)except ClientError as e:print(e.response['Error']['Message'])else:print("Email sent! Message ID:"),print(response['MessageId'])# if Access Key Age is greater then 90 days, send email alert and inactive access keysif active_days >= datetime.timedelta(days=int(os.environ['days_90'])):userTags = iam.list_user_tags(UserName=keyValue['UserName'])email_tag = list(filter(lambda tag: tag['Key'] == 'email', userTags['Tags']))user1_tag = list(filter(lambda tag: tag['Key'] == 'UserType', userTags['Tags']))if(len(email_tag) == 1):email = email_tag[0]['Value']email_90_list.append(email)print("The User: ", IAMUserName, ", with the email: ", email, ", is having access key age is greater then 90 days")if(len(user1_tag) == 1):user1tag = user1_tag[0]['Value']if user1tag == "Employee":iam.update_access_key(AccessKeyId=UserAccessKeyID,Status='Inactive',UserName=IAMUserName)print("Status has been updated to Inactive")email_unique = list(set(email_90_list))print("Email list: ", email_unique)RECIPIENTS = email_uniqueSENDER = os.environ['sender_email']print("Sender: ", SENDER)AWS_REGION = os.environ['region']SUBJECT_90 = os.environ['SUBJECT_90']BODY_TEXT_90 = os.environ['BODY_TEXT_90']BODY_HTML_90 = os.environ['BODY_HTML_90']CHARSET = "UTF-8"client = boto3.client('ses',region_name=AWS_REGION)try:response = client.send_email(Destination={'ToAddresses': RECIPIENTS,},Message={'Body': {'Html': {'Charset': CHARSET,'Data': BODY_HTML_90,},'Text': {'Charset': CHARSET,'Data': BODY_TEXT_90,},},'Subject': {'Charset': CHARSET,'Data': SUBJECT_90,},},Source=SENDER,)except ClientError as e:print(e.response['Error']['Message'])else:print("Email sent! Message ID:"),print(response['MessageId'])

Enable CloudWatch event rule:

Now we just need to create a CloudWatch event rule (now AWS EventBridge) to trigger this Lambda function at specific time. In my case I have enabled this cron job at 9am morning.

AWS EventBridge Scheduler and Target

Conclusion

This is AWS security best practice to rotate IAM access keys so no one can able to access your account resources. Lambda function is the best tool to perform these kind of tasks and trigger it from other services like CloudWatch.

I hope you enjoyed reading this article, please feel free to contact me Syedusmanahmad if you have any questions.

--

--

Syedusmanahmad

AWS & DevOps Architect | Linux, Docker, Kubernetes, Terraform, Jenkins, Git&GitHub, Ansible expert