2022-11-23 09:26:26 -05:00
|
|
|
use std::fs;
|
2022-11-25 11:47:24 -05:00
|
|
|
use std::os::unix::prelude::PermissionsExt;
|
2022-11-25 07:07:04 -05:00
|
|
|
use std::path::{Path, PathBuf};
|
2022-11-25 17:57:56 -05:00
|
|
|
use nix::unistd::sync;
|
2022-11-23 09:26:26 -05:00
|
|
|
|
2022-11-24 07:33:01 -05:00
|
|
|
use anyhow::{Context, Result};
|
2022-11-23 09:26:26 -05:00
|
|
|
|
|
|
|
use crate::bootspec::Bootspec;
|
|
|
|
use crate::esp::EspPaths;
|
2022-11-23 14:40:01 -05:00
|
|
|
use crate::pe;
|
2022-11-23 05:59:54 -05:00
|
|
|
use crate::signer::Signer;
|
|
|
|
|
2022-11-25 19:24:33 -05:00
|
|
|
use tempfile::tempdir;
|
|
|
|
|
2022-11-25 19:50:51 -05:00
|
|
|
use std::process::Command;
|
|
|
|
|
2022-11-23 14:40:01 -05:00
|
|
|
pub fn install(
|
2022-11-23 05:59:54 -05:00
|
|
|
public_key: &Path,
|
|
|
|
private_key: &Path,
|
2022-11-25 21:14:21 -05:00
|
|
|
pki_bundle: &Option<PathBuf>,
|
|
|
|
auto_enroll: bool,
|
|
|
|
bootspec: &Path,
|
|
|
|
generations: Vec<PathBuf>,
|
|
|
|
lanzaboote_stub: &Path,
|
|
|
|
initrd_stub: &Path,
|
|
|
|
) -> Result<()> {
|
|
|
|
for generation in generations {
|
|
|
|
let generation_version = extract_generation_version(&generation).with_context(|| {
|
|
|
|
format!(
|
|
|
|
"Failed to extract generation version from generation: {}",
|
|
|
|
generation.display()
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
|
|
|
|
println!("Installing generation {generation_version}");
|
|
|
|
|
|
|
|
install_generation(
|
|
|
|
generation_version,
|
|
|
|
public_key,
|
|
|
|
private_key,
|
|
|
|
pki_bundle,
|
|
|
|
auto_enroll,
|
|
|
|
bootspec,
|
|
|
|
lanzaboote_stub,
|
|
|
|
initrd_stub,
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn extract_generation_version(path: impl AsRef<Path>) -> Result<u64> {
|
|
|
|
let file_name = path.as_ref().file_name().ok_or(anyhow::anyhow!(
|
|
|
|
"Failed to extract file name from generation"
|
|
|
|
))?;
|
|
|
|
let file_name_str = file_name
|
|
|
|
.to_str()
|
|
|
|
.with_context(|| "Failed to convert file name of generation to string")?;
|
|
|
|
|
|
|
|
let generation_version = file_name_str
|
|
|
|
.split("-")
|
|
|
|
.nth(1)
|
|
|
|
.ok_or(anyhow::anyhow!("Failed to extract version from generation"))?;
|
|
|
|
|
|
|
|
Ok(generation_version
|
|
|
|
.parse()
|
|
|
|
.with_context(|| format!("Failed to parse generation version: {}", generation_version))?)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn install_generation(
|
|
|
|
generation: u64,
|
|
|
|
public_key: &Path,
|
|
|
|
private_key: &Path,
|
|
|
|
_pki_bundle: &Option<PathBuf>,
|
2022-11-24 08:12:00 -05:00
|
|
|
_auto_enroll: bool,
|
2022-11-23 14:40:01 -05:00
|
|
|
bootspec: &Path,
|
|
|
|
lanzaboote_stub: &Path,
|
|
|
|
initrd_stub: &Path,
|
|
|
|
) -> Result<()> {
|
2022-11-24 07:33:01 -05:00
|
|
|
println!("Reading bootspec...");
|
|
|
|
|
|
|
|
let bootspec_doc: Bootspec =
|
|
|
|
serde_json::from_slice(&fs::read(bootspec).context("Failed to read bootspec file")?)
|
|
|
|
.context("Failed to parse bootspec json")?;
|
2022-11-23 09:26:26 -05:00
|
|
|
|
2022-11-25 21:14:21 -05:00
|
|
|
let esp_paths = EspPaths::new(&bootspec_doc.extension.esp, generation, &bootspec_doc)?;
|
2022-11-23 09:26:26 -05:00
|
|
|
|
2022-11-24 07:33:01 -05:00
|
|
|
println!("Assembling lanzaboote image...");
|
2022-11-25 07:07:04 -05:00
|
|
|
|
|
|
|
let kernel_cmdline = assemble_kernel_cmdline(bootspec_doc.init, bootspec_doc.kernel_params);
|
2022-11-24 07:33:01 -05:00
|
|
|
|
2022-11-25 19:24:33 -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-11-25 09:15:58 -05:00
|
|
|
let lanzaboote_image = pe::lanzaboote_image(
|
2022-11-25 19:24:33 -05:00
|
|
|
&secure_temp_dir,
|
2022-11-23 14:40:01 -05:00
|
|
|
lanzaboote_stub,
|
2022-11-24 06:26:32 -05:00
|
|
|
&bootspec_doc.extension.os_release,
|
2022-11-24 10:51:43 -05:00
|
|
|
&kernel_cmdline,
|
2022-11-23 09:26:26 -05:00
|
|
|
&esp_paths.kernel,
|
|
|
|
&esp_paths.initrd,
|
2022-11-24 11:14:55 -05:00
|
|
|
&esp_paths.esp,
|
2022-11-23 09:26:26 -05:00
|
|
|
)
|
2022-11-24 07:33:01 -05:00
|
|
|
.context("Failed to assemble stub")?;
|
2022-11-23 09:26:26 -05:00
|
|
|
|
2022-11-24 07:33:01 -05:00
|
|
|
println!("Wrapping initrd into a PE binary...");
|
2022-11-23 14:40:01 -05:00
|
|
|
|
2022-11-25 19:50:51 -05:00
|
|
|
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)?;
|
|
|
|
}
|
2022-11-24 07:33:01 -05:00
|
|
|
let wrapped_initrd =
|
2022-11-25 19:50:51 -05:00
|
|
|
pe::wrap_initrd(&secure_temp_dir, initrd_stub, &initrd_location).context("Failed to assemble stub")?;
|
2022-11-23 11:26:56 -05:00
|
|
|
|
2022-11-24 07:33:01 -05:00
|
|
|
println!("Copy files to EFI system partition...");
|
2022-11-23 09:26:26 -05:00
|
|
|
|
2022-11-24 05:10:19 -05:00
|
|
|
let systemd_boot = bootspec_doc
|
|
|
|
.extension
|
|
|
|
.systemd
|
|
|
|
.join("lib/systemd/boot/efi/systemd-bootx64.efi");
|
|
|
|
|
2022-11-24 07:33:01 -05:00
|
|
|
let files_to_copy = [
|
2022-11-23 05:59:54 -05:00
|
|
|
(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),
|
2022-11-24 07:33:01 -05:00
|
|
|
];
|
|
|
|
|
|
|
|
for (source, target) in files_to_copy {
|
|
|
|
copy(&source, &target)?;
|
|
|
|
}
|
2022-11-24 05:10:19 -05:00
|
|
|
|
2022-11-25 19:24:33 -05:00
|
|
|
// TODO: we should implement sign_and_copy which would be secure
|
|
|
|
// by construction for TOCTOU.
|
|
|
|
|
2022-11-25 07:07:04 -05:00
|
|
|
println!("Signing files...");
|
|
|
|
|
|
|
|
let signer = Signer::new(&public_key, &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 {
|
2022-11-25 09:46:33 -05:00
|
|
|
println!("Signing {}...", file.display());
|
2022-11-25 07:07:04 -05:00
|
|
|
signer
|
|
|
|
.sign_file(&file)
|
|
|
|
.with_context(|| format!("Failed to sign file {}", &file.display()))?;
|
2022-11-25 17:57:56 -05:00
|
|
|
sync();
|
2022-11-25 07:07:04 -05:00
|
|
|
}
|
2022-11-23 05:59:54 -05:00
|
|
|
|
2022-11-24 07:33:01 -05:00
|
|
|
println!(
|
2022-11-25 17:57:56 -05:00
|
|
|
"Successfully installed lanzaboote to '{}'",
|
2022-11-24 07:33:01 -05:00
|
|
|
esp_paths.esp.display()
|
|
|
|
);
|
2022-11-25 07:07:04 -05:00
|
|
|
|
2022-11-24 07:33:01 -05:00
|
|
|
Ok(())
|
|
|
|
}
|
2022-11-23 09:26:26 -05:00
|
|
|
|
2022-11-25 19:50:51 -05:00
|
|
|
pub fn append_initrd_secrets(append_initrd_secrets_path: &Path, initrd_path: &PathBuf) -> Result<()> {
|
|
|
|
let status = Command::new(append_initrd_secrets_path)
|
|
|
|
.args(vec![
|
|
|
|
initrd_path
|
|
|
|
])
|
|
|
|
.status()
|
|
|
|
.context("Failed to append initrd secrets")?;
|
|
|
|
if !status.success() {
|
|
|
|
return Err(anyhow::anyhow!("Failed to append initrd secrets with args `{:?}`", vec![append_initrd_secrets_path, initrd_path]).into());
|
|
|
|
}
|
|
|
|
|
|
|
|
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()))?;
|
2022-11-25 11:47:24 -05:00
|
|
|
|
|
|
|
// 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(())
|
|
|
|
}
|