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
13 KiB
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 Cbits, ondeH = -\sum p_c \log_2 p_c(entropia de Shannon por caractere) eCé a soma dos caracteres dos subdomínios únicos sem os pontos. - Avaliação na hora de pico:
I_{\max} = \max_h I. ExigeU_{\text{pico}} \ge 10subdomínios únicos; caso contrário, o status éinsufficient_datacom 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 32768bits/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 pilari(0.0caso 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 cadam_i, um pilar isolado atinge no máximo0.75, dois pilares0.9375e três pilares0.984375. Invariante à permutação dos alertas de entrada. - A fusão classifica os hosts em
AnalysisReport.hostspor(S \text{ desc}, orig\_h \text{ asc}); fornece priorização de investigação sem fabricar categorias sintéticas de alerta.
Limitações Conhecidas
- Em capturas ao vivo, sessões em aberto permanecem invisíveis até que o Zeek descarregue o registro por encerramento ou timeout.
- 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.
- A sensibilidade do Pilar 3 é de cerca de 100 consultas de túnel únicas por hora.
- 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
- 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.
- 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-privilegiadoacaua(UID 10001), filesystem somente leitura e capabilities revogadas. - Compilação nativa:
scripts/fetch-libduckdb.sh ~/.cache/acaua/duckdbseguido decargo 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.