import datetime import logging import string import aws AUDIT_ISSUE_USER_MUST_NOT_HAVE_LOGIN_PROFILE = 0b00000001 AUDIT_ISSUE_USER_MUST_NOT_HAVE_ACTIVE_ACCESS_KEYS = 0b00000010 AUDIT_ISSUE_USER_MUST_HAVE_ROLE_TAG = 0b00000100 AUDIT_ISSUE_USER_MUST_HAVE_EMAIL_TAG = 0b00001000 AUDIT_ISSUE_USER_MUST_HAVE_MFA = 0b00010000 AUDIT_ISSUE_USER_MUST_HAVE_PASSWORD_UPDATED = 0b00100000 AUDIT_ISSUE_USER_MUST_HAVE_ACCESS_KEYS_UPDATED = 0b01000000 ACCESS_KEY_WARNING_THRESHOLD_DAYS = 90 account_id = '' account_aliases = [] def init(): """Initialize the module.""" global account_id, account_aliases account_id = aws.get_account_id() account_aliases = aws.get_account_aliases() def analyze_users(iam_users): """Run analysis on IAM users and report found issues. Args: iam_users: List of all IAM users. """ logging.info('Analyzing IAM users') for iam_user in iam_users: _analyze_user(iam_user) _report_user(iam_user) # DEBUG: Remove the conditional to run the script for all users. if iam_user['UserName'] in ['dpinchukov', 'rutra']: _remediate_user(iam_user) def _analyze_user(iam_user): """Run analysis on a single IAM user. Args: iam_user: IAM user object. """ iam_user['audit'] = { 'active': True, 'name': '', 'role': '', 'email': '', 'violations': 0 } tags = {} for tag in iam_user['Tags']: tags[tag['Key']] = tag['Value'] if 'Deactivated' in tags: iam_user['audit']['active'] = False if 'name' in tags: iam_user['audit']['name'] = tags['name'] if 'role' in tags: iam_user['audit']['role'] = tags['role'] if 'email' in tags: iam_user['audit']['email'] = tags['email'] # Analyze deactivated user. if not iam_user['audit']['active']: _verify_user_has_no_login_profile(iam_user) _verify_user_has_no_active_access_keys(iam_user) return # Analyze user with the "business" role. if iam_user['audit']['role'] == 'business': _verify_user_has_no_login_profile(iam_user) return # Analyze user with the "service-user" role. if iam_user['audit']['role'] == 'service-user': _verify_user_has_no_login_profile(iam_user) return # Analyze standard user. _verify_user_has_role_tag(iam_user) _verify_user_has_email_tag(iam_user) _verify_user_has_mfa_assigned(iam_user) _verify_user_has_password_updated(iam_user) _verify_user_has_access_keys_updated(iam_user) def _verify_user_has_no_login_profile(iam_user): """Verify that IAM user doesn't have login profile. Args: iam_user: IAM user object. """ if iam_user['LoginProfile']: iam_user['audit']['violations'] |= ( AUDIT_ISSUE_USER_MUST_NOT_HAVE_LOGIN_PROFILE ) def _verify_user_has_no_active_access_keys(iam_user): """Verify that IAM user doesn't have active access keys. Args: iam_user: IAM user object. """ for access_key in iam_user['AccessKeyMetadata']: if access_key['Status'] == 'Active': iam_user['audit']['violations'] |= ( AUDIT_ISSUE_USER_MUST_NOT_HAVE_ACTIVE_ACCESS_KEYS ) def _verify_user_has_role_tag(iam_user): """Verify that IAM user has role tag. Args: iam_user: IAM user object. """ for tag in iam_user['Tags']: if tag['Key'] == 'role': return iam_user['audit']['violations'] |= ( AUDIT_ISSUE_USER_MUST_HAVE_ROLE_TAG ) def _verify_user_has_email_tag(iam_user): """Verify that IAM user has email tag. Args: iam_user: IAM user object. """ for tag in iam_user['Tags']: if tag['Key'] == 'email': return iam_user['audit']['violations'] |= ( AUDIT_ISSUE_USER_MUST_HAVE_EMAIL_TAG ) def _verify_user_has_mfa_assigned(iam_user): """Verify that IAM user has MFA assigned. Args: iam_user: IAM user object. """ if not iam_user['MFADevices']: iam_user['audit']['violations'] |= ( AUDIT_ISSUE_USER_MUST_HAVE_MFA ) def _verify_user_has_password_updated(iam_user): """Verify that IAM user's password doesn't require reset. This verification is applied only to login profiles that are older than 7 days, so it won't affect new users. Args: iam_user: IAM user object. """ if not iam_user['LoginProfile']: return now = datetime.datetime.now(datetime.timezone.utc) login_profile_created_days_ago = ( now - iam_user['LoginProfile']['CreateDate'] ).days if login_profile_created_days_ago <= 7: return if iam_user['LoginProfile']['PasswordResetRequired']: iam_user['audit']['violations'] |= ( AUDIT_ISSUE_USER_MUST_HAVE_PASSWORD_UPDATED ) def _verify_user_has_access_keys_updated(iam_user): """Verify that IAM user doesn't have stale access keys. Args: iam_user: IAM user object. """ for access_key in iam_user['AccessKeyMetadata']: if access_key['Status'] != 'Active': continue now = datetime.datetime.now(datetime.timezone.utc) access_key_created_days_ago = (now - access_key['CreateDate']).days if access_key_created_days_ago >= ACCESS_KEY_WARNING_THRESHOLD_DAYS: iam_user['audit']['violations'] |= ( AUDIT_ISSUE_USER_MUST_HAVE_ACCESS_KEYS_UPDATED ) def _report_user(iam_user): """Report IAM user violations. Args: iam_user: IAM user object. """ name = iam_user['UserName'] active = iam_user['audit']['active'] role = iam_user['audit']['role'] email = iam_user['audit']['email'] violations = iam_user['audit']['violations'] if not violations: return if not role: role = 'undefined' if not email: email = 'undefined' prefix = 'User {} (active={}, role={}, email={})'.format( name, active, role, email) if violations & AUDIT_ISSUE_USER_MUST_NOT_HAVE_LOGIN_PROFILE: logging.warning('{} must not have login profile'.format(prefix)) if violations & AUDIT_ISSUE_USER_MUST_NOT_HAVE_ACTIVE_ACCESS_KEYS: logging.warning('{} must not have active access keys'.format(prefix)) if violations & AUDIT_ISSUE_USER_MUST_HAVE_ROLE_TAG: logging.warning('{} must have role tag'.format(prefix)) if violations & AUDIT_ISSUE_USER_MUST_HAVE_EMAIL_TAG: logging.warning('{} must have email tag'.format(prefix)) if violations & AUDIT_ISSUE_USER_MUST_HAVE_MFA: logging.warning('{} must have MFA device assigned'.format(prefix)) if violations & AUDIT_ISSUE_USER_MUST_HAVE_PASSWORD_UPDATED: logging.warning('{} must have Console password updated'.format(prefix)) if violations & AUDIT_ISSUE_USER_MUST_HAVE_ACCESS_KEYS_UPDATED: logging.warning('{} must have access keys updated'.format(prefix)) def _remediate_user(iam_user): """Run remediation actions on IAM user audit violations. Args: iam_user: IAM user object. """ active = iam_user['audit']['active'] role = iam_user['audit']['role'] if not active or role not in ['tech', 'infosec', 'business']: return login = iam_user['UserName'] if iam_user['audit']['name']: name = iam_user['audit']['name'] else: name = login email = iam_user['audit']['email'] locking_key_ids = [] for access_key in iam_user['AccessKeyMetadata']: if access_key['Status'] != 'Active': continue now = datetime.datetime.now(datetime.timezone.utc) access_key_created_days_ago = (now - access_key['CreateDate']).days if access_key_created_days_ago >= ACCESS_KEY_WARNING_THRESHOLD_DAYS: locking_key_ids.append(access_key['AccessKeyId']) continue if locking_key_ids: aliases = ', '.join(account_aliases) access_keys = ', '.join(locking_key_ids) with open('email-access-keys-update.tpl', 'r') as file: content = file.read().rstrip() tpl = string.Template(content) body = tpl.substitute(account_id=account_id, account_aliases=aliases, user_name=name, user_login=login, user_access_keys=access_keys, audit_threshold=ACCESS_KEY_WARNING_THRESHOLD_DAYS ) aws.send_email(email, 'AWS IAM access keys audit notification', body)