AWS – Python – Usuários sem MFA e chaves expiradas

Objetivo

Fazer o levantamento de usuários IAM sem MFA configurado e com chaves de acesso com mais de 180 dias de criadas.

Método

Este script faz o levantamento em todas as contas AWS dos usuários IAM que não tem MFA e das access keys com mais de 180 dias.

Para que o script execute corretamente o arquivo credentials só pode ter uma entrada de perfil para cada conta AWS. Se houver mais de uma access key/secret key no arquivo credentials, então, a saída do script ficará duplicada. Se não houver uma credencial associada a alguma conta AWS, a conta em questão não será analisada. Assim, se queremos analisar 20 contas AWS precisaremos de 20 credenciais cadastradas.

O formato de cada credencial no arquivo credentials deve ser semelhante a esta:

[aplicacoes]

aws_access_key_id = AKIADF4CHAVEFAKEOPYNDW2VF4

aws_secret_access_key = i7U!!9887chavefakehKUuyh9ajQVnC6BE4bgqW

Em máquinas Windows, por padrão, o arquivo credentials fica localizado em %USERPROFILE%\.aws\

O script também considera que as 3 primeiras linhas do arquivo credentials são destinadas para o perfil default. Se o perfil default não existe no arquivo adicione 3 linhas em branco no início do arquivo.

O script está todo comentado com a finalidade de ser o mais didático possível.

Código
				
					import boto3
import os.path
from datetime import date

'''
Este script faz o levantamento em todas as contas dos usuários IAM que não tem MFA e das access keys com mais de
180 dias.
Para que o script execute corretamente o arquivo credencials só pode ter uma entrada de perfil para cada conta AWS.
Se houver mais de uma access key/secret key no arquivo credecials a saída do script ficará duplicada.
Se não houver uma credencial associada a alguma conta AWS, a conta em questão não será analisada.
Assim, se queremos analisar 20 contas AWS precisaremos de 20 credenciais cadastradas.
O formato de cada credencial no arquivo credencials deve ser semelhante a esta:

[aplicacoes]
aws_access_key_id = AKIADF4CHAVEFAKEOPYNDW2VF4
aws_secret_access_key = i7U!!9887chavefakehKUuyh9ajQVnC6BE4bgqW

Em máquinas Windows, por padrão, o arquivo credentials fica localizado em %USERPROFILE%\.aws\ 

O script também considera que as 3 primeiras linhas do arquivo credencials são destinadas para o perfil default.
 '''

def cria_user_session(perfil):
    session = boto3.Session(profile_name=perfil)  # abre uma sessão
    return session

def cria_sts_client_object(session):
    client_sts = session.client('sts')  # abre um cliente do tipo sts para recuperar o account_id.
    return client_sts

def cria_iam_client_object(session):
    client_iam = session.client('iam')  # abre um cliente do tipo iam para criar os usuários.
    return client_iam

def lista_aws_accounts():
    path = "~/.aws/credentials"    # path do arquivo credencials.
    full_path = os.path.expanduser(path)    # full_path armazena o path completo do arquivo credencials.

    contasAWS = []

    with open(full_path, 'r') as f:    # abre o arquivo de credenciais.
        next(f)    # ignora a credencial "default" (3 primeiras linhas).
        next(f)
        next(f)
        lines = f.readlines()    # lê o arquivo e joga as linhas do arquivo como itens de lista lines.
        f.close()

    for line in lines:    # faz um loop em lines para verificar se é um nome de profile ou access_key ou secret_access_key.
        if line.startswith('['):    # se a linha começar com colchetes então é o nome do perfil AWS.
            perfil = line[1:-2]
            contasAWS.append(perfil)

    return contasAWS

def lista_usuarios_da_conta(client_iam):

    iam_users = []

    response_iam = client_iam.list_users(MaxItems=1000)  # Limite máximo de usuários retornados por página 1000

    for user in response_iam['Users']:  # cria uma lista com todos os IAM users.
        iam_users.append(user['UserName'])

    while 'Marker' in response_iam:  # Adiciona novos usuários à lista, caso ocorra paginação. Procura pela key \
        # "Marker" no dicionário.
        response_iam = client_iam.list_users(Marker=response_iam['Marker'])  # Faz busca na nova página usando o \
        # parâmetro "Marker" na chamada do método list_users.
        for user in response_iam['Users']:  # Adiciona os usuários da nova página na lista iam_users.
            iam_users.append(user['UserName'])

    return iam_users

def conta_usuarios_sem_mfa(iam_users, client_iam):
    count_no_mfa = 0  # variável que armazena o número de usuários sem MFA.

    for iam_user in iam_users:  # cria uma lista dos usuários que não tem MFA e conta o número de usuários.
        resp1 = client_iam.list_mfa_devices(UserName=iam_user)  # recupera os dados de MFA de um usuário.
        if not resp1['MFADevices']:  # se não houver a key 'MFADevices', então, conta como usuário sem MFA.
            count_no_mfa = count_no_mfa + 1

    return count_no_mfa

def lista_usuarios_sem_mfa(iam_users, client_iam):
    no_mfa_users = []  # lista de todos os usuários sem MFA

    for iam_user in iam_users:  # cria uma lista dos usuários que não tem MFA e conta o número de usuários.
        resp1 = client_iam.list_mfa_devices(UserName=iam_user)  # recupera os dados de MFA de um usuário.
        if not resp1['MFADevices']:  # se não houver a key 'MFADevices', então, conta como usuário sem MFA.
            no_mfa_users.append(iam_user)  # adiciona o usuário na lista de usuários sem MFA.

    return no_mfa_users

def conta_chaves_expiradas(iam_users, client_iam):
    count_out_key = 0  # variável que armazena o número de chaves de acesso sem rotação.
    key_age = []  # lista da idade de criação das keys em dias

    for iam_user in iam_users:  # conta os usuários com a chave expirada.
        resp2 = client_iam.list_access_keys(UserName=iam_user)  # recupera os dados de chaves de um usuário.

        if len(resp2['AccessKeyMetadata']) > 0:  # verifica se exite uma AccessKey para o usuário e se houver \
            # vai listar a idade da key.
            accesskeydate = resp2['AccessKeyMetadata'][0]['CreateDate'].date()  # recupera a idade da chave.
            currentdate = date.today()  # recupera a data corrente.
            active_days = currentdate - accesskeydate  # calcula a idade da chave em dias.
            if active_days.days > 180:  # imprime se a key for maior que 180 dias e conta o número de keys expiradas.
                count_out_key = count_out_key + 1
    return count_out_key

def lista_usuarios_chaves_expiradas(iam_users, client_iam):
    key_age = []  # lista da idade de criação das keys em dias
    users_out_key = {}

    for iam_user in iam_users:  # conta os usuários com a chave expirada.
        resp2 = client_iam.list_access_keys(UserName=iam_user)  # recupera os dados de chaves de um usuário.

        if len(resp2['AccessKeyMetadata']) > 0:  # verifica se exite uma AccessKey para o usuário e se houver \
            # vai listar a idade da key.
            accesskeydate = resp2['AccessKeyMetadata'][0]['CreateDate'].date()  # recupera a idade da chave.
            currentdate = date.today()  # recupera a data corrente.
            active_days = currentdate - accesskeydate  # calcula a idade da chave em dias.
            if active_days.days > 180:  # imprime se a key for maior que 180 dias e conta o número de keys expiradas.
                users_out_key[iam_user] = active_days.days
    return users_out_key

def main():

    contasAWS = lista_aws_accounts()

    for perfil in contasAWS:

        session = cria_user_session(perfil)  # abre uma sessão.

        client_sts = cria_sts_client_object(session)  # abre um cliente do tipo sts para recuperar o account_id.

        client_iam = cria_iam_client_object(session)  # abre um cliente do tipo iam para buscar os usuários.

        iam_users = []  # lista com todos os usuários IAM de uma conta AWS

        iam_users = lista_usuarios_da_conta(client_iam)

        no_mfa_users = lista_usuarios_sem_mfa(iam_users, client_iam)

        count_no_mfa = conta_usuarios_sem_mfa(iam_users, client_iam)

        count_out_key = conta_chaves_expiradas(iam_users, client_iam)

        users_out_key = lista_usuarios_chaves_expiradas(iam_users, client_iam)

        print('Account_ID :', client_sts.get_caller_identity().get('Account'))  # imprime o ID da conta AWS.

        print('→ Número de Usuários IAM sem MFA: ', count_no_mfa)
        print('→ Usuários IAM sem MFA: ')
        for user in no_mfa_users:    # iteração para imprimir os usuários sem MFA.
            print('\tUser: {}'.format(user))

        print('→ Número de chaves expiradas: ', count_out_key)
        print('→ Lista de usuários com access key expirada e idade da chave em dias:')
        for user_out_key, days in users_out_key.items():
            print('\tUser: {}\tDays: {}'.format(user_out_key, days))
        print('++++++++++++++++++++++++++++++++++++')    # printa separador.

if __name__=="__main__":
    main()

				
			

It’s done!

Bruno Veiga

Bruno Veiga

Arquiteto Cloud e Arquiteto de Soluções. Me dedicando em compartilhar conhecimento e ajudar empresas a encontrar as melhores soluções tecnológicas para os problemas do negócio com agilidade, segurança, equipes alinhadas e dentro do orçamento.