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:
co-authored by
Claude Fable 5
parent
6a848c38cc
commit
cb301b1aef
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user