feat: the WireGuard overlay, converged under the window of decision 0018 #49

Merged
marcos-mendez merged 4 commits from feat/wireguard-overlay into main 2026-09-30 03:47:30 -03:00
marcos-mendez commented 2026-09-30 02:49:54 -03:00 (Migrated from github.com)

Step 2 of the order of handbook decision 0020: the WireGuard overlay in the spec, converged under the window of decision 0018.

The fields (docs/spec.md, "overlay")

network:
  overlay:
    wireguard:
      interface: wg0                  # default
      address: fd00:6b65:1::1/64      # required, IPv6
      ipv4_address: 10.66.0.1/24      # optional
      listen_port: 51820              # default
      private_key:
        file: /etc/wireguard/wg0.key  # default; made on first converge
      peers:
        - public_key: 0niNkgzhpbKmTSrWCzukb6jaYogKZkGhW+xWlh52Mh8=
          endpoint: "[2001:db8:2::20]:51820"   # optional
          allowed_ips: [fd00:6b65:1::2/128]
          persistent_keepalive: 25             # optional

How it is converged

  • Reuses keel#41/#48's machinery: same marker, lock, timers and boot unit. The marker gains a kind (uplink or overlay) and absent (the change created the file). The overlay sequence is wg-quick down on the old file, file in place (0600), wg-quick up, the same in both directions. A first overlay reverts by removing its file.
  • Converged in a container even with managed_by: host, as 0018 decides. So the change is dated on the process start clock (a thread's start time). lxcfs's /proc/uptime counts from the container's start and could not tell an old session from a new one.
  • wg-quick@<if> is enabled on confirm, not on apply. A reboot inside the window of a first overlay therefore leaves nothing up.
  • Only one change waits in the window at a time. An overlay change is refused in a run whose uplink moves.
  • Refusals: no wireguard-tools; a container whose host has not loaded the module (the message says to run modprobe wireguard on the host); a change already pending; a key file that is not 0600 and owned by root.
  • The key is made with wg genkey on the machine, never under --root (keel-core#8), and it never enters the rendered file: PostUp = wg set %i private-key FILE. --skip-network (first boot) still makes the key.
  • inspect reads /etc/wireguard back and reports the public key (wg pubkey reads the key file on stdin). diff compares peers by public key.
  • New commands: keel network wireguard key and keel network wireguard suggest-address.

Which sessions confirm an overlay change

A new session from another machine is accepted when it arrives at an address the overlay declares, or at an address held by another interface (the uplink). An overlay change can break two paths: the overlay itself, and the uplink, whose replies wg-quick's routes for allowed_ips can capture. A new session over either path shows that path survived. Accepting only the overlay would make pairing impossible, because the first node's overlay carries nothing until the second node declares it. confirm says which path was tested. A console or a host attach is accepted, as for the uplink. Older sessions, sessions from the machine to itself, and sessions arriving at an address the overlay does not declare are refused.

Needs from the layer

wireguard-tools is not in the core layer: Keel-Linux/keel-core#15. The package Suggests: wireguard-tools.

Tests

  • Coverage 99% (gate 95). New test files: test_spec_overlay, test_system_overlay, test_network_overlay_window, test_inspect_overlay, test_network_wireguard, test_network_wireguard_cli.
  • Real tools, not mocks (tests/wgtools.py). wg genkey and wg pubkey make and read the key pair. wg-quick strip parses the rendered file. wg-quick up brings it up in a network namespace of its own, where wg show must report the declared key, port, endpoints, allowed IPs and keepalive, and ip the addresses. CI installs wireguard-tools, and these tests fail there rather than skip.
  • On VM1 (KVM, ifupdown-ng; a second node in a network namespace on it; everything removed afterwards):
    • first overlay, unconfirmed: the file was removed at the end of the window and the interface was down
    • confirmed over the overlay from node B: "overlay was tested", and wg-quick@wg0 was enabled
    • a breaking port change: confirm from the older session was refused (exit 21), then the change reverted to the old file and the handshake came back
    • confirmed over the uplink: "overlay itself was not tested"
    • reboot inside the window: keel-network-revert restored the file at 7.0 s, before wg-quick@wg0 at 7.4 s
  • CI green
Step 2 of the order of handbook decision 0020: the WireGuard overlay in the spec, converged under the window of decision 0018. ## The fields (docs/spec.md, "overlay") ```yaml network: overlay: wireguard: interface: wg0 # default address: fd00:6b65:1::1/64 # required, IPv6 ipv4_address: 10.66.0.1/24 # optional listen_port: 51820 # default private_key: file: /etc/wireguard/wg0.key # default; made on first converge peers: - public_key: 0niNkgzhpbKmTSrWCzukb6jaYogKZkGhW+xWlh52Mh8= endpoint: "[2001:db8:2::20]:51820" # optional allowed_ips: [fd00:6b65:1::2/128] persistent_keepalive: 25 # optional ``` ## How it is converged - Reuses keel#41/#48's machinery: same marker, lock, timers and boot unit. The marker gains a `kind` (uplink or overlay) and `absent` (the change created the file). The overlay sequence is `wg-quick down` on the old file, file in place (0600), `wg-quick up`, the same in both directions. A first overlay reverts by removing its file. - Converged in a container even with `managed_by: host`, as 0018 decides. So the change is dated on the process start clock (a thread's start time). lxcfs's `/proc/uptime` counts from the container's start and could not tell an old session from a new one. - `wg-quick@<if>` is enabled on confirm, not on apply. A reboot inside the window of a first overlay therefore leaves nothing up. - Only one change waits in the window at a time. An overlay change is refused in a run whose uplink moves. - Refusals: no wireguard-tools; a container whose host has not loaded the module (the message says to run `modprobe wireguard` on the host); a change already pending; a key file that is not 0600 and owned by root. - The key is made with `wg genkey` on the machine, never under `--root` (keel-core#8), and it never enters the rendered file: `PostUp = wg set %i private-key FILE`. `--skip-network` (first boot) still makes the key. - inspect reads `/etc/wireguard` back and reports the public key (`wg pubkey` reads the key file on stdin). diff compares peers by public key. - New commands: `keel network wireguard key` and `keel network wireguard suggest-address`. ## Which sessions confirm an overlay change A new session from another machine is accepted when it arrives at an address the overlay declares, or at an address held by another interface (the uplink). An overlay change can break two paths: the overlay itself, and the uplink, whose replies wg-quick's routes for `allowed_ips` can capture. A new session over either path shows that path survived. Accepting only the overlay would make pairing impossible, because the first node's overlay carries nothing until the second node declares it. confirm says which path was tested. A console or a host attach is accepted, as for the uplink. Older sessions, sessions from the machine to itself, and sessions arriving at an address the overlay does not declare are refused. ## Needs from the layer wireguard-tools is not in the core layer: Keel-Linux/keel-core#15. The package `Suggests: wireguard-tools`. ## Tests - Coverage 99% (gate 95). New test files: test_spec_overlay, test_system_overlay, test_network_overlay_window, test_inspect_overlay, test_network_wireguard, test_network_wireguard_cli. - Real tools, not mocks (tests/wgtools.py). `wg genkey` and `wg pubkey` make and read the key pair. `wg-quick strip` parses the rendered file. `wg-quick up` brings it up in a network namespace of its own, where `wg show` must report the declared key, port, endpoints, allowed IPs and keepalive, and `ip` the addresses. CI installs wireguard-tools, and these tests fail there rather than skip. - On VM1 (KVM, ifupdown-ng; a second node in a network namespace on it; everything removed afterwards): - [x] first overlay, unconfirmed: the file was removed at the end of the window and the interface was down - [x] confirmed over the overlay from node B: "overlay was tested", and wg-quick@wg0 was enabled - [x] a breaking port change: confirm from the older session was refused (exit 21), then the change reverted to the old file and the handshake came back - [x] confirmed over the uplink: "overlay itself was not tested" - [x] reboot inside the window: keel-network-revert restored the file at 7.0 s, before wg-quick@wg0 at 7.4 s - [ ] CI green
Sign in to join this conversation.
No description provided.