Files
acaua/docs/pt-BR/arquitetura.md
T
Lucas RayanandClaude Sonnet 5 c64bbbf775 feat: implement Pillars 2-4 and fix a production false-positive in Pillar 1
Pillar 1 v1.1 (shipped earlier) flagged benign browsing bursts as
beaconing: on short captures, grid periods longer than the observed
span never complete a cycle, so significance saturates to 1 and the
score sticks at exactly 0.5. v1.2 caps the grid at a third of the
observed span, requires a minimum number of completed cycles, and
excludes protocols that are periodic by nature (NTP, DNS, mDNS,
ICMP). Validated against 300 seeds per scenario: false positives on
bursty traffic drop from the 290-300/300 that v1.1 produced to 0/300,
with every beacon-detection scenario scoring unchanged.

Add Pillar 2 (persistent/long-lived sessions, with Zeek's fragmented
TCP sessions rejoined after a 5-minute gap), Pillar 3 (DNS tunneling
via Shannon-entropy information volume per subdomain per hour; the
Jensen-Shannon divergence was measured and does not separate tunnels
from benign CDN subdomains, so it stays in the evidence at zero
weight instead of in the score), and Pillar 4 (deterministic
noisy-OR fusion across the three pillars).

The fusion formula was validated against 8 labeled real-traffic
vectors (IoT-23, CTU-Normal) with tolerance <= 1e-4. A known
limitation is kept, not hidden: a benign Philips HUE bulb's strict
periodic polling and a 21.9h session score it above three real C2
hosts. No weight or threshold was tuned to mask this; it is
documented in the architecture docs instead.

Also fixes real bugs found along the way: ingest_dir's partition
count summed file names instead of distinct dates, DuckDB errors
were counted as bad rows, /api/alerts always returned an empty list
regardless of the last analysis, and the dashboard still claimed
every pillar was unimplemented.

Restores four design corrections that were missing from
docs/architecture.md (KL to Jensen-Shannon, the persistent-session
detector, the JA4 licensing boundary, and deterministic reduction
order), and catalogs the public datasets used for validation
(IoT-23, CTU-Normal, Mendeley DTQBC) with their licenses and SHA-256
checksums under research/validation/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
2026-11-01 00:28:54 -03:00

13 KiB
Raw Permalink Blame History

Arquitetura

English · Português

O Acauã é um produto único em Rust: um daemon/CLI, um núcleo compartilhado e um dashboard web embutido. O runtime distribuído não tem Python nem banco externo. Este documento registra as decisões estruturais e as seis correções aplicadas ao blueprint original.

Stack

Camada Escolha Motivo
Core, CLI e daemon Rust 2021 (MSRV 1.88) Toolchain única, segurança de memória, sem dependências externas de runtime
API HTTP Axum + Tokio + Tower HTTP Superfície assíncrona enxuta, fácil de embutir
Dashboard Web HTML/CSS/JS vanilla Sem etapa de build de frontend, assets embutidos no binário via rust-embed
Armazenamento analítico DuckDB + Parquet Armazenamento colunar embutido obrigatório, linkado à libduckdb.so oficial pré-compilada
Implantação Docker / docker-compose Daemon de SOC coletivo, usuário não-root sem privilégios, rootfs somente leitura
Validação científica Python apenas em research/ astropy/scipy/networkx para fixtures, nunca distribuído

O workspace possui dois crates: acaua-core e acaua-cli. Ambos utilizam version.workspace = true.

Componentes

Logs Zeek ──► acaua-core::zeek ──► eventos normalizados ──► storage
                                      │
                                      ├──► pillar1 (beaconing)
                                      ├──► pillar2 (sessões persistentes)
                                      ├──► pillar3 (anomalias de protocolo)
                                      └──► pillar4 (fusão) ──► alertas + evidência
                                                                    │
acaua-cli (serve) ──► API axum ──► dashboard web embutido ──────────┘
  • zeek: parser delimitado e confinamento de caminhos. Nunca segue symlink fora da raiz do dataset.
  • storage: motor DuckDB em memória sobre arquivos Parquet particionados por dia com TTL de sete dias.
  • pillar1–pillar4: funções puras sobre eventos normalizados. Retornam scores e evidência, nunca apenas um float.
  • fusion: combinação limitada em ordem fixa. Deve ser simétrica e reproduzível.
  • doctor: checagens do ambiente local; reporta linkagem da DuckDB pré-compilada e status do Zeek em container.
  • update: checa novas versões consultando a API de Releases do Gitea.

As seis correções

1. Rayleigh/Schuster, não Lomb–Scargle puro

Timestamps de eventos são um processo pontual unidimensional: não há valor y para ajustar. O periodograma primário é, portanto, Rayleigh/Schuster:

P(T) = (2 / N) * [ (sum cos(2*pi*t_i / T))^2 + (sum sin(2*pi*t_i / T))^2 ]

Lomb–Scargle generalizado fica reservado para a extensão futura que avaliará um atributo real de resposta (como tamanho de payload ou duração) contra o tempo.

2. Proteger o caso de dispersão zero no MADM

Um beacon sintético ou sem jitter produz intervalos perfeitamente iguais. O desvio absoluto mediano em relação à mediana (MADM) é então 0.0. Dividir por 0.0 ou tratá-lo como dado ausente é incorreto: dispersão zero é o resultado mais suspeito possível. A implementação protege esse caso com atribuição explícita de score (1.0) e sentinela documentado.

3. Substituir assimetria de Pearson por assimetria de Bowley

A assimetria amostral baseada no terceiro momento central é sensível a outliers individuais e pode divergir. A assimetria de Bowley utiliza quartis:

B = (Q3 + Q1 - 2*Q2) / (Q3 - Q1)

Ela é limitada em [-1.0, 1.0] por definição e resistente a outliers extremos. Quando Q3 == Q1 (dados concentrados), a implementação atribui 0.0 com flag sentinela.

4. Divergência de Jensen-Shannon em vez de Kullback-Leibler

A divergência de Kullback-Leibler diverge para infinito quando Q(x) = 0 sobre suporte não-vazio de P(x). O Acauã adota a Divergência de Jensen-Shannon (JSD): limitada em [0, 1], simétrica e estritamente finita mesmo em suportes disjuntos. Medições empíricas em tráfego de túnel DNS real e CDNs benignas demonstraram que a JSD isolada não separa CDNs de túneis; assim, na v1 ela entra na evidência do alerta com peso 0.0 sem distorcer o score.

5. Detector dedicado de sessões persistentes (Pilar 2)

Sessões interativas de longa duração ou canais C2 estáveis não geram reconexões frequentes (Δt), tornando-se invisíveis para a análise pontual de periodicidade. O Acauã implementa um detector dedicado de sessões persistentes com encadeamento TCP que reagrupa segmentos divididos pelo timeout de inatividade de 5 minutos do Zeek.

6. Fronteira de licenciamento do JA4

Apenas a impressão digital original de cliente TLS JA4 está sob licença BSD-3. Os outros módulos (JA4S, JA4H, JA4X, JA4SSH, JA4L) utilizam a FoxIO License 1.1 proprietária (não-OSI). O Acauã não embute nem depende de módulos sob licença FoxIO por padrão em sua arquitetura de código aberto.

7. Determinismo e ordem fixa de redução

Reduções paralelas (ex.: via rayon) alteram a ordem de somas e multiplicações em ponto flutuante entre threads, quebrando a reprodutibilidade bit a bit. Todas as reduções, somas de periodograma e combinações de evidência são executadas em ordem sequencial fixa e determinística, produzindo evidências JSON idênticas byte a byte.

8. Isolar o espaço de pesquisa Python

research/ tem seu próprio ambiente virtual e dependências. Ele é excluído do workspace Cargo, ignorado em builds Docker e releases de CI, e testado apenas quando scripts de validação de pesquisa forem invocados diretamente.

9. Modo degradado documentado para NetFlow/IPFIX

NetFlow v9 e IPFIX não carregam nomes de consulta DNS, SNI TLS, cadeias de certificados ou cabeçalhos HTTP. Qualquer análise que dependa de recursos de protocolo fica indisponível. O Acauã documenta isso como modo degradado e nunca alega paridade entre registros de fluxo e logs Zeek completos.

10. Fronteira única HTTP e RPC

O daemon expõe uma API Axum em loopback por padrão. A UI web embutida se comunica com o daemon exclusivamente por chamadas HTTP padrão.

Detecção (v1)

Pilar 1: Beaconing C2 Low-and-Slow (v1.2)

Avalia inícios de sessão por (orig_h, resp_h, resp_p, proto) através de um periodograma de Rayleigh/Schuster sobre uma grade efetiva limitada a span / 3 (exigindo no mínimo 3 ciclos completos observados). A significância é controlada pela regularidade da dispersão (gate = min(1, regularidade / 0.25)), eliminando falsos positivos em tráfego de navegação em rajadas. Limiar de alerta: score >= 0.40. Protocolos não-TCP/UDP, endereços multicast e broadcast, e portas de infraestrutura (ex.: NTP 123, DNS 53, mDNS 5353, NetBIOS, DHCP) são auditados e excluídos com transparência.

Pilar 2: Sessões Persistentes (v1.0)

Encadeia segmentos de conexão TCP divididos por inatividade no Zeek (intervalo <= 3600s sem flag SYN S no histórico). O score de persistência é logarítmico: persistence = clamp(ln(L / 3600) / ln(24), 0, 1). Sessões com L >= 7200s (score >= 0.2181) disparam alerta. A razão produtor-consumidor (PCR), taxa de bytes por hora e contagem de tentativas falhas são registradas na evidência.

Pilar 3: Capacidade de Informação em Subdomínios DNS (v1.0)

Mede a capacidade física de informação transmitida por consultas DNS agregando subdomínios únicos por (orig_h, base_domain, hora_utc). Normaliza para minúsculas, remove ponto final e exclui consultas reversas (.in-addr.arpa, .ip6.arpa), .local e portas de descoberta local (5353, 5355, 137):

  • Capacidade de informação: I = H \times C bits, onde H = -\sum p_c \log_2 p_c (entropia de Shannon por caractere) e C é a soma dos caracteres dos subdomínios únicos sem os pontos.
  • Avaliação na hora de pico: I_{\max} = \max_h I. Exige U_{\text{pico}} \ge 10 subdomínios únicos; caso contrário, o status é insufficient_data com score 0.0.
  • Pontuação: \text{score} = \text{clamp}(\log_2(I_{\max} / 8192) / \log_2(32), 0, 1) (8\text{ kbit} \to 0.0, 32\text{ kbit} \to 0.40, 256\text{ kbit} \to 1.0).
  • Limiar de alerta: \text{score} \ge 0.40 (\iff I_{\max} \ge 32768 bits/h).
  • A Divergência Jensen-Shannon (JSD) contra a mistura de outros domínios da janela entra na evidência com peso 0.0 (túneis reais deram 0.157–0.193 vs. CDNs benignas 0.226–0.227, o que inverteria a classificação).

Pilar 4: Fusão de Evidências por Host (v1.0)

Combina alertas por orig_h usando um modelo noisy-OR ponderado sobre os pilares 1, 2 e 3:

  • Para cada host, m_i é a pontuação máxima de alerta do pilar i (0.0 caso não haja alertas do pilar).
  • Pesos fixos: w_1 = w_2 = w_3 = 0.75.
  • Fórmula: S = 1 - \prod_{i=1}^3 (1 - w_i m_i) calculado na ordem sequencial fixa [1, 2, 3].
  • Propriedades: S \in [0, 1], monotônica em cada m_i, um pilar isolado atinge no máximo 0.75, dois pilares 0.9375 e três pilares 0.984375. Invariante à permutação dos alertas de entrada.
  • A fusão classifica os hosts em AnalysisReport.hosts por (S \text{ desc}, orig\_h \text{ asc}); fornece priorização de investigação sem fabricar categorias sintéticas de alerta.

Limitações Conhecidas

  1. Em capturas ao vivo, sessões em aberto permanecem invisíveis até que o Zeek descarregue o registro por encerramento ou timeout.
  2. Polling legítimo de nuvem e serviços persistentes (ex.: dispositivos IoT como lâmpada Philips HUE, websockets, push) exibem periodicidade e persistência reais, ficando com pontuação fundida elevada sem contexto global da rede.
  3. A sensibilidade do Pilar 3 é de cerca de 100 consultas de túnel únicas por hora.
  4. A extração de domínio-base usa heurística eficiente sem embutir a lista completa da Public Suffix List (PSL).

Dados de Validação

Datasets públicos utilizados para verificação empírica (detalhados em research/validation/datasets.tsv):

  • IoT-23 (cenários 34-1, 20-1, 8-1, 42-1, Honeypot 4-1): verdade de referência CC BY 2.0 para beaconing C2, persistência e controles benignos IoT.
  • CTU-Normal (capturas 20, 18, 26): capturas públicas de tráfego benigno de navegação e Facebook.
  • Mendeley dtqbc-m-test (Y. Bubnov): dataset sob CC BY 4.0 contendo consultas reais de túneis DNS (dns2tcp, dnscapy, tuns).

API

A API HTTP é servida por acaua serve:

Método Caminho Significado
GET /api/health Liveness e versão
GET /api/status Uptime, diretório de dados, sumário do dataset
GET /api/alerts Alertas paginados com evidência
POST /api/analyze Ingestão e análise de diretório de dataset
GET /api/update Checagem de novas releases
GET /logo.png Asset de marca embutido
GET /* Fallback de UI embutida (rust-embed)

serve escuta em 127.0.0.1:8787 por padrão. Expô-lo em uma rede externa exige um proxy reverso com autenticação.

Armazenamento

DuckDB é uma dependência obrigatória linkada dinamicamente à libduckdb.so oficial pré-compilada (versão 1.5.6 fixada com SHA-256 em scripts/fetch-libduckdb.sh). Extensões Parquet, JSON e ICU já vêm compiladas estaticamente na biblioteca oficial. O esquema consiste em arquivos Parquet particionados por dia sob o diretório de dados configurado, com consultas DuckDB em memória e TTL de sete dias.

Atualizações

Atualizações do Acauã são distribuídas por imagens de container (docker compose pull && docker compose up -d). O comando acaua update consulta a API de Releases do Gitea para ciberfauna/acaua e notifica os operadores quando uma tag de versão mais nova estiver disponível. A sobrescrita binária em disco (update --apply) foi removida: em um daemon de SOC coletivo orquestrado via container, atualizações devem ser controladas por puxar imagens novas em vez de processos que alteram o próprio executável.

Alternativas rejeitadas

  1. Serviço Python com FastAPI/ClickHouse: Rejeitado. Um runtime Python introduziria imagens pesadas e fronteiras de memória desnecessárias; o ClickHouse adicionaria um servidor externo e custo operacional para datasets que cabem perfeitamente em um armazenamento colunar embutido. O produto é 100% Rust.
  2. Aplicativo desktop (Tauri / AppImage / NSIS): Rejeitado. Diferente do Guará (forense em disco bruto local) e do Sabiá (hardware Wi-Fi em modo monitor), o Acauã não tem dependência de hardware local: é um daemon de SOC multiusuário consumindo logs do Zeek. Um wrapper desktop traria manutenção e superfície de segurança sem ganho operacional.

Implantação

  • Docker Compose (Oficial): Imagem multi-arch (git.lucasrguerra.dev.br/ciberfauna/acaua), rodando com usuário não-privilegiado acaua (UID 10001), filesystem somente leitura e capabilities revogadas.
  • Compilação nativa: scripts/fetch-libduckdb.sh ~/.cache/acaua/duckdb seguido de cargo build --release -p acaua-cli.

Comportamento em falhas

Entradas malformadas são ignoradas com diagnóstico delimitado e contado. Um log ausente não é fatal. Um banco corrompido é reportado pelo doctor e nunca recriado silenciosamente sobre dados existentes.

Versionamento e compatibilidade

SemVer 2.0.0. O JSON de evidência carrega uma versão de esquema. Qualquer alteração em uma pontuação ou no formato de evidência é breaking change para versões minor 0.x e deve constar no changelog.