# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 --- schemaVersion: "0.3" description: | ### Document Name - AWSConfigRemediation-RevokeUnusedIAMUserCredentials ## What does this document do? This document revokes unused IAM passwords and active access keys. This document will deactivate expired access keys by using the [UpdateAccessKey API](https://docs.aws.amazon.com/IAM/latest/APIReference/API_UpdateAccessKey.html) and delete expired login profiles by using the [DeleteLoginProfile API](https://docs.aws.amazon.com/IAM/latest/APIReference/API_DeleteLoginProfile.html). Please note, this automation document requires AWS Config to be enabled. ## Input Parameters * AutomationAssumeRole: (Required) The ARN of the role that allows Automation to perform the actions on your behalf. * IAMResourceId: (Required) IAM resource unique identifier. * MaxCredentialUsageAge: (Required) Maximum number of days within which a credential must be used. The default value is 90 days. ## Output Parameters * RevokeUnusedIAMUserCredentialsAndVerify.Output - Success message or failure Exception. assumeRole: "{{ AutomationAssumeRole }}" parameters: AutomationAssumeRole: type: String description: (Required) The ARN of the role that allows Automation to perform the actions on your behalf. allowedPattern: '^arn:(?:aws|aws-us-gov|aws-cn):iam::\d{12}:role/[\w+=,.@-]+$' IAMResourceId: type: String description: (Required) IAM resource unique identifier. allowedPattern: ^[\w+=,.@_-]{1,128}$ MaxCredentialUsageAge: type: String description: (Required) Maximum number of days within which a credential must be used. The default value is 90 days. allowedPattern: ^(\b([0-9]|[1-8][0-9]|9[0-9]|[1-8][0-9]{2}|9[0-8][0-9]|99[0-9]|[1-8][0-9]{3}|9[0-8][0-9]{2}|99[0-8][0-9]|999[0-9]|10000)\b)$ default: "90" outputs: - RevokeUnusedIAMUserCredentialsAndVerify.Output mainSteps: - name: RevokeUnusedIAMUserCredentialsAndVerify action: aws:executeScript timeoutSeconds: 600 isEnd: true description: | ## RevokeUnusedIAMUserCredentialsAndVerify This step deactivates expired IAM User access keys, deletes expired login profiles and verifies credentials were revoked ## Outputs * Output: Success message or failure Exception. inputs: Runtime: python3.8 Handler: unused_iam_credentials_handler InputPayload: IAMResourceId: "{{ IAMResourceId }}" MaxCredentialUsageAge: "{{ MaxCredentialUsageAge }}" Script: |- import boto3 from datetime import datetime from datetime import timedelta iam_client = boto3.client("iam") config_client = boto3.client("config") responses = {} responses["DeactivateUnusedKeysResponse"] = [] def list_access_keys(user_name): return iam_client.list_access_keys(UserName=user_name).get("AccessKeyMetadata") def deactivate_key(user_name, access_key): responses["DeactivateUnusedKeysResponse"].append({"AccessKeyId": access_key, "Response": iam_client.update_access_key(UserName=user_name, AccessKeyId=access_key, Status="Inactive")}) def deactivate_unused_keys(access_keys, max_credential_usage_age, user_name): for key in access_keys: last_used = iam_client.get_access_key_last_used(AccessKeyId=key.get("AccessKeyId")).get("AccessKeyLastUsed") if last_used.get("LastUsedDate"): last_used_date = last_used.get("LastUsedDate").replace(tzinfo=None) last_used_days = (datetime.now() - last_used_date).days if last_used_days >= max_credential_usage_age: deactivate_key(user_name, key.get("AccessKeyId")) else: create_date = key.get("CreateDate").replace(tzinfo=None) days_since_creation = (datetime.now() - create_date).days if days_since_creation >= max_credential_usage_age: deactivate_key(user_name, key.get("AccessKeyId")) def get_login_profile(user_name): try: return iam_client.get_login_profile(UserName=user_name)["LoginProfile"] except iam_client.exceptions.NoSuchEntityException: return False def delete_unused_password(user_name, max_credential_usage_age): user = iam_client.get_user(UserName=user_name).get("User") password_last_used_days = 0 login_profile = get_login_profile(user_name) if login_profile and user.get("PasswordLastUsed"): password_last_used = user.get("PasswordLastUsed").replace(tzinfo=None) password_last_used_days = (datetime.now() - password_last_used).days elif login_profile and not user.get("PasswordLastUsed"): password_creation_date = login_profile.get("CreateDate").replace(tzinfo=None) password_last_used_days = (datetime.now() - password_creation_date).days if password_last_used_days >= max_credential_usage_age: responses["DeleteUnusedPasswordResponse"] = iam_client.delete_login_profile(UserName=user_name) def verify_expired_credentials_revoked(responses, user_name): if responses.get("DeactivateUnusedKeysResponse"): for key in responses.get("DeactivateUnusedKeysResponse"): key_data = next(filter(lambda x: x.get("AccessKeyId") == key.get("AccessKeyId"), list_access_keys(user_name))) if key_data.get("Status") != "Inactive": error_message = "VERIFICATION FAILED. ACCESS KEY {} NOT DEACTIVATED".format(key_data.get("AccessKeyId")) raise Exception(error_message) if responses.get("DeleteUnusedPasswordResponse"): try: iam_client.get_login_profile(UserName=user_name) error_message = "VERIFICATION FAILED. IAM USER {} LOGIN PROFILE NOT DELETED".format(user_name) raise Exception(error_message) except iam_client.exceptions.NoSuchEntityException: pass return { "output": "Verification of unused IAM User credentials is successful.", "http_responses": responses } def get_user_name(resource_id): list_discovered_resources_response = config_client.list_discovered_resources( resourceType='AWS::IAM::User', resourceIds=[resource_id] ) resource_name = list_discovered_resources_response.get("resourceIdentifiers")[0].get("resourceName") return resource_name def unused_iam_credentials_handler(event, context): iam_resource_id = event.get("IAMResourceId") user_name = get_user_name(iam_resource_id) max_credential_usage_age = int(event.get("MaxCredentialUsageAge")) access_keys = list_access_keys(user_name) unused_keys = deactivate_unused_keys(access_keys, max_credential_usage_age, user_name) delete_unused_password(user_name, max_credential_usage_age) return verify_expired_credentials_revoked(responses, user_name) outputs: - Name: Output Selector: $.Payload Type: StringMap