# 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.