Posted on Sun 29 March 2020
aws python boto3 cis benkmarkIn AWS, control 1.3 of the CIS AWS Foundations talks about making sure that keys that are older than 90 days are disabled. You can check this manually of course using the AWS console as explained in the documentation or you can write a script that does those checks for you with python.
Using the terminal, in your projects folder create a new directory for this experiment.
mkdir iam-key-scanner && cd iam-key-scanner
I like having virtual environments for all my projects, so I will create a new one and activate it.
python3 -m venv venv
source ./venv/bin/activate
You will need to install 2 packages to follow along. One is of course boto3 and the other one is python-dotenv for storing the AWS keys used to to the scan.
pip install boto3 python-dotenv
Create a .env file for storing the AWS credentials. If later you decide to convert this into a lambda, you would of course not need this and you would create a role, but for running it from the computer this approach is a good option.
touch .env
Edit the .env file in your favorite editor and add the following 2 lines with your credentials.
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
First thing that we have to do is to generate the report and then after it's generated we can read it and take action. To keep this simple I will break it into the two parts, however they can be part of the same script when you are ready for production.
Here is the script that will generate the report.
touch generate-report.py
And the code.
import os
import boto3
from dotenv import load_dotenv
load_dotenv()
AWS_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY')
client = boto3.client(
'iam',
aws_access_key_id=AWS_ACCESS_KEY_ID,
aws_secret_access_key=AWS_SECRET_ACCESS_KEY
)
response = client.generate_credential_report()
Now run it.
python generate-report.py
The report generation is pretty fast, but if you want to wait for it just look at the response. When the State is 'COMPLETE', the report is ready.
print(response['State'])
For parsing the report I decided to use namedtuple so I can access the data more easily.
I am using timedelta, so I can calculate the age of a user key. You will see that there are two user keys for each user so we need to make sure we look at both. I am also declaring a variable called format that I am using together with strptime to get the returned date as I want it.
This script will just print out the users, but you can easily create another function that you can call to disable the expired key or do perform key rotation. Also note that the script will show you the users with keys older than 90 days even if the key is inactive. You can add an extra check if you would like to only show the ones with active keys.
touch parse-user-report.py
And now the code.
import os
import boto3
from dotenv import load_dotenv
from collections import namedtuple
from datetime import datetime, timedelta, timezone
retentionDate = datetime.now() - timedelta(days=90)
format = '%Y-%m-%d'
load_dotenv()
User = namedtuple('User', 'user arn user_creation_time password_enabled password_last_used password_last_changed password_next_rotation mfa_active access_key_1_active access_key_1_last_rotated access_key_1_last_used_date access_key_1_last_used_region access_key_1_last_used_service access_key_2_active access_key_2_last_rotated access_key_2_last_used_date access_key_2_last_used_region access_key_2_last_used_service cert_1_active cert_1_last_rotated cert_2_active cert_2_last_rotated')
AWS_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY')
client = boto3.client(
'iam',
aws_access_key_id=AWS_ACCESS_KEY_ID,
aws_secret_access_key=AWS_SECRET_ACCESS_KEY
)
response = client.get_credential_report()
body = response['Content'].decode('utf-8')
lines = body.split('\n')
users = [User(*line.split(',')) for line in lines[1:]]
for user in users:
if (user.access_key_1_last_rotated != 'N/A' and datetime.strptime(user.access_key_1_last_rotated[:10], format) < retentionDate) or (user.access_key_2_last_rotated != 'N/A' and datetime.strptime(user.access_key_2_last_rotated[:10], format) < retentionDate):
print(user.user)
And let's run it.
python parse-user-report.py
Please let me know if you have any questions or if you would have done something differently.
Tech used
Website is powered by Pelican, which takes great advantage of Python. Icon from icons8. Template was built using Tailwindcss.