AWS IAM — Access Key Rotation and Notification using Boto3

AWS IAM Access Credentials — Access Key and Secret Key
  1. IAM Role for AWS Lambda to access resources for the Solution
  2. SNS Topic for notifying Users
  3. Lambda Function for Rotating Keys and Notifying users periodically
  4. EventBridge Rule to trigger Lambda periodically
  5. Cloudformation Template to Automate the deployment of the above resources (optional).

1. IAM Role for the Lambda Function

  1. In the AWS Management Console, under Services, navigate to the IAM Console.
  2. Create a policy with the following permissions, or copy the following policy:
{
"Version": "2012-10-17",
"Statement":[
{
"Effect": "Allow",
"Action": [
"iam:ListUsers",
"iam:CreateAccessKey",
"iam:DeleteAccessKey",
"iam:GetAccessKeyLastUsed",
"iam:GetUser",
"iam:ListAccessKeys",
"iam:UpdateAccessKey",
"sns:*",
"logs:*"
],
"Resource": "*"
}
]
}

2. SNS Topic for User Notification

  1. Create a Standard SNS Topic with default settings and then create email Subscriptions for it, or however, you want to receive the Notification.
  2. You’ll receive an email to confirm your subscription to the SNS Topic. Click the link to confirm.

3. The Lambda Function

  1. Navigate to the AWS Lambda console and create a Lambda function.
  2. Add a Suitable name, set Runtime as Python 3.8, and for Execution role>Use an Existing Role and Select the role you created in the previous step.
Lambda Configuration
Lambda Basic Configuration
import boto3
from botocore.exceptions import ClientError
import datetime
import json
import os
iam_client = boto3.client('iam')
sns_client = boto3.client('sns')
topic_arn = os.environ["SNS"]
def list_access_key(user, days_filter, status_filter):
keydetails=iam_client.list_access_keys(UserName=user)
key_details={}
user_iam_details=[]

# Some user may have 2 access keys.
for keys in keydetails['AccessKeyMetadata']:
if (days:=time_diff(keys['CreateDate'])) >= days_filter and keys['Status']==status_filter:
key_details['UserName']=keys['UserName']
key_details['AccessKeyId']=keys['AccessKeyId']
key_details['days']=days
key_details['status']=keys['Status']
user_iam_details.append(key_details)
key_details={}

return user_iam_details

def time_diff(keycreatedtime):
now=datetime.datetime.now(datetime.timezone.utc)
diff=now-keycreatedtime
return diff.days
def create_key(username):
access_key_metadata = iam_client.create_access_key(UserName=username)
access_key = access_key_metadata['AccessKey']['AccessKeyId']
secret_key = access_key_metadata['AccessKey']['SecretAccessKey']
return access_key,secret_key

def disable_key(access_key, username):
try:
iam_client.update_access_key(UserName=username, AccessKeyId=access_key, Status="Inactive")
print(access_key + " has been disabled.")
except ClientError as e:
print("The access key with id %s cannot be found" % access_key)
def delete_key(access_key, username):
try:
iam_client.delete_access_key(UserName=username, AccessKeyId=access_key)
print (access_key + " has been deleted.")
except ClientError as e:
print("The access key with id %s cannot be found" % access_key)
def lambda_handler(event, context):
# details = iam_client.list_users(MaxItems=300)
# print(details)
users=[]
user_iam_details=[]
text_list=["Following IAM users require replacing their Access Keys as it is older than 45 days :\n"]
for user in iam_client.list_users()['Users']:
users.append(user['UserName'])
print("User: {0}\nUserID: {1}\nARN: {2}\nCreatedOn: {3}\n".format(user['UserName'],user['UserId'],user['Arn'],user['CreateDate']))

for user in users:
# user_iam_details=list_access_key(user=user,days_filter=90,status_filter='Active')
user_iam_details=list_access_key(user=user,days_filter=45,status_filter='Active')
print(user_iam_details)

for _ in user_iam_details:
text="Username= {}\tAccess Key ID= {}\tDays Active= {}".format(_['UserName'],_['AccessKeyId'],_['days'])
text_list.append(text+'\n')
# To replace the expired key and to create a text message for sns with accessID and SecretID (List to Multiline String)
for _ in user_iam_details:
disable_key(access_key=_['AccessKeyId'], username=_['UserName'])
delete_key(access_key=_['AccessKeyId'], username=_['UserName'])
access_key,secret_key = create_key(username=_['UserName'])
text=" IAM User = {} The Access Key = {} Secret Key = {}".format(user,access_key,secret_key)
text_list.append(text+'\n')

message = ''.join(map(str, text_list))
print(message)
sns_client.publish(TopicArn=topic_arn,Message=message)
  • If you only want to notify the Users of the expired Keys (older than 45 days), then you can Comment out the replacement logic and the rest of the function should work fine.
  • IAM Access Keys can cause major security vulnerabilities therefore consider the subscribers of the SNS topic used to send out the new access key and secret key, you can also save Access keys as S3 files, later to be accessed by the User.

5. EventBridge Rule for Periodic Invocation of Lambda

  1. Under Amazon EventBridge, Select Create Rule.
  2. Add a suitable Name, under Define pattern, select Schedule.
  3. You can either set —
  • Rate — Trigger your Lambda every x minutes/hours/days
  • Cron Expression — Custom expression to define the trigger on specific hours or days of the week.

CloudFormation Template to Automate Deployment (opt)

Prerequisites :

  1. Place your Lambda Function code as lambda_function.py in a zip file called access-key-rotation.zip and upload it to S3 for parameter BucketName.
  2. For the SNS Subscriber emails — the respective users will receive confirmation emails that need to be confirmed.
  3. EnvironmentName acts as a prefix to all resource names.
---
AWSTemplateFormatVersion: "2010-09-09"
Description: Cloudformation Template to create cron job for Lambda to rotate Access Keys for IAM UsersParameters:
EnvironmentName:
Type: String
Description: Environment Name
CronExpression:
Type: String
Description: Recurring schedule for this action, in Unix cron syntax format
Default: cron(0 11 ? * MON-FRI *)
BucketName:
Type: String
Description: S3 bucket where Lambda Deployment Package is present
APPVERSION:
Type: String
Description: Application Version to Deploy
Default: "2"
TopicSubscriber1:
Type: String
Description: Enter Email ID of the Subscriper
TopicSubscriber2:
Type: String
Description: Enter Email ID of the Subscriper
Resources:
ScheduledRule:
DependsOn:
- LambdaFunction
Type: AWS::Events::Rule
Properties:
Description: Event Rule to Trigger Lambda
Name: !Join ["_", [!Ref "EnvironmentName", "AccessKeyRotationEvent"]]
ScheduleExpression: !Ref CronExpression
State: ENABLED
Targets:
- Arn:
Fn::GetAtt: ["LambdaFunction", "Arn"]
Id: "TargetFunctionV1"
PermissionForEventsToInvokeLambda:
Type: AWS::Lambda::Permission
DependsOn:
- ScheduledRule
Properties:
FunctionName: !Ref "LambdaFunction"
Action: "lambda:InvokeFunction"
Principal: "events.amazonaws.com"
SourceArn:
Fn::GetAtt: ["ScheduledRule", "Arn"]
#Create an IAM role for Lambda
LambdaExecutionRole:
Type: "AWS::IAM::Role"
Properties:
Description: IAM role for Lambda
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- lambda.amazonaws.com
Action:
- "sts:AssumeRole"
Path: "/"
Policies:
- PolicyName: CustomPolicyForLambda
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "iam:ListUsers"
- "iam:CreateAccessKey"
- "iam:DeleteAccessKey"
- "iam:GetAccessKeyLastUsed"
- "iam:GetUser"
- "iam:ListAccessKeys"
- "iam:UpdateAccessKey"
- "sns:*"
- "logs:*"
Resource: "*"
RoleName:
Fn::Join:
- "-"
- - !Ref "EnvironmentName"
- accesskeyrotation-role
LambdaFunction:
DependsOn:
- MySNSTopic1
- LambdaExecutionRole
Type: "AWS::Lambda::Function"
Properties:
Handler: lambda_function.lambda_handler
Role: !GetAtt [LambdaExecutionRole, Arn]
Environment:
Variables:
APPVERSION: !Ref APPVERSION
SNS: !Ref MySNSTopic1
FunctionName: !Join ["_", [!Ref "EnvironmentName", "AccessKeyRotation"]]
Code:
S3Bucket: !Ref BucketName
S3Key: access-key-rotation.zip
Runtime: python3.8
Timeout: 900
MySNSTopic1:
Type: AWS::SNS::Topic
Properties:
TopicName: !Join ["_", [!Ref "EnvironmentName", "AccessKeyRotationSNS"]]
PESubscription1:
Type: AWS::SNS::Subscription
Properties:
Endpoint: !Ref TopicSubscriber1
Protocol: email
TopicArn: !Ref "MySNSTopic1"
PESubscription2:
Type: AWS::SNS::Subscription
Properties:
Endpoint: !Ref TopicSubscriber2
Protocol: email
TopicArn: !Ref "MySNSTopic1"

--

--

--

Head first into AWS DevOps, SecOps and Development on cloud . 2x AWS Certified.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Cross platform Mobile Apps

Virtual Hackathon — Accountabud: Hack for Black Lives Matter Pt. 2

What happens when you type ls l in the shell?

Automate Sending Emails in Python

iOS App Architecture: Coordinators

We have been busy (and we’ll prove it)!

Cronos World рада объявить о стратегическом партнерстве с BitLiberte.

Zalenium — Container based Selenium Grid with super cool features

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Aishwarya Gupta

Aishwarya Gupta

Head first into AWS DevOps, SecOps and Development on cloud . 2x AWS Certified.

More from Medium

Automate AWS S3 Bucket sync using Lambda with AWS CLI

Recap of My First AWS Project

AWS Cloud9 concepts illustrated

Be careful with the AWS Pricing Calculator

AWS Pricing Calculator