2023-08-12 08:23:21 -05:00
|
|
|
use anyhow::Result;
|
|
|
|
use base32ct::{Base32Unpadded, Encoding};
|
2023-08-13 05:02:35 -05:00
|
|
|
use tempfile::tempdir;
|
2023-02-15 16:03:12 -06:00
|
|
|
|
|
|
|
mod common;
|
|
|
|
|
|
|
|
use common::{
|
|
|
|
count_files, hash_file, remove_signature, setup_generation_link_from_toplevel, verify_signature,
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Install two generations that point at the same toplevel.
|
|
|
|
/// This should install two lanzaboote images and one kernel and one initrd.
|
|
|
|
#[test]
|
|
|
|
fn do_not_install_duplicates() -> Result<()> {
|
|
|
|
let esp = tempdir()?;
|
|
|
|
let tmpdir = tempdir()?;
|
|
|
|
let profiles = tempdir()?;
|
|
|
|
let toplevel = common::setup_toplevel(tmpdir.path())?;
|
|
|
|
|
|
|
|
let generation_link1 = setup_generation_link_from_toplevel(&toplevel, profiles.path(), 1)?;
|
|
|
|
let generation_link2 = setup_generation_link_from_toplevel(&toplevel, profiles.path(), 2)?;
|
|
|
|
let generation_links = vec![generation_link1, generation_link2];
|
|
|
|
|
|
|
|
let stub_count = || count_files(&esp.path().join("EFI/Linux")).unwrap();
|
|
|
|
let kernel_and_initrd_count = || count_files(&esp.path().join("EFI/nixos")).unwrap();
|
|
|
|
|
|
|
|
let output1 = common::lanzaboote_install(0, esp.path(), generation_links)?;
|
|
|
|
assert!(output1.status.success());
|
|
|
|
assert_eq!(stub_count(), 2, "Wrong number of stubs after installation");
|
|
|
|
assert_eq!(
|
|
|
|
kernel_and_initrd_count(),
|
|
|
|
2,
|
|
|
|
"Wrong number of kernels & initrds after installation"
|
|
|
|
);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
tool: stop most overwriting in the ESP
Since most files (stubs, kernels and initrds) on the ESP are properly
input-addressed or content-addressed now, there is no point in
overwriting them any more. Hence we detect what generations are already
properly installed, and don't reinstall them any more.
This approach leads to two distinct improvements:
* Rollbacks are more reliable, because initrd secrets and stubs do not
change any more for existing generations (with the necessary exception
of stubs in case of signature key rotation). In particular, the risk
of a newer stub breaking (for example, because of bad interactions
with certain firmware) old and previously working generations is
avoided.
* Kernels and initrds that are not going to be (re)installed anyway are
not read and hashed any more. This significantly reduces the I/O and
CPU time required for the installation process, particularly when
there is a large number of generations.
The following drawbacks are noted:
* The first time installation is performed after these changes, most of
the ESP is re-written at a different path; as a result, the disk usage
increases to roughly the double until the GC is performed.
* If multiple generations share a bare initrd, but have different
secrets scripts, the final initrds will now be separated, leading to
increased disk usage. However, this situation should be rare, and the
previous behavior was arguably incorrect anyway.
* If the files on the ESP are corrupted, running the installation again
will not overwrite them with the correct versions. Since the files are
written atomically, this situation should not happen except in case of
file system corruption, and it is questionable whether overwriting
really fixes the problem in this case.
2023-08-13 11:29:40 -05:00
|
|
|
fn do_not_overwrite_images() -> Result<()> {
|
2023-02-15 16:03:12 -06:00
|
|
|
let esp = tempdir()?;
|
|
|
|
let tmpdir = tempdir()?;
|
|
|
|
let profiles = tempdir()?;
|
2023-08-13 05:02:35 -05:00
|
|
|
let toplevel = common::setup_toplevel(tmpdir.path())?;
|
2023-02-15 16:03:12 -06:00
|
|
|
|
2023-08-13 05:02:35 -05:00
|
|
|
let image1 = common::image_path(&esp, 1, &toplevel)?;
|
|
|
|
let image2 = common::image_path(&esp, 2, &toplevel)?;
|
2023-02-15 16:03:12 -06:00
|
|
|
|
2023-08-13 05:02:35 -05:00
|
|
|
let generation_link1 = setup_generation_link_from_toplevel(&toplevel, profiles.path(), 1)?;
|
|
|
|
let generation_link2 = setup_generation_link_from_toplevel(&toplevel, profiles.path(), 2)?;
|
2023-02-15 16:03:12 -06:00
|
|
|
let generation_links = vec![generation_link1, generation_link2];
|
|
|
|
|
|
|
|
let output1 = common::lanzaboote_install(0, esp.path(), generation_links.clone())?;
|
|
|
|
assert!(output1.status.success());
|
|
|
|
|
|
|
|
remove_signature(&image1)?;
|
|
|
|
assert!(!verify_signature(&image1)?);
|
|
|
|
assert!(verify_signature(&image2)?);
|
|
|
|
|
|
|
|
let output2 = common::lanzaboote_install(0, esp.path(), generation_links)?;
|
|
|
|
assert!(output2.status.success());
|
|
|
|
|
tool: stop most overwriting in the ESP
Since most files (stubs, kernels and initrds) on the ESP are properly
input-addressed or content-addressed now, there is no point in
overwriting them any more. Hence we detect what generations are already
properly installed, and don't reinstall them any more.
This approach leads to two distinct improvements:
* Rollbacks are more reliable, because initrd secrets and stubs do not
change any more for existing generations (with the necessary exception
of stubs in case of signature key rotation). In particular, the risk
of a newer stub breaking (for example, because of bad interactions
with certain firmware) old and previously working generations is
avoided.
* Kernels and initrds that are not going to be (re)installed anyway are
not read and hashed any more. This significantly reduces the I/O and
CPU time required for the installation process, particularly when
there is a large number of generations.
The following drawbacks are noted:
* The first time installation is performed after these changes, most of
the ESP is re-written at a different path; as a result, the disk usage
increases to roughly the double until the GC is performed.
* If multiple generations share a bare initrd, but have different
secrets scripts, the final initrds will now be separated, leading to
increased disk usage. However, this situation should be rare, and the
previous behavior was arguably incorrect anyway.
* If the files on the ESP are corrupted, running the installation again
will not overwrite them with the correct versions. Since the files are
written atomically, this situation should not happen except in case of
file system corruption, and it is questionable whether overwriting
really fixes the problem in this case.
2023-08-13 11:29:40 -05:00
|
|
|
assert!(!verify_signature(&image1)?);
|
2023-02-15 16:03:12 -06:00
|
|
|
assert!(verify_signature(&image2)?);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-08-13 05:02:35 -05:00
|
|
|
#[test]
|
|
|
|
fn detect_generation_number_reuse() -> Result<()> {
|
|
|
|
let esp = tempdir()?;
|
|
|
|
let tmpdir = tempdir()?;
|
|
|
|
let profiles = tempdir()?;
|
|
|
|
let toplevel1 = common::setup_toplevel(tmpdir.path())?;
|
|
|
|
let toplevel2 = common::setup_toplevel(tmpdir.path())?;
|
|
|
|
|
|
|
|
let image1 = common::image_path(&esp, 1, &toplevel1)?;
|
|
|
|
// this deliberately gets the same number!
|
|
|
|
let image2 = common::image_path(&esp, 1, &toplevel2)?;
|
|
|
|
|
|
|
|
let generation_link1 = setup_generation_link_from_toplevel(&toplevel1, profiles.path(), 1)?;
|
|
|
|
let output1 = common::lanzaboote_install(0, esp.path(), vec![generation_link1])?;
|
|
|
|
assert!(output1.status.success());
|
|
|
|
assert!(image1.exists());
|
|
|
|
assert!(!image2.exists());
|
|
|
|
|
|
|
|
std::fs::remove_dir_all(profiles.path().join("system-1-link"))?;
|
|
|
|
let generation_link2 = setup_generation_link_from_toplevel(&toplevel2, profiles.path(), 1)?;
|
|
|
|
let output2 = common::lanzaboote_install(0, esp.path(), vec![generation_link2])?;
|
|
|
|
assert!(output2.status.success());
|
|
|
|
assert!(!image1.exists());
|
|
|
|
assert!(image2.exists());
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-02-15 16:03:12 -06:00
|
|
|
#[test]
|
2023-08-12 08:23:21 -05:00
|
|
|
fn content_addressing_works() -> Result<()> {
|
2023-02-15 16:03:12 -06:00
|
|
|
let esp = tempdir()?;
|
|
|
|
let tmpdir = tempdir()?;
|
|
|
|
let profiles = tempdir()?;
|
|
|
|
let toplevel = common::setup_toplevel(tmpdir.path())?;
|
|
|
|
|
|
|
|
let generation_link = setup_generation_link_from_toplevel(&toplevel, profiles.path(), 1)?;
|
|
|
|
let generation_links = vec![generation_link];
|
|
|
|
|
2023-08-12 08:23:21 -05:00
|
|
|
let kernel_hash_source =
|
|
|
|
hash_file(&toplevel.join("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-6.1.1/kernel"));
|
2023-02-15 16:03:12 -06:00
|
|
|
|
|
|
|
let output0 = common::lanzaboote_install(1, esp.path(), generation_links)?;
|
|
|
|
assert!(output0.status.success());
|
|
|
|
|
2023-08-12 08:23:21 -05:00
|
|
|
let kernel_path = esp.path().join(format!(
|
|
|
|
"EFI/nixos/kernel-6.1.1-{}.efi",
|
|
|
|
Base32Unpadded::encode_string(&kernel_hash_source)
|
|
|
|
));
|
2023-02-15 16:03:12 -06:00
|
|
|
|
2023-08-12 08:23:21 -05:00
|
|
|
// Implicitly assert that the content-addressed file actually exists.
|
|
|
|
let kernel_hash = hash_file(&kernel_path);
|
|
|
|
// Assert the written kernel is the source kernel.
|
|
|
|
assert_eq!(kernel_hash_source, kernel_hash);
|
2023-02-15 16:03:12 -06:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|