Integrate host escalation with production sessions

This commit is contained in:
ms2k 2026-07-18 04:03:39 +09:00
commit 696dae610a

View file

@ -14,6 +14,8 @@ use lazaret_core::records::{
};
use lazaret_escalation_client::{ClientEvent, EscalationClient};
use lazaret_linux::filesystem::RegularFileMailbox;
use nix::sys::signal::{SigSet, SigmaskHow, Signal};
use nix::sys::signalfd::{SfdFlags, SignalFd};
struct Arguments {
inbox: PathBuf,
@ -37,12 +39,23 @@ fn main() -> ExitCode {
}
fn run() -> Result<u8, Box<dyn std::error::Error>> {
let arguments = parse_arguments(std::env::args_os().skip(1).collect())?;
let publication: ProtocolPublicationIdentity = read_record(&arguments.publication)?;
let bundle: SandboxCredentialBundle = read_record(&arguments.sandbox_credentials)?;
let registration: SessionRegistrationResponse = read_record(&arguments.registration)?;
let binding: SessionBinding = read_record(&arguments.session_binding)?;
let sandbox_key_bytes: [u8; 32] = bundle.sandbox_private_key.as_slice().try_into()?;
let arguments = parse_arguments_with_session(
std::env::args_os().skip(1).collect(),
std::env::var_os("LAZARET_SESSION_ID"),
)?;
let publication: ProtocolPublicationIdentity =
read_record(&arguments.publication).map_err(|error| context("publication", error))?;
let bundle: SandboxCredentialBundle = read_record(&arguments.sandbox_credentials)
.map_err(|error| context("sandbox credentials", error))?;
let registration: SessionRegistrationResponse = read_record(&arguments.registration)
.map_err(|error| context("session registration", error))?;
let binding: SessionBinding = read_record(&arguments.session_binding)
.map_err(|error| context("session binding", error))?;
let sandbox_key_bytes: [u8; 32] = bundle
.sandbox_private_key
.as_slice()
.try_into()
.map_err(|error| context("sandbox signing key", error))?;
let sandbox_key = SigningKey::from_bytes(&sandbox_key_bytes);
let session = VerifiedSession::new(
publication.trust_manifest_sha256,
@ -52,16 +65,29 @@ fn run() -> Result<u8, Box<dyn std::error::Error>> {
bundle.coordinator_protocol_credential(),
bundle.sandbox_protocol_credential(),
registration.session_capability,
)?;
)
.map_err(|error| context("session authority", error))?;
let client = EscalationClient::new(
RegularFileMailbox::open(&arguments.inbox)?,
RegularFileMailbox::open(&arguments.outbox)?,
RegularFileMailbox::open(&arguments.inbox)
.map_err(|error| context("request mailbox", error))?,
RegularFileMailbox::open(&arguments.outbox)
.map_err(|error| context("result mailbox", error))?,
session,
sandbox_key,
);
let mut request = client.request(arguments.command_argv, arguments.justification)?;
let mut cancellation =
CancellationSignals::register().map_err(|error| context("cancellation signals", error))?;
let mut request = client
.request(arguments.command_argv, arguments.justification)
.map_err(|error| context("request publication", error))?;
loop {
match request.poll()? {
if cancellation.requested()? {
request.cancel()?;
}
match request
.poll()
.map_err(|error| context("result collection", error))?
{
ClientEvent::Output { stream, bytes, .. } => match stream {
lazaret_core::records::ResultStream::Stdout => {
io::stdout().write_all(&bytes)?;
@ -89,6 +115,58 @@ fn run() -> Result<u8, Box<dyn std::error::Error>> {
}
}
fn context(label: &str, error: impl std::fmt::Display) -> io::Error {
io::Error::other(format!("{label}: {error}"))
}
struct CancellationSignals {
descriptor: SignalFd,
previous: SigSet,
published: bool,
}
impl CancellationSignals {
fn register() -> io::Result<Self> {
let mut signals = SigSet::empty();
signals.add(Signal::SIGINT);
signals.add(Signal::SIGTERM);
let previous = signals
.thread_swap_mask(SigmaskHow::SIG_BLOCK)
.map_err(io::Error::from)?;
let descriptor =
SignalFd::with_flags(&signals, SfdFlags::SFD_CLOEXEC | SfdFlags::SFD_NONBLOCK)
.map_err(io::Error::from)?;
Ok(Self {
descriptor,
previous,
published: false,
})
}
fn requested(&mut self) -> io::Result<bool> {
if self.published {
return Ok(false);
}
if self
.descriptor
.read_signal()
.map_err(io::Error::from)?
.is_some()
{
self.published = true;
Ok(true)
} else {
Ok(false)
}
}
}
impl Drop for CancellationSignals {
fn drop(&mut self) {
let _ = self.previous.thread_set_mask();
}
}
fn read_record<T>(path: &std::path::Path) -> Result<T, Box<dyn std::error::Error>>
where
T: serde::de::DeserializeOwned + serde::Serialize,
@ -117,7 +195,10 @@ where
Ok(decode(&bytes)?)
}
fn parse_arguments(mut arguments: Vec<OsString>) -> Result<Arguments, io::Error> {
fn parse_arguments_with_session(
mut arguments: Vec<OsString>,
session: Option<OsString>,
) -> Result<Arguments, io::Error> {
let separator = arguments
.iter()
.position(|argument| argument == "--")
@ -156,6 +237,28 @@ fn parse_arguments(mut arguments: Vec<OsString>) -> Result<Arguments, io::Error>
_ => return Err(io::Error::other("unknown client option")),
}
}
if let Some(session) = session {
let session = session
.into_string()
.map_err(|_| io::Error::other("LAZARET_SESSION_ID is not UTF-8"))?;
if session.len() != 64
|| !session
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
{
return Err(io::Error::other("LAZARET_SESSION_ID is invalid"));
}
let credentials = PathBuf::from("/run/lazaret/credentials");
inbox.get_or_insert_with(|| PathBuf::from("/run/lazaret/mailbox/requests"));
outbox.get_or_insert_with(|| PathBuf::from("/run/lazaret/mailbox/results"));
publication.get_or_insert_with(|| credentials.join("publication.postcard"));
sandbox_credentials.get_or_insert_with(|| credentials.join("sandbox-credentials.postcard"));
registration.get_or_insert_with(|| {
credentials.join(format!("session-{session}-registration.postcard"))
});
session_binding
.get_or_insert_with(|| credentials.join(format!("session-{session}-binding.postcard")));
}
Ok(Arguments {
inbox: inbox.ok_or_else(|| io::Error::other("inbox path is missing"))?,
outbox: outbox.ok_or_else(|| io::Error::other("outbox path is missing"))?,
@ -173,7 +276,7 @@ fn parse_arguments(mut arguments: Vec<OsString>) -> Result<Arguments, io::Error>
#[cfg(test)]
mod tests {
use super::parse_arguments;
use super::parse_arguments_with_session;
use std::ffi::OsString;
fn valid_arguments() -> Vec<OsString> {
@ -204,7 +307,7 @@ mod tests {
#[test]
fn parser_preserves_exact_argv_without_shell_interpretation() {
let parsed = parse_arguments(valid_arguments()).unwrap();
let parsed = parse_arguments_with_session(valid_arguments(), None).unwrap();
assert_eq!(
parsed.command_argv,
[
@ -219,14 +322,48 @@ mod tests {
fn parser_refuses_missing_separator_unknown_options_and_empty_argv() {
let mut missing_separator = valid_arguments();
missing_separator.retain(|argument| argument != "--");
assert!(parse_arguments(missing_separator).is_err());
assert!(parse_arguments_with_session(missing_separator, None).is_err());
let mut unknown = valid_arguments();
unknown[0] = OsString::from("--unknown");
assert!(parse_arguments(unknown).is_err());
assert!(parse_arguments_with_session(unknown, None).is_err());
let mut empty = valid_arguments();
empty.truncate(empty.iter().position(|argument| argument == "--").unwrap() + 1);
assert!(parse_arguments(empty).is_err());
assert!(parse_arguments_with_session(empty, None).is_err());
}
#[test]
fn session_environment_supplies_only_fixed_generation_mount_paths() {
let arguments = ["--justification", "needed", "--", "/usr/bin/tool"]
.into_iter()
.map(OsString::from)
.collect();
let session = "11".repeat(32);
let parsed =
parse_arguments_with_session(arguments, Some(OsString::from(&session))).unwrap();
assert_eq!(
parsed.inbox,
std::path::Path::new("/run/lazaret/mailbox/requests")
);
assert_eq!(
parsed.outbox,
std::path::Path::new("/run/lazaret/mailbox/results")
);
assert_eq!(
parsed.registration,
std::path::Path::new(&format!(
"/run/lazaret/credentials/session-{session}-registration.postcard"
))
);
let arguments = ["--justification", "needed", "--", "/usr/bin/tool"]
.into_iter()
.map(OsString::from)
.collect();
assert!(
parse_arguments_with_session(arguments, Some(OsString::from("not-an-identity")))
.is_err()
);
}
}