ADR 0046 — OOBI Hello payload encryption + host_id + engaged_in_test¶
- Status: Accepted (2026-05-14)
- Date: 2026-05-14
- Deciders: TLSStress.Art project (operator directive 2026-05-14)
- Builds on: ADR 0043 (OOBI VLAN), ADR 0044 (intrusion), ADR 0045 (Hello mesh)
- Targets: v4.0.0
Context¶
ADR 0045 shipped the Hello/KeepAlive mesh on VLAN 2777 with a JSON-over-UDP wire format chosen for operator debuggability (tcpdump -i oobi0 udp port 47802 -A). On 2026-05-14 the operator captured a follow-up requirement:
Devemos implementar uma funcionalidade, onde o payload com mensagem contida no pacote KeepAlive, seja "embaralhada" criptografada, por uma chave de criptografia/decriptografia que seja de conhecimento somente dos elementos do software, para evitar que um SPAN port feito pelo operador, capture os pacotes na rede OOBI e consiga decifrar a mensagem contida no payload dos pacotes keepalive. Também juntamente no payload dos keepalives, ao gerar o pacote, também deve ser inserida identificacao do Host em que o elemento reside, assim como informar se naquele momento estava ou não engajado em algum teste de performance ativo.
Two distinct requirements:
- Confidentiality against a SPAN-port observer. The operator who configures a SPAN session on the customer's switch must NOT be able to read Hello payloads — they MUST see opaque bytes.
- Two new payload fields:
host_id(which physical host the MÓDULO runs on) andengaged_in_test(whether the MÓDULO is currently servicing an operator performance test).
Decision¶
Cryptographic primitive¶
AES-256-GCM (RFC 5116). Rationale:
- Stdlib (
crypto/aes+crypto/cipher.NewGCM) — no third-party dep. - AEAD: confidentiality + authenticity in one pass. Tag mismatch = drop + alert.
- 256-bit key + 96-bit nonce per packet. Fresh random nonce per Seal call.
- Standard, audited, FIPS-validated.
Wire envelope¶
[12 bytes nonce] || [N bytes ciphertext] || [16 bytes GCM tag]
crypto/cipher.AEAD.Seal appends to the nonce slice → output starts with the nonce. Receiver slices off the first 12 bytes to use as nonce, passes the remainder to Open.
Key provisioning¶
Pre-shared key (PSK), 32 bytes / 256 bits, hex-encoded in the env var OOBI_HELLO_PSK. Deployed via a Kubernetes Secret mounted as env (NOT volume — env is the smallest blast radius for the value).
Bootstrap CLI: pkg/oobi/hello exposes GeneratePSK() (hex, error) so an operator can seed a fresh key once and propagate it to every MÓDULO Secret. No automatic rotation in MVP — manual rotation requires a coordinated rolling restart across all 6 MÓDULOs.
Fail-closed default¶
NewDefaultMonitor returns an error when OOBI_HELLO_PSK is unset and LabMode == false. Production MÓDULOs cannot start without the PSK — the confidentiality guarantee is enforced by the binary, not by hope. The existing --auth-lab-mode flag propagates to hello.LabMode so dev/lab boots without secrets continue to work, but the operator must explicitly request it.
New payload fields¶
{
// ... existing ADR 0045 fields ...
"host_id": "node-1.us-east-1.k8s.local",
"engaged_in_test": false
}
host_id— provisioned viaOOBI_HELLO_HOST_IDenv (Kubernetes downward API:valueFrom.fieldRef.fieldPath: spec.nodeName). Falls back toos.Hostname()when unset.engaged_in_test— sourced fromEngagementProvider func() boolinjected per MÓDULO. Default = constantfalse(infra-only MÓDULOs). TREX.Art will plug its scenario orchestrator in a follow-up; other MÓDULOs may add their own once they grow active-test semantics.
Both fields live INSIDE the encrypted payload — an attacker who breaks confidentiality reads the slot too, no extra leak.
Metrics surface¶
Two new info-style gauges drive the orthogonal Dashboard map:
oobi_hello_module_info{detected_by, host} 1
oobi_hello_engaged_in_test{detected_by} 0|1
oobi_hello_neighbor_host_info{detected_by, neighbor_slot, neighbor_module, host} 1
oobi_hello_neighbor_engaged_in_test{detected_by, neighbor_slot, neighbor_module} 0|1
oobi_hello_decrypt_errors_total{detected_by} counter
Cardinality budget unchanged: ≤ (modules × neighbor_slots) ≈ 1369 per MÓDULO.
oobi_hello_decrypt_errors_total is the strongest intrusion signal in the mesh — every PSK-less attacker who reaches VLAN 2777 will tick this counter. Alertmanager rule shipped in PR-OOBI-HELLO-CRYPT-2.
Consequences¶
Positive¶
- A SPAN-port observer sees only opaque bytes. Host topology, engagement state, neighbor list — all confidential.
- Decryption-failure counter is a high-signal intrusion alert.
- Fail-closed boot prevents accidental plaintext deployment.
host_id+engaged_in_testunlock the orthogonal Dashboard map (PR-OOBI-HELLO-CRYPT-2) and the "do not disturb that node — it's mid-test" UX.
Negative¶
tcpdump -Ano longer inline-readable. Operators debugging the protocol must usehello-cli decrypt(TBD CLI in a follow-up) or temporarily start a MÓDULO in--auth-lab-modeagainst an isolated VLAN.- PSK rotation requires coordinated restart. MVP: manual.
- Adds 28 bytes per packet (12 nonce + 16 tag). Negligible for a 30 s cadence.
- One env var more in the deployment pipeline.
Neutral¶
- AES-GCM is constant-time; no measurable CPU at 30 s × 6 MÓDULOs.
Rollout¶
- PR-OOBI-HELLO-CRYPT-1 (this PR) — library: cipher, packet fields, default.go env wiring, metrics, tests, ADR.
- PR-OOBI-HELLO-CRYPT-2 — Dashboard orthogonal host-grouped map UI + Grafana panel + Alertmanager rule for
oobi_hello_decrypt_errors_total. - Future —
hello-clidecrypt utility for operator debugging; automatic PSK rotation when CONTROL plane supports it.
References¶
- Memo:
project_oobi_hello_encrypted_payload_2026_05_14.md - ADR 0045: OOBI Hello / KeepAlive mesh
- ADR 0044: OOBI intrusion detection
- Package:
pkg/oobi/hello/crypto.go - Bootstrap secret template:
k8s/oobi/05-hello-psk-secret.yaml