/** * ADR-111 Phase 2 — WgMeshService. * * Pure-projection service: takes the federation peer registry + local * WG identity and produces (a) a `wg-quick`-compatible config string, * (b) per-peer `wg set` commands the operator runs to converge an * already-running interface, and (c) the AllowedIPs slice that maps to * each peer's trust level. * * Deliberately does NOT shell out. Bringing up a network interface * requires root and modifies system state — per CLAUDE.md's * destructive-actions guidance the service emits the commands; the * operator (or a thin shell wrapper they audit) runs them. * * Phase 3 (breaker integration) consumes this service: peer state * transitions invoke removeAllowedIPs/removePeer/restoreAllowedIPs, * which produce commands without side effects. */ import { TrustLevel } from '../entities/trust-level.js'; import { FederationNode } from '../entities/federation-node.js'; import type { WgLocalKey } from '../value-objects/wg-config.js'; export interface WgPortRule { readonly proto: 'tcp' | 'udp' | 'all'; readonly port?: number; readonly portRange?: readonly [number, number]; } /** * Trust-level → reachability. ADR-111's `WG_NETWORK_GATES`. * * Note: WireGuard's `AllowedIPs` is L3 routing, not L4 ACL — these port * rules describe what the L4 firewall (Phase 4: nftables/pf) should * enforce. v1 (this phase) only produces the rules; Phase 4 projects * them into kernel firewall syntax. Until then, callers using v1 * fall back to the simpler "AllowedIPs = peer's mesh IP" model (option * (b) in the ADR — app-layer auth carries the access decision). */ export declare const WG_NETWORK_GATES: Record; /** * Minimum trust level whose peers get a `[Peer]` block in the WG config. * UNTRUSTED stays out of the mesh entirely — it's the explicit drop bucket. */ export declare const WG_MIN_MESH_TRUST: TrustLevel; export interface WgPeerFields { readonly wgPublicKey: string; readonly wgMeshIP: string; readonly wgEndpoint: string; } /** * Return the validated peer wg fields, or null if any is malformed or * out-of-range. Callers treat null as "peer not eligible for mesh" — the * peer stays in the federation discovery registry but is excluded from * the wg config and any wg commands. */ export declare function readSafePeerWgFields(peer: FederationNode): WgPeerFields | null; export interface WgMeshServiceConfig { /** Local interface name. Defaults to `ruflo-fed`. */ readonly interfaceName?: string; /** Local UDP listen port. WireGuard's standard is 51820. */ readonly listenPort?: number; /** Mesh subnet for AllowedIPs filtering. Defaults to DEFAULT_MESH_SUBNET. */ readonly meshSubnet?: string; } export interface WgPeerSummary { readonly nodeId: string; readonly trustLevel: TrustLevel; readonly trustLabel: string; readonly meshIP: string; readonly endpoint: string; readonly publicKey: string; readonly state: 'active' | 'suspended' | 'evicted'; readonly allowedIPs: readonly string[]; } /** * A planned `wg(8)` mutation. Service consumers either execute these or * surface them to the operator. Strings are validated for shell metachars * before being formatted into a command — callers should still review. */ export interface WgCommand { readonly verb: 'set-allowed-ips' | 'remove-allowed-ips' | 'remove-peer' | 'add-peer'; /** Public key of the peer this command targets. */ readonly peerPublicKey: string; /** Render to a `wg set peer ...` shell-ready string. */ readonly cmd: string; /** Human-readable rationale for audit logs. */ readonly rationale: string; } export declare class WgMeshService { private readonly interfaceName; private readonly listenPort; private readonly meshSubnet; private localKey; private localMeshIP; /** Per-peer AllowedIPs after the last applyTrustLevelToAllowedIPs() call. Drives restore from breaker. */ private readonly lastAppliedAllowedIPs; /** Peer-pubkey → suspended flag for breaker integration. */ private readonly suspended; /** Peer-pubkey → evicted flag. */ private readonly evicted; constructor(config?: WgMeshServiceConfig); /** Bind the local WG identity. Required before buildInterfaceConfig(). */ setLocalIdentity(key: WgLocalKey, meshIP: string): void; getInterfaceName(): string; getMeshSubnet(): string; /** * Build a `wg-quick`-compatible config string from current peers. * * Peers below WG_MIN_MESH_TRUST (UNTRUSTED) are excluded entirely. * Suspended peers stay in the config but with `AllowedIPs =` empty * (soft-block). Evicted peers are dropped completely. * * Operator writes this to `/etc/wireguard/.conf` and runs * `wg-quick up `. */ buildInterfaceConfig(peers: readonly FederationNode[]): string; /** * Compute the AllowedIPs slice for a peer at its current trust level. * v1: each peer gets its own /32 mesh IP. Phase 4 will narrow this * further via firewall rules; v1 keeps L3 broad and relies on the app * layer + trust gates for access decisions. */ computeAllowedIPs(peer: FederationNode, meshIP: string): readonly string[]; /** * Apply trust level → AllowedIPs as a `wg set` command for an * already-running interface. Used when a peer joins or its trust level * changes without needing a full config rewrite. */ applyTrustLevelToAllowedIPs(peer: FederationNode, meshIP: string, pubkey: string): WgCommand; /** * Phase 3 hook — peer SUSPENDED. Clear AllowedIPs (soft-block: keeps * the peer's public key registered with the interface but blocks all * routing to/from it). */ removeAllowedIPs(peer: FederationNode, pubkey: string, reason?: string): WgCommand; /** * Phase 3 hook — peer reactivated. Restore the AllowedIPs slice that * was active before suspension. Returns null if the peer was never * suspended (idempotent). */ restoreAllowedIPs(peer: FederationNode, meshIP: string, pubkey: string): WgCommand | null; /** * Phase 3 hook — peer EVICTED. Terminal removal from the mesh; the * peer's `[Peer]` block is dropped on next config rebuild and an * immediate `wg set peer remove` is emitted to flush * runtime state without a config reload. */ removePeer(peer: FederationNode, pubkey: string, reason?: string): WgCommand; /** Summarize the mesh state for `federation_wg_status` (Phase 6) and audit. */ summarize(peers: readonly FederationNode[]): readonly WgPeerSummary[]; /** * Defense-in-depth: validate that all bits we splice into a shell * command are alphanumeric / base64 / WG-allowed chars. Refuses the * command rather than ship a substring that might escape its slot. */ private formatCmd; } //# sourceMappingURL=wg-mesh-service.d.ts.map