@@ -175,6 +175,69 @@ def tag_user(self, user_name: str, tags: list):
175175 except Exception as err :
176176 raise err
177177
178+ def untag_user (self , user_name : str , tag_keys : list ):
179+ """
180+ Removes the given tag keys from the IAM user.
181+ :param user_name: The name of the IAM user.
182+ :param tag_keys: List of tag key names to remove (e.g. ['UnusedAccessKey1InactiveDate']).
183+ """
184+ if not tag_keys :
185+ return
186+ try :
187+ self .iam_client .untag_user (UserName = user_name , TagKeys = tag_keys )
188+ logger .info (f"Untagged user '{ user_name } ': { tag_keys } " )
189+ except Exception as err :
190+ logger .error (f"Failed to untag user '{ user_name } ': { err } " )
191+ raise err
192+
193+ def delete_user_access_key (
194+ self ,
195+ username : str ,
196+ access_key_label : str ,
197+ remove_inactive_tag : bool = True ,
198+ access_key_id : str = None ,
199+ ):
200+ """
201+ Deletes the specified access key for the given IAM user. Optionally removes the
202+ UnusedAccessKeyNInactiveDate tag (only when this policy had set it by deactivating the key).
203+ :param username: IAM user name.
204+ :param access_key_label: "Access key 1" or "Access key 2" (case-insensitive).
205+ :param remove_inactive_tag: If True, remove UnusedAccessKeyNInactiveDate after delete.
206+ Set False when the key was not deactivated by this policy (e.g. manually deactivated).
207+ :param access_key_id: Optional. When provided, delete this key by ID instead of resolving by
208+ label.
209+ """
210+ access_key_label_lower = (access_key_label or '' ).lower ()
211+ if not access_key_label_lower or 'access key' not in access_key_label_lower :
212+ logger .warning ("Invalid access key label for deletion." )
213+ return
214+ key_num = access_key_label_lower .split ()[- 1 ]
215+ inactive_tag_key = f"UnusedAccessKey{ key_num } InactiveDate"
216+
217+ if not access_key_id :
218+ try :
219+ access_keys = self .iam_client .list_access_keys (UserName = username )['AccessKeyMetadata' ]
220+ except Exception as e :
221+ logger .error (f"Failed to list access keys for user '{ username } ': { e } " )
222+ raise
223+ access_keys .sort (key = lambda k : k ['CreateDate' ])
224+ idx = self .ACCESS_KEY_LABEL_MAP .get (access_key_label_lower )
225+ if idx is None or idx >= len (access_keys ):
226+ logger .warning (f"Access key label '{ access_key_label } ' not found for user '{ username } '" )
227+ return
228+ access_key_id = access_keys [idx ]['AccessKeyId' ]
229+
230+ try :
231+ self .iam_client .delete_access_key (UserName = username , AccessKeyId = access_key_id )
232+ logger .info (f"Deleted access key '{ access_key_id } ' for user '{ username } '" )
233+ except Exception as e :
234+ logger .error (f"Failed to delete access key '{ access_key_id } ' for user '{ username } ': { e } " )
235+ raise
236+ tag_keys_to_remove = [access_key_id ]
237+ if remove_inactive_tag :
238+ tag_keys_to_remove .append (inactive_tag_key )
239+ self .untag_user (username , tag_keys_to_remove )
240+
178241 def get_iam_users_access_keys (self ):
179242 """
180243 Retrieves IAM users and summarizes:
@@ -208,8 +271,8 @@ def get_iam_users_access_keys(self):
208271 for user in page ['Users' ]:
209272 username = user ['UserName' ]
210273 result [username ] = {}
211- # Access keys
212274 access_keys = self .iam_client .list_access_keys (UserName = username )['AccessKeyMetadata' ]
275+ access_keys = sorted (access_keys , key = lambda k : k ['CreateDate' ])
213276 for idx , key in enumerate (access_keys , start = 1 ):
214277 label = f"Access key { idx } "
215278 status = key ['Status' ].lower ()
@@ -229,7 +292,13 @@ def get_iam_users_access_keys(self):
229292 logger .error (f"Failed to get last used date for access key" )
230293 last_used_days = None
231294
232- result [username ][label ] = {'label' : label , 'status' : status , 'age_days' : age_days , 'last_activity_days' : last_used_days }
295+ result [username ][label ] = {
296+ 'label' : label ,
297+ 'status' : status ,
298+ 'age_days' : age_days ,
299+ 'last_activity_days' : last_used_days ,
300+ 'access_key_id' : key ['AccessKeyId' ],
301+ }
233302
234303 # Tags as list of dicts
235304 try :
@@ -312,6 +381,11 @@ def deactivate_user_access_key(self, username: str, **kwargs):
312381 Status = 'Inactive'
313382 )
314383 logger .info (f"Access key '{ access_key_id } ' deactivated for user '{ username } '" )
384+ # Tag the user so we only delete keys we deactivated (after DELETE_ACCESS_KEY_DAYS)
385+ key_num = access_key_label .split ()[- 1 ]
386+ inactive_tag_key = f"UnusedAccessKey{ key_num } InactiveDate"
387+ inactive_date = datetime .now (timezone .utc ).strftime ("%Y-%m-%d" )
388+ self .tag_user (username , [{'Key' : inactive_tag_key , 'Value' : inactive_date }])
315389 except Exception as e :
316390 logger .error (f"Failed to deactivate access key '{ access_key_id } ' for user '{ username } ': { e } " )
317391 else :
0 commit comments