use isoloom_plan::secrets::VM_DEFAULT_MODE;
use isoloom_plan::{Environment, MachineShape, SecretFile};
use isoloom_spec::model::{SecretSource, Spec, secret_variable};
use isoloom_spec::shell::quote;
use crate::boot;
pub mod windows;
pub const KEPT_DIR: &str = "/var/lib/isoloom/secrets";
pub const DELIVERED: &str = "/var/lib/isoloom/secrets/.delivered";
pub const REACH_DIR: &str = ".isoloom-reach";
pub const DELIVERY_TIMEOUT: u32 = 1800;
pub const PLACE_SERVICE: &str = "apply-secrets";
#[must_use]
pub fn receive(names: &[String], files: &[SecretFile]) -> String {
let kept: String = names
.iter()
.map(|name| {
let kept = format!("{KEPT_DIR}/{name}");
format!("IFS= read -r v || [ -n \"$v\" ]; printf '%s\\n' \"$v\" | base64 -d > {kept}.new; mv -f {kept}.new {kept}\n")
})
.collect::<Vec<_>>()
.concat();
format!(
"set -e\numask 077\nmkdir -p {KEPT_DIR}\nchmod 700 {KEPT_DIR}\n{kept}unset v\ntouch {DELIVERED}\n{}",
place(files)
)
}
#[must_use]
pub fn place(files: &[SecretFile]) -> String {
files
.iter()
.map(|f| {
format!(
"install -D -o {} -g {} -m {:04o} {KEPT_DIR}/{} {}\n",
f.uid.unwrap_or(0),
f.gid.unwrap_or(0),
f.mode.unwrap_or(VM_DEFAULT_MODE),
f.name,
quote(&f.path)
)
})
.collect::<Vec<_>>()
.concat()
}
#[must_use]
pub fn wait(who: &str) -> String {
let (waiting, missing) = messages(who);
let (waiting, missing) = (quote(&waiting), quote(&missing));
let tries = DELIVERY_TIMEOUT / 5;
format!(
"n=0; until [ -f {DELIVERED} ]; do [ \"$n\" -gt 0 ] || echo {waiting}; n=$((n+1)); if [ \"$n\" -gt {tries} ]; then echo {missing} >&2; exit 1; fi; sleep 5; done\n"
)
}
fn messages(who: &str) -> (String, String) {
(
format!("{who}: waiting for its secrets, which the runner (isothrum up) delivers"),
format!("{who}: its secrets never came; start the environment with the runner (isothrum up), which reads them from their stores and delivers them"),
)
}
#[must_use]
pub fn step(machine: &str, files: &[SecretFile]) -> String {
format!("set -e\n{}{}", wait(machine), placed(files))
}
fn placed(files: &[SecretFile]) -> String {
boot::at_boot(PLACE_SERVICE, "The machine's secrets, put in place", &place(files), boot::Trigger::Boot, &[])
}
#[must_use]
pub fn from_environment(values: &[(String, String)], files: &[SecretFile]) -> String {
let kept = values
.iter()
.map(|(name, var)| {
let kept = format!("{KEPT_DIR}/{name}");
format!("v=\"$(printenv {var} || true)\"; if [ -n \"$v\" ] || [ ! -e {kept} ]; then printf '%s' \"$v\" | base64 -d > {kept}.new; mv -f {kept}.new {kept}; fi\n")
})
.collect::<Vec<_>>()
.concat();
format!(
"set -e\numask 077\nmkdir -p {KEPT_DIR}\nchmod 700 {KEPT_DIR}\n{kept}touch {DELIVERED}\n{}",
placed(files)
)
}
#[must_use]
pub fn compose_variable(spec: &Spec, name: &str) -> String {
match spec.secrets.get(name) {
Some(SecretSource::Input { input }) => input.clone(),
_ => secret_variable(name),
}
}
#[must_use]
pub fn host_environment(spec: &Spec, names: &[String]) -> String {
if names.is_empty() {
return String::new();
}
let read: String = names
.iter()
.map(|name| {
let var = compose_variable(spec, name);
format!("{var}=\"$(cat {KEPT_DIR}/{name}; printf x)\"; {var}=\"${{{var}%x}}\"; export {var}\n")
})
.collect::<Vec<_>>()
.concat();
wait("the Docker host") + &read
}
#[must_use]
pub fn of_containers(env: &Environment) -> Vec<String> {
env.spec()
.secrets
.keys()
.filter(|name| {
env.machines
.values()
.any(|m| m.shape == MachineShape::Container && m.secrets.iter().any(|s| &s.name == *name))
})
.cloned()
.collect()
}
#[cfg(test)]
mod tests;