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:
parent
491459e6a0
commit
b9f27ce1e7
5 changed files with 1447 additions and 29 deletions
|
|
@ -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
86
src/bin/idunn-launch.rs
Normal 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);
|
||||
}
|
||||
105
src/drivers.rs
105
src/drivers.rs
|
|
@ -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
1277
src/launcher.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue