This document tracks pending architectural improvements and technical debt.
- Context:
internal/controller/edgenode_controller.gousesmgr.GetEventRecorderForwith arecord.EventRecorderfield. - Task: Migrate to the new events API (
mgr.GetEventRecorderreturningevents.EventRecorder) and rewrite every.Event()call toEventfwith the regarding/related/action signature. - When: Only once controller-runtime announces actual removal of the classic API; today it is still supported and the migration touches every event call site for no functional gain.
- Context: Envoy already active-health-checks each uplink's
/ready(spec.edge.healthCheck), so a downed replica is ejected after roughlyinterval * unhealthyThreshold(~10s with the defaults). During that detection window new connections round-robined onto the just-failed replica still fail (connect timeout or RST). - Task: Add optional passive ejection (
outlier_detectionin the CDS cluster) so a replica is dropped on the first real connection failures, without waiting for the active-check threshold. Tuneable from a newspec.edge.outlierDetectionblock (enabled,consecutiveFailures,ejectionDuration), off by default, defaults applied in the planner likehealthCheck. - When: Only if the ~10s window during a single-uplink failure proves too painful in practice. Active health checks already cover the steady state and the all-down case (
healthy_panic_thresholdis hard-coded to 0 for fast failure); this only tightens the transient. Keep it out of scope until there is a real need, to avoid the flapping and tuning surface it adds.
- Context: On the edge, Envoy LDS/CDS are pushed as files that Envoy hot-reloads on its own. Today the operator writes them over SSH, separately from the WireGuard document that
tunnelctlapplies. - Task: Add an optional
envoysection to thetunnelctldesired-state document (the rendered LDS/CDS) soapplywrites those files atomically andstatusreports Envoy health (active + loaded config hash) alongside WireGuard. Oneapply/statusper node, with the symmetrywireguard+ routing (envoyon the edge,nftableson the uplink). - Boundary:
tunnelctlapplies config only. Installing the Envoy binary, the immutable bootstrap and the systemd unit stay in the operator enroll (one-time host setup tied to--envoy-version). - When: The WireGuard + nftables document already ships and is applied natively by
tunnelctl; this extends the same document with the Envoy section.
- Context:
tunnelctlprograms WireGuard through the kernel module. Hosts without it (OpenVZ/LXC "VPS", very old or minimal kernels) cannot bring the interface up. - Task: When the kernel module is absent, fall back to a userspace data plane (
wireguard-go, orboringtunif it benchmarks better) shipped as a static binary liketunnelctl. Thewgctrlconfiguration path is unchanged (same UAPI), so only interface creation/teardown differs behind the existing backend seam ininternal/wg. Note this fallback IS a genuine resident daemon on the host (the userspace data plane), unlike the kernel path. - Does NOT change privileges: userspace WireGuard still needs
CAP_NET_ADMIN(creating the TUN via/dev/net/tun+TUNSETIFF, and the address/MTU/up/route netlink calls that are shared with the kernel backend). It only removes the kernel-module dependency, not the root/cap requirement (see item 5). - When: Choose wireguard-go vs boringtun by performance.
- Context: The uplink container runs as root (
gcr.io/distroless/static:latest) because creating the WireGuard link and programming nftables go through netlink, which needs an effectiveCAP_NET_ADMIN. A non-root uid does not get caps in its effective set even in a privileged pod (we hitoperation not permittedwith the:nonrootimage), so root is the simple path today. - Task: Drop root (and ideally
privileged) by giving the binary file capabilities:setcap cap_net_admin+ep /usr/local/bin/uplinkinDockerfile.uplink, run as a non-root uid, and addsecurityContext.capabilities.add: [NET_ADMIN](the bounding set the file cap is raised into). This is independent of the WireGuard backend (works for the kernel path and item 4's userspace path; the latter also needs/dev/net/tunmounted). Kubernetes ambient capabilities would be the cleaner mechanism but are still alpha/poorly exposed, so file caps are the pragmatic route. - When: Only if running the data-plane pod as root/privileged becomes a real concern (hardened clusters, PSA
restricted). Not urgent.
- Context:
provision.installEnvoyBinarydownloads the Envoy release over HTTPS from the official GitHub releases and runs it with no integrity check beyond TLS. A compromised mirror/redirect, a truncated download or a corrupted artifact would be installed and started as the sole public-facing process on the VPS. - Task: Pin the expected SHA-256 per
--envoy-version(a small embedded map, or a sidecar.sha256fetched from the same release and verified) and have the install command compute and compare the digest before the atomicmvinto/usr/local/bin/envoy; fail the enroll loudly on mismatch. Keep it arch-aware (amd64/arm64 differ). - When: Worth doing as supply-chain hardening; the edge binary is security-sensitive. Low effort once the per-version digests are tracked alongside
DefaultEnvoyVersion.
- Context: The CRD CEL rules enforce "exactly one of
target.service/target.address" but cannot requiretarget.portwhenaddressis used:portis an optional non-pointer int, so an omitted value serialises as absent and slips past theMinimum=1marker.planner.resolveTargetthen rejectsport <= 0at reconcile time, surfacing asPlanBuildFailedon the EdgeNode rather than as a clear admission error on the offending PortBinding. - Task: Add an XValidation rule on PortBinding requiring
target.portto be set (and in range) whenevertarget.addressis set, so the bad spec is rejected at apply time with a precise message instead of failing a downstream EdgeNode reconcile. Keep the planner check as defense-in-depth. - When: Small, purely additive validation; do it next time the API CRDs are regenerated (it is a three-part CRD edit per DESIGN_AND_RULES §5).
- Context: The test suite covers everything fakeable (envtest + FakeExecutor for SSH, a root-gated netlink test for routes); what remains genuinely needs a real VPS + Kind session with real WireGuard handshakes and a running Envoy.
- Task: Verify on a real enroll: TLS passthrough actually forwards a connection end to end (the catch-all filter chain accepts any SNI, the prior
server_names: ["*"]was render-only and untested against Envoy); thetunnel-bootoneshot brings WireGuard back after a VPS reboot before Envoy starts (no admin-bind failure, no outage window); a uplink rolling update drains gracefully (the SIGTERM 503 ejects the replica before the link drops, no client resets); the PortBindingReadytransition driven by the watch chain; stale-route deletion visible inip routeafter an uplink scale-down; the uplink headless Service resolving per-pod DNS and disappearing on EdgeNode deletion; the.tunnel.*orphan sweep on a cancelled transfer; the forced SSH teardown path against a silently dead peer; and that leader election does not get in the way undermake run(disable with--leader-elect=falseif it does). - When: Blocked on regenerating access to the dev VPS (recreated; the
vps-ssh-secretis stale).
- Context: Host kernel/NIC tuning is split across two write boundaries. WireGuard, nftables and the
netdevGRO/GSO toggle are all applied natively bytunnelctlfrom the desired-state document; the sysctls (ip_forward,net.core.rmem_max/wmem_max) are written byinternal/provisionover SSH as the/etc/sysctl.d/99-tunnel.confdrop-in. The drop-in persists on its own (systemd-sysctl reapplies it on boot), which is why it was never put behind thetunnel-bootoneshot like the non-persistent kernel state. - Task: Move the sysctls into a section of the
tunnelctldocument (e.g. alongsidenetdev, sincermem_max/wmem_maxis host network tuning) so a single boundary applies everything that touches the host kernel.tunnelctlwould write the values natively (the drop-in for boot persistence plus a live apply) and the operator stops writing sysctls over SSH. - When: Consistency cleanup, not a correctness fix: the current split works and the drop-in already survives reboot. Worth doing the next time the document contract is touched (it pairs naturally with item 3's
envoysection), to collapse to one rule: everything that programs the host kernel goes throughtunnelctl. - Note: When this moves, drop the on-disk drop-in entirely and apply the sysctls in RAM, the same way WireGuard/nftables/netdev are applied. The
tunnel-bootoneshot then reapplies them on boot like the rest, so the boundary stays uniform (kernel state in RAM, reapplied at boot) instead oftunnelctlwriting some things to RAM and one thing to disk.