"""Lambda aws_break_glass_cleanup function module.""" import datetime import re import boto3 import botocore from lambdacommon.common_config import logger import sentry_sdk from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration import config sentry_sdk.init( dsn=config.SENTRY_DSN, integrations=[AwsLambdaIntegration()], traces_sample_rate=1.0, ) client = boto3.client('iam') def get_policies(): """ Get list of expired BreakGlass IAM policies. Returns: list(str): List of expired IAM policies. """ logger.info('Searching for IAM policies') policies = [] current_date = datetime.datetime.now(datetime.timezone.utc) try: paginator = client.get_paginator('list_policies') response_iterator = paginator.paginate( Scope='Local', OnlyAttached=False, PathPrefix='/', PolicyUsageFilter='PermissionsPolicy', PaginationConfig={'PageSize': 100}, ) for response in response_iterator: for policy in response['Policies']: policy_name = policy['PolicyName'] regex = r'^{}([\w\.]+)-([\d\-]+)$'.format( re.escape(config.IAM_POLICY_PREFIX)) match = re.search(regex, policy_name) if not match: continue policy_creation_date = datetime.datetime.strptime( match.group(2), '%Y-%m-%d-%H-%M-%S') policy_creation_date = policy_creation_date.replace( tzinfo=datetime.timezone.utc) policy_expiration_date = policy_creation_date + \ datetime.timedelta( seconds=config.IAM_ROLE_SESSION_DURATION) if current_date < policy_expiration_date: logger.info(f'Skipping non-expired policy ' f'"{policy_name}" with expiration date ' f'"{policy_expiration_date}"') continue logger.info(f'Found expired policy "{policy_name}" with ' f'expiration date "{policy_expiration_date}"') policies.append(policy) except botocore.exceptions.ClientError as error: logger.exception('Error fetching BreakGlass IAM policies: {}'.format( error.response['Error']['Message'])) raise error return policies def detach_policy_from_users(policy_arn: str) -> None: """ Detach iam policy from all users it is attached to. :param policy_arn: arn of the policy to detach users from :type policy_arn: str """ paginator = client.get_paginator("list_entities_for_policy") for page in paginator.paginate(PolicyArn=policy_arn, EntityFilter="User"): for u in page.get("PolicyUsers", []): user = u["UserName"] if user not in policy_arn: logger.warning(f'Policy {policy_arn} is attached to an arbitrary {user} user') client.detach_user_policy(UserName=user, PolicyArn=policy_arn) logger.info(f"Detached from user: {user}") def delete_policy(policy_name, policy_arn): """ Delete detached BreakGlass IAM policy. Args: policy_name (str): IAM policy name. policy_arn (str): IAM policy ARN. """ logger.info(f'Deleting "{policy_name}" policy') try: client.get_policy( PolicyArn=policy_arn ) except client.exceptions.NoSuchEntityException: logger.warning(f"Policy {policy_name} doesn't exist, nothing to " f'delete') return try: client.delete_policy( PolicyArn=policy_arn ) logger.info(f'Successfully deleted {policy_name} policy') except botocore.exceptions.ClientError as error: logger.exception('Error deleting IAM policy {}: {}'.format( policy_name, error.response['Error']['Message'])) raise error def handler(event, context): """Lambda entry point.""" try: logger.info('Break-Glass Cleanup Lambda started') policies = get_policies() for policy in policies: policy_name = policy['PolicyName'] policy_arn = policy['Arn'] logger.info(f'Processing "{policy_name}" policy') detach_policy_from_users(policy_arn) delete_policy(policy_name, policy_arn) logger.info('Break-Glass Cleanup Lambda finished') response = { 'statusCode': 200, 'statusDescription': '200 OK', 'isBase64Encoded': False, } return response except Exception as error: logger.exception(str(error)) sentry_sdk.capture_exception(error) response = { 'statusCode': 500, 'statusDescription': '500 Internal Server Error', 'isBase64Encoded': False, 'headers': { 'Content-Type': 'text/html' }, 'body': f'
{str(error)}
',
        }
        return response