Files
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

247 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Arquitetura
[English](../architecture.md) · **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:
```text
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:
```text
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.