---
title: "Python para SOC: análise de logs e alertas defensivos"
url: "https://python.dev.br/blog/python-analise-logs-soc-seguranca/"
markdown_url: "https://python.dev.br/blog/python-analise-logs-soc-seguranca.MD"
description: "Aprenda a analisar logs com Python para SOC: leia JSON Lines, normalize eventos, detecte falhas repetidas e gere alertas defensivos com testes na prática."
date: "2026-08-24"
author: "Equipe Python Dev BR"
---

# Python para SOC: análise de logs e alertas defensivos

Aprenda a analisar logs com Python para SOC: leia JSON Lines, normalize eventos, detecte falhas repetidas e gere alertas defensivos com testes na prática.

**Python para SOC** é útil para transformar logs brutos em eventos consistentes, contar padrões e gerar alertas que uma pessoa analista possa revisar. Um bom projeto inicial lê dados sintéticos em JSON Lines, valida campos, agrupa falhas de autenticação dentro de uma janela de tempo e exporta um relatório. A regra não “detecta ataques” sozinha: ela produz um sinal defensivo que precisa de contexto, investigação e procedimentos definidos pela organização.

Neste tutorial, você vai construir esse fluxo apenas com a biblioteca padrão do Python e `pytest`. O exemplo não faz varredura, exploração, coleta de terceiros nem conexão com sistemas reais. Essa abordagem é adequada para estudar, montar portfólio e se preparar para [estágio em segurança da informação com Python](/carreira/como-conseguir-estagio-seguranca-informacao-python/) sem ultrapassar limites éticos.

## O que é SOC e onde Python entra

SOC é a sigla de *Security Operations Center*, ou Centro de Operações de Segurança. Dependendo da empresa, a equipe acompanha alertas, investiga eventos, documenta incidentes, administra processos de detecção e trabalha com ferramentas como SIEM, EDR, plataformas de identidade, serviços de nuvem e sistemas de chamados.

Python aparece como ferramenta de apoio em tarefas como:

- converter logs entre formatos;
- validar campos antes da ingestão;
- remover duplicidades;
- agrupar eventos por usuário, IP, ativo ou período;
- enriquecer indicadores por meio de APIs internas e autorizadas;
- gerar relatórios Markdown, CSV ou JSON;
- testar regras de detecção com dados conhecidos;
- automatizar etapas repetitivas de um playbook;
- conferir se um lote contém segredos ou dados indevidos antes do compartilhamento.

Em um ambiente profissional, essas automações devem respeitar controle de acesso, retenção, privacidade, revisão de código e gestão de mudanças. O script deste artigo é um laboratório. Ele ensina o raciocínio, mas não deve ser conectado à produção sem revisão da equipe responsável.

Se seu objetivo é carreira, veja também [Python para cibersegurança júnior](/carreira/python-ciberseguranca-junior/). O guia explica as trilhas de SOC, automação, resposta a incidentes e portfólio; aqui, o foco é a implementação técnica.

## Arquitetura do projeto

Vamos criar um analisador pequeno com esta estrutura:

```text
soc-logs-python/
├── dados/
│   └── autenticacao.jsonl
├── src/
│   ├── __init__.py
│   └── analisador.py
├── tests/
│   └── test_analisador.py
└── README.md
```

O arquivo usa **JSON Lines** (`.jsonl`): cada linha contém um objeto JSON independente. Isso permite processar o arquivo aos poucos, sem carregar tudo na memória, e facilita identificar qual linha está inválida.

Crie o ambiente e instale somente a dependência de desenvolvimento:

```bash
python -m venv .venv
source .venv/bin/activate
python -m pip install pytest
```

No PowerShell:

```powershell
python -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install pytest
```

Você também pode usar o [uv para gerenciar o projeto](/blog/uv-gerenciador-pacotes-python/), mas a lógica do analisador será a mesma.

## Criando logs sintéticos e seguros

Salve em `dados/autenticacao.jsonl`:

```json
{"timestamp":"2026-08-24T09:00:00+00:00","usuario":"ana","ip":"192.0.2.10","resultado":"falha","sistema":"portal"}
{"timestamp":"2026-08-24T09:00:35+00:00","usuario":"ana","ip":"192.0.2.10","resultado":"falha","sistema":"portal"}
{"timestamp":"2026-08-24T09:01:10+00:00","usuario":"ana","ip":"192.0.2.10","resultado":"falha","sistema":"portal"}
{"timestamp":"2026-08-24T09:01:42+00:00","usuario":"ana","ip":"192.0.2.10","resultado":"falha","sistema":"portal"}
{"timestamp":"2026-08-24T09:02:05+00:00","usuario":"ana","ip":"192.0.2.10","resultado":"sucesso","sistema":"portal"}
{"timestamp":"2026-08-24T09:04:00+00:00","usuario":"bruno","ip":"198.51.100.8","resultado":"sucesso","sistema":"vpn"}
```

Os blocos `192.0.2.0/24` e `198.51.100.0/24` são reservados para documentação. Os nomes e sistemas são fictícios. Em um repositório público, não troque esse conjunto por logs da empresa em que você trabalha ou estagia.

Logs reais podem conter e-mail, CPF, IP interno, hostname, caminho de arquivo, identificador de sessão, token e outras informações sensíveis. “Remover a senha” não torna automaticamente um log publicável. Use dados produzidos especificamente para o laboratório.

## Modelando e validando cada evento

Crie `src/analisador.py`:

```python
from __future__ import annotations

import json
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Iterator


RESULTADOS_VALIDOS = {"sucesso", "falha"}


@dataclass(frozen=True)
class EventoAutenticacao:
    timestamp: datetime
    usuario: str
    ip: str
    resultado: str
    sistema: str


def converter_evento(dados: dict) -> EventoAutenticacao:
    campos = {"timestamp", "usuario", "ip", "resultado", "sistema"}
    ausentes = campos - dados.keys()

    if ausentes:
        nomes = ", ".join(sorted(ausentes))
        raise ValueError(f"Campos ausentes: {nomes}")

    resultado = str(dados["resultado"]).strip().lower()
    if resultado not in RESULTADOS_VALIDOS:
        raise ValueError(f"Resultado inválido: {resultado}")

    return EventoAutenticacao(
        timestamp=datetime.fromisoformat(str(dados["timestamp"])),
        usuario=str(dados["usuario"]).strip(),
        ip=str(dados["ip"]).strip(),
        resultado=resultado,
        sistema=str(dados["sistema"]).strip(),
    )


def ler_eventos(caminho: Path) -> Iterator[EventoAutenticacao]:
    with caminho.open(encoding="utf-8") as arquivo:
        for numero_linha, linha in enumerate(arquivo, start=1):
            if not linha.strip():
                continue

            try:
                dados = json.loads(linha)
                yield converter_evento(dados)
            except (json.JSONDecodeError, TypeError, ValueError) as erro:
                raise ValueError(
                    f"Evento inválido na linha {numero_linha}: {erro}"
                ) from erro
```

Há quatro escolhas importantes nesse código:

1. `dataclass(frozen=True)` cria um registro legível e evita alteração acidental depois da validação;
2. `datetime.fromisoformat()` converte a data para um tipo adequado a comparações;
3. o leitor é um gerador, portanto processa uma linha por vez;
4. a mensagem de erro inclui o número da linha, facilitando a correção do lote.

Em produção, você talvez envie registros inválidos para uma quarentena em vez de interromper todo o processamento. Para o primeiro projeto, falhar de forma explícita é melhor do que ignorar silenciosamente um evento ruim. Leia [tratamento de erros em Python](/blog/tratamento-de-erros-python/) para comparar estratégias.

## Regra defensiva: falhas repetidas em uma janela

Uma quantidade alta de falhas do mesmo usuário e IP em poucos minutos merece triagem. Porém, o limite é apenas uma hipótese. Pode ser senha expirada, aplicativo antigo, serviço mal configurado ou atividade maliciosa.

Adicione ao mesmo arquivo:

```python
from collections import defaultdict, deque
from dataclasses import asdict
from datetime import timedelta


@dataclass(frozen=True)
class Alerta:
    regra: str
    usuario: str
    ip: str
    sistema: str
    inicio: datetime
    fim: datetime
    total_falhas: int


def detectar_falhas_repetidas(
    eventos: Iterator[EventoAutenticacao],
    limite: int = 4,
    janela: timedelta = timedelta(minutes=5),
) -> list[Alerta]:
    historico: dict[
        tuple[str, str, str],
        deque[datetime],
    ] = defaultdict(deque)
    alertas: list[Alerta] = []

    for evento in sorted(eventos, key=lambda item: item.timestamp):
        if evento.resultado != "falha":
            continue

        chave = (evento.usuario, evento.ip, evento.sistema)
        ocorrencias = historico[chave]
        inicio_minimo = evento.timestamp - janela

        while ocorrencias and ocorrencias[0] < inicio_minimo:
            ocorrencias.popleft()

        ocorrencias.append(evento.timestamp)

        if len(ocorrencias) == limite:
            alertas.append(
                Alerta(
                    regra="falhas_repetidas_autenticacao",
                    usuario=evento.usuario,
                    ip=evento.ip,
                    sistema=evento.sistema,
                    inicio=ocorrencias[0],
                    fim=ocorrencias[-1],
                    total_falhas=len(ocorrencias),
                )
            )

    return alertas
```

A `deque` guarda apenas os horários relevantes para cada combinação de usuário, IP e sistema. Antes de adicionar o evento atual, o código remove ocorrências antigas que ficaram fora da janela de cinco minutos.

A condição `len(ocorrencias) == limite`, em vez de `>=`, evita gerar um alerta novo a cada falha posterior da mesma sequência. Uma implementação real precisaria definir reabertura, supressão, severidade e identificação única do alerta.

### O que essa regra não prova

Ela não prova que houve brute force, comprometimento ou incidente. Também não considera:

- IPs compartilhados por NAT ou proxy;
- contas de serviço;
- mudança recente de senha;
- falha de integração;
- autenticação multifator;
- perfil histórico do usuário;
- criticidade do sistema;
- lista de origens conhecidas;
- sucesso posterior e atividade da sessão.

Esse trecho é importante no README e na entrevista. Segurança defensiva exige distinguir **sinal**, **evidência** e **conclusão**.

## Exportando um relatório JSON

Adicione:

```python
def serializar_alerta(alerta: Alerta) -> dict:
    dados = asdict(alerta)
    dados["inicio"] = alerta.inicio.isoformat()
    dados["fim"] = alerta.fim.isoformat()
    return dados


def salvar_relatorio(alertas: list[Alerta], destino: Path) -> None:
    conteudo = {
        "gerado_em": datetime.now().astimezone().isoformat(),
        "total_alertas": len(alertas),
        "alertas": [serializar_alerta(alerta) for alerta in alertas],
    }

    destino.write_text(
        json.dumps(conteudo, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )
```

E um ponto de entrada:

```python
if __name__ == "__main__":
    origem = Path("dados/autenticacao.jsonl")
    destino = Path("relatorio_alertas.json")

    eventos = ler_eventos(origem)
    alertas = detectar_falhas_repetidas(eventos)
    salvar_relatorio(alertas, destino)

    print(f"Relatório salvo em {destino} com {len(alertas)} alerta(s).")
```

Execute:

```bash
python -m src.analisador
```

A saída esperada terá um alerta para `ana`, com quatro falhas dentro da janela. O `ensure_ascii=False` mantém acentos legíveis. Em um sistema corporativo, o relatório precisaria de política de retenção, acesso restrito e sanitização de campos; não salve indiscriminadamente dados de autenticação em diretórios públicos.

Para projetos maiores, use [logging em Python](/blog/logging-em-python/) em vez de depender apenas de `print`, mas evite registrar tokens, cookies e payloads sensíveis.

## Escrevendo testes com pytest

Crie `tests/test_analisador.py`:

```python
from datetime import datetime, timedelta, timezone

import pytest

from src.analisador import (
    EventoAutenticacao,
    converter_evento,
    detectar_falhas_repetidas,
)


def evento(minuto: int, resultado: str = "falha") -> EventoAutenticacao:
    return EventoAutenticacao(
        timestamp=datetime(2026, 8, 24, 9, minuto, tzinfo=timezone.utc),
        usuario="ana",
        ip="192.0.2.10",
        resultado=resultado,
        sistema="portal",
    )


def test_detecta_quatro_falhas_em_cinco_minutos() -> None:
    eventos = [evento(0), evento(1), evento(2), evento(3)]

    alertas = detectar_falhas_repetidas(
        iter(eventos),
        limite=4,
        janela=timedelta(minutes=5),
    )

    assert len(alertas) == 1
    assert alertas[0].total_falhas == 4
    assert alertas[0].usuario == "ana"


def test_nao_detecta_eventos_fora_da_janela() -> None:
    eventos = [evento(0), evento(2), evento(4), evento(8)]

    alertas = detectar_falhas_repetidas(
        iter(eventos),
        limite=4,
        janela=timedelta(minutes=5),
    )

    assert alertas == []


def test_ignora_autenticacao_com_sucesso() -> None:
    eventos = [evento(0), evento(1), evento(2), evento(3, "sucesso")]

    assert detectar_falhas_repetidas(iter(eventos), limite=4) == []


def test_rejeita_campo_obrigatorio_ausente() -> None:
    dados = {
        "timestamp": "2026-08-24T09:00:00+00:00",
        "usuario": "ana",
        "resultado": "falha",
        "sistema": "portal",
    }

    with pytest.raises(ValueError, match="ip"):
        converter_evento(dados)
```

Rode:

```bash
pytest -q
```

Os testes mostram mais do que domínio de sintaxe. Eles documentam o significado da regra: quantidade, janela, comportamento diante de sucesso e validação de entrada. Em um teste técnico, essa clareza costuma valer mais do que adicionar muitas funcionalidades sem cobertura.

Aprofunde fixtures e parametrização no guia de [testes com pytest](/guias/testes-com-pytest/).

## Como reduzir falsos positivos

Uma regra inicial deve ser simples, mas você pode evoluí-la com cuidado:

### Separar conta humana de conta de serviço

Contas técnicas podem falhar repetidamente por segredo expirado. Em vez de ignorá-las, use uma regra e um fluxo de atendimento específicos.

### Considerar o sistema afetado

Quatro falhas em um portal de treinamento e quatro falhas em uma conta administrativa não têm a mesma prioridade. Criticidade é contexto, não propriedade do IP.

### Correlacionar sucesso após falhas

Um sucesso depois de várias falhas pode merecer revisão, mas também pode ser apenas a pessoa finalmente digitando a senha correta. Combine com origem incomum, horário, dispositivo e política de identidade.

### Aplicar supressão

Se a causa já está em investigação, gerar centenas de alertas iguais aumenta ruído. Uma chave de deduplicação e um período de supressão ajudam, desde que não escondam mudança relevante.

### Registrar a justificativa

Inclua no alerta os campos que explicam por que ele foi criado: regra, período, total, usuário, origem e sistema. Uma pontuação misteriosa sem evidência dificulta a triagem.

O objetivo não é zerar falsos positivos. É produzir sinais úteis, explicáveis e compatíveis com a capacidade de investigação do time.

## JSON Lines, CSV ou texto livre?

| Formato | Vantagem | Limitação |
| --- | --- | --- |
| JSON Lines | campos explícitos e leitura incremental | arquivos maiores que CSV em alguns cenários |
| CSV | simples para tabela e planilha | tipos, delimitadores e quebras exigem cuidado |
| Texto livre | comum em sistemas antigos | parsing frágil e dependente do formato |
| Syslog estruturado | padrão conhecido em operações | variações entre produtores e transporte |

Para portfólio, JSON Lines facilita validação e demonstra uso real de [JSON com Python](/blog/trabalhando-com-json-python/). Se quiser praticar dados tabulares, adapte o projeto para CSV com o módulo `csv`. Evite começar por regex complexa sobre texto livre: primeiro entregue um pipeline confiável com formato conhecido.

## Como transformar o projeto em portfólio

Um repositório convincente deve conter:

- descrição do problema;
- aviso de dados totalmente sintéticos;
- instruções de instalação e execução;
- exemplo de entrada e saída;
- explicação da janela e do limite;
- testes automatizados;
- seção de falsos positivos;
- limitações do projeto;
- próximos passos realistas;
- licença e escopo de uso defensivo;
- histórico de commits compreensível.

Uma boa descrição para currículo seria:

> Desenvolvi analisador defensivo de logs em Python com leitura incremental de JSON Lines, validação de eventos, correlação de falhas por janela temporal, relatório JSON e testes com pytest. Usei somente dados sintéticos e documentei falsos positivos e limites da regra.

Essa frase é mais forte do que “criei detector de hackers”, porque descreve a entrega sem fazer uma alegação que o código não sustenta.

Para organizar o repositório, consulte [projetos de portfólio Python](/carreira/projetos-portfolio-python/) e o [modelo de currículo para vaga júnior](/carreira/curriculo-python-vaga-junior/).

## Próximas evoluções seguras

Depois da primeira versão, você pode:

1. aceitar origem e destino por argumentos com `argparse`;
2. colocar registros inválidos em quarentena;
3. produzir métricas por sistema e resultado;
4. gerar relatório Markdown para leitura humana;
5. adicionar configuração TOML para limite e janela;
6. testar arquivos grandes com geradores;
7. criar uma API local para receber eventos sintéticos;
8. construir dashboard sobre dados fictícios;
9. empacotar o projeto com `pyproject.toml`;
10. executar testes e lint no GitHub Actions.

Se integrar uma API autorizada, configure timeout, retry e limite de chamadas. O guia de [HTTPX moderno](/blog/python-httpx-requests-moderno/) mostra esses padrões. Nunca inclua chaves no código ou no repositório; para geração de valores seguros, veja o módulo [`secrets`](/blog/python-secrets-tokens-senhas-seguros/).

Evite transformar a evolução em scanner de terceiros, coletor de vazamentos ou ferramenta de tentativa de senha. Há muito espaço para aprender Blue Team com dados sintéticos, laboratórios próprios e plataformas educacionais autorizadas.

## Perguntas frequentes

### Como Python é usado em um SOC?

Python normaliza e correlaciona eventos, integra APIs permitidas, gera relatórios e automatiza partes repetitivas da triagem. A linguagem apoia o trabalho; SIEM, EDR, processos, conhecimento do ambiente e análise humana continuam essenciais.

### Qual formato de log é melhor para analisar com Python?

JSON Lines é uma ótima escolha didática por manter um objeto por linha e permitir processamento incremental. CSV funciona bem para tabelas. Texto livre é comum, porém precisa de parsing mais cuidadoso e testes contra mudanças de formato.

### Contar falhas de login detecta um ataque?

Não. A contagem encontra um padrão que merece contexto. Senha esquecida, integração quebrada, NAT e atividade maliciosa podem produzir sinais parecidos. Trate o resultado como alerta para triagem, não como veredito.

### Posso publicar logs reais no meu portfólio?

Não publique dados corporativos, pessoais ou internos sem autorização formal e processo adequado. Para GitHub público, produza um dataset sintético e use endereços reservados para documentação.

### Esse projeto ajuda a conseguir estágio em segurança da informação?

Sim. Ele demonstra arquivos, JSON, datas, estruturas de dados, testes e pensamento defensivo. Para gerar valor na candidatura, documente como executar, por que escolheu a regra e quais situações podem gerar falsos positivos.

## Conclusão

Analisar logs com Python é um projeto acessível e relevante para quem quer trabalhar em SOC ou começar em segurança da informação. O fluxo fundamental é: receber dados conhecidos, validar cada evento, aplicar uma regra explicável, gerar um relatório e testar o comportamento esperado.

A principal lição não é o limite de quatro falhas. É aprender a não confundir automação com certeza. Uma regra defensiva gera evidência para investigação; seu valor depende da qualidade dos dados, do contexto e da forma como a equipe responde.

Implemente primeiro a versão com JSON Lines e testes. Depois, publique o repositório com dados sintéticos e uma seção honesta de limitações. Continue pelo guia de [estágio em segurança da informação](/carreira/como-conseguir-estagio-seguranca-informacao-python/), revise [Python para cibersegurança júnior](/carreira/python-ciberseguranca-junior/) e acompanhe as [vagas de estágio](/vagas/estagio/) para comparar seu projeto com os requisitos que aparecem no mercado brasileiro.
