remove the swarm wireguard mesh; tombstone its options
This commit is contained in:
parent
fbfc6f392a
commit
adc61c9043
10 changed files with 330 additions and 1068 deletions
|
|
@ -50,7 +50,6 @@
|
|||
./glue-nats-auth-bao-identity.nix
|
||||
./glue-nats-bao-identity.nix
|
||||
./glue-nats-oidc-client.nix
|
||||
./glue-nats-wireguard.nix
|
||||
./glue-builder-oidc-client.nix
|
||||
./glue-ci-metrics-otel-oidc-client.nix
|
||||
./glue-ci-runner-otel-oidc-client.nix
|
||||
|
|
@ -95,7 +94,6 @@
|
|||
./swarm-victorialogs.nix
|
||||
./swarm-victoriametrics-service.nix
|
||||
./swarm-victoriametrics.nix
|
||||
./swarm-wireguard.nix
|
||||
./swarm.nix
|
||||
./hive-options-removed.nix
|
||||
./host-ca-removed.nix
|
||||
|
|
|
|||
|
|
@ -74,6 +74,14 @@ let
|
|||
The snapshot store was removed; agent-state backups are being rebuilt as
|
||||
swarm-archive. Remove this definition.
|
||||
'';
|
||||
|
||||
# Shared by the removed options of the WireGuard mesh.
|
||||
wireguardRemoved = ''
|
||||
The WireGuard mesh was removed. bao and nats are reached through
|
||||
their routable listeners (`services.hyperhive.deploy.bao.extraListenAddresses`
|
||||
with `openFirewall`, and `services.hyperhive.deploy.nats.openFirewall`; both
|
||||
off by default). Remove this definition.
|
||||
'';
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
|
|
@ -181,6 +189,41 @@ in
|
|||
"snapshotStore"
|
||||
"port"
|
||||
] snapshotStoreRemoved)
|
||||
(lib.mkRemovedOptionModule [
|
||||
"services"
|
||||
"hyperhive"
|
||||
"deploy"
|
||||
"wireguard"
|
||||
"enable"
|
||||
] wireguardRemoved)
|
||||
(lib.mkRemovedOptionModule [
|
||||
"services"
|
||||
"hyperhive"
|
||||
"deploy"
|
||||
"wireguard"
|
||||
"privateKeyFile"
|
||||
] wireguardRemoved)
|
||||
(lib.mkRemovedOptionModule [
|
||||
"services"
|
||||
"hyperhive"
|
||||
"deploy"
|
||||
"wireguard"
|
||||
"address"
|
||||
] wireguardRemoved)
|
||||
(lib.mkRemovedOptionModule [
|
||||
"services"
|
||||
"hyperhive"
|
||||
"deploy"
|
||||
"wireguard"
|
||||
"listenPort"
|
||||
] wireguardRemoved)
|
||||
(lib.mkRemovedOptionModule [
|
||||
"services"
|
||||
"hyperhive"
|
||||
"deploy"
|
||||
"wireguard"
|
||||
"persistentKeepalive"
|
||||
] wireguardRemoved)
|
||||
|
||||
# Same type, same meaning, new path — so a rename carries it exactly
|
||||
# and existing configs keep evaluating with one warning naming both
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
# Glue: where the queue and the wireguard mesh share a host, the queue's port
|
||||
# is open on the mesh interface.
|
||||
#
|
||||
# Interface-scoped, never host-wide: the firewall is default-deny before a
|
||||
# packet reaches the listener, so without this no other hive can reach the
|
||||
# queue. Gated on BOTH being here — the queue's own module gate
|
||||
# (`deploy.nats.enable`) is restated below; a change there must change here.
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
hyperhiveCfg = config.services.hyperhive;
|
||||
deployCfg = hyperhiveCfg.deploy;
|
||||
in
|
||||
{
|
||||
config = lib.mkIf (deployCfg.nats.enable && deployCfg.wireguard.enable) {
|
||||
networking.firewall.interfaces.wg-hive.allowedTCPPorts = [ hyperhiveCfg.swarm.nats.port ];
|
||||
};
|
||||
}
|
||||
|
|
@ -40,9 +40,6 @@ let
|
|||
nft = lib.getExe pkgs.nftables;
|
||||
br = cfg.bridgeName;
|
||||
|
||||
# The mesh interface (swarm-wireguard.nix): inter-hive traffic is not egress.
|
||||
meshInterface = "wg-hive";
|
||||
|
||||
# With `refuseEch`, the redirect of port 443 ends at `hive-priv
|
||||
# egress-ech-relay`, which refuses a ClientHello that carries the ECH
|
||||
# extension and sends the rest on to squid's HTTPS intercept port. The relay
|
||||
|
|
@ -143,8 +140,8 @@ let
|
|||
|
||||
# IPv4 TCP from the agent bridge to ports 80 and 443. `fib daddr oifname` is
|
||||
# the interface the route to the destination leaves by: the bridge (another
|
||||
# container), `lo` (an address of the host: `fib daddr type local`) and the
|
||||
# mesh interface are not egress. The redirect rewrites the destination to
|
||||
# container) and `lo` (an address of the host: `fib daddr type local`) are
|
||||
# not egress. The redirect rewrites the destination to
|
||||
# the bridge address and the port of squid's intercept listener (for port 443
|
||||
# with `refuseEch`, of the relay), which reads the original destination from
|
||||
# conntrack. IPv6 is left alone: the agent bridge has no IPv6 addressing.
|
||||
|
|
@ -153,7 +150,7 @@ let
|
|||
type nat hook prerouting priority dstnat; policy accept;
|
||||
iifname "${br}" meta nfproto ipv4 ether saddr @direct_mac return
|
||||
iifname "${br}" meta nfproto ipv4 fib daddr type local return
|
||||
iifname "${br}" meta nfproto ipv4 fib daddr oifname { "${br}", "${meshInterface}" } return
|
||||
iifname "${br}" meta nfproto ipv4 fib daddr oifname "${br}" return
|
||||
iifname "${br}" meta nfproto ipv4 tcp dport 80 counter redirect to :${toString egressCfg.intercept.httpPort}
|
||||
iifname "${br}" meta nfproto ipv4 tcp dport 443 counter redirect to :${
|
||||
toString (if refuseEch then intercept.relayPort else intercept.httpsPort)
|
||||
|
|
@ -171,8 +168,8 @@ let
|
|||
# Priority `filter - 10` runs before the NixOS firewall's forward handling,
|
||||
# which sits at `filter` on both backends (`FORWARD` of the iptables backend,
|
||||
# the `forward` chain of `nixos-fw`). An `accept` here ends this chain only,
|
||||
# so it allows nothing those refuse; a `reject` ends the packet. The mesh
|
||||
# and the MACs in `direct_mac` (the unrestricted agents and the service
|
||||
# so it allows nothing those refuse; a `reject` ends the packet. The MACs
|
||||
# in `direct_mac` (the unrestricted agents and the service
|
||||
# containers, the traffic the intercept chain leaves alone) come first in
|
||||
# both modes, so a `log` line names what `reject` would refuse. Traffic to
|
||||
# the host's own addresses (squid, dnsmasq, the gateway) is delivered
|
||||
|
|
@ -183,7 +180,6 @@ let
|
|||
|
||||
chain forward {
|
||||
type filter hook forward priority filter - 10; policy accept;
|
||||
iifname "${br}" oifname "${meshInterface}" accept
|
||||
${fromBridge} ether saddr @direct_mac accept
|
||||
${fromBridge} counter
|
||||
${lib.concatStringsSep "\n " (
|
||||
|
|
@ -315,7 +311,7 @@ in
|
|||
default = "log";
|
||||
description = ''
|
||||
What the `inet hive_egress` table does with traffic the host forwards
|
||||
from the agent bridge to anywhere but the bridge and the mesh. In `log` and
|
||||
from the agent bridge to anywhere but the bridge. In `log` and
|
||||
`reject` the traffic of the MACs in the `direct_mac` set (the unrestricted
|
||||
agents and the service containers) is accepted and never logged.
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ let
|
|||
d.victoriametrics.enable
|
||||
d.victorialogs.enable
|
||||
d.swarm-secret-publisher.enable
|
||||
d.wireguard.enable
|
||||
h.gateway.enable
|
||||
h.gateway.dns.enable
|
||||
h.network.enable
|
||||
|
|
|
|||
|
|
@ -3113,8 +3113,8 @@ in
|
|||
store is always one of its readers. Every other address depends on
|
||||
which network the reading hives share with this one, and that is a
|
||||
deployment fact no other module's config can be read to infer — a
|
||||
swarm meshed over wireguard names its mesh address, one on a trusted
|
||||
LAN names that interface, and an all-local swarm names nothing at all.
|
||||
swarm on a trusted LAN names that interface, and an all-local swarm
|
||||
names nothing at all.
|
||||
|
||||
Addresses only, no port: a store reachable on two ports is a
|
||||
misconfiguration rather than a topology.
|
||||
|
|
|
|||
|
|
@ -56,8 +56,7 @@ in
|
|||
sits outside hyperhive's claimed ranges (dashboard 7000, forge
|
||||
3000, every agent in 8100..8999 via FNV-1a hash).
|
||||
|
||||
Opened on the bridge interface, for agent containers, and on
|
||||
`wg-hive` when this host is on the mesh, for other hives. Host-wide
|
||||
Opened on the bridge interface, for agent containers. Host-wide
|
||||
only when {option}`services.hyperhive.deploy.nats.openFirewall` is
|
||||
set. TLS only: a client that does not speak it is refused.
|
||||
|
||||
|
|
|
|||
|
|
@ -522,10 +522,9 @@ in
|
|||
Open {option}`services.hyperhive.swarm.nats.port` in the host firewall
|
||||
on every interface, so hives on other hosts can reach the queue on
|
||||
this host's LAN or public address. Off by default: the port is
|
||||
otherwise open only on the agent bridge and, when this host is on the
|
||||
mesh, on `wg-hive`.
|
||||
otherwise open only on the agent bridge.
|
||||
|
||||
⚠️ This exposes the queue beyond the tunnel. The server binds every
|
||||
⚠️ This exposes the queue beyond the agent bridge. The server binds every
|
||||
address, so the firewall is the only thing this option changes. What
|
||||
protects the port is the queue itself: TLS is required, and
|
||||
`auth_callout` refuses every client it cannot identify. Whether that
|
||||
|
|
@ -846,8 +845,8 @@ in
|
|||
# TLS required on the client port: a server with a `tls`
|
||||
# block advertises `tls_required` and refuses a client that
|
||||
# does not upgrade. No `allow_non_tls`, which would keep the
|
||||
# plaintext path open across the mesh. The 2s timeout is for
|
||||
# a handshake that crosses the mesh; upstream's 0.5s is sized
|
||||
# plaintext path open on the network. The 2s timeout is for
|
||||
# a handshake that crosses the network; upstream's 0.5s is sized
|
||||
# for a LAN.
|
||||
tls = {
|
||||
cert_file = tlsCertPath;
|
||||
|
|
|
|||
|
|
@ -1,152 +0,0 @@
|
|||
# The WireGuard inter-hive mesh for the local host. Split out of
|
||||
# ./swarm.nix because the two are different concerns with different
|
||||
# audiences: that file declares WHO the peers are (consumed by
|
||||
# swarm-controller's hive roster and, here, the mesh), while this one
|
||||
# is plain host networking that a machine which runs no hive can also enable.
|
||||
#
|
||||
# The two stay coupled by data, not by structure: the per-peer
|
||||
# `wireguard*` fields live on the peer submodule in ./swarm.nix, since
|
||||
# that is where a peer is described, and this module reads them.
|
||||
#
|
||||
# Everything declared here is `deploy.*`, not `swarm.*`: by ./deploy.nix's
|
||||
# rule, `swarm.*` is what a hive needs to be a *client* of the swarm, and
|
||||
# none of this is. A peer needs this host's `wireguardEndpoint` (the roster
|
||||
# entry in ./swarm.nix); what interface this host brings up, on which
|
||||
# address, with which key, is nobody else's business. `listenPort` included
|
||||
# — it is what this host binds, while the port a peer dials is the one
|
||||
# inside `wireguardEndpoint`.
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
# WireGuard mesh config for the local host.
|
||||
# When enabled, a `wg-hive` interface connects to all peers that have
|
||||
# `wireguardPublicKey` declared. Peers reachable over the mesh are
|
||||
# preferred for inter-hive traffic (no public TLS round-trip needed);
|
||||
# peers without a public key still work via normal HTTPS.
|
||||
options.services.hyperhive.deploy.wireguard = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enable the WireGuard inter-hive mesh. When true, a `wg-hive`
|
||||
interface is brought up connecting to all swarm peers that
|
||||
declare a `wireguardPublicKey`. Requires
|
||||
`privateKeyFile` to be set.
|
||||
'';
|
||||
};
|
||||
|
||||
privateKeyFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
example = "/etc/wireguard/hive.key";
|
||||
description = ''
|
||||
Path to the host's WireGuard private key file. The file must
|
||||
be readable by root and should have mode 0400. Generate with
|
||||
`wg genkey > /etc/wireguard/hive.key`. Required when
|
||||
`deploy.wireguard.enable = true`.
|
||||
'';
|
||||
};
|
||||
|
||||
address = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
example = "10.100.0.1/24";
|
||||
description = ''
|
||||
IP address (with prefix) of this host on the WireGuard mesh.
|
||||
Use a /24 (or broader) prefix so the routing table covers all
|
||||
peer /32 routes. Example: `"10.100.0.1/24"` for a 256-host mesh.
|
||||
'';
|
||||
};
|
||||
|
||||
listenPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 51820;
|
||||
description = ''
|
||||
UDP port the local WireGuard interface listens on. Must be
|
||||
reachable from peer hosts when they initiate the tunnel.
|
||||
Default: 51820 (standard WireGuard port).
|
||||
'';
|
||||
};
|
||||
|
||||
persistentKeepalive = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.int;
|
||||
default = 25;
|
||||
example = 25;
|
||||
description = ''
|
||||
Seconds between keepalive packets sent to each peer. Useful
|
||||
when this host (or a peer) is behind NAT — keeps the UDP hole
|
||||
open. Set to null to disable. Default: 25 seconds.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
# Gated on the mesh itself, NOT on `deploy.hive-controller.enable`. The
|
||||
# mesh is host networking, not a c0re feature: a swarm host that runs no
|
||||
# hive can join the mesh too.
|
||||
# Nothing below is c0re-specific; the peer data c0re consumes
|
||||
# (HIVE_PEER_CA_PATHS) is rendered in ./hive-c0re and stays gated there.
|
||||
config = lib.mkIf config.services.hyperhive.deploy.wireguard.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.services.hyperhive.deploy.wireguard.privateKeyFile != null;
|
||||
message = ''
|
||||
services.hyperhive.deploy.wireguard.enable requires
|
||||
services.hyperhive.deploy.wireguard.privateKeyFile to be set.
|
||||
Generate a key: wg genkey > /etc/wireguard/hive.key
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = config.services.hyperhive.deploy.wireguard.address != "";
|
||||
message = ''
|
||||
services.hyperhive.deploy.wireguard.enable requires
|
||||
services.hyperhive.deploy.wireguard.address to be set
|
||||
(e.g. "10.100.0.1/24").
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
# WireGuard inter-hive mesh. Brings up a `wg-hive` interface and
|
||||
# connects to each peer that has `wireguardPublicKey` set.
|
||||
networking.wireguard.interfaces =
|
||||
let
|
||||
wgCfg = config.services.hyperhive.deploy.wireguard;
|
||||
# `swarm.hives` minus this host's own hive: a mesh that included
|
||||
# our own entry would configure a tunnel to ourselves.
|
||||
meshPeers = lib.filterAttrs (
|
||||
name: p:
|
||||
name != config.services.hyperhive.deploy.hive-controller.hiveName
|
||||
&& p.wireguardPublicKey != null
|
||||
&& p.wireguardAddress != null
|
||||
) config.services.hyperhive.swarm.hives;
|
||||
in
|
||||
{
|
||||
wg-hive = {
|
||||
ips = [ wgCfg.address ];
|
||||
listenPort = wgCfg.listenPort;
|
||||
privateKeyFile = wgCfg.privateKeyFile;
|
||||
peers = lib.mapAttrsToList (
|
||||
_name: p:
|
||||
{
|
||||
publicKey = p.wireguardPublicKey;
|
||||
allowedIPs = [ p.wireguardAddress ];
|
||||
}
|
||||
// lib.optionalAttrs (p.wireguardEndpoint != null) {
|
||||
endpoint = p.wireguardEndpoint;
|
||||
}
|
||||
// lib.optionalAttrs (wgCfg.persistentKeepalive != null) {
|
||||
persistentKeepalive = wgCfg.persistentKeepalive;
|
||||
}
|
||||
) meshPeers;
|
||||
};
|
||||
};
|
||||
|
||||
# Open the WireGuard UDP port on the host firewall (host-level
|
||||
# networking — not inside containers).
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
config.services.hyperhive.deploy.wireguard.listenPort
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
@ -11,10 +11,7 @@
|
|||
# detected it. One entry per hive makes that unrepresentable.
|
||||
#
|
||||
# Consumed by swarm-controller's own hive directory (its `/api/hives`,
|
||||
# swarm-controller.nix) and the mesh in ./swarm-wireguard.nix. The mesh
|
||||
# lives there rather than here because bringing up an interface is
|
||||
# host networking rather than swarm bookkeeping, and a host that runs
|
||||
# no hive still needs it.
|
||||
# swarm-controller.nix).
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
|
|
@ -27,6 +24,30 @@ let
|
|||
|
||||
hiveNames = lib.attrNames swarmCfg.hives;
|
||||
|
||||
# Per-hive fields of the removed WireGuard mesh. Declared hidden so a
|
||||
# directory that still sets one reaches the assertion below. A removed
|
||||
# option cannot be tombstoned with `mkRemovedOptionModule` here: it writes
|
||||
# top-level `assertions`, which a submodule does not have.
|
||||
removedMeshFields = [
|
||||
"wireguardPublicKey"
|
||||
"wireguardEndpoint"
|
||||
"wireguardAddress"
|
||||
];
|
||||
removedMeshOptions = lib.genAttrs removedMeshFields (
|
||||
_:
|
||||
lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.raw;
|
||||
default = null;
|
||||
visible = false;
|
||||
}
|
||||
);
|
||||
meshFieldsSet = lib.concatMap (
|
||||
name:
|
||||
map (field: "${name}.${field}") (
|
||||
lib.filter (f: swarmCfg.hives.${name}.${f} != null) removedMeshFields
|
||||
)
|
||||
) hiveNames;
|
||||
|
||||
nameGuards = import ./lib/name-guards.nix { inherit lib; };
|
||||
|
||||
# Names no hive may BE, and words no hive name may CONTAIN. Both files
|
||||
|
|
@ -166,62 +187,15 @@ in
|
|||
something else.
|
||||
'';
|
||||
};
|
||||
|
||||
wireguardPublicKey = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "base64pubkey=";
|
||||
description = ''
|
||||
WireGuard public key for this hive's host. Required when
|
||||
`services.hyperhive.deploy.wireguard.enable = true` and
|
||||
you want this hive reachable over the mesh. Null = TLS-
|
||||
only peering (public internet, no mesh tunnel).
|
||||
'';
|
||||
};
|
||||
|
||||
wireguardEndpoint = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "203.0.113.1:51820";
|
||||
description = ''
|
||||
WireGuard endpoint for this hive in `host:port` form.
|
||||
Null = this hive has no reachable endpoint, so the tunnel
|
||||
is initiated from the other side.
|
||||
|
||||
Reads like a fact about the relationship and is not: it
|
||||
says whether *this* hive can be dialled, which every other
|
||||
hive in the swarm needs the same answer to.
|
||||
'';
|
||||
};
|
||||
|
||||
wireguardAddress = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "10.100.0.2/32";
|
||||
description = ''
|
||||
IP address (with prefix) of this hive's host on the
|
||||
WireGuard mesh. Used as the `allowedIPs` for its
|
||||
WireGuard config entry (`./swarm-wireguard.nix`), so
|
||||
intra-swarm traffic can route over the mesh address
|
||||
rather than the public domain. Required to include
|
||||
a hive in the mesh (entries missing this field are
|
||||
silently excluded from `wg-hive`).
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
// removedMeshOptions;
|
||||
}
|
||||
)
|
||||
);
|
||||
default = { };
|
||||
example = {
|
||||
pr1ma = {
|
||||
wireguardAddress = "10.100.0.1/32";
|
||||
wireguardEndpoint = "203.0.113.1:51820";
|
||||
};
|
||||
edge = {
|
||||
domain = "edge.elsewhere.example";
|
||||
wireguardAddress = "10.100.0.2/32";
|
||||
};
|
||||
pr1ma = { };
|
||||
edge.domain = "edge.elsewhere.example";
|
||||
};
|
||||
description = ''
|
||||
Every hive in this swarm, keyed by `hiveName` — **including this
|
||||
|
|
@ -289,6 +263,14 @@ in
|
|||
);
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = meshFieldsSet == [ ];
|
||||
message = ''
|
||||
The WireGuard mesh was removed; bao and nats are reached
|
||||
through their routable listeners. Remove services.hyperhive.swarm.hives.<name>.wireguard{PublicKey,Endpoint,Address}
|
||||
from: ${lib.concatStringsSep ", " meshFieldsSet}.
|
||||
'';
|
||||
}
|
||||
(nameGuards.mustBeIdent {
|
||||
option = "services.hyperhive.swarm.hives";
|
||||
names = hiveNames;
|
||||
|
|
|
|||
Loading…
Reference in a new issue