Como Gerar Relatório de Usuários Inativos com Python

13 min de leitura Automação
Como Gerar Relatório de Usuários Inativos com Python

Automatize a Auditoria de Segurança: Relatório de Usuários Inativos com Python

A administração de sistemas em ambientes Linux exige vigilância constante. Um dos vetores de ataque mais comuns e subestimados é o acúmulo de contas de usuário inativas ao longo do tempo. Seja por desligamentos não processados, projetos abandonados ou testes temporários esquecidos, cada conta ativa representa um potencial ponto de entrada para invasores. A prática recomendada de segurança (hardening) determina que apenas usuários com necessidade funcional devem permanecer ativos no sistema.

Neste tutorial técnico, demonstraremos como criar um script Python robusto para identificar, analisar e gerar relatórios de usuários inativos. Utilizaremos a biblioteca padrão do Python, garantindo portabilidade sem dependências externas complexas. O objetivo é fornecer uma ferramenta de automação que sysadmins possam integrar em pipelines CI/CD ou agendar via cron jobs para manter a postura de segurança do servidor sempre atualizada.

1. Fundamentos da Identificação de Usuários Inativos

Antes de escrever o código, é crucial entender como o Linux gerencia usuários e como definimos "inatividade". Em sistemas Unix/Linux, as informações básicas dos usuários estão armazenadas no arquivo /etc/passwd. No entanto, para determinar a inatividade, precisamos cruzar dados desse arquivo com logs de autenticação ou registros de uso.

Definição Técnica: Consideraremos um usuário como "inativo" se:

  • Não possui shell interativo válido (ex: /usr/sbin/nologin).
  • O shell é válido, mas não houve logins autenticados nos últimos X dias (configurável).
  • A conta foi criada há muito tempo e nunca foi utilizada para ações de sistema críticas.

Para este script, focaremos na análise do arquivo /etc/passwd combinada com o comando lastlog, que registra a última vez que um usuário fez login. Essa abordagem oferece uma visão precisa da atividade real de autenticação.

2. Preparando o Ambiente de Desenvolvimento

Não é necessário instalar bibliotecas de terceiros como pandas ou psutil para esta tarefa básica, pois o Python 3 já oferece módulos poderosos para manipulação de arquivos e execução de comandos do sistema. Isso mantém o script leve e seguro.

Certifique-se de que o ambiente possui as seguintes ferramentas:

  • Sistema operacional Linux (Ubuntu, CentOS, Debian, RHEL).
  • Python 3.6 ou superior.
  • Permissões de root ou usuário com acesso de leitura ao arquivo /etc/passwd e ao comando lastlog.

Crie um diretório para o projeto:

mkdir ~/audit-scripts
cd ~/audit-scripts
touch check_inactive_users.py

3. Estrutura do Script Python

O script será dividido em três funções principais para garantir modularidade e facilidade de manutenção:

  1. parse_passwd(): Lê e interpreta o arquivo /etc/passwd.
  2. get_last_login(): Executa o comando do sistema para obter a data do último login.
  3. generate_report(): Processa os dados, aplica filtros e gera a saída (JSON ou CSV).

Abaixo, apresentamos o código completo. Copie e cole em seu arquivo check_inactive_users.py.

#!/usr/bin/env python3
"""
Script de Auditoria de Usuários Inativos
Autor: Toda Solução - Depto. Técnico
Descrição: Identifica usuários do sistema sem login nos últimos X dias.
"""

import os
import subprocess
import argparse
import json
import csv
from datetime import datetime, timedelta
from pathlib import Path

# Configurações Padrão
DEFAULT_INACTIVITY_DAYS = 90
PASSWD_FILE = "/etc/passwd"
OUTPUT_FORMATS = ["json", "csv"]

def parse_passwd_file(passwd_path):
    """
    Lê o arquivo /etc/passwd e retorna uma lista de dicionários com informações do usuário.
    Ignora usuários de sistema padrão (UID < 1000) se necessário, mas aqui listamos todos para auditoria completa.
    """
    users = []
    try:
        with open(passwd_path, 'r') as f:
            for line in f:
                if line.startswith('#'):
                    continue
                
                parts = line.strip().split(':')
                if len(parts) >= 7:
                    user_info = {
                        'username': parts[0],
                        'uid': int(parts[2]),
                        'gid': int(parts[3]),
                        'home_dir': parts[5],
                        'shell': parts[6]
                    }
                    users.append(user_info)
    except PermissionError:
        print("Erro: Permissão negada para ler /etc/passwd. Execute como root.")
        return []
    
    return users

def get_last_login_time(username):
    """
    Usa o comando 'lastlog' para encontrar a data do último login de um usuário específico.
    Retorna uma string formatada ou None se nunca tiver feito login.
    """
    try:
        # O comando lastlog pode variar ligeiramente entre distros, mas geralmente funciona assim:
        result = subprocess.run(
            ['lastlog', '-u', username], 
            stdout=subprocess.PIPE, 
            stderr=subprocess.PIPE,
            text=True
        )
        
        output = result.stdout.strip()
        
        # Verifica se o usuário nunca fez login (geralmente mostra "Password Last Set" ou similar com "<never>")
        if "<never>" in output or "Password last set" in output and "never" in output.lower():
            return None
        
        # Tenta extrair a data. O formato do lastlog é geralmente:
        # Username     Port     From           Latest
        # user1        tty2     localhost      Mon Jan 15 09:30 +0300 2023
        parts = output.split()
        if len(parts) >= 6:
            # Reconstrói a data e hora (ajuste conforme o idioma do sistema)
            # Nota: Isso é uma simplificação. Em produção robusta, use lib dateutil ou parse específico.
            # Para este exemplo, assumimos que 'lastlog' retorna datas legíveis.
            # Uma abordagem mais segura é usar a data de modificação do .bash_history ou logs auth.log
            # Mas vamos usar o último campo da saída padrão do lastlog se estiver no formato ISO ou similar.
            
            # Hack rápido para extrair data: lastlog mostra "Day Month Date Time Zone Year"
            # Vamos tentar parsear manualmente os últimos elementos
            date_str = f"{parts[4]} {parts[3]} {parts[5]} {parts[6]}" 
            try:
                # Formato comum em sistemas EN: Mon Jan 15 09:30 +0300 2023
                # Em PT-BR pode variar. Vamos usar datetime.strptime com formato genérico ou lidar com exceções.
                # Para máxima compatibilidade sem bibliotecas externas, vamos confiar que o sistema retorna datas consistentes.
                # Se falhar, retornamos None (inativo).
                dt_obj = datetime.strptime(date_str, "%a %b %d %H:%M %z %Y")
                return dt_obj
            except ValueError:
                # Tenta outro formato comum se o primeiro falhar
                try:
                    dt_obj = datetime.strptime(date_str, "%a %b %d %H:%M %Y")
                    return dt_obj
                except ValueError:
                    return None
        return None
        
    except Exception as e:
        # Se lastlog falhar (ex: usuário não existe no banco de dados de logs), retorna None
        return None

def analyze_users(users, threshold_days):
    """
    Cruza os dados do /etc/passwd com o último login.
    Filtra usuários que excederam o tempo de inatividade.
    """
    inactive_users = []
    now = datetime.now()
    threshold_date = now - timedelta(days=threshold_days)
    
    for user in users:
        # Ignora usuários de sistema padrão (UID < 1000) para evitar ruído, a menos que seja explícito
        if user['uid'] < 1000 and user['username'] != 'root':
            continue
            
        last_login = get_last_login_time(user['username'])
        
        record = {
            "username": user['username'],
            "uid": user['uid'],
            "home_dir": user['home_dir'],
            "shell": user['shell'],
            "last_login": str(last_login) if last_login else "Never",
            "status": "INACTIVE"
        }
        
        # Se last_login for None ou anterior ao limite, marca como inativo
        if last_login is None or last_login < threshold_date:
            record['days_inactive'] = (now - last_login).days if last_login else "N/A"
            inactive_users.append(record)
            
    return inactive_users

def save_report(data, output_file, fmt):
    """
    Salva o relatório em JSON ou CSV.
    """
    try:
        if fmt == 'json':
            with open(output_file, 'w') as f:
                json.dump(data, f, indent=4, default=str)
        elif fmt == 'csv':
            if not data:
                return
            
            fieldnames = ['username', 'uid', 'home_dir', 'shell', 'last_login', 'days_inactive']
            with open(output_file, 'w', newline='') as f:
                writer = csv.DictWriter(f, fieldnames=fieldnames)
                writer.writeheader()
                writer.writerows(data)
        print(f"Relatório salvo com sucesso em: {output_file}")
    except IOError as e:
        print(f"Erro ao salvar arquivo: {e}")

def main():
    parser = argparse.ArgumentParser(description='Gerador de Relatório de Usuários Inativos')
    parser.add_argument('-d', '--days', type=int, default=DEFAULT_INACTIVITY_DAYS,
                        help=f'Dias de inatividade para considerar o usuário como inativo (padrão: {DEFAULT_INACTIVITY_DAYS})')
    parser.add_argument('-o', '--output', type=str, default='inactive_users_report.json',
                        help='Nome do arquivo de saída')
    parser.add_argument('-f', '--format', type=str, choices=OUTPUT_FORMATS, default='json',
                        help='Formato de saída (json ou csv)')
    
    args = parser.parse_args()
    
    print(f"[*] Iniciando auditoria...")
    print(f"[*] Limiar de inatividade: {args.days} dias")
    
    # 1. Ler usuários
    users = parse_passwd_file(PASSWD_FILE)
    if not users:
        print("[-] Erro ao ler usuários. Verifique permissões.")
        return

    # 2. Analisar inatividade
    inactive_list = analyze_users(users, args.days)
    
    # 3. Exibir resumo no console
    print(f"[+] Usuários analisados: {len(users)}")
    print(f"[!] Usuários inativos encontrados: {len(inactive_list)}")
    
    if inactive_list:
        print("\n--- Resumo de Usuários Inativos ---")
        for u in inactive_list[:5]: # Mostra apenas os 5 primeiros no console
            print(f"  - {u['username']} (UID: {u['uid']}) - Último login: {u['last_login']}")
        if len(inactive_list) > 5:
            print(f"  ... e mais {len(inactive_list) - 5} usuários.")
    
    # 4. Salvar relatório completo
    save_report(inactive_list, args.output, args.format)

if __name__ == "__main__":
    main()

4. Explicação Técnica dos Componentes

O script acima utiliza boas práticas de engenharia de software para ambientes de produção:

Análise de UID: A linha if user['uid'] < 1000 and user['username'] != 'root': continue é crítica. Em distribuições modernas, usuários de sistema (como www-data, mysql, daemon) possuem UIDs baixos. Eles muitas vezes nunca fazem login interativo, mas são essenciais para o funcionamento do serviço. Ignorá-los evita alertas falsos positivos.

Manipulação de Data: A função get_last_login_time tenta interpretar a saída do comando nativo lastlog. O formato da data pode variar dependendo da localização (locale) do servidor. Em ambientes corporativos, recomenda-se forçar o ambiente para inglês (LC_ALL=C) antes de executar o subprocesso para garantir consistência na análise de strings.

Saída Flexível: A opção de exportar em JSON ou CSV permite que o relatório seja ingerido por outras ferramentas. Por exemplo, um JSON pode ser enviado via webhook para um sistema de ticketing (como Jira ou ServiceNow), enquanto um CSV é ideal para análise no Excel.

5. Executando o Script

Para executar o script, você precisa de permissões elevadas, pois a leitura de logs de autenticação e certos metadados pode requerer acesso privilegiado.

Comando Básico (Padrão):

sudo python3 check_inactive_users.py

Comando Personalizado (90 dias, formato CSV):

sudo python3 check_inactive_users.py -d 90 -o report_90dias.csv -f csv

Comando para Auditoria Rápida (30 dias):

sudo python3 check_inactive_users.py -d 30 -o critical_report.json

6. Automatização com Cron

A verdadeira força da automação reside na execução recorrente. Configure um job no cron para rodar este script semanalmente e enviar o relatório por e-mail ou armazená-lo em um repositório versionado.

Edite o crontab do root:

sudo crontab -e

Adicione a seguinte linha para rodar toda segunda-feira às 06:00 e salvar o relatório em um diretório específico:

0 6 * * 1 /usr/bin/python3 /root/audit-scripts/check_inactive_users.py -d 90 -o /var/log/security/inactive_users_$(date +\%Y\%m\%d).json

Dica de Segurança: Certifique-se de que o diretório /var/log/security/ tenha permissões restritas (chmod 700) para evitar que usuários comuns acessem dados sensíveis de auditoria.

7. Integração com Ferramentas de Monitoramento

Para profissionais de administração de sistemas avançados, este script pode ser a base para alertas proativos. Se o número de usuários inativos exceder um limiar crítico (ex: mais de 5 novos usuários inativos na semana), o script pode emitir um código de saída diferente ou acionar uma API.

Você pode integrar isso ao Zabbix, Prometheus (via Exportador de Texto) ou até mesmo ao Slack/Discord via Webhooks. A estrutura JSON gerada facilita a parseamento por scripts shell externos ou ferramentas de orquestração como Ansible.

8. Boas Práticas e Considerações Finais

Ao implementar essa solução em produção, considere os seguintes pontos:

  1. Backup do /etc/passwd: Antes de qualquer ação corretiva (como bloquear contas), tenha um backup recente.
  2. Comunicação: Em ambientes corporativos, notifique os gestores dos usuários antes de desativar contas. O script ajuda a identificar *quem* está inativo, mas o processo humano de validação é essencial.
  3. Logs de Auditoria: Registre as execuções do script em /var/log/syslog ou arquivos dedicados para rastrear quem acessou os dados de auditoria.
  4. Migração de Dados: Se um usuário inativo era responsável por arquivos críticos, o script deve ser estendido para verificar a existência de arquivos em seu home_dir antes do bloqueio.

A segurança não é um estado, é um processo. Utilizar scripts como este transforma a gestão de identidades de uma tarefa manual e propensa a erros em um fluxo contínuo e auditable. Com Python, você ganha flexibilidade para adaptar o script às necessidades específicas da sua infraestrutura, seja ela on-premise ou cloud.

Para dúvidas sobre implementação ou integração com serviços de hospedagem e VPS, consulte a documentação técnica da Toda Solução ou entre em contato com nosso suporte especializado.

Compartilhar: Link copiado!
Esse tutorial foi útil?

Comentários (0)

Seja o primeiro a comentar.

Deixe seu comentário

Seu comentário será analisado antes de ser publicado.

0/2000