feat(orchestrator): complete container migration and release hardening

This commit is contained in:
archipelago
2026-04-28 15:00:58 -04:00
parent ce39430b33
commit 43de3b73b2
94 changed files with 5034 additions and 1003 deletions

View File

@@ -70,8 +70,8 @@ pub async fn load(data_dir: &Path) -> Result<Vec<SeedAnchor>> {
let bytes = tokio::fs::read(&path)
.await
.with_context(|| format!("read {}", path.display()))?;
let anchors: Vec<SeedAnchor> = serde_json::from_slice(&bytes)
.with_context(|| format!("parse {}", path.display()))?;
let anchors: Vec<SeedAnchor> =
serde_json::from_slice(&bytes).with_context(|| format!("parse {}", path.display()))?;
Ok(anchors)
}
@@ -125,12 +125,7 @@ pub async fn apply(anchors: &[SeedAnchor]) -> Vec<ApplyResult> {
let mut results = Vec::with_capacity(anchors.len());
for anchor in anchors {
let out = Command::new("fipsctl")
.args([
"connect",
&anchor.npub,
&anchor.address,
&anchor.transport,
])
.args(["connect", &anchor.npub, &anchor.address, &anchor.transport])
.output()
.await;
let result = match out {

View File

@@ -13,7 +13,9 @@ use anyhow::{Context, Result};
use std::path::Path;
use tokio::process::Command;
use super::{DAEMON_CONFIG_PATH, DAEMON_KEY_PATH, DAEMON_PUB_PATH, DEFAULT_TCP_PORT, DEFAULT_UDP_PORT};
use super::{
DAEMON_CONFIG_PATH, DAEMON_KEY_PATH, DAEMON_PUB_PATH, DEFAULT_TCP_PORT, DEFAULT_UDP_PORT,
};
/// Write the FIPS daemon config based on the local npub and default
/// transports. Overwrites any existing file — callers are expected to

View File

@@ -109,7 +109,7 @@ fn encode_query(id: u16, npub: &str) -> Result<Vec<u8>> {
encode_label(&mut out, npub)?;
encode_label(&mut out, FIPS_DNS_SUFFIX)?;
out.push(0); // root
// QTYPE + QCLASS
// QTYPE + QCLASS
out.extend_from_slice(&QTYPE_AAAA.to_be_bytes());
out.extend_from_slice(&QCLASS_IN.to_be_bytes());
Ok(out)
@@ -247,11 +247,7 @@ pub struct PeerRequest<'a> {
}
impl<'a> PeerRequest<'a> {
pub fn new(
fips_npub: Option<&'a str>,
onion_host: &'a str,
path: &'a str,
) -> Self {
pub fn new(fips_npub: Option<&'a str>, onion_host: &'a str, path: &'a str) -> Self {
Self {
fips_npub,
onion_host,
@@ -312,9 +308,7 @@ impl<'a> PeerRequest<'a> {
}
/// GET with optional header-based auth.
pub async fn send_get(
&self,
) -> Result<(reqwest::Response, crate::transport::TransportKind)> {
pub async fn send_get(&self) -> Result<(reqwest::Response, crate::transport::TransportKind)> {
use crate::settings::transport::TransportPref;
let pref = self.preference().await;
if matches!(pref, TransportPref::Auto | TransportPref::Fips) {
@@ -392,19 +386,14 @@ impl<'a> PeerRequest<'a> {
}
}
async fn send_tor_post_json<B: serde::Serialize>(
&self,
body: &B,
) -> Result<reqwest::Response> {
async fn send_tor_post_json<B: serde::Serialize>(&self, body: &B) -> Result<reqwest::Response> {
let url = self.tor_url();
let client = self.tor_client()?;
let mut rb = client.post(&url).json(body);
for (k, v) in &self.headers {
rb = rb.header(*k, v);
}
rb.send()
.await
.with_context(|| format!("Tor POST {}", url))
rb.send().await.with_context(|| format!("Tor POST {}", url))
}
async fn send_tor_get(&self) -> Result<reqwest::Response> {
@@ -414,9 +403,7 @@ impl<'a> PeerRequest<'a> {
for (k, v) in &self.headers {
rb = rb.header(*k, v);
}
rb.send()
.await
.with_context(|| format!("Tor GET {}", url))
rb.send().await.with_context(|| format!("Tor GET {}", url))
}
fn tor_url(&self) -> String {
@@ -449,7 +436,7 @@ mod tests {
assert_eq!(&q[0..2], &[0x12, 0x34]);
assert_eq!(&q[2..4], &[0x01, 0x00]); // flags RD=1
assert_eq!(&q[4..6], &[0x00, 0x01]); // QDCOUNT=1
// Tail: QTYPE=28, QCLASS=1
// Tail: QTYPE=28, QCLASS=1
assert_eq!(&q[q.len() - 4..], &[0x00, 0x1C, 0x00, 0x01]);
}
@@ -471,7 +458,7 @@ mod tests {
r.extend_from_slice(&1u16.to_be_bytes()); // ANCOUNT
r.extend_from_slice(&0u16.to_be_bytes()); // NSCOUNT
r.extend_from_slice(&0u16.to_be_bytes()); // ARCOUNT
// Question: 1 label "a" + "fips"
// Question: 1 label "a" + "fips"
r.extend_from_slice(b"\x01a\x04fips\x00");
r.extend_from_slice(&QTYPE_AAAA.to_be_bytes());
r.extend_from_slice(&QCLASS_IN.to_be_bytes());

View File

@@ -24,9 +24,7 @@ pub const FIPS_IFACE: &str = "fips0";
/// - Link-local (`fe80::/10`) and non-ULA addresses are ignored — we
/// only want the mesh-routable ULA that `<npub>.fips` DNS resolves to.
pub fn fips0_ula() -> Option<Ipv6Addr> {
addresses_on(FIPS_IFACE)
.into_iter()
.find(|a| is_ula(a))
addresses_on(FIPS_IFACE).into_iter().find(|a| is_ula(a))
}
/// List every IPv6 address bound to a given interface from

View File

@@ -122,8 +122,7 @@ impl FipsStatus {
};
let service_state = service::unit_state(SERVICE_UNIT).await;
let upstream_service_state = service::unit_state(UPSTREAM_SERVICE_UNIT).await;
let service_active =
service_state == "active" || upstream_service_state == "active";
let service_active = service_state == "active" || upstream_service_state == "active";
let key_present = crate::identity::fips_key_exists(&identity_dir);
// Prefer the seed-derived npub; otherwise read the daemon's own

View File

@@ -150,11 +150,10 @@ pub async fn peer_connectivity_summary(anchor_candidates: &[String]) -> (u32, bo
Ok(o) if o.status.success() => o.stdout,
_ => return (0, false),
};
let parsed: serde_json::Value =
match serde_json::from_slice(&peers_json) {
Ok(v) => v,
Err(_) => return (0, false),
};
let parsed: serde_json::Value = match serde_json::from_slice(&peers_json) {
Ok(v) => v,
Err(_) => return (0, false),
};
let peers = parsed
.get("peers")
.and_then(|p| p.as_array())