lanzaboote/rust/lanzatool/src/install.rs

232 lines
7.0 KiB
Rust
Raw Normal View History

2022-11-23 09:26:26 -05:00
use std::fs;
use std::os::unix::prelude::PermissionsExt;
2022-11-25 07:07:04 -05:00
use std::path::{Path, PathBuf};
2022-11-26 08:55:15 -05:00
use std::process::Command;
2022-11-23 09:26:26 -05:00
2022-11-24 07:33:01 -05:00
use anyhow::{Context, Result};
2022-11-26 08:55:15 -05:00
use nix::unistd::sync;
use tempfile::tempdir;
2022-11-23 09:26:26 -05:00
use crate::esp::EspPaths;
use crate::generation::Generation;
2022-11-23 14:40:01 -05:00
use crate::pe;
2022-11-26 17:19:08 -05:00
use crate::signature::KeyPair;
2022-11-26 08:55:15 -05:00
pub struct Installer {
lanzaboote_stub: PathBuf,
2022-11-26 17:19:08 -05:00
key_pair: KeyPair,
esp: PathBuf,
generations: Vec<PathBuf>,
2022-11-26 08:55:15 -05:00
}
impl Installer {
pub fn new(
lanzaboote_stub: PathBuf,
2022-11-26 17:19:08 -05:00
key_pair: KeyPair,
esp: PathBuf,
2022-11-26 08:55:15 -05:00
generations: Vec<PathBuf>,
) -> Self {
Self {
lanzaboote_stub,
2022-11-26 17:19:08 -05:00
key_pair,
esp,
2022-11-26 08:55:15 -05:00
generations,
}
2022-11-25 19:50:51 -05:00
}
2022-11-26 08:55:15 -05:00
pub fn install(&self) -> Result<()> {
for toplevel in &self.generations {
let generation_result = Generation::from_toplevel(toplevel).with_context(|| {
format!("Failed to build generation from toplevel: {toplevel:?}")
});
2022-11-25 07:07:04 -05:00
let generation = match generation_result {
Ok(generation) => generation,
Err(e) => {
println!("Malformed generation: {:?}", e);
continue;
}
};
2022-11-26 08:55:15 -05:00
println!("Installing generation {generation}");
2022-11-25 07:07:04 -05:00
2022-11-27 05:19:02 -05:00
self.install_generation(&generation)
.context("Failed to install generation")?;
for (name, bootspec) in &generation.spec.bootspec.specialisation {
let specialised_generation = generation.specialise(name, bootspec)?;
2022-11-27 05:19:02 -05:00
println!("Installing specialisation: {name} of generation: {generation}");
self.install_generation(&specialised_generation)
.context("Failed to install specialisation")?;
}
2022-11-26 08:55:15 -05:00
}
2022-11-25 07:07:04 -05:00
2022-11-26 08:55:15 -05:00
Ok(())
2022-11-25 07:07:04 -05:00
}
fn install_generation(&self, generation: &Generation) -> Result<()> {
let bootspec = &generation.spec.bootspec;
let secureboot_extensions = &generation.spec.extensions;
2022-11-26 08:55:15 -05:00
2022-11-27 05:19:02 -05:00
let esp_paths = EspPaths::new(&self.esp, generation)?;
2022-11-26 08:55:15 -05:00
let kernel_cmdline =
assemble_kernel_cmdline(&bootspec.init, bootspec.kernel_params.clone());
2022-11-26 08:55:15 -05:00
// prepare a secure temporary directory
// permission bits are not set, because when files below
// are opened, they are opened with 600 mode bits.
// hence, they cannot be read except by the current user
// which is assumed to be root in most cases.
// TODO(Raito): prove to niksnur this is actually acceptable.
let secure_temp_dir = tempdir()?;
2022-12-03 07:16:46 -05:00
println!("Appending secrets to initrd...");
2022-11-26 08:55:15 -05:00
let initrd_location = secure_temp_dir.path().join("initrd");
copy(
bootspec
.initrd
.as_ref()
.context("Lanzaboote does not support missing initrd yet")?,
&initrd_location,
)?;
if let Some(initrd_secrets_script) = &bootspec.initrd_secrets {
2022-11-26 17:19:08 -05:00
append_initrd_secrets(initrd_secrets_script, &initrd_location)?;
2022-11-26 08:55:15 -05:00
}
let systemd_boot = bootspec
.toplevel
.0
.join("systemd/lib/systemd/boot/efi/systemd-bootx64.efi");
2022-11-26 08:55:15 -05:00
[
2022-11-26 09:32:43 -05:00
(&systemd_boot, &esp_paths.efi_fallback),
(&systemd_boot, &esp_paths.systemd_boot),
(&bootspec.kernel, &esp_paths.kernel),
]
.into_iter()
.try_for_each(|(from, to)| install_signed(&self.key_pair, from, to))?;
// The initrd doesn't need to be signed. Lanzaboote has its
2022-12-03 07:16:46 -05:00
// hash embedded and will refuse loading it when the hash
// mismatches.
2022-12-03 07:16:46 -05:00
install(&initrd_location, &esp_paths.initrd).context("Failed to install initrd to ESP")?;
let lanzaboote_image = pe::lanzaboote_image(
&secure_temp_dir,
&self.lanzaboote_stub,
&secureboot_extensions.os_release,
&kernel_cmdline,
&esp_paths.kernel,
&esp_paths.initrd,
&esp_paths.esp,
)
.context("Failed to assemble stub")?;
2022-11-26 08:55:15 -05:00
install_signed(
&self.key_pair,
&lanzaboote_image,
&esp_paths.lanzaboote_image,
)
.context("Failed to install lanzaboote")?;
2022-11-26 08:55:15 -05:00
// Sync files to persistent storage. This may improve the
// chance of a consistent boot directory in case the system
// crashes.
sync();
2022-11-26 08:55:15 -05:00
println!(
"Successfully installed lanzaboote to '{}'",
esp_paths.esp.display()
);
Ok(())
}
2022-11-24 07:33:01 -05:00
}
2022-11-23 09:26:26 -05:00
/// Install a PE file. The PE gets signed in the process.
2022-12-03 07:16:46 -05:00
///
/// The file is only signed and copied if it doesn't exist at the destination
fn install_signed(key_pair: &KeyPair, from: &Path, to: &Path) -> Result<()> {
2022-12-03 07:16:46 -05:00
if to.exists() {
println!("{} already exists, skipping...", to.display());
} else {
println!("Signing and installing {}...", to.display());
ensure_parent_dir(to);
key_pair
.sign_and_copy(from, to)
.with_context(|| format!("Failed to copy and sign file from {:?} to {:?}", from, to))?;
}
2022-12-03 07:16:46 -05:00
Ok(())
}
/// Install an arbitrary file
///
/// The file is only copied if it doesn't exist at the destination
fn install(from: &Path, to: &Path) -> Result<()> {
if to.exists() {
println!("{} already exists, skipping...", to.display());
} else {
println!("Installing {}...", to.display());
ensure_parent_dir(to);
copy(from, to)?;
}
Ok(())
}
2022-11-26 08:55:15 -05:00
pub fn append_initrd_secrets(
append_initrd_secrets_path: &Path,
initrd_path: &PathBuf,
) -> Result<()> {
2022-11-25 19:50:51 -05:00
let status = Command::new(append_initrd_secrets_path)
2022-11-26 08:55:15 -05:00
.args(vec![initrd_path])
2022-11-25 19:50:51 -05:00
.status()
.context("Failed to append initrd secrets")?;
if !status.success() {
2022-11-26 08:55:15 -05:00
return Err(anyhow::anyhow!(
"Failed to append initrd secrets with args `{:?}`",
vec![append_initrd_secrets_path, initrd_path]
2022-11-26 17:19:08 -05:00
));
2022-11-25 19:50:51 -05:00
}
Ok(())
}
fn assemble_kernel_cmdline(init: &Path, kernel_params: Vec<String>) -> Vec<String> {
let init_string = String::from(
init.to_str()
.expect("Failed to convert init path to string"),
);
2022-11-25 07:07:04 -05:00
let mut kernel_cmdline: Vec<String> = vec![format!("init={}", init_string)];
kernel_cmdline.extend(kernel_params);
kernel_cmdline
}
2022-11-24 07:33:01 -05:00
fn copy(from: &Path, to: &Path) -> Result<()> {
2022-11-26 09:32:43 -05:00
ensure_parent_dir(to);
2022-11-24 07:33:01 -05:00
fs::copy(from, to)
.with_context(|| format!("Failed to copy from {} to {}", from.display(), to.display()))?;
// Set permission of all files copied to 0o755
let mut perms = fs::metadata(to)
.with_context(|| format!("File {} doesn't have metadata", to.display()))?
.permissions();
perms.set_mode(0o755);
fs::set_permissions(to, perms)
.with_context(|| format!("Failed to set permissions to: {}", to.display()))?;
2022-11-23 09:26:26 -05:00
Ok(())
}
2022-11-26 09:32:43 -05:00
// Ensures the parent directory of an arbitrary path exists
fn ensure_parent_dir(path: &Path) {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).ok();
}
}