Rotation des clés d’accès AWS IAM à l’aide de la fonction Lambda
Conformément aux bonnes pratiques de sécurité AWS, nous devons régulièrement faire pivoter nos clés d’accès IAM pour améliorer la sécurité de nos comptes AWS.
Dans cet article, je vais vous apprendre comment nousautomatiserle processus de rotation des clés d’accès IAM en fonction de l’âge des clés d’accès à l’aide de la fonction Lambda.
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 (Gestion des identités et des accès) fournit un contrôle d’accès basé sur la recherche dans l’ensemble d’AWS.
Clés d’accès AWSsont des informations d’identification à long terme pour un utilisateur IAM. Vous pouvez utiliser des clés d’accès pour signer des requêtes programmatiques vers l’interface de ligne de commande AWS ou l’API AWS (directement ou à l’aide du kit SDK AWS).
AWS LambdaIl s’agit d’un service de calcul sans serveur, piloté par les événements, qui vous permet d’exécuter du code pour pratiquement n’importe quel type d’application ou de service backend, sans provisionner ni gérer de serveurs. Vous pouvez déclencher Lambda à partir de plus de 200 services et logiciels AWS en tant que service (SaaS) applications, et ne payez que ce que vous utilisez.
Diagramme d’architecture
Rotation des clés d’accès IAM à l’aide d’AWS Lambda et envoi de notifications à l’aide d’AWS SES
Le diagramme montre le flux de travail suivant :
1- Les utilisateurs IAM se connecteront de manière programmatique à l’aide de clés d’accès IAM
2- L’événement CloudWatch lance une fonction Lambda toutes les 24 heures
3- La fonction Lambda lance une fonction Lambda pour chaque ID de compte AWS et lui transmet les métadonnées pour un traitement supplémentaire. Il vérifiera l’âge de tous les utilisateurs, accédera aux clés et lancera l’envoi de l’e-mail au propriétaire du compte
4- Dans mon cas, j’ai configuré les trois conditions pour envoyer des e-mails en utilisant AWS SES.
Script Python dans la fonction Lambda :
J’ai écrit le script de rotation des touches d’accès en python. Ce script python obtiendra d’abord les informations/métadonnées de chaque utilisateur à partir d’AWS IAM. Comme vous le savez, l’utilisateur AWS IAM peut être humain ou être une machine, j’ai donc correctement étiqueté chaque utilisateur avec les informations suivantes :
- Nom
- Type d’utilisateur (employé ou machine)
- Messagerie électronique (abc@xyz.com)
Recommandé par LinkedIn
La fonction Lambda obtiendra ces informations ci-dessus à partir des balises d’utilisateur IAM et si le UserType est "employé» et la clé vieillit 70e, 80e ou 90e jour, puis il enverra l’e-mail de notification à l’aide d’AWS SES et désactivera la clé d’accès. Si le UserType est "machine" et l’âge clé 70e, 80e ou 90e jour, il enverra simplement l’e-mail de notification à l’équipe concernée (comme l’informatique, les opérations, etc.).
Conditions d’envoi d’alertes et de désactivation des clés d’accès :
Première condition si l’âge de la clé d’accès IAM de l’utilisateur est de 70 jours puis envoi de l’email d’alerte"Aujourd’hui, c’est le 70e jour de vos clés d’accès. Veuillez tourner votre clé d’accès avant qu’elle n’atteigne ses 90 jours..
Deuxième condition si l’âge de la clé d’accès IAM de l’utilisateur est de 80 jours, puis envoyez l’e-mail d’alerte« Aujourd’hui, c’est le 80e jour de vos clés d’accès. Veuillez tourner votre clé d’accès avant qu’elle n’atteigne ses 90 jours..
Troisième condition si l’âge de la clé d’accès IAM de l’utilisateur est de 90 jours puis envoyer l’email d’alerte"Aujourd’hui est le 90e jour de vos clés d’accès et votre clé a été désactivée. Veuillez générer une nouvelle clé ou contacter l’équipe d’assistance informatique".
Code de la fonction Lambda :
Récupérez le code formaté approprié à partir de mon dépôt github : https://github.com/engr-usman/blogs.git
import boto3, os, time, datetime, sys, jso
from datetime import date
from botocore.exceptions import ClientError
iam = 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 warning
if 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_unique
SENDER = 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 alert
if 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_unique
SENDER = 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 keys
if 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_unique
SENDER = 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'])n
Remarque : L’indentation n’est pas correcte du code ci-dessus :( et je sais qu’il y a beaucoup d’amélioration du code, c’est juste pour une idée.
Activez la règle d’événement CloudWatch :
Il ne nous reste plus qu’à créer une règle d’événement CloudWatch (désormais AWS EventBridge) pour déclencher cette fonction Lambda à un moment précis. Dans mon cas, j’ai activé ce travail cron à 9 heures du matin.
Planificateur et cible AWS EventBridge
Conclusion
Il s’agit de la bonne pratique de sécurité AWS qui consiste à faire pivoter les clés d’accès IAM afin que personne ne puisse accéder aux ressources de votre compte. La fonction Lambda est le meilleur outil pour effectuer ce type de tâches et le déclencher à partir d’autres services comme CloudWatch.
J’espère que vous avez apprécié la lecture de cet article, n’hésitez pas à me contacter si vous avez des questions.
Usman Ahmad what are the roles you provided your lambda function ?
Hi Usman Ahmad I am trying to fetch the details of both sender and region details here. bot its not happening. pls let me know whats this sender refers to? how i should try in my code. as you said in the post i have created the mail in ses and mentioned the VAULE in the IAM user tags. can you help me what i am missing inorder to get to get the region and sender details
Hi Usman! Can you publish script in the correct format (at least screenshot), please? I'm not Python specialist for now, maybe in the future. Thank you.
Very useful
Amazing