fix(wifi,fips): self-heal polkit rule + fast-retry anchors after daemon restart

Two fleet fixes that OTA can deliver (unlike the ISO-only polkit rule):

- WiFi (issue #99): nodes drive NetworkManager from a system-level service
  with no logind seat, so the stock polkit rule denies them and Wi-Fi setup
  fails with 'Insufficient privileges'. Fresh ISOs since 2026-05 ship the
  scoped rule, but OTA'd nodes never got it (OTA replaces binary + web UI,
  not host system config). Add a startup self-heal that installs the rule if
  missing and best-effort ensures polkitd — both wrapped so an offline/locked
  apt can never fail startup; the rule is written regardless so it activates
  once polkitd lands. Idempotent on the rule's unique subject.user marker.

- FIPS: regenerating fips.yaml (this build does, once, on first boot after
  the OTA) restarts the fips daemon; for a few seconds /run/fips/control.sock
  is gone and every seed-anchor dial fails, islanding the node until the next
  5-min tick. Detect that exact failure (all dials fail on control.sock) and
  retry in 15s instead — bounded to 8 fast retries/episode so a node with no
  fips daemon falls back to the steady cadence rather than busy-looping.

FIPS unit suite: 33/33 pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-20 16:39:02 -04:00
co-authored by Claude Fable 5
parent 6a848c38cc
commit cb301b1aef
2 changed files with 96 additions and 3 deletions
+27 -3
View File
@@ -688,12 +688,27 @@ impl Server {
let fips_peer_registry = fips_peer_registry.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(30)).await;
let mut interval = tokio::time::interval(Duration::from_secs(300));
// Steady cadence, but retry fast right after a daemon restart:
// regenerating fips.yaml (this build does, once, on first boot
// after the OTA) restarts the fips daemon, and for a few seconds
// `/run/fips/control.sock` is gone so every `fipsctl connect`
// fails and the node islands until the next tick. Detect that
// exact failure and retry in 15s instead of 5 min — bounded, so a
// node with no fips daemon falls back to the steady cadence
// rather than busy-looping.
const STEADY: Duration = Duration::from_secs(300);
const FAST: Duration = Duration::from_secs(15);
const MAX_FAST_RETRIES: u32 = 8; // ≤2 min of fast retries/episode
let mut fast_retries: u32 = 0;
loop {
interval.tick().await;
let mut daemon_restarting = false;
match crate::fips::anchors::load(&data_dir).await {
Ok(list) if !list.is_empty() => {
let _ = crate::fips::anchors::apply(&list).await;
let results = crate::fips::anchors::apply(&list).await;
daemon_restarting = !results.is_empty()
&& results
.iter()
.all(|r| !r.ok && r.message.contains("control.sock"));
}
Ok(_) => { /* no seed anchors configured yet */ }
Err(e) => {
@@ -717,6 +732,15 @@ impl Server {
let _ = crate::fips::anchors::apply(&direct).await;
}
}
let next = if daemon_restarting && fast_retries < MAX_FAST_RETRIES {
fast_retries += 1;
FAST
} else {
fast_retries = 0;
STEADY
};
tokio::time::sleep(next).await;
}
});
}