Automatically rotate AWS access keys using Keeper Secrets Manager rotations
Overview
This documentation explains how to rotate AWS IAM user access keys using KeeperPAM's rotation option called "Run PAM scripts only". This is a setting in the PAM User rotation settings which tells the Gateway to skip the primary rotation method and directly execute the post-rotation script attached to the PAM User record in the vault.
This guide includes prerequisites, step-by-step instructions, and a Python script example. This provided script supports both provided admin credentials (AWS Access key for an admin account) and EC2 instance role authentication. The script ensures secure access key rotation, including deletion of previous user keys. The new key is stored in the Keeper record after rotation is complete.
Prerequisites
KSM role: Ensure that the Keeper user has a role providing access to Keeper Secrets Manager, and Keeper Secrets Manager rotations.
A Linux instance to run the Keeper Gateway: The Gateway can be deployed in an EC2 instance, any private Cloud or on-prem. This script is capable of leveraging EC2 Instance Roles Authentication to perform the Access Key rotation if the Gateway runs in an EC2 instance. If the gateway runs somewhere else, then an Access Key with role privilege needs to be provided in the Keeper vault to perform the rotation of the end user Access Key.
Rotation Script Logic Flow
1. Admin Credentials Retrieval
The script retrieve admin credentials in three ways:
Record directly attached to the post rotation script.
The access key provided to the AWS PAM config selected for the rotation. This will be used if no access key is found in the record attached (method 1 above) to the post rotation script.
Uses AWS instance role authentication if no credentials are provided from either methods above. The gateway needs to be running on an EC2 Instance with an EC2 Instance Role in place.
2. Key Rotation Logics
The script provides two modes of operation based on the delete_all_keys_before_rotating custom field:
If False (default), a new access key is created first, then the old ones are deleted, keeping only the newly created key. This will fail if the user account has already two access keys: AWS will not allow the script to create a third one.
If this custom field is set to True, the script deletes all existing access keys for the IAM user before creating a new one. This helps in the scenario described above where the end user account has already two access keys.
3. Updating the Keeper PAM User Record in the Vault
After key rotation, the script updates the rotated PAM User Keeper record with the new AWS access key ID, secret access key, creation date, and any deleted access keys IDs.
PAM User Record - Fields Requirements
You need to create a PAM User record where the rotation will be configured later on. The fields below need to be created.
Fields required:
Field Name
Description
Login
Name of the user account in AWS where the access key needs to be rotated.
Password
It will be a dummy value in this case. The password field gets automatically rotated, but it is not used anywhere. This is still required field.
Custom fields required:
Custom Field Label
Field Type
Description
aws_access_key_id
Text
This field will receive the new access key id after the rotation.
aws_secret_access_key
Hidden Field
This field will receive the new secret access key after the rotation.
CreateDate
Text
This field will contain the timestamp of when the new key has been generated by the script.
Access_Key_ID_Removed_during_previous_rotation
Text
This field will contain the old access key id(s) removed from the user account in AWS during the rotation.
delete_all_keys_before_rotating
Text
This custom field is optional. It could be set to “False” or “True”, the default value is “False”.
If set to “True” then the rotation script will start by deleting all existing access keys on the user account before creating a new one. This is needed when the user has already 2 access keys setup. AWS will not allow the script to create a third one, hence the need to delete the existing keys before adding a new one.
NOOP
Text
This rotation requires the gateway to only execute the rotation script, and not try to rotate something using the built-in rotation features.
The value has to be:
True
Private Key Type
Text
Second field to enable NOOP.
The value has to be:
rsa-ssh
Instead of creating the PAM User record manually using the details above, you could also import the csv file below. It will create a template record you can amend and duplicate as needed.
Importing the file will generate a Login record type: make sure to convert it to PAM User.
CSV file to easily create a PAM User template record via an import in the vault
PAM User record example
Setting Up the Rotation in the Keeper Vault
When the gateway runs in an EC2 instance, you don’t need to provide an admin access key to the script. The gateway will leverage the AWS Instance Role permissions assigned to the VM.
The steps below explains how to set up an EC2 Instance role to the gateway EC2 instance with minimal permissions:
Steps to Add/Configure Instance Role With Minimum Permissions to Rotate Access Keys:
Attach the necessary IAM policies (e.g., the policy we created above with the minimum permissions).
Attach the policy to the role in AWS
Step 3: Assign the Role to Your EC2 Instance
In the EC2 Management Console, find your instance.
Click Actions > Security > Modify IAM Role.
EC2 Instance contextual menu
Select the role you created and click Update
Assign IAM role to EC2 instance
Verify Instance Role Permissions
You can ensure that the instance role has appropriate permissions to interact with IAM by running the command below on the Gateway EC2 Instance:
aws sts get-caller-identity
After configuring this role, your EC2 instance, in this case your Keeper Gateway, will automatically use the attached role credentials for your script, allowing it to perform actions like creating and deleting IAM access keys without needing explicit access key credentials.
Rotation Configuration From the Vault:
Create a shared folder in the vault
Create a PAM User record in the shared folder with the fields and custom fields described above.
In the Secret Manager tab of the Keeper vault, create a new application for the gateway if there is no gateway yet.
Make sure the Application has edit permissions on the shared folder created above.
Provision the gateway (gateway tab after selecting the application) on an EC2 instance.
On the EC2 Instance run the install command provided by the Keeper vault and make sure boto3 and keeper_secrets_manager_core are installed by running the following commands in the EC2 instance:
In the Secret Manager tab of the Keeper vault, go to the PAM Configurations tab. Create a new PAM configuration if needed.
Under Environment you can select “Local Network” or “AWS”.
If you select “AWS”, please make sure to leave the “Access Key” and “Secret Access Key” field empty. If you provide one, it will be automatically used by the script instead of using the Instance Role authentication. You will still need to provide the AWS Account ID to the AWS PAM configuration.
Select the gateway, select the shared folder and save the PAM configuration.
PAM Configuration without providing an AWS access key
Edit the PAM User record previously described in this documentation:
Password Rotation Settings: select your desired schedule and the PAM configuration created above.
Add PAM Script to the record: select the provided file below and make sure to specify the script command:
python3
Attach PAM script with a script command
When the gateway does not run in an EC2 instance, it will require an admin access key to authenticate against AWS and rotate another user's access key. Here we will be using the admin access key provided in the AWS PAM Configuration.
Configuration From the Keeper Vault:
Create a shared folder in the vault
Create a PAM User record in the shared folder with the fields and custom fields described above.
PAM User record in the shared folder
In the Secret Manager tab of the Keeper vault, create a new application for the gateway if there is no gateway yet.
Make sure the Application has edit permissions on the shared folder created above.
Provision the gateway (gateway tab after selecting the application) on a Linux box.
Simply run the install command provided by the Keeper vault and make sure boto3 and keeper_secrets_manager_core are installed by running the following commands on the Linux box:
In the Secret Manager tab of the Keeper vault, go to the PAM Configurations tab. Create a new PAM configuration if needed.
Under Environment, please select “AWS”, select the Gateway, select the shared folder, provide the “AWS ID”, the “Access Key” and “Secret Access Key”. This will be the admin access key that the script uses to rotate a user access key.
AWS PAM Config
Edit the PAM User record previously described in this documentation:
Password Rotation Settings: select your desired schedule and the PAM configuration created above.
Add PAM Script to the record: select the provided file below and make sure to specify the script command:
python3
Attach PAM script with a script command
When the gateway does not run in an EC2 instance, it will require an admin access key to authenticate against AWS and rotate another user's access key. Here we will be using the admin access key provided in another Keeper record. This option also allows to rotate the admin access key the same way Keeper rotates an user access key.
You may have followed any of the two other tabs available in this doc (Using AWS instance role, or Using AWS PAM Config) to set up the rotation: you will have a PAM configuration that contains or does not contain an access key. Following the steps below will force the use of an admin access key stored in another Keeper record.
Configuration From the Keeper Vault:
When attaching the PAM Script to the PAM user record, it is possible to attach Rotation Credentials.
Attach Rotation Credentials to a PAM Script
The attached record could be any record type. It needs at least the two custom fields “aws_access_key_id” and “aws_secret_access_key” with the admin access key.
Using the PAM User record type to store the admin access key allows you to also automate the rotation of the admin access key. Make sure to follow those requirements in that case.
Using AWS AssumeRole to rotate user access keys across other AWS accounts
When attaching a record to the PAM script itself to provide an admin AWS access keys also allows to leverage AWS AssumeRole to rotate an AWS user access key across multiple AWS accounts.
To leverage this feature, you need to add a new custom field to the record attached to the PAM script (Rotation Credentials).
The custom field label needs to be:
role_arn
This field will contain the role arn from your AWS environment that has the permissions to rotate access keys in other AWS accounts.
When this field exists, the rotation script will use it along with the provided admin access key ID and secret to generate a new temporary access key to rotate the end user’s one in the other AWS account. The script will then update the Keeper PAM User record with the new key and information, the same way it usually does without this extra field.
import boto3
import sys
import base64
import json
from keeper_secrets_manager_core import SecretsManager
from keeper_secrets_manager_core.storage import FileKeyValueStorage
from botocore.exceptions import ClientError, NoCredentialsError
# sys.stdin is not an array, it cannot be subscripted (i.e., sys.stdin[0])
for base64_params in sys.stdin:
# Retrieve params from the gateway
params = json.loads(base64.b64decode(base64_params).decode('utf-8'))
break
# Retrieve records attached to the post-rotation script in Keeper
records = json.loads(base64.b64decode(params.get('records')).decode('utf-8'))
# Initiate Keeper Secrets Manager SDK using the gateway configuration
secrets_manager = SecretsManager(config=FileKeyValueStorage('/etc/keeper-gateway/gateway-config.json'))
# Function to create a new access key for a user
def create_new_access_key(iam_client, user_name: str) -> tuple:
new_key = iam_client.create_access_key(UserName=user_name)
return new_key['AccessKey']['AccessKeyId'], new_key['AccessKey']['SecretAccessKey'], new_key['AccessKey']['CreateDate']
# Function to rotate user's access key
def rotate_user_access_key(iam_client, user_name: str) -> dict:
if not user_name:
raise ValueError("User name is required for rotation")
try:
# Step 1: Create a new access key for the user
new_access_key_id, new_secret_access_key, create_date = create_new_access_key(iam_client, user_name)
print(f"New Access Key created successfully for user {user_name}")
# Step 2: List all existing access keys for the user
existing_keys = iam_client.list_access_keys(UserName=user_name)
deleted_keys = [access_key['AccessKeyId'] for access_key in existing_keys['AccessKeyMetadata']
if access_key['AccessKeyId'] != new_access_key_id]
# Step 3: Delete all other access keys, except the newly created one
for access_key_id in deleted_keys:
iam_client.delete_access_key(UserName=user_name, AccessKeyId=access_key_id)
# Return the results
return {
'NewAccessKeyId': new_access_key_id,
'NewSecretAccessKey': new_secret_access_key,
'CreateDate': create_date,
'DeletedAccessKeys': deleted_keys
}
except ClientError as e:
print(f"An error occurred: {e}")
raise e
# Function to delete all keys and then rotate user's access key
def delete_then_rotate_user_access_key(iam_client, user_name: str) -> dict:
try:
# Step 1: List all existing access keys for the user
existing_keys = iam_client.list_access_keys(UserName=user_name)
deleted_keys = [access_key['AccessKeyId'] for access_key in existing_keys['AccessKeyMetadata']]
# Step 2: Delete all keys
for access_key_id in deleted_keys:
iam_client.delete_access_key(UserName=user_name, AccessKeyId=access_key_id)
# Step 3: Create a new access key for the user
new_access_key_id, new_secret_access_key, create_date = create_new_access_key(iam_client, user_name)
print(f"New Access Key created successfully for user {user_name}")
# Return the results
return {
'NewAccessKeyId': new_access_key_id,
'NewSecretAccessKey': new_secret_access_key,
'CreateDate': create_date,
'DeletedAccessKeys': deleted_keys
}
except ClientError as e:
print(f"An error occurred: {e}")
raise e
# Function to assume the role in the target AWS account
def assume_role_in_target_account(role_arn, session_name='CrossAccountSession'):
sts_client = boto3.client('sts')
try:
assumed_role = sts_client.assume_role(
RoleArn=role_arn,
RoleSessionName=session_name
)
credentials = assumed_role['Credentials']
return {
'aws_access_key_id': credentials['AccessKeyId'],
'aws_secret_access_key': credentials['SecretAccessKey'],
'aws_session_token': credentials['SessionToken']
}
except ClientError as e:
print(f"Error assuming role: {e}")
raise e
# Inputs from the gateway and full PAM user query using KSM
pam_user_to_update = secrets_manager.get_secrets([params.get('userRecordUid')])[0]
user_name = params.get('user')
# Retrieve admin user account access key
aws_admin_cred_provided = False
for record in records:
if "role_arn" in record: # AssumeRole auth detected from the Keeper record
aws_admin_cred_provided = True
role_arn = record["role_arn"]
aws_access_key_id = record["aws_access_key_id"]
aws_secret_access_key = record["aws_secret_access_key"]
print(f"Admin access key provided along with role arn for AWS AssumeRole. Using Access Key ID {aws_access_key_id}, from record UID {record['uid']}.")
# Assume the role and get temporary credentials
assumed_role_credentials = assume_role_in_target_account(role_arn)
# Initialize IAM client using the assumed role's credentials
iam_client = boto3.client(
'iam',
aws_access_key_id=assumed_role_credentials['aws_access_key_id'],
aws_secret_access_key=assumed_role_credentials['aws_secret_access_key'],
aws_session_token=assumed_role_credentials['aws_session_token'],
region_name='us-east-1'
)
break
if "aws_access_key_id" in record:
aws_admin_cred_provided = True
aws_access_key_id = record["aws_access_key_id"]
aws_secret_access_key = record["aws_secret_access_key"]
print(f"Admin access key provided. Using Access Key ID {aws_access_key_id}, from record UID {record['uid']}.")
# Initialize IAM client with admin credentials
iam_client = boto3.client('iam',
aws_access_key_id=aws_access_key_id,
aws_secret_access_key=aws_secret_access_key,
region_name='us-east-1')
break
if "accessKeyId" in record:
aws_admin_cred_provided = True
aws_access_key_id = record["accessKeyId"]
aws_secret_access_key = record["accessSecretKey"]
print(f"Admin access key provided. Using Access Key ID {aws_access_key_id}, from record UID {record['uid']}.")
# Initialize IAM client with admin credentials
iam_client = boto3.client('iam',
aws_access_key_id=aws_access_key_id,
aws_secret_access_key=aws_secret_access_key,
region_name='us-east-1')
break
else:
try:
# Try to retrieve admin access key from the PAM config using Keeper Secrets Manager
pam_config = secrets_manager.get_secrets([params.get('providerRecordUid')])[0]
aws_access_key_id = pam_config.field('accessKeyId', single=True)
aws_secret_access_key = pam_config.field('accessSecretKey', single=True)
# Check if keys are retrieved successfully
if aws_access_key_id and aws_secret_access_key:
aws_admin_cred_provided = True
print(f"Admin access key retrieved from the PAM Config. Using Access Key ID {aws_access_key_id}, from the PAM Config.")
# Initialize IAM client with admin credentials
iam_client = boto3.client('iam',
aws_access_key_id=aws_access_key_id,
aws_secret_access_key=aws_secret_access_key,
region_name='us-east-1')
else:
print("Access key ID or secret key not found in the retrieved Keeper Secrets Manager record.")
except Exception as e:
# Handle any error that occurs during retrieval
print(f"Error retrieving admin access key: {e}")
# Check for instance role credentials if no admin access key is provided
if not aws_admin_cred_provided:
print("No admin access key provided. Checking for instance role authentication...")
try:
# Test if instance role credentials are available by creating an IAM client without credentials
iam_client = boto3.client('iam')
iam_client.list_access_keys(UserName=user_name) # Test if we can access the API
aws_admin_cred_provided = True
print("Instance role credentials found. Using instance role authentication.")
except NoCredentialsError:
print("No instance role credentials available.")
except ClientError as e:
print(f"Error using instance role credentials: {e}")
# Exit if no credentials were provided
if not aws_admin_cred_provided:
raise RuntimeError("No admin access key or instance role credentials provided. The script will exit.")
# Check if the user specified a delete_all_keys_before_rotating custom field
try:
delete_all_keys_before_rotating = pam_user_to_update.custom_field('delete_all_keys_before_rotating', single=True)
delete_all_keys_before_rotating = str(delete_all_keys_before_rotating).lower() in ['true', 'yes', '1']
except Exception as e:
print(f"OPTIONAL field 'delete_all_keys_before_rotating' not found: {e}")
delete_all_keys_before_rotating = False # Set to false if an error occurs
# Call the right rotate function based on user parameters
if delete_all_keys_before_rotating:
result = delete_then_rotate_user_access_key(iam_client, user_name=user_name)
else:
result = rotate_user_access_key(iam_client, user_name=user_name)
if result:
print("\nRotation Result:")
print(f"New Access Key ID: {result['NewAccessKeyId']}")
print(f"Create Date: {result['CreateDate']}")
print(f"Deleted Access Keys: {result['DeletedAccessKeys']}")
# Update the PAM User record in Keeper
pam_user_to_update.custom_field('aws_access_key_id', result['NewAccessKeyId'])
pam_user_to_update.custom_field('aws_secret_access_key', result['NewSecretAccessKey'])
create_date_str = result['CreateDate'].isoformat() if hasattr(result['CreateDate'], 'isoformat') else str(result['CreateDate']) # Convert the create_date to string format
pam_user_to_update.custom_field('CreateDate', create_date_str)
deleted_keys_str = ', '.join(result['DeletedAccessKeys']) # Convert deleted access keys to a comma-separated string
pam_user_to_update.custom_field('Access_Key_ID_Removed_during_previous_rotation', deleted_keys_str)
secrets_manager.save(pam_user_to_update)