ADR-0093: RELAY.Art — Phase B (real protocols + Vault/cert-manager + k-anonymity + sessionmux + Multus floating-IP + SIEM forwarder)¶
- Status: Accepted (2026-05-24) — RELAY.Art Phase B wave (RELAY-B1 + RELAY-B2 + RELAY-B3 + this PR) shipped
- Date: 2026-05-24
- Driver: closes ADR-0092 §"Deferred (Phase B)" — every Phase A placeholder either gets a real impl or a Phase B-late escalation seam
- Related: PRs #1068 (RELAY-B1) · #1069 (RELAY-B2) · #1070 (RELAY-B3) · this PR (B4); ADR-0089 (Phase B foundation adapter-pattern reuse); patent provisional draft
docs/patents/relay-art-bridge-claim-12-provisional-draft.md
Context¶
ADR-0092 shipped RELAY.Art Phase A: foundation + ingress (raw byte forwarding) + egress orchestrator (read-only default + UnlockWindow + RateLimited + FileCredentialSource) + HA standby Deployment. The §"Deferred (Phase B)" list contained seven items, every one of which a real customer deployment will hit before Phase A is enough.
This Phase B wave delivers all seven plus a provisional patent draft for claim #12 reserved in the strategic memo.
Decision¶
Ship 4 sub-PRs + this ADR covering the seven deferred items:
| Sub-PR | Scope | PR |
|---|---|---|
| RELAY-B1 | Real protocol parsers — NetFlow v5 fixed format + NetFlow v9 + IPFIX (shared template cache, decode 12 core IEs) + sFlow v5 header. Parser interface + FlowRecord wire shape + 16 unit tests |
#1068 |
| RELAY-B2 | VaultSource (vault://) + CertManagerSource (k8s-secret://) + MultiSource dispatcher + Rotator (Schedule + RotateFunc + Writer + RotateOnce + Run loop). HTTP-only, no Vault SDK / k8s client-go. 17 unit tests |
#1069 |
| RELAY-B3 | kanonymity.Counter (distinct-source LRU; threshold 10 default; ErrBelowThreshold gate) + sessionmux.Pool (per-(Vendor, Target) Session cache; IdleTimeout/MaxAge/Healthy eviction; race-safe build outside lock). 21 unit tests |
#1070 |
| RELAY-B4 | This PR — siemforward.Splunk HEC + QRadarSyslog (RFC 5424) + Disabled/Fake test doubles (15 tests) + k8s/oobi/61-relay-art-multus-floating-ip.yaml Multus whereabouts NAD + ConfigMap with strategic-merge-patches + ADR-0093 + provisional patent draft + recovery memo |
(this) |
Locked decisions (Phase B)¶
| # | Decision | Rationale |
|---|---|---|
| 1 | Adapter pattern continues from Phase A: every Phase B impl ships alongside a Disabled / Fake sibling. Real impls are opt-in via Config; silent fallback when unavailable. Same hard rule as ADR-0089 — /forward-* MUST NEVER fail because of an unavailable backend |
|
| 2 | Protocol parsers extract canonical 5-tuple + counters only. Vendor-specific Information Elements (Cisco ASA extended fields, Palo Alto APP-ID, Fortinet meta-fields) defer to Phase B-late. The 12 core IEs cover every memo §"Functions — Ingress" use case | |
| 3 | TemplateCache is per-RELAY-pod, not cluster-shared. NetFlow v9 / IPFIX exporters re-send templates every N minutes (configurable per device); the standby pod populates its cache organically within minutes of becoming active. Cluster-shared cache (Redis) is Phase B-late if/when cold-start latency matters |
|
| 4 | VaultSource static token for Phase A → k8s-auth refresh Phase B-late. The token rotation cycle (when Vault revokes a static token) is handled by Vault's k8s-auth sidecar; we keep the HTTP-only interface so the sidecar can mount a new token without restarting RELAY |
|
| 5 | CertManagerSource uses K8s API directly, not cert-manager client-go. The Secret read path is plain HTTP+Bearer — no need to bundle the entire cert-manager v1 typed clientset. Phase B-late wires the cert-manager Renewal API for the rotation write path |
|
| 6 | Rotator.RotateFunc is vendor-specific; impls land per-customer demand. The interface contract (func(ctx, current) (new, error)) covers FMC token refresh / Panorama API key cycle / FortiManager fabric-key generator + every other vendor's flavor. Phase B-late ships the first concrete vendor impl when the first customer asks |
|
| 7 | K-anonymity threshold defaults to 10 per memo, operator-configurable per pipeline. The Counter is in-memory LRU; persistent store (Redis) + differential-privacy noise are Phase B-late | |
| 8 | sessionmux.Pool holds AT MOST ONE Session per (Vendor, Target). Memo §"Vendor-specific session multiplexers" — multi-session pooling adds bookkeeping for ~zero benefit at single-RELAY scale. Phase B-late lifts this when concurrent operator commands per target become a real bottleneck |
|
| 9 | Multus floating-IP via whereabouts for true active/standby. K8s Service load-balance + readiness drop (Phase A) covers the common case; whereabouts covers the case where customer ACLs gate on a single source IP and need cross-pod stability | |
| 10 | SIEM forwarder is opt-in per CPOS. Operator declares each siemforward.Forwarder (Splunk HEC URL+token, QRadar Syslog address) per customer; multiplexing one event to N forwarders is Phase B-late |
|
| 11 | QRadarSyslog uses RFC 5424, not LEEF. RFC 5424 works against any RFC 5424 SIEM (Sumo Logic, Graylog, generic syslog) — gives the operator a wider blast radius for the same code. LEEF is Phase B-late for QRadar-specific feature use | |
| 12 | buildRFC5424 facility = 16 (local0). The Syslog RFC reserves local0-7 for site-specific use; local0 is the most common default in observability tooling |
Wire format additions¶
| Package | Type | Purpose |
|---|---|---|
parsers |
Parser interface · FlowRecord · FieldType enum · TemplateCache · Protocol enum |
Decode raw UDP into structured 5-tuple+counters |
credsource |
VaultSource · CertManagerSource · MultiSource · Rotator · Schedule · RotateFunc · Writer · MemoryWriter/MemoryResolver · ErrRefSchemeMismatch · ErrRefNotFound |
Vault + cert-manager credential resolution + auto-rotation |
kanonymity |
Counter · Config{Threshold, MaxKeys} · ErrBelowThreshold |
Distinct-source k-anonymity gate |
sessionmux |
Pool · Key{Vendor, Target} · Vendor enum · Session interface · SessionFactory · FakeSession |
Per-vendor long-lived session pool |
siemforward |
Forwarder interface · Event · SplunkHEC · QRadarSyslog · DisabledForwarder · FakeForwarder · ErrForwarderDisabled |
Customer SIEM forwarding |
Consequences¶
Positive
- Closes ALL of ADR-0092 §"Deferred (Phase B)" with a single coherent wave
- Adapter pattern (Phase A + Phase B + Phase C of every package: Disabled/Fake/real) keeps the door open for Phase B-late vendor-specific impls without re-architecture
- Wire formats locked + 69 new unit tests (16 parsers + 17 credsource + 21 kanon+sessionmux + 15 siemforward) means future Phase B-late edits stay safe
- Multus floating-IP makes RELAY.Art real-customer-deployable — single source IP from the customer's ACL perspective regardless of which pod is active
- SIEM forwarder closes the last operator-facing observability gap; their existing Splunk / QRadar dashboards now see RELAY-normalized events from day one
Negative / deferred (Phase B-late)
- Vendor-specific IEs in parsers (Cisco ASA extended, Palo Alto APP-ID, Fortinet meta-fields)
- sFlow per-sample raw-packet-header parser (RFC 3176 §5.1; libpcap-like Ethernet/IP/TCP/UDP walker)
- Options templates (v9 flowset id=1 / IPFIX flowset id=3) — currently ignored
- Variable-length IEs (IPFIX length 65535 marker)
- Vault k8s-auth refresh sidecar (currently static token)
- cert-manager Certificate Renewal trigger in the Rotator write path
- Vendor-specific RotateFunc impls (FMC, Panorama, FortiManager) — one per customer engagement
- Redis-backed kanonymity Counter + differential-privacy noise on surface count
- Per-pipeline normalization rules for kanonymity keys
- Multi-session pooling in sessionmux (when concurrent commands per target become a bottleneck)
- LEEF format in QRadarSyslog (currently RFC 5424 only)
- One-event-to-many-forwarders multiplexer in siemforward
- Batch + compression + retry-with-backoff in SplunkHEC (currently one POST per event)
Patent posture¶
The ADR-0092 memo reserved claim #12 in the DOM/OOBI/GATEWAY family:
"Bidirectional MGMT-plane bridge between immutable overlay fabric and operator's existing prod-MGMT network with hard separation of data-plane vs MGMT-plane traffic, vault-isolated per-target credential scoping, and ML-cortex-integrated auto-discovery."
This Phase B wave delivers the impl that supports the claim. The provisional patent draft lands as docs/patents/relay-art-bridge-claim-12-provisional-draft.md. Total patent claims: 18 (unchanged — claim #12 was already reserved + counted; this PR materializes the implementation backing).
Compliance¶
Per CLAUDE.md: operator opts in to each Phase B impl via CPOS / Config — no kubectl edit against relay-art pods.
Per ZTP-prem posture: every Phase B impl runs in the existing relay-art / relay-art-standby pod security context (readOnlyRootFilesystem + runAsNonRoot:2096 + drop: [ALL]). Multus whereabouts requires no additional capabilities beyond what Multus already needs at the cluster level.
Per ADR-0044 (OOBI Intrusion Detection): SIEM forwarder targets are operator-declared per CPOS; the forwarder's outbound traffic is subject to the same NetworkPolicy egress allowlist that gates the rest of relay-art.
References¶
- ADR-0092 (RELAY.Art Phase A) — §"Deferred (Phase B)" this ADR closes
- ADR-0089 (Phase B foundation) — adapter-pattern reuse precedent
discuss_module_relay_art_2026_05_10— strategic memo (claim #12 reserved)- PRs #1068 (RELAY-B1) · #1069 (RELAY-B2) · #1070 (RELAY-B3) · this PR (RELAY-B4)
- Provisional patent:
docs/patents/relay-art-bridge-claim-12-provisional-draft.md