idunn-launch: presence launcher, lease hold, closed-set config renderer, route acceptor (unbuilt)

Does not build-verified yet; tests follow. drivers.rs: holder-side lease read, one lease-binding rule shared with grant.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-22 18:53:29 +02:00
commit b9f27ce1e7
5 changed files with 1447 additions and 29 deletions

View file

@ -18,6 +18,10 @@ path = "src/bin/idunn-provision.rs"
name = "idunn-host"
path = "src/bin/idunn-host.rs"
[[bin]]
name = "idunn-launch"
path = "src/bin/idunn-launch.rs"
[lib]
name = "idunn_daemon"
path = "src/lib.rs"

86
src/bin/idunn-launch.rs Normal file
View file

@ -0,0 +1,86 @@
//! The unit's process for an upstream daemon: parse the recipe's argv, read
//! what Idunn set, run the launcher, and map the outcome to an exit code.
#[cfg(unix)]
fn main() -> std::process::ExitCode {
use std::sync::atomic::{AtomicBool, Ordering};
use anyhow::Context;
use idunn_daemon::deployment::{
IDUNN_PROCESS_WRITE_LEASE_ENVIRONMENT, IDUNN_RUNTIME_BUNDLE_ENVIRONMENT,
IDUNN_RUNTIME_CANDIDATE_BIND_ENVIRONMENT,
};
use idunn_daemon::launcher::{self, *};
static STOP: AtomicBool = AtomicBool::new(false);
extern "C" fn on_signal(_: libc::c_int) {
STOP.store(true, Ordering::SeqCst);
}
fn launch() -> anyhow::Result<Outcome> {
let contract = LaunchContract::parse(std::env::args().skip(1))?;
let bundle = std::env::var_os(IDUNN_RUNTIME_BUNDLE_ENVIRONMENT)
.context("the Idunn runtime bundle is not set")?;
let bind = std::env::var(IDUNN_RUNTIME_CANDIDATE_BIND_ENVIRONMENT)
.context("the Idunn candidate bind is not set")?;
let (activation, provider) = launcher::inherited_authority_files()?;
let authority = RuntimeAuthority::open(bundle.as_ref(), activation, provider)?;
authority.require_candidate_bind(&bind)?;
let lease_path = std::env::var_os(IDUNN_PROCESS_WRITE_LEASE_ENVIRONMENT);
anyhow::ensure!(
lease_path.is_some() == authority.expected().write_lease_required,
"the process write lease binding differs from Expected"
);
let route = std::net::TcpListener::bind(&bind).context("binding the candidate endpoint")?;
let sink = OdinRudpSink::from_expected(authority.expected())?;
let lease = DriverLeaseReader::new(
&authority.expected().target,
lease_path.as_deref().unwrap_or_default().as_ref(),
);
let health_path = contract.health_path.clone();
let ports = Ports {
credentials: std::sync::Arc::new(EnvCredentials),
clock: std::sync::Arc::new(SystemClock),
sink: std::sync::Arc::new(sink),
lease: std::sync::Arc::new(lease),
spawner: Box::new(ProcessSpawner),
health: Box::new(move |endpoint| {
std::sync::Arc::new(HttpHealthProbe {
endpoint: endpoint.clone(),
path: health_path.clone(),
})
}),
};
// SAFETY: the handler only stores to an atomic.
unsafe {
libc::signal(libc::SIGTERM, on_signal as libc::sighandler_t);
libc::signal(libc::SIGINT, on_signal as libc::sighandler_t);
}
Ok(launcher::run(
&contract,
authority,
route,
&std::env::temp_dir(),
ports,
&STOP,
))
}
match launch() {
Ok(outcome) => {
if let Outcome::Failed(code) = &outcome {
eprintln!("idunn-launch ended: {code}");
}
std::process::ExitCode::from(outcome.exit_code() as u8)
}
Err(error) => {
eprintln!("idunn-launch refused: {error}");
std::process::ExitCode::from(2)
}
}
}
#[cfg(not(unix))]
fn main() {
eprintln!("idunn-launch runs only on Unix");
std::process::exit(2);
}

View file

@ -4110,6 +4110,20 @@ impl CultCacheWriteLeaseDriver {
fn current(&self) -> Result<Option<(CultCacheEnvelope, IdunnProcessWriteLeaseRecord)>> {
validate_root_authority_path(&self.record_path)?;
self.read_record()
}
/// The lease as its holder reads it: the same custody, envelope, key and
/// target validation Idunn applies, without requiring the reader to be
/// root. The holder is a service uid by construction; root is who writes.
pub(crate) fn current_for_holder(
&self,
) -> Result<Option<(CultCacheEnvelope, IdunnProcessWriteLeaseRecord)>> {
validate_authority_path_custody(&self.record_path)?;
self.read_record()
}
fn read_record(&self) -> Result<Option<(CultCacheEnvelope, IdunnProcessWriteLeaseRecord)>> {
if !self.record_path.exists() {
return Ok(None);
}
@ -4156,36 +4170,61 @@ impl CultCacheWriteLeaseDriver {
expected.validate()?;
activation.validate()?;
lease.validate()?;
let expected_sha256 = expected.canonical_sha256()?;
let activation_sha256 = activation.canonical_sha256()?;
ensure!(
self.target == expected.target
&& lease.target == expected.target
&& lease.expected_projection_sha256 == expected_sha256
&& lease.plan_id == expected.plan_id
&& lease.incarnation_id == expected.incarnation_id
&& lease.sealed_release_id == expected.sealed_release_id
&& lease.activation_witness_sha256 == activation_sha256
&& lease.runtime_id == expected.runtime_id
&& lease.runtime_instance_id == activation.runtime_instance_id
&& lease.state_schema_generation
== expected
.state_schema_generation
.as_deref()
.context("write-lease Expected has no state generation")?
&& lease.state_contract_sha256
== expected
.state_contract_sha256
.as_deref()
.context("write-lease Expected has no state contract")?
&& lease.warming_presence_sha256 == warming.signed_presence_sha256()
&& warming.runtime_instance_id() == activation.runtime_instance_id.as_str(),
self.target == expected.target,
"process write lease does not bind the exact warming candidate"
);
Ok(())
ensure!(
warming.runtime_instance_id() == activation.runtime_instance_id.as_str(),
"process write lease does not bind the exact warming candidate"
);
lease_binds_candidate(
expected,
activation,
lease,
std::slice::from_ref(&warming.signed_presence_sha256().to_owned()),
)
}
}
/// The one rule for what makes a process write lease this incarnation's: it
/// binds this Expected, this activation and one of the given provider-signed
/// Warming proofs. Idunn grants with it and the lease holder accepts with it.
pub(crate) fn lease_binds_candidate(
expected: &IdunnExpectedIncarnationRecord,
activation: &IdunnRuntimeActivationRecord,
lease: &IdunnProcessWriteLeaseRecord,
warming_presence_sha256es: &[String],
) -> Result<()> {
let expected_sha256 = expected.canonical_sha256()?;
let activation_sha256 = activation.canonical_sha256()?;
ensure!(
lease.target == expected.target
&& lease.expected_projection_sha256 == expected_sha256
&& lease.plan_id == expected.plan_id
&& lease.incarnation_id == expected.incarnation_id
&& lease.sealed_release_id == expected.sealed_release_id
&& lease.activation_witness_sha256 == activation_sha256
&& lease.runtime_id == expected.runtime_id
&& lease.runtime_instance_id == activation.runtime_instance_id
&& lease.state_schema_generation
== expected
.state_schema_generation
.as_deref()
.context("write-lease Expected has no state generation")?
&& lease.state_contract_sha256
== expected
.state_contract_sha256
.as_deref()
.context("write-lease Expected has no state contract")?
&& warming_presence_sha256es
.iter()
.any(|sha256| sha256 == &lease.warming_presence_sha256),
"process write lease does not bind the exact warming candidate"
);
Ok(())
}
impl WriteLeasePort for CultCacheWriteLeaseDriver {
fn revoke_exact(&self, incumbent: Option<&IdunnProcessWriteLeaseRecord>) -> Result<()> {
if let Some(incumbent) = incumbent {
@ -5861,7 +5900,7 @@ impl<R: Read> Answer<R> {
}
}
fn request_tcp_snapshot(target: SocketAddr, payload: &[u8]) -> Result<Vec<u8>, ChallengeFailure> {
pub(crate) fn request_tcp_snapshot(target: SocketAddr, payload: &[u8]) -> Result<Vec<u8>, ChallengeFailure> {
let mut stream = connect_route_socket(target)
.with_context(|| format!("connecting stable CultNet TCP route {target}"))
.map_err(silent)?;
@ -6070,7 +6109,7 @@ fn connect_route_socket(target: SocketAddr) -> std::io::Result<TcpStream> {
}
}
fn exact_route_snapshot_response(
pub(crate) fn exact_route_snapshot_response(
response: CultNetMessage,
message_id: &str,
target: &str,
@ -8335,12 +8374,17 @@ fn authority_lock_path(path: &Path) -> PathBuf {
#[cfg(unix)]
fn validate_root_authority_path(path: &Path) -> Result<()> {
use std::os::unix::fs::{MetadataExt, PermissionsExt};
ensure!(
unsafe { libc::geteuid() } == 0,
"process write-lease actuation requires root Idunn"
);
validate_authority_path_custody(path)
}
#[cfg(unix)]
fn validate_authority_path_custody(path: &Path) -> Result<()> {
use std::os::unix::fs::{MetadataExt, PermissionsExt};
let parent = path
.parent()
.context("process write-lease path has no parent")?;
@ -8383,6 +8427,11 @@ fn validate_root_authority_path(_path: &Path) -> Result<()> {
bail!("process write-lease actuation requires a Unix authority path")
}
#[cfg(not(unix))]
fn validate_authority_path_custody(_path: &Path) -> Result<()> {
bail!("process write-lease actuation requires a Unix authority path")
}
#[cfg(unix)]
fn harden_root_authority_file(path: &Path) -> Result<()> {
use std::os::unix::fs::{MetadataExt, PermissionsExt};

1277
src/launcher.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -4,5 +4,7 @@ pub mod deployment_plan;
pub mod drivers;
pub mod host;
pub mod host_actuator;
#[cfg(unix)]
pub mod launcher;
mod incident;
pub mod provisioning;