crates/gunnar-pairing-door/src/front.rs
//! **The front's role, run by the box the installer laid on the FRONT slot**
//! (edda nordisk-pairing 0.4).
//!
//! vm1 boots its OWN medium, the front medium (`gunnar-front-installer-open`,
//! the `gunnar-front` binary; owner 2026-09-25: "twin/main gunnar share ISO,
//! but NOT the ui/monetize"). The installer's ledger has a FRONT slot at its
//! address, so the installer feeds it role `front`.
//! Then this box is what `gunnar-pairing-door --ledger` was on the old front,
//! from what the installer FED it instead of a ledger pushed by the operator:
//!
//! * **the relay** — the WireGuard hub on udp/[`nordisk_pairing::RELAY_PORT`],
//! keyed with this box's OWN tunnel key (the key its presentation carried,
//! so the key the installer feeds every box as the `front` peer — edda's
//! `Door::front_tunnel`), listing every fed box peer by its overlay /32;
//! * **the twin's certification** — once the fed peers name a primary and a
//! twin with utility addresses (0.4.1 `Peer::utility`), signed with the
//! front's own certify key. The front mints it and OFFERS its public half
//! ([`OFFER_CERTIFY_PUBKEY`]); the installer feeds it onward and the elected
//! primary installs it as the machine admin [`CERTIFY_PRINCIPAL`] — so no
//! console key is carried to the front by anybody.
use crate::certify::{self, Record, Target};
use crate::grpc::GrpcBoxes;
use nordisk_pairing::join::{JoinReply, Peer};
use nordisk_pairing::{Role, CONTROL_PORT};
use std::collections::BTreeMap;
use std::net::Ipv4Addr;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::Duration;
/// The offer key of the front's certify key (edda nordisk-pairing 0.3.1
/// `GET /install/offer`): its OpenSSH public key line. Offered only by a box
/// fed role `front`.
pub const OFFER_CERTIFY_PUBKEY: &str = "certify_pubkey";
/// The principal the primary installs the offered certify key as: a machine
/// with the gunnar-admin grant — what the twin's enrolment and certificate
/// need signed ([`crate::certify`]).
pub const CERTIFY_PRINCIPAL: &str = "front-certify";
/// Which boxes to certify, from what the installer FED the front: the fed
/// peers with role primary/twin and their utility addresses. `None` until the
/// election has spoken and both carry one.
pub fn target_of(reply: &JoinReply) -> Option<Target> {
let of = |role: Role| reply.peers.iter().find(|p| p.role == role);
let twin = of(Role::Twin)?;
let primary = of(Role::Primary)?;
// Over the tunnel (WireGuard-only, owner 2026-10-04): the overlay
// addresses. The twin's own loop becomes the twin of the primary's
// overlay address — the SAME string must be given again for
// BecomeTwin's idempotent answer (`tunnel_addr::primary_control`).
let twin_at = twin.overlay;
let primary_at = reply.primary_overlay.unwrap_or(primary.overlay);
// A box laid again keeps its overlay address: which boxes these ARE is
// their utility address (else their tunnel key), so a reborn twin is
// certified again.
let who = |p: &Peer, u: Option<Ipv4Addr>| {
u.or(p.utility)
.map(|u| u.to_string())
.unwrap_or_else(|| p.tunnel_pubkey.trim().to_string())
};
let generation = format!(
"{} {}",
who(twin, None),
who(primary, reply.primary_utility)
);
Some(Target {
twin: twin.name.clone(),
primary: primary.name.clone(),
twin_ctl: format!("{twin_at}:{CONTROL_PORT}"),
primary_ctl: format!("{primary_at}:{CONTROL_PORT}"),
primary_for_twin: format!("{primary_at}:{CONTROL_PORT}"),
generation,
})
}
/// **Should the twin be certified (again) for `t`?**
///
/// Never certified: yes. Certified for exactly this pair: no. Certified for
/// ANOTHER pair — the main was recharged at a new address, or a new twin was
/// laid (the installer re-admitted it) — yes. A record written before the
/// pair was kept ([`Record::for_target`] empty) and certified: no, as before.
pub fn certify_again(record: &Record, t: &certify::Target) -> bool {
!record.certified || (!record.for_target.is_empty() && record.for_target != t.key())
}
/// **The front's own interface on the overlay** (WireGuard-only, owner
/// 2026-10-04): a kernel tun holding [`nordisk_pairing::OVERLAY_FRONT`]/24,
/// fed by the relay ([`wg_appliance_core::hubrelay::TunPort`]). Every
/// connection the front makes to a box — the console, monetize, the forge
/// forward, the certification — goes through it to the box's overlay
/// address; the clock and the plugin door are served on it.
pub const HUB_IFACE: &str = "wg-hub";
static HUB_PORT: std::sync::OnceLock<Arc<dyn wg_appliance_core::hubrelay::HubPort>> =
std::sync::OnceLock::new();
/// **Open the front's overlay interface** — once, at boot, before the clock
/// and the plugin door bind on it; the relay takes it when the front's role
/// starts. Needs CAP_NET_ADMIN (PID 1 on the front medium has it).
pub fn open_hub() -> Result<std::net::Ipv4Addr, String> {
if HUB_PORT.get().is_some() {
return Ok(nordisk_pairing::OVERLAY_FRONT);
}
let (net, prefix) = nordisk_pairing::OVERLAY_NET;
let _ = net;
let tun = wg_appliance_core::tun::TunDevice::create(HUB_IFACE, 1420)
.map_err(|e| format!("{HUB_IFACE}: {e} (CAP_NET_ADMIN?)"))?;
wg_appliance_core::addr::assign_ipv4(HUB_IFACE, nordisk_pairing::OVERLAY_FRONT, prefix)
.map_err(|e| {
format!(
"{HUB_IFACE} {}/{prefix}: {e}",
nordisk_pairing::OVERLAY_FRONT
)
})?;
wg_appliance_core::addr::link_up(HUB_IFACE).map_err(|e| format!("{HUB_IFACE} up: {e}"))?;
let _ = HUB_PORT.set(Arc::new(wg_appliance_core::hubrelay::TunPort(tun)));
Ok(nordisk_pairing::OVERLAY_FRONT)
}
/// The front's overlay interface, when [`open_hub`] opened it.
pub fn hub_port() -> Option<Arc<dyn wg_appliance_core::hubrelay::HubPort>> {
HUB_PORT.get().cloned()
}
/// Pure: **where the front serves its egress door** (the lean estate, A19 rows 10-14: main
/// and the twin hold no public IPv4 and leave through this front, Skidbladnir
/// nordisk-pairing `egress`): the overlay address its hub opened, never the utility one.
/// `None`: no overlay, no door.
pub fn egress_door_bind(overlay: Option<Ipv4Addr>) -> Option<std::net::SocketAddr> {
overlay
.filter(|o| *o == nordisk_pairing::OVERLAY_FRONT)
.map(|o| std::net::SocketAddr::from((o, nordisk_pairing::egress::EGRESS_PORT)))
}
/// The relay's private key from the box's `wg0.key` (base64, as the tunnel
/// lane keeps it) — the key its presentation carried.
pub fn relay_key_from_b64(b64: &str) -> Result<[u8; 32], String> {
wg_appliance_core::decode_key(b64.trim())
.map_err(|e| format!("the box's tunnel key is not a WireGuard key: {}", e.0))
}
/// Pure: (name, tunnel key, overlay) of every fed box peer with a key — the
/// front's own entry excluded.
pub fn to_list(peers: &[Peer]) -> Vec<(String, String, Ipv4Addr)> {
peers
.iter()
.filter(|p| p.role != Role::Front && !p.tunnel_pubkey.trim().is_empty())
.map(|p| {
(
p.name.clone(),
p.tunnel_pubkey.trim().to_string(),
p.overlay,
)
})
.collect()
}
/// The jera job that certifies the twin once the election is in (A32-14).
pub const CERTIFY_JOB: &str = "certify-twin";
/// **The front, running.** Owned for the life of the process: dropping it
/// stops the relay.
pub struct FrontRole {
relay: wg_appliance_core::hubrelay::HubRelay,
/// The key each overlay address is listed with, and the PSK (in memory
/// only, as the fed reply itself is).
listed: BTreeMap<Ipv4Addr, (String, Option<String>)>,
key: gunnar_core::ssh_key::PrivateKey,
record: Arc<Mutex<Record>>,
record_path: PathBuf,
/// The pair ([`certify::Target::key`]) being or last certified by this process.
certifying: Option<String>,
/// The pair the certify loops are told is wanted ([`certify::Wanted`]).
wanted: certify::Wanted,
}
impl FrontRole {
/// Bind the relay on udp/`port` with this box's tunnel private key, and
/// hold the certify key; the certify record is kept at `record_path`.
pub fn start(
port: u16,
relay_private: [u8; 32],
certify_key: gunnar_core::ssh_key::PrivateKey,
record_path: &Path,
) -> Result<FrontRole, String> {
// With the front's own overlay interface open, the hub is a box on its
// overlay: what the boxes send to .1 reaches the front's processes, and
// what they send to a box is sealed to it (WireGuard-only).
// A32-14, stays a task: the front's relay, held by the role for its life.
let relay = match hub_port() {
Some(p) => wg_appliance_core::hubrelay::HubRelay::spawn_with_port(
port,
relay_private,
nordisk_pairing::OVERLAY_FRONT,
p,
),
// A32-14, stays a task: the same relay on the default port.
None => wg_appliance_core::hubrelay::HubRelay::spawn(
port,
relay_private,
nordisk_pairing::OVERLAY_FRONT,
),
}
.map_err(|e| format!("the relay could not bind udp/{port}: {e}"))?;
let record = Arc::new(Mutex::new(Record::load(record_path)?));
Ok(FrontRole {
relay,
listed: BTreeMap::new(),
key: certify_key,
record,
record_path: record_path.to_path_buf(),
certifying: None,
wanted: Arc::new(Mutex::new(None)),
})
}
/// The relay's public key (base64) — the box's own tunnel key.
pub fn public(&self) -> &str {
&self.relay.public_b64
}
/// `(overlay, key)` of every box the relay lists.
pub fn relay_peers(&self) -> Vec<(Ipv4Addr, String)> {
self.relay
.verdict()
.peers
.iter()
.map(|(a, k, _)| (*a, wg_appliance_core::encode_key(k)))
.collect()
}
/// The overlay addresses of the boxes the relay holds a pre-shared key for.
pub fn relay_psk(&self) -> Vec<Ipv4Addr> {
self.relay.verdict().with_psk
}
/// The pair a certify loop may still work on (`<twin_ctl> <primary_ctl>`).
pub fn wanted(&self) -> Option<String> {
self.wanted.lock().map(|w| w.clone()).unwrap_or_default()
}
/// The certify record so far.
pub fn record(&self) -> Record {
self.record.lock().map(|r| r.clone()).unwrap_or_default()
}
/// **Take what the installer fed** (every time it changes): list new box
/// peers on the relay, and once the verdict is fed start certifying the
/// twin in a thread of its own (once per process; a record that says
/// certified is not redone). Returns what was done, to be said.
pub fn take(&mut self, reply: &JoinReply) -> Vec<String> {
let mut said = Vec::new();
for (name, key, overlay) in to_list(&reply.peers) {
// The PSK the installer fed the front for this box (`wg_psk.<box>`,
// 2026-10-06): the relay keys every session with it. Remembered with
// the key, so a re-fed PSK relists the box and a restart's same feed
// changes nothing.
let fed_psk = reply
.feed
.get(&nordisk_pairing::installer::feed_wg_psk_for(&name))
.map(|p| p.trim().to_string());
let psk = match fed_psk.as_deref().map(wg_appliance_core::decode_key) {
None => None,
Some(Ok(p)) => Some(p),
Some(Err(_)) => {
said.push(format!("`{name}` was fed a value that is not a WireGuard pre-shared key — not listed with it"));
continue;
}
};
let memo = (key.clone(), fed_psk.clone());
if self.listed.get(&overlay) == Some(&memo) {
continue;
}
match wg_appliance_core::decode_key(&key) {
Ok(k) => {
let known = self.listed.contains_key(&overlay);
let rekeyed = known
&& self.listed.get(&overlay).map(|(k, _)| k.as_str()) != Some(key.as_str());
// Named, so a relay timer line says `wg{peer=relay:<name>:<fp>}`.
// A re-feed after a re-admission: the box at this address was
// laid again with a key of its own — re-keyed (its name kept),
// never refused. A restart re-feeds the same pairs: no change.
let listed = if known {
self.relay.relist_peer_with_psk(k, psk, overlay)
} else {
self.relay
.add_named_peer_with_psk(k, psk, overlay, &name)
.map(|()| true)
.or_else(|_| self.relay.relist_peer_with_psk(k, psk, overlay))
};
let with = if psk.is_some() {
" with its pre-shared key"
} else {
""
};
match listed {
Ok(true) if rekeyed => said.push(format!("`{name}` re-keyed on the relay at {overlay}{with} (the box was laid again)")),
Ok(true) if known => said.push(format!("`{name}` takes a new pre-shared key on the relay at {overlay}")),
Ok(_) => said.push(format!("`{name}` listed on the relay as {overlay}{with}")),
Err(e) => said.push(format!("`{name}` could not be listed at {overlay}: {e}")),
}
}
Err(_) => said.push(format!(
"`{name}` was fed a tunnel key that is not a WireGuard key — not listed"
)),
}
self.listed.insert(overlay, memo);
}
// L1: a box listed before and no longer named with a key DIED — the installer is
// laying it again. Its session is forgotten now, not after two minutes of handshakes
// toward a machine that is gone; its new key lists it again.
let fed: std::collections::BTreeSet<Ipv4Addr> = to_list(&reply.peers)
.into_iter()
.map(|(_, _, o)| o)
.collect();
let gone: Vec<Ipv4Addr> = self
.listed
.keys()
.filter(|o| !fed.contains(o))
.copied()
.collect();
for overlay in gone {
self.listed.remove(&overlay);
if self.relay.forget_peer(overlay) {
let name = reply
.peers
.iter()
.find(|p| p.overlay == overlay)
.map(|p| p.name.clone())
.unwrap_or_else(|| overlay.to_string());
said.push(format!("`{name}` forgotten on the relay at {overlay}: the installer's feed no longer names it with a key (the box is being laid again)"));
}
}
if let Some(target) = target_of(reply) {
let pair = target.key();
if self.certifying.as_deref() != Some(pair.as_str()) {
self.certifying = Some(pair.clone());
// Every loop started before for another pair stops (R2 after U3: the
// loop for the DEAD twin kept dialling it between the new loop's tries).
*self.wanted.lock().unwrap_or_else(|p| p.into_inner()) = Some(pair.clone());
if !certify_again(&self.record(), &target) {
// A record kept from an older build names no pair (a keep-upgrade from
// prod's d697fa4): it is THIS pair's, adopted, so a later move of the
// twin or the primary is certified again rather than skipped.
let adopted = {
let mut r = self.record.lock().unwrap_or_else(|p| p.into_inner());
if r.for_target.is_empty() {
r.for_target = pair.clone();
Some(r.clone())
} else {
None
}
};
if let Some(r) = adopted {
if let Err(e) = r.save(&self.record_path) {
said.push(format!("the kept certify record could not be updated: {e}"));
}
}
said.push(format!(
"twin `{}` is already certified ({}), not redone",
target.twin,
self.record_path.display()
));
} else {
said.push(format!(
"the election is in — certifying twin `{}` ({}) against primary `{}` ({})",
target.twin, target.twin_ctl, target.primary, target.primary_ctl
));
let (key, record, path, wanted) = (
self.key.clone(),
Arc::clone(&self.record),
self.record_path.clone(),
Arc::clone(&self.wanted),
);
// A named jera job (A32-14): a certify that panics is said (ALERT) and a
// FAILED row, and asked again after the settle; the front's stop ends it.
let say: gunnar_common::jobs::Say =
Arc::new(|l: &str| tracing::error!("front: {l}"));
let mut settled = false;
let spawned =
gunnar_common::jobs::start(CERTIFY_JOB, certify::SETTLE, say, move || {
// Ordered after the installer's re-feed of the other
// boxes (this front's certify key), as far as a wait
// can: see `certify::SETTLE` and `certify::Tone`.
if !std::mem::replace(&mut settled, true) {
return gunnar_common::jobs::Round::After(certify::SETTLE);
}
match GrpcBoxes::new(key.clone(), Duration::from_secs(15)) {
Ok(boxes) => {
certify::run_while(
&boxes,
&target,
&record,
Some(&path),
certify::door_waits(),
&wanted,
);
}
Err(e) => record.lock().unwrap_or_else(|p| p.into_inner()).why = e,
}
gunnar_common::jobs::Round::Done
});
if let Err(e) = spawned {
said.push(format!("the certify job could not start: {e}"));
self.certifying = None;
}
}
}
}
said
}
}
#[cfg(test)]
mod tests {
use super::*;
use nordisk_pairing::join::Assigned;
use std::collections::BTreeMap;
fn peer(
name: &str,
role: Role,
key: &str,
overlay: Ipv4Addr,
utility: Option<Ipv4Addr>,
) -> Peer {
Peer {
name: name.into(),
role,
tunnel_pubkey: key.into(),
overlay,
endpoint: None,
utility,
}
}
fn fed_front(peers: Vec<Peer>, primary_utility: Option<Ipv4Addr>) -> JoinReply {
JoinReply {
estate: "gunnar.rs".into(),
me: Assigned {
name: "front".into(),
overlay: nordisk_pairing::OVERLAY_FRONT,
},
cert_pem: None,
role: Some(Role::Front),
primary_overlay: None,
primary_utility,
peers,
feed: BTreeMap::new(),
}
}
const K1: &str = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
/// The front lists the fed boxes, never itself, and certifies from the fed
/// verdict over the TUNNEL (WireGuard-only, owner 2026-10-04): the overlay
/// addresses, the same string the twin joined the primary at.
#[test]
fn the_front_lists_the_fed_boxes_and_certifies_over_the_tunnel() {
let front = nordisk_pairing::join::FrontTunnel {
tunnel_pubkey: K1.into(),
endpoint: "95.0.0.1:51820".into(),
}
.as_peer();
let main = peer(
"main",
Role::Primary,
"KM",
nordisk_pairing::OVERLAY_BOX_A,
Some(Ipv4Addr::new(10, 13, 8, 2)),
);
let twin = peer(
"twin",
Role::Twin,
"",
nordisk_pairing::OVERLAY_BOX_B,
Some(Ipv4Addr::new(10, 13, 8, 3)),
);
let r = fed_front(
vec![front, main.clone(), twin.clone()],
Some(Ipv4Addr::new(10, 13, 8, 2)),
);
assert_eq!(
to_list(&r.peers),
vec![(
"main".to_string(),
"KM".to_string(),
nordisk_pairing::OVERLAY_BOX_A
)],
"not the front, not a box without a key"
);
let t = target_of(&r).expect("the verdict is fed");
assert_eq!(
(
t.twin.as_str(),
t.twin_ctl.as_str(),
t.primary_ctl.as_str(),
t.primary_for_twin.as_str()
),
(
"twin",
"10.44.0.3:50051",
"10.44.0.2:50051",
"10.44.0.2:50051"
)
);
// The door's own statement of the primary's overlay is the one the twin joined.
let said = JoinReply {
primary_overlay: Some(nordisk_pairing::OVERLAY_BOX_A),
..fed_front(vec![main, twin], None)
};
assert_eq!(
target_of(&said).unwrap().primary_for_twin,
"10.44.0.2:50051"
);
}
/// The relay runs on the box's own key and lists a fed box once.
#[test]
fn the_relay_runs_on_the_boxes_own_key_and_lists_each_fed_box_once() {
let dir = tempfile_dir();
let private = [7u8; 32];
let public = wg_appliance_core::encode_key(
&wg_appliance_core::keys::public_from_private_bytes(&private),
);
let key =
gunnar_core::ssh_key::PrivateKey::from_openssh(gunnar_core::mint_ed25519().unwrap())
.unwrap();
let mut f = FrontRole::start(0, private, key, &dir.join("certify.toml")).unwrap();
assert_eq!(
f.public(),
public,
"the relay is keyed with the box's own tunnel key"
);
let r = fed_front(
vec![peer(
"main",
Role::Primary,
K1,
nordisk_pairing::OVERLAY_BOX_A,
None,
)],
None,
);
assert_eq!(
f.take(&r),
vec![format!(
"`main` listed on the relay as {}",
nordisk_pairing::OVERLAY_BOX_A
)]
);
assert!(f.take(&r).is_empty(), "listed once");
}
const PSK: &str = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=";
const PSK2: &str = "ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8=";
/// **Each box is listed with the pre-shared key the installer fed the
/// front for it** (2026-10-06, the PQ transport lane: `wg_psk.<box>`), and
/// said so — never the key. The same feed again changes nothing; a new PSK
/// for the same box relists it; a fed value that is not a key is said and
/// the box is not listed with it.
#[test]
fn each_fed_box_is_listed_with_its_preshared_key_and_a_new_one_relists_it() {
let dir = tempfile_dir();
let key =
gunnar_core::ssh_key::PrivateKey::from_openssh(gunnar_core::mint_ed25519().unwrap())
.unwrap();
let mut f = FrontRole::start(0, [6u8; 32], key, &dir.join("certify.toml")).unwrap();
let mut r = fed_front(
vec![peer(
"main",
Role::Primary,
K1,
nordisk_pairing::OVERLAY_BOX_A,
None,
)],
None,
);
r.feed.insert(
nordisk_pairing::installer::feed_wg_psk_for("main"),
PSK.into(),
);
let said = f.take(&r);
assert_eq!(
said,
vec![format!(
"`main` listed on the relay as {} with its pre-shared key",
nordisk_pairing::OVERLAY_BOX_A
)]
);
assert!(!said.concat().contains(PSK));
assert_eq!(f.relay_psk(), vec![nordisk_pairing::OVERLAY_BOX_A]);
assert!(f.take(&r).is_empty(), "the same feed: nothing to do");
r.feed.insert(
nordisk_pairing::installer::feed_wg_psk_for("main"),
PSK2.into(),
);
let said = f.take(&r);
assert!(
said.iter()
.any(|l| l.contains("`main`") && l.contains("pre-shared key")),
"{said:?}"
);
assert_eq!(f.relay_psk(), vec![nordisk_pairing::OVERLAY_BOX_A]);
r.feed.insert(
nordisk_pairing::installer::feed_wg_psk_for("main"),
"not-a-key".into(),
);
let said = f.take(&r);
assert!(
said.iter()
.any(|l| l.contains("`main`") && l.contains("not a WireGuard pre-shared key")),
"{said:?}"
);
}
/// **A box laid again** (the installer re-admits it: nordisk-pairing
/// `Door::readmit`) keeps its overlay address and brings a NEW tunnel key:
/// the relay re-keys that address instead of refusing the new box.
#[test]
fn a_relaid_box_is_relisted_at_its_address_with_its_new_key() {
let dir = tempfile_dir();
let key =
gunnar_core::ssh_key::PrivateKey::from_openssh(gunnar_core::mint_ed25519().unwrap())
.unwrap();
let mut f = FrontRole::start(0, [9u8; 32], key, &dir.join("certify.toml")).unwrap();
let (_, fresh) = wg_appliance_core::keys::generate_keypair_b64();
let before = fed_front(
vec![peer(
"main",
Role::Primary,
K1,
nordisk_pairing::OVERLAY_BOX_A,
None,
)],
None,
);
f.take(&before);
let after = fed_front(
vec![peer(
"main",
Role::Primary,
&fresh,
nordisk_pairing::OVERLAY_BOX_A,
None,
)],
None,
);
let said = f.take(&after);
assert!(
said.iter()
.any(|l| l.contains("`main`") && l.contains("re-keyed")),
"{said:?}"
);
assert_eq!(
f.relay_peers(),
vec![(nordisk_pairing::OVERLAY_BOX_A, fresh.clone())],
"the slot took the new key"
);
assert!(f.take(&after).is_empty(), "then nothing to do");
}
/// **L1 (estate-box check 3, release-3 recharge): a box the re-feed no longer names is
/// forgotten on the relay at once.** The main died; the installer re-fed the front with
/// the twin only (the new main has no key until it pairs). The relay kept the dead
/// main's session and sealed the twin's every packet to it — `HANDSHAKE(REKEY_TIMEOUT)`
/// every 5 s, then `ERROR CONNECTION_EXPIRED` (MEASURED: 2 min of it, estate-box gate1).
/// The new box's key, when it comes, lists it again.
#[test]
fn a_box_the_refeed_no_longer_names_is_forgotten_and_its_new_key_lists_it_again() {
let dir = tempfile_dir();
let key =
gunnar_core::ssh_key::PrivateKey::from_openssh(gunnar_core::mint_ed25519().unwrap())
.unwrap();
let mut f = FrontRole::start(0, [5u8; 32], key, &dir.join("certify.toml")).unwrap();
let (_, twin_key) = wg_appliance_core::keys::generate_keypair_b64();
let twin = peer(
"twin",
Role::Twin,
&twin_key,
nordisk_pairing::OVERLAY_BOX_B,
None,
);
f.take(&fed_front(
vec![
peer(
"main",
Role::Primary,
K1,
nordisk_pairing::OVERLAY_BOX_A,
None,
),
twin.clone(),
],
None,
));
// The re-feed after main died: main with no key (being laid again).
let said = f.take(&fed_front(
vec![
peer(
"main",
Role::Primary,
"",
nordisk_pairing::OVERLAY_BOX_A,
None,
),
twin.clone(),
],
None,
));
assert!(
said.iter()
.any(|l| l.contains("`main`") && l.contains("forgotten")),
"{said:?}"
);
assert!(
f.take(&fed_front(vec![twin.clone()], None)).is_empty(),
"forgotten once"
);
// The new main's key lists it again.
let (_, fresh) = wg_appliance_core::keys::generate_keypair_b64();
let said = f.take(&fed_front(
vec![
peer(
"main",
Role::Primary,
&fresh,
nordisk_pairing::OVERLAY_BOX_A,
None,
),
twin,
],
None,
));
assert!(
said.iter()
.any(|l| l.contains("`main`")
&& l.contains(&nordisk_pairing::OVERLAY_BOX_A.to_string())),
"{said:?}"
);
assert!(
f.relay_peers()
.contains(&(nordisk_pairing::OVERLAY_BOX_A, fresh)),
"the slot took the new key"
);
}
/// **A re-feed at new addresses hands the certify loop the new pair** (estate-box3
/// life1, R2 after U3: row recharge-twin-certify-stale): the loop started for the dead
/// twin must learn it is no longer wanted, and only the new pair is.
#[test]
fn a_refeed_at_new_addresses_hands_the_certify_loop_the_new_pair() {
let dir = tempfile_dir();
let key =
gunnar_core::ssh_key::PrivateKey::from_openssh(gunnar_core::mint_ed25519().unwrap())
.unwrap();
let mut f = FrontRole::start(0, [3u8; 32], key, &dir.join("certify.toml")).unwrap();
let (_, main_key) = wg_appliance_core::keys::generate_keypair_b64();
let (_, twin_key) = wg_appliance_core::keys::generate_keypair_b64();
let main = peer(
"main",
Role::Primary,
&main_key,
nordisk_pairing::OVERLAY_BOX_A,
Some(Ipv4Addr::new(127, 6, 0, 14)),
);
let before = fed_front(
vec![
main.clone(),
peer(
"twin",
Role::Twin,
&twin_key,
nordisk_pairing::OVERLAY_BOX_B,
Some(Ipv4Addr::new(127, 6, 0, 13)),
),
],
Some(Ipv4Addr::new(127, 6, 0, 14)),
);
let said = f.take(&before);
assert!(
said.iter()
.any(|l| l.contains("certifying twin `twin` (10.44.0.3:50051)")),
"{said:?}"
);
assert_eq!(
f.wanted().as_deref(),
Some("10.44.0.3:50051 10.44.0.2:50051 127.6.0.13 127.6.0.14")
);
// R2: the twin laid again as vm5 at new addresses, with a new tunnel key.
let (_, reborn) = wg_appliance_core::keys::generate_keypair_b64();
let after = fed_front(
vec![
main,
peer(
"twin",
Role::Twin,
&reborn,
nordisk_pairing::OVERLAY_BOX_B,
Some(Ipv4Addr::new(127, 6, 0, 15)),
),
],
Some(Ipv4Addr::new(127, 6, 0, 14)),
);
let said = f.take(&after);
assert!(
said.iter()
.any(|l| l.contains("certifying twin `twin` (10.44.0.3:50051)")),
"the reborn twin keeps its overlay address and is certified again: {said:?}"
);
assert_eq!(
f.wanted().as_deref(),
Some("10.44.0.3:50051 10.44.0.2:50051 127.6.0.15 127.6.0.14"),
"the loop for the dead twin is no longer wanted"
);
}
/// The twin is certified again when the estate MOVED (a recharged main
/// or a new twin at another address) — never twice for the same pair, and
/// a record written before this field reads as certified.
#[test]
fn a_certification_is_redone_only_for_a_moved_pair() {
let t = |twin: &str, primary: &str| certify::Target {
twin: "twin".into(),
primary: "main".into(),
twin_ctl: twin.into(),
primary_ctl: primary.into(),
primary_for_twin: primary.into(),
generation: String::new(),
};
let a = t("10.13.8.3:50051", "10.13.8.2:50051");
let done = certify::Record {
certified: true,
for_target: a.key(),
..Default::default()
};
assert!(!certify_again(&done, &a), "the same pair: not redone");
assert!(
certify_again(&done, &t("10.13.8.3:50051", "10.13.9.140:50051")),
"the main moved"
);
assert!(
certify_again(&done, &t("10.13.9.77:50051", "10.13.8.2:50051")),
"a new twin"
);
assert!(
certify_again(&certify::Record::default(), &a),
"never certified"
);
assert!(
!certify_again(
&certify::Record {
certified: true,
..Default::default()
},
&a
),
"an older record, certified: kept"
);
}
/// **A certify record kept from an older build** (prod d697fa4: no `for_target`)
/// parses, is not redone for the pair it was fed, and is adopted as that pair's — so
/// a twin recharged later at new addresses is certified again (R2 after a
/// keep-upgrade), never skipped as "already certified".
#[test]
fn an_older_record_kept_across_an_upgrade_is_adopted_for_its_pair() {
let dir = tempfile_dir();
let path = dir.join("certify.toml");
std::fs::write(&path, "certified = true\nattempts = 3\ntwin = \"twin\"\nprimary = \"main\"\nwhy = \"\"\nreplication = \"10.44.0.2:50052\"\ndays_left = 300\nat_unix_ms = 1\n").unwrap();
let key =
gunnar_core::ssh_key::PrivateKey::from_openssh(gunnar_core::mint_ed25519().unwrap())
.unwrap();
let mut f = FrontRole::start(0, [4u8; 32], key, &path).unwrap();
let (_, mk) = wg_appliance_core::keys::generate_keypair_b64();
let (_, tk) = wg_appliance_core::keys::generate_keypair_b64();
let main = peer(
"main",
Role::Primary,
&mk,
nordisk_pairing::OVERLAY_BOX_A,
Some(Ipv4Addr::new(127, 7, 0, 14)),
);
let said = f.take(&fed_front(
vec![
main.clone(),
peer(
"twin",
Role::Twin,
&tk,
nordisk_pairing::OVERLAY_BOX_B,
Some(Ipv4Addr::new(127, 7, 0, 13)),
),
],
Some(Ipv4Addr::new(127, 7, 0, 14)),
));
assert!(
said.iter().any(|l| l.contains("already certified")),
"{said:?}"
);
assert_eq!(
certify::Record::load(&path).unwrap().for_target,
"10.44.0.3:50051 10.44.0.2:50051 127.7.0.13 127.7.0.14",
"adopted"
);
let said = f.take(&fed_front(
vec![
main,
peer(
"twin",
Role::Twin,
&tk,
nordisk_pairing::OVERLAY_BOX_B,
Some(Ipv4Addr::new(127, 7, 0, 15)),
),
],
Some(Ipv4Addr::new(127, 7, 0, 14)),
));
assert!(
said.iter()
.any(|l| l.contains("certifying twin `twin` (10.44.0.3:50051)")),
"a moved twin is certified again: {said:?}"
);
}
fn tempfile_dir() -> PathBuf {
let d = std::env::temp_dir().join(format!(
"gunnar-front-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&d).unwrap();
d
}
/// ★ **The front serves its egress door on its overlay address, never the utility one**
/// (the lean estate, A19 rows 10-14: main and the twin hold no public IPv4 and leave through
/// this front; the door is the TCP forward their mail, webhooks, mirrors and SSO ride).
#[test]
fn the_egress_door_is_served_on_the_overlay_only() {
assert_eq!(
egress_door_bind(Some(nordisk_pairing::OVERLAY_FRONT)),
Some("10.44.0.1:3128".parse().unwrap())
);
assert_eq!(
egress_door_bind(None),
None,
"no overlay: no door (never on the utility address)"
);
assert_eq!(
egress_door_bind(Some(Ipv4Addr::new(10, 6, 0, 2))),
None,
"only the front's overlay address"
);
}
}
/// ★ **The twin's certify is a named jera job** (A32-14): it was a raw `certify-twin` thread whose
/// handle was dropped, so a certify that panicked left the pair "certifying" for good, without a
/// word. Read off the source (the lib and the door binary).
#[cfg(test)]
mod a32_jobs {
#[test]
fn the_twins_certify_is_a_jera_job_not_a_thread() {
for (file, src) in [
("front.rs", include_str!("front.rs")),
(
"bin/gunnar-pairing-door.rs",
include_str!("bin/gunnar-pairing-door.rs"),
),
] {
let code = &src[..src.find("#[cfg(test)]").unwrap_or(src.len())];
assert!(
code.contains("gunnar_common::jobs::start(CERTIFY_JOB"),
"{file}: the certify starts as a jera job"
);
assert!(
!code.contains(".name(\"certify-twin\".into())"),
"{file}: the certify is still a raw thread"
);
}
}
}