Skip to content

ADR 0042 — MÓDULO Network Attachment Matrix (4-pattern generalization of ADR 0041)

  • Status: Accepted (2026-05-13)
  • Date: 2026-05-13
  • Deciders: TLSStress.Art project
  • Targets: v3.10.x → v4.x — cross-cutting infrastructure for any MÓDULO that needs an additional in-Pod interface beyond the OOBI overlay, in any of four directional patterns
  • Supersedes (in scope, not in spirit): ADR 0041 — Customer MGMT Receiver Pattern. ADR 0041 is preserved as historical; this ADR is the current authoritative reference. The ConfigMap key + on-disk shape are stable across ADR 0041 → 0042 — existing entries continue to work as direction: inbound.
  • Builds on: ADR 0037 (pkg/oobi/client/), ADR 0039 (SyslogReflector.Art), ADR 0040 (HyperBridge NetFlow synthesis), ADR 0041 (the original single-direction receiver pattern)

Context

ADR 0041 introduced the "Customer MGMT Receiver" pattern: a way to dual-home MGMT-plane MÓDULOs (FLOW.Art, SyslogReflector.Art) so the customer's DUT — which can't reach our OOBI VXLAN overlay — could send telemetry inbound. That solved one direction (DUT → MÓDULO).

Three other directional gaps remained:

  1. Outbound (MÓDULO → DUT). SNMP.Art polls the DUT over UDP/161. CLI.Art opens SSH/Telnet sessions. API INFRA.Art posts REST calls. None of these can use the OOBI overlay (the DUT isn't on it) and none can use the inbound NAD (wrong direction at the NetworkPolicy level — egress vs ingress).
  2. Data-plane (MÓDULO → DATA-plane VLAN). TREX.Art (DPDK line-rate) and the canary-injector need to put packets ON the customer's DATA-plane VLAN (typically 20=PW, 30=K6, 101-120= Synthetic Personas). The OOBI overlay is the wrong network; the inbound NAD is the wrong direction.
  3. Cloud-egress (MÓDULO → Internet). GATEWAY.Art is the cloud-split control plane bridge in the future CONTROL.cloud-on-SaaS / DATA.on-prem split (see discuss_cloud_split_control_plane_saas_2026_05_10.md). It needs a controlled Internet uplink that does NOT route through Cloner.

The four directions share enough infrastructure (Multus + macvlan + host VLAN sub-interface DaemonSet + controller) that one generalized pattern is the right shape. The original ADR 0041 mechanism is the foundation; ADR 0042 is its 4-way generalization.

Decision

Generalize the controller (pkg/customer-mgmt-receiver/) and its operator surface (Dashboard /admin/module-network-attachments, API /api/module-network-attachments) to handle all four directional patterns behind one ConfigMap + one reconciler.

The four directions

direction Use case NetworkPolicy direction Required fields
inbound (default) DUT pushes telemetry to MÓDULO (ADR 0041) Ingress listen_port, protocols
outbound MÓDULO polls / pushes to DUT Egress peer_cidrs (catch-all rejected)
data-plane MÓDULO injects test traffic into DATA-plane VLAN Egress data_plane_vlan
cloud-egress MÓDULO uses controlled Internet uplink Egress dns_servers, peer_cidrs OR cloud_egress_unrestricted: true

Six design decisions (FINAL)

# Decision Choice Rationale
1 Backwards compat with ADR 0041 direction field optional; empty = inbound Operators with existing ADR 0041 entries do nothing — they continue to work. Wire format is the same.
2 IPAM model across all 4 patterns DHCP default + Static option, dual-stack The CNI dhcp daemon is already deployed cluster-wide (k8s/dut/30-cni-dhcp-daemon.yaml). Consistent across patterns simplifies the Dashboard UI.
3 Multi-tenant safety on outbound + cloud-egress peer_cidrs required; catch-all rejected An accidental empty peer_cidrs could turn the attachment into a 0.0.0.0/0 default route. Forcing the operator to enumerate peers makes audit logs and revocation trivial. Cloud-egress can explicitly opt-in to 0.0.0.0/0 via cloud_egress_unrestricted: true — single boolean = trail-of-evidence.
4 NAD shape unchanged across directions macvlan + bridge mode The difference between directions is at the NetworkPolicy + IPAM layer (routes, DNS, default-gw), not the CNI plugin. Reusing macvlan keeps the host-side DaemonSet trivial.
5 Cross-namespace target Deployments First-class SNMP exporter lives in web-agents (not oobi-fabric). validator-art also lives in web-agents. The reconciler patches whichever namespace the target Deployment's TargetModuleSpec declares. NAD + NetworkPolicy land in the same namespace.
6 Cloud-egress does NOT route through Cloner Separate path per MÓDULO Cloner is the persona-cloning orchestrator (Fn 1-9). It is not a generic Internet proxy. Each cloud-egress MÓDULO declares its own attachment, with its own DNS resolvers + peer set.

Schema (additive to ADR 0041)

# customer-mgmt-receivers ConfigMap (oobi-fabric namespace).
# ConfigMap name kept stable across ADR 0041 → 0042 for backwards compat.
receivers:
  - name: <label-safe; [a-z0-9-]{1,32}>
    target_module: <flow-art | syslog-reflector | snmp-art | cli-art |
                    api-infra-art | trex-art | relay-art | gateway-art>
    direction: <inbound (default) | outbound | data-plane | cloud-egress>
    host_nic: <physical NIC, e.g. eth1>
    vlan_tag: <1-4094>
    ipam: { ... }                # same as ADR 0041
    listen_port: <1-65535>       # 0 allowed for outbound/data-plane initiators
    protocols: [udp|tcp, ...]
    # ── direction-specific ───────────────────────────────────────
    peer_cidrs: [<CIDR>, ...]    # outbound: required; cloud-egress: required unless unrestricted
    data_plane_vlan: <1-4094>    # data-plane only
    dns_servers: [<IP>, ...]     # cloud-egress only
    cloud_egress_unrestricted: <bool>  # cloud-egress only; default false

Materialized resources per direction

Direction NAD shape NetworkPolicy Default route
inbound macvlan + bridge + DHCP/static IPAM Ingress, peer = receiver CIDR (or RFC1918+ULA for DHCP), ports = listen_port × protocols None — listener only
outbound macvlan + bridge + DHCP/static IPAM + default-route via gateway Egress, peer = peer_cidrs, ports = listen_port × protocols (or empty if no port) Yes (gateway from IPAM)
data-plane macvlan + bridge + IPAM in DATA-VLAN subnet Egress, peer = receiver CIDR (DATA VLAN range), ports = optional Optional
cloud-egress macvlan + bridge + DHCP/static + DNS block + default-route Egress: main rule (peer = peer_cidrs or 0.0.0.0/0 if unrestricted) + DNS rule (53 UDP+TCP to dns_servers) Yes

Shell-empty MÓDULO plumbing (ready before code lands)

Four MÓDULOs (TREX.Art, RELAY.Art, GATEWAY.Art, API INFRA.Art) are shell-empty in v3.9.x. ADR 0042 ships their target-module opt-in labels + module.network.tlsstress.art/wants annotation now — so the moment the binary lands, the Dashboard can already provision the attachments operators need:

MÓDULO wants annotation Patterns
snmp-exporter (SNMP.Art) outbound Polls DUT MGMT IF
trex-art data-plane DPDK line-rate on DATA VLANs
relay-art inbound,outbound Ingress collectors + egress orchestrator legs
gateway-art cloud-egress CONTROL.cloud bridge

Implementation — same controller, broader brain

The Go package pkg/customer-mgmt-receiver/ is unchanged in boundaries: stdlib-only + kubectl shell-out + 30s reconcile cadence. ADR 0042 adds direction-aware logic in three internal packages:

  • internal/types/Direction enum, per-direction Validate*() branches, expanded KnownTargetModules.
  • internal/nad/ — direction-aware NetworkPolicy synthesis (ingress vs egress, DNS rules for cloud-egress, default-route IPAM injection for outbound/cloud-egress).
  • internal/reconciler/ — cross-namespace inventory tracking (NADs/policies can live in web-agents for snmp-art / validator-art, in oobi-fabric for the others).

The controller still consumes the same ConfigMap (customer-mgmt-receivers, key receivers.yaml). Dashboard adds a new URL /admin/module-network-attachments with 4 tabs (Receiver / Initiator / Data Plane / Cloud Egress); the old URL /admin/customer-mgmt-receivers redirects to it. Both /api/customer-mgmt-receivers and /api/module-network-attachments work — same backend.

Threat model

  • Inbound (unchanged from ADR 0041): kernel netfilter is the fine-grained gate. NetworkPolicy ingress narrows to receiver CIDR (static) or RFC1918+ULA (DHCP fallback).
  • Outbound: NetworkPolicy egress narrows to peer_cidrs. A bug or operator mistake that empties the list is caught by Validate at PUT time (catch-all rejected). Outbound carries a kernel default route to the gateway, which routes only to peer_cidrs by virtue of NetworkPolicy.
  • Data-plane: This is the highest-blast-radius direction — injecting packets ON a customer VLAN. The peer set is bounded by the VLAN's subnet (in the IPAM block) + NetworkPolicy egress. The operator is trusted to pick the right VLAN; the controller does not auto-discover.
  • Cloud-egress: peer_cidrs required by default. 0.0.0.0/0 requires explicit cloud_egress_unrestricted: true — single boolean, surfaces in operator-facing audit logs.

Tier classification

Tier A (per ADR 0028 — operational plumbing, no patent-claimed primitives). Same as ADR 0041 — this is cross-cutting infra; ships in every distribution.

Consequences

Positive:

  • 1 ConfigMap + 1 controller covers 4 directional patterns. Operators learn the schema once.
  • ADR 0041 entries roll forward with no operator action — direction field is optional.
  • Shell-empty MÓDULO scaffolds (TREX, RELAY, GATEWAY) get attachment plumbing now. When their code lands, the Dashboard already shows the right attachment editor.
  • Cross-namespace targets work — snmp-art (in web-agents) is a first-class peer alongside flow-art (in oobi-fabric).
  • Cloud-egress is multi-tenant-safe by default — explicit opt-in required for 0.0.0.0/0.

Negative / accepted trade-offs:

  • The Receiver struct grew 6 optional fields. Validation is direction-aware, which means more switch branches in Receiver.Validate(). The branches are flat and well-typed — acceptable.
  • MergeNADAnnotations signature changed from (string, []string) string to (string, []string, map[string]string) string. Internal API only — no impact on operators.
  • 4 tabs in the Dashboard adds visual complexity. Mitigated by filtering: each tab shows only its own entries.

Out of scope (NOT this ADR):

  • Auto-discovery of host NICs (still operator-named).
  • Per-direction Prometheus metrics (next wave).
  • TLS NetFlow (still rejected per ADR 0041 Decision #6).

Alternatives considered

  • Keep 4 separate ConfigMaps, one per direction — Rejected. Doubles+ the operator's mental model + audit surface. The directions share 90% of the same fields.
  • Encode direction in target_module name (flow-art-inbound, snmp-art-outbound) — Rejected. Mixes orthogonal concepts and breaks the symmetry that lets one MÓDULO accept multiple directions (RELAY wants both inbound + outbound).
  • CRD per direction — Rejected for MVP. Same reasoning as ADR 0041: ConfigMap is simpler, auditable, no controller library bloat.
  • Use a generic egress proxy (e.g., Istio EgressGateway) for outbound + cloud-egress — Rejected. Istio's footprint is ~10× the Multus + DaemonSet pattern, and the multi-tenant boundary (one peer-set per attachment, not shared across MÓDULOs) is awkward to express in mesh policies.

Implementation plan

Wave Scope
MNA-1 (this PR) 4-pattern generalization in Go + Dashboard + ADR + matrix doc
MNA-2 TREX.Art binary lands; operator wires data-plane attachments
MNA-3 SNMP.Art Dashboard page (/admin/snmp-art) uses outbound attachments
MNA-4 GATEWAY.Art cloud-egress materialization (post v5.0 cloud-split GA)

References

  • ADR 0041 (docs/ADR/0041-customer-mgmt-receiver-pattern.md) — the original single-direction pattern
  • pkg/customer-mgmt-receiver/ — controller source + tests (37 Go tests across types/nad/reconciler/cmd)
  • dashboard/src/app/admin/module-network-attachments/ — operator UI (4 tabs)
  • dashboard/src/app/api/module-network-attachments/ — ADR 0042 canonical route (alias of /api/customer-mgmt-receivers)
  • docs/network-attachment-matrix.md — per-MÓDULO matrix (what each MÓDULO needs by pattern)
  • k8s/oobi/85-trex-art.yaml, 60-relay-art.yaml, 50-gateway-art.yaml — shell-empty scaffold MÓDULOs with module.network.tlsstress.art/wants annotations
  • k8s/dut/60-snmp-exporter.yaml — SNMP.Art with dut-mgmt + oobi-mgmt dual base attachment + wants: outbound

Last verified against shipping code: v3.10.0-dev (2026-05-13).