141 lines
5.1 KiB
Rust
141 lines
5.1 KiB
Rust
|
|
//! FIPS (Free Internetworking Peering System) daemon integration.
|
|||
|
|
//!
|
|||
|
|
//! github.com/jmcorgan/fips — a spanning-tree mesh routing protocol that
|
|||
|
|
//! uses Nostr secp256k1 keys as native node identity. Archipelago ships
|
|||
|
|
//! the daemon as an apt package, feeds it the seed-derived key from
|
|||
|
|
//! `/data/identity/fips_key`, and supervises it via
|
|||
|
|
//! `archipelago-fips.service`.
|
|||
|
|
//!
|
|||
|
|
//! This module is the in-process bridge:
|
|||
|
|
//! - [`service`]: systemctl status / start / stop / restart / unmask.
|
|||
|
|
//! - [`config`]: materialise `/etc/fips/fips.yaml` + install the key.
|
|||
|
|
//! - [`update`]: query GitHub (tracking `main`) for a newer build,
|
|||
|
|
//! verify SHA256, install via dpkg, restart.
|
|||
|
|
//!
|
|||
|
|
//! Privileged operations shell out via `sudo systemctl …` and `sudo dpkg …`
|
|||
|
|
//! (mirroring the vpn/update patterns already in the codebase); the
|
|||
|
|
//! sudoers rule shipped in the ISO whitelists exactly those commands for
|
|||
|
|
//! the `archipelago` service user.
|
|||
|
|
//!
|
|||
|
|
//! FIPS is dark on the wire until onboarding writes the key. Before that,
|
|||
|
|
//! `FipsStatus::installed` reports the package state and `service_active`
|
|||
|
|
//! returns false; the transport router keeps routing via Tor.
|
|||
|
|
|
|||
|
|
// Consumers land in the next phase (RPC endpoints + onboarding hookup);
|
|||
|
|
// the module is deliberately API-ready ahead of those call-sites.
|
|||
|
|
#![allow(dead_code)]
|
|||
|
|
|
|||
|
|
pub mod config;
|
|||
|
|
pub mod service;
|
|||
|
|
pub mod update;
|
|||
|
|
|
|||
|
|
use serde::{Deserialize, Serialize};
|
|||
|
|
use std::path::{Path, PathBuf};
|
|||
|
|
|
|||
|
|
/// Systemd unit name supervised by archipelago.
|
|||
|
|
pub const SERVICE_UNIT: &str = "archipelago-fips.service";
|
|||
|
|
|
|||
|
|
/// Path the FIPS daemon reads its config from (Debian package default).
|
|||
|
|
pub const DAEMON_CONFIG_PATH: &str = "/etc/fips/fips.yaml";
|
|||
|
|
|
|||
|
|
/// Path the FIPS daemon reads its private key from.
|
|||
|
|
pub const DAEMON_KEY_PATH: &str = "/etc/fips/fips.key";
|
|||
|
|
|
|||
|
|
/// Path the FIPS daemon reads its public key from.
|
|||
|
|
pub const DAEMON_PUB_PATH: &str = "/etc/fips/fips.pub";
|
|||
|
|
|
|||
|
|
/// Upstream repository the updater tracks (branch `main`).
|
|||
|
|
pub const UPSTREAM_REPO: &str = "jmcorgan/fips";
|
|||
|
|
|
|||
|
|
/// Default UDP port the daemon listens on.
|
|||
|
|
pub const DEFAULT_UDP_PORT: u16 = 8668;
|
|||
|
|
|
|||
|
|
/// Aggregated runtime status of the FIPS subsystem, surfaced to the dashboard.
|
|||
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|||
|
|
pub struct FipsStatus {
|
|||
|
|
/// Whether the `fips` debian package is installed on the host.
|
|||
|
|
pub installed: bool,
|
|||
|
|
/// Installed daemon version string reported by `fipsctl --version`,
|
|||
|
|
/// or None if not installed / not queryable.
|
|||
|
|
pub version: Option<String>,
|
|||
|
|
/// `systemctl is-active archipelago-fips.service` result: "active",
|
|||
|
|
/// "inactive", "failed", "masked", "unknown".
|
|||
|
|
pub service_state: String,
|
|||
|
|
/// True iff service_state == "active".
|
|||
|
|
pub service_active: bool,
|
|||
|
|
/// Whether the seed-derived FIPS key has been materialised on disk.
|
|||
|
|
/// The service cannot start meaningfully until this is true.
|
|||
|
|
pub key_present: bool,
|
|||
|
|
/// Local FIPS npub (bech32), present only once the key is on disk.
|
|||
|
|
pub npub: Option<String>,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
impl FipsStatus {
|
|||
|
|
/// Snapshot the current state across package, key, and service.
|
|||
|
|
pub async fn query(identity_dir: &Path) -> Self {
|
|||
|
|
let installed = service::package_installed().await;
|
|||
|
|
let version = if installed {
|
|||
|
|
service::daemon_version().await.ok()
|
|||
|
|
} else {
|
|||
|
|
None
|
|||
|
|
};
|
|||
|
|
let service_state = service::unit_state(SERVICE_UNIT).await;
|
|||
|
|
let service_active = service_state == "active";
|
|||
|
|
let key_present = crate::identity::fips_key_exists(identity_dir);
|
|||
|
|
let npub = crate::identity::fips_npub(identity_dir)
|
|||
|
|
.await
|
|||
|
|
.unwrap_or(None);
|
|||
|
|
|
|||
|
|
Self {
|
|||
|
|
installed,
|
|||
|
|
version,
|
|||
|
|
service_state,
|
|||
|
|
service_active,
|
|||
|
|
key_present,
|
|||
|
|
npub,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Compose a data-dir–relative identity directory path.
|
|||
|
|
/// Mirrors the convention used elsewhere in the codebase so callers don't
|
|||
|
|
/// have to repeat the `.join("identity")` each time.
|
|||
|
|
pub fn identity_dir_from(data_dir: &Path) -> PathBuf {
|
|||
|
|
data_dir.join("identity")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[cfg(test)]
|
|||
|
|
mod tests {
|
|||
|
|
use super::*;
|
|||
|
|
|
|||
|
|
#[tokio::test]
|
|||
|
|
async fn test_status_reports_no_key_pre_onboarding() {
|
|||
|
|
let dir = tempfile::tempdir().unwrap();
|
|||
|
|
let id_dir = dir.path().join("identity");
|
|||
|
|
tokio::fs::create_dir_all(&id_dir).await.unwrap();
|
|||
|
|
|
|||
|
|
let status = FipsStatus::query(&id_dir).await;
|
|||
|
|
assert!(!status.key_present, "no key before onboarding");
|
|||
|
|
assert!(status.npub.is_none());
|
|||
|
|
// `installed`, `service_state`, `version` depend on the host and are
|
|||
|
|
// not asserted here — query() must return cleanly regardless.
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[test]
|
|||
|
|
fn test_identity_dir_from() {
|
|||
|
|
let data = Path::new("/var/lib/archipelago");
|
|||
|
|
assert_eq!(
|
|||
|
|
identity_dir_from(data),
|
|||
|
|
Path::new("/var/lib/archipelago/identity")
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[test]
|
|||
|
|
fn test_constants_have_expected_shape() {
|
|||
|
|
assert!(SERVICE_UNIT.ends_with(".service"));
|
|||
|
|
assert!(DAEMON_CONFIG_PATH.starts_with('/'));
|
|||
|
|
assert!(DAEMON_KEY_PATH.starts_with('/'));
|
|||
|
|
assert_eq!(UPSTREAM_REPO, "jmcorgan/fips");
|
|||
|
|
}
|
|||
|
|
}
|