lanzaboote/rust/lanzatool/src/install.rs

205 lines
6.2 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::bootspec::Bootspec;
use crate::esp::EspPaths;
2022-11-26 08:55:15 -05:00
use crate::generation::Generation;
2022-11-23 14:40:01 -05:00
use crate::pe;
use crate::signer::Signer;
2022-11-26 08:55:15 -05:00
pub struct Installer {
lanzaboote_stub: PathBuf,
initrd_stub: PathBuf,
public_key: PathBuf,
private_key: PathBuf,
_pki_bundle: Option<PathBuf>,
_auto_enroll: bool,
bootspec: PathBuf,
generations: Vec<PathBuf>,
2022-11-26 08:55:15 -05:00
}
impl Installer {
pub fn new(
lanzaboote_stub: PathBuf,
initrd_stub: PathBuf,
public_key: PathBuf,
private_key: PathBuf,
_pki_bundle: Option<PathBuf>,
_auto_enroll: bool,
bootspec: PathBuf,
generations: Vec<PathBuf>,
) -> Self {
Self {
lanzaboote_stub,
initrd_stub,
public_key,
private_key,
2022-11-26 08:55:15 -05:00
_pki_bundle,
_auto_enroll,
bootspec,
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 = Generation::from_toplevel(toplevel).with_context(|| {
format!("Failed to extract generation version from: {toplevel:?}")
})?;
2022-11-25 07:07:04 -05:00
2022-11-26 08:55:15 -05:00
println!("Installing generation {generation}");
2022-11-25 07:07:04 -05:00
2022-11-26 08:55:15 -05:00
self.install_generation(generation)?
}
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
}
2022-11-26 08:55:15 -05:00
pub fn install_generation(&self, generation: Generation) -> Result<()> {
println!("Reading bootspec...");
let bootspec_doc: Bootspec = serde_json::from_slice(
&fs::read(&self.bootspec).context("Failed to read bootspec file")?,
)
.context("Failed to parse bootspec json")?;
let esp_paths = EspPaths::new(&bootspec_doc.extension.esp, generation, &bootspec_doc)?;
println!("Assembling lanzaboote image...");
let kernel_cmdline = assemble_kernel_cmdline(bootspec_doc.init, bootspec_doc.kernel_params);
// 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()?;
let lanzaboote_image = pe::lanzaboote_image(
&secure_temp_dir,
&self.lanzaboote_stub,
&bootspec_doc.extension.os_release,
&kernel_cmdline,
&esp_paths.kernel,
&esp_paths.initrd,
&esp_paths.esp,
)
.context("Failed to assemble stub")?;
println!("Wrapping initrd into a PE binary...");
let initrd_location = secure_temp_dir.path().join("initrd");
copy(&bootspec_doc.initrd, &initrd_location)?;
if let Some(initrd_secrets_script) = bootspec_doc.initrd_secrets {
append_initrd_secrets(&initrd_secrets_script, &initrd_location)?;
}
let wrapped_initrd = pe::wrap_initrd(&secure_temp_dir, &self.initrd_stub, &initrd_location)
.context("Failed to assemble stub")?;
println!("Copy files to EFI system partition...");
let systemd_boot = bootspec_doc
.extension
.systemd
.join("lib/systemd/boot/efi/systemd-bootx64.efi");
let files_to_copy = [
(bootspec_doc.kernel, &esp_paths.kernel),
(wrapped_initrd, &esp_paths.initrd),
(lanzaboote_image, &esp_paths.lanzaboote_image),
(systemd_boot.clone(), &esp_paths.efi_fallback),
(systemd_boot, &esp_paths.systemd_boot),
];
for (source, target) in files_to_copy {
copy(&source, &target)?;
}
// TODO: we should implement sign_and_copy which would be secure
// by construction for TOCTOU.
println!("Signing files...");
let signer = Signer::new(&self.public_key, &self.private_key);
let files_to_sign = [
&esp_paths.efi_fallback,
&esp_paths.systemd_boot,
&esp_paths.lanzaboote_image,
&esp_paths.kernel,
&esp_paths.initrd,
];
for file in files_to_sign {
println!("Signing {}...", file.display());
signer
.sign_file(&file)
.with_context(|| format!("Failed to sign file {}", &file.display()))?;
sync();
}
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
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]
)
.into());
2022-11-25 19:50:51 -05:00
}
Ok(())
}
2022-11-25 07:07:04 -05:00
fn assemble_kernel_cmdline(init: PathBuf, kernel_params: Vec<String>) -> Vec<String> {
let init_string = init
.into_os_string()
.into_string()
.expect("Failed to convert init path to string");
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<()> {
match to.parent() {
Some(parent) => fs::create_dir_all(parent).unwrap_or(()),
_ => (),
};
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(())
}