Integrate host escalation with production sessions
This commit is contained in:
parent
254bb37764
commit
696dae610a
1 changed files with 1175 additions and 96 deletions
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue