Python script that checks the age of AWS IAM access keys and disabled them if they are paste the defined expiration. It has been build in a way that can be automatically called in an AWS Lambda function.
Input Parameters
The script takes the following parameters in as environment variables.
Name | Example | Description |
---|---|---|
USER_GROUP_TARGET | Employees | Users in the group will only checked |
SOURCE_ADDRESS | [email protected] | Source email to send from |
CC_EMAIL_ADDRESS | [email protected] | Email to carbon copy when key is disabled |
WARN_DAYS | 70 | When to start warning about key expiration |
DISABLE_DAY | 90 | When to disable the key |
DELETE_DAY | 120 | When to delete the key |
Python Script
The code has been tested with up to python3.11
and the entry point is lambda_handler
.
import os
import boto3
from datetime import date
# Email stuff
SOURCE_ADDRESS = os.environ.get('SOURCE_ADDRESS')
CC_EMAIL_ADDRESS = os.environ.get('CC_EMAIL_ADDRESS')
USER_GROUP_TARGET = os.environ.get('USER_GROUP_TARGET')
# Day settings
WARN_DAYS = int(os.environ.get('WARN_DAYS'))
DISABLE_DAY = int(os.environ.get('DISABLE_DAY'))
DELETE_DAY = int(os.environ.get('DELETE_DAY'))
def findOldAccessKeys():
iam = boto3.client('iam')
currentdate = date.today()
# List all users
for user in iam.list_users()['Users']:
emailBool = False
userGroups = iam.list_groups_for_user(UserName=user['UserName'])
# For each group
for groupName in userGroups['Groups']:
# For users in the group 'GroupName'
if USER_GROUP_TARGET in groupName['GroupName']:
res = iam.list_access_keys(UserName=user['UserName'])
# For each access key they have
for AccessKeys in res['AccessKeyMetadata']:
active_days = currentdate - AccessKeys['CreateDate'].date()
# If the key is active
if (AccessKeys['Status'] == 'Active'):
# If it is past its due date
response = iam.list_user_tags(
UserName = user['UserName'],
MaxItems = 123
)
for tag in response['Tags']:
if (tag['Key'] == 'email'):
emailBool = True
email = tag['Value']
# Delete key if range
if (active_days.days >= DELETE_DAY):
print(f"Username: {user['UserName']} ({active_days.days}) is over {DELETE_DAY} days, deleting the access key.")
delete_access_key(iam, AccessKeys['AccessKeyId'], user['UserName'])
# Disable key
elif (active_days.days >= DISABLE_DAY):
print(f"Username: {user['UserName']} ({active_days.days}) is over {DISABLE_DAY} days, disabling access key & sending email.")
disable_access_key(iam, AccessKeys['AccessKeyId'], user['UserName'])
if (emailBool == True):
send_disable_email(user['UserName'], email)
else:
print(f"[!] Email tag does not exist for: {user['UserName']}")
send_email_dne(user['UserName'])
# In the warning range
elif (active_days.days >= WARN_DAYS):
print(f"Username: {user['UserName']} ({active_days.days}) is over {WARN_DAYS} days, sending reminder email.")
if (emailBool == True):
sendWarningEmail(user['UserName'], email, active_days.days)
else:
print(f"[!] Email tag does not exist for: {user['UserName']}")
send_email_dne(user['UserName'])
def delete_access_key(iam, keyID, username):
iam.delete_access_key(
AccessKeyId = keyID,
UserName = username
)
def disable_access_key(iam, keyID, username):
iam.update_access_key(
AccessKeyId = keyID,
Status = 'Inactive',
UserName = username
)
def send_email_dne(username):
ses = boto3.client('ses')
bodyEmail = f"Could not find a email for the AWS user {username}"
ses.send_email(
Source = SOURCE_ADDRESS,
Destination = {
'ToAddresses': [
SOURCE_ADDRESS,
]
},
Message = {
'Subject': {
'Data': 'Email Does Not Exist | AWS Access Key'
},
'Body': {
'Html': {
'Data': bodyEmail
}
}
}
)
def send_disable_email(username, email):
ses = boto3.client('ses')
bodyEmail = f"""Hello {username},<br><br>
Your aws cli access key is outdated, and has been disabled.<br>
Please login to aws and create a new access key.<br><br>
Guide to update your access key<br>
https://aws.amazon.com/blogs/security/how-to-find-update-access-keys-password-mfa-aws-management-console/<br>
Linux / OSX cli tool<br>
https://github.com/stefansundin/aws-rotate-key<br>
"""
ses.send_email(
Source = SOURCE_ADDRESS,
Destination = {
'ToAddresses': [
email,
],
'CcAddresses': [
CC_EMAIL_ADDRESS,
]
},
Message = {
'Subject': {
'Data': 'Outdated AWS Access Key'
},
'Body': {
'Html': {
'Data': bodyEmail
}
}
}
)
def sendWarningEmail(username, email, days):
ses = boto3.client('ses')
bodyEmail = f"""Hello {username},<br><br>
Your aws cli access key is reaching its expiration date.<br>
Please replace your access key, it will be disabled in {DISABLE_DAY - days} days.<br><br>
Guide to update your access key<br>
https://aws.amazon.com/blogs/security/how-to-find-update-access-keys-password-mfa-aws-management-console/<br>
Linux / OSX cli tool<br>
https://github.com/stefansundin/aws-rotate-key<br>
"""
ses.send_email(
Source = SOURCE_ADDRESS,
Destination = {
'ToAddresses': [
email,
]
},
Message = {
'Subject': {
'Data': 'Outdated AWS Access Key Warning'
},
'Body': {
'Html': {
'Data': bodyEmail
}
}
}
)
def lambda_handler(event, context):
findOldAccessKeys()
IAM Permissions
Configure the following on the IAM role that the lambda assumes. Change the ARN and condition for the ses identity as well as the name of the function in the logging section (sid: VisualEditor3).
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"iam:DeleteAccessKey",
"iam:GetAccessKeyLastUsed",
"iam:GetUser",
"iam:ListAccessKeys",
"iam:ListAttachedGroupPolicies",
"iam:ListGroupPolicies",
"iam:ListGroupsForUser",
"iam:ListUserTags",
"iam:UpdateAccessKey"
],
"Resource": [
"arn:aws:iam::0123456789:group/*",
"arn:aws:iam::0123456789:user/*"
]
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": [
"iam:ListUsers",
"iam:ListGroups",
"iam:GetAccountSummary"
],
"Resource": "*"
},
{
"Sid": "VisualEditor2",
"Effect": "Allow",
"Action": "ses:SendEmail",
"Resource": "arn:aws:ses:us-east-1:0123456789:identity/[email protected]",
"Condition": {
"ForAllValues:StringLike": {
"ses:Recipients": "*@example.com"
}
}
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "arn:aws:logs:us-east-1:0123456789:*"
},
{
"Sid": "VisualEditor3",
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:us-east-1:0123456789:log-group:/aws/lambda/AccessKeyUpdateReminder:*"
}
]
}