mirror of
https://github.com/ostreedev/ostree.git
synced 2025-02-25 21:57:42 +03:00
Merge pull request #3021 from cgwalters/insttest-composefs-binding
tests: Add an integration test for composefs signatures
This commit is contained in:
commit
13be0786bb
@ -1,12 +1,138 @@
|
||||
use std::io::Write;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use ostree_ext::glib;
|
||||
use ostree_ext::{gio, glib};
|
||||
use xshell::cmd;
|
||||
|
||||
use crate::test::reboot;
|
||||
|
||||
const BINDING_KEYPATH: &str = "/etc/ostree/initramfs-root-binding.key";
|
||||
const PREPARE_ROOT_PATH: &str = "/etc/ostree/prepare-root.conf";
|
||||
|
||||
struct Keypair {
|
||||
public: Vec<u8>,
|
||||
private: Vec<u8>,
|
||||
}
|
||||
|
||||
fn generate_raw_ed25519_keypair(sh: &xshell::Shell) -> Result<Keypair> {
|
||||
let keydata = cmd!(sh, "openssl genpkey -algorithm ed25519 -outform PEM")
|
||||
.output()?
|
||||
.stdout;
|
||||
let mut public = cmd!(sh, "openssl pkey -outform DER -pubout")
|
||||
.stdin(&keydata)
|
||||
.output()?
|
||||
.stdout;
|
||||
assert_eq!(public.len(), 44);
|
||||
let _ = public.drain(..12);
|
||||
let mut seed = cmd!(sh, "openssl pkey -outform DER")
|
||||
.stdin(&keydata)
|
||||
.stdin(&keydata)
|
||||
.output()?
|
||||
.stdout;
|
||||
assert_eq!(seed.len(), 48);
|
||||
let _ = seed.drain(..16);
|
||||
assert_eq!(seed.len(), 32);
|
||||
let private = seed.iter().chain(&public).copied().collect::<Vec<u8>>();
|
||||
Ok(Keypair { public, private })
|
||||
}
|
||||
|
||||
fn read_booted_metadata() -> Result<glib::VariantDict> {
|
||||
let metadata = std::fs::read("/run/ostree-booted")?;
|
||||
let metadata = glib::Variant::from_bytes::<glib::VariantDict>(&glib::Bytes::from(&metadata));
|
||||
Ok(glib::VariantDict::new(Some(&metadata)))
|
||||
}
|
||||
|
||||
fn verify_composefs_sanity(sh: &xshell::Shell, metadata: &glib::VariantDict) -> Result<()> {
|
||||
let fstype = cmd!(sh, "findmnt -n -o FSTYPE /").read()?;
|
||||
assert_eq!(fstype.as_str(), "overlay");
|
||||
|
||||
assert_eq!(metadata.lookup::<bool>("composefs").unwrap(), Some(true));
|
||||
|
||||
let private_dir = Path::new("/run/ostree/.private");
|
||||
assert_eq!(
|
||||
std::fs::symlink_metadata(private_dir)?.mode() & !libc::S_IFMT,
|
||||
0
|
||||
);
|
||||
assert!(std::fs::read_dir(private_dir.join("cfsroot-lower"))?
|
||||
.next()
|
||||
.is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_composefs_signed(sh: &xshell::Shell) -> Result<()> {
|
||||
let sysroot = ostree_ext::ostree::Sysroot::new_default();
|
||||
sysroot.load(gio::Cancellable::NONE)?;
|
||||
|
||||
// Generate a keypair, writing the public half to /etc and the private stays in memory
|
||||
let keypair = generate_raw_ed25519_keypair(sh)?;
|
||||
let mut pubkey = base64::encode(keypair.public);
|
||||
pubkey.push_str("\n");
|
||||
std::fs::write(BINDING_KEYPATH, pubkey)?;
|
||||
let mut tmp_privkey = tempfile::NamedTempFile::new()?;
|
||||
let priv_base64 = base64::encode(keypair.private);
|
||||
tmp_privkey
|
||||
.as_file_mut()
|
||||
.write_all(priv_base64.as_bytes())?;
|
||||
|
||||
// Note rpm-ostree initramfs-etc changes the final commit hash
|
||||
std::fs::create_dir_all("/etc/ostree")?;
|
||||
std::fs::write(
|
||||
PREPARE_ROOT_PATH,
|
||||
r##"[composefs]
|
||||
enabled=signed
|
||||
"##,
|
||||
)?;
|
||||
cmd!(
|
||||
sh,
|
||||
"rpm-ostree initramfs-etc --track {BINDING_KEYPATH} --track {PREPARE_ROOT_PATH}"
|
||||
)
|
||||
.run()?;
|
||||
|
||||
sysroot.load_if_changed(gio::Cancellable::NONE)?;
|
||||
let pending_deployment = sysroot.staged_deployment().expect("staged deployment");
|
||||
let target_commit = &pending_deployment.csum();
|
||||
|
||||
// Sign
|
||||
let tmp_privkey_path = tmp_privkey.path();
|
||||
cmd!(
|
||||
sh,
|
||||
"ostree sign -s ed25519 --keys-file {tmp_privkey_path} {target_commit}"
|
||||
)
|
||||
.run()?;
|
||||
println!("Signed commit");
|
||||
// And verify
|
||||
cmd!(
|
||||
sh,
|
||||
"ostree sign --verify --keys-file {BINDING_KEYPATH} {target_commit}"
|
||||
)
|
||||
.run()?;
|
||||
|
||||
// We explicitly throw away the private key now
|
||||
tmp_privkey.close()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_composefs_signed(sh: &xshell::Shell, metadata: &glib::VariantDict) -> Result<()> {
|
||||
verify_composefs_sanity(sh, metadata)?;
|
||||
// Verify signature
|
||||
assert!(metadata
|
||||
.lookup::<String>("composefs.signed")
|
||||
.unwrap()
|
||||
.is_some());
|
||||
cmd!(
|
||||
sh,
|
||||
"journalctl -u ostree-prepare-root --grep='Validated commit signature'"
|
||||
)
|
||||
.run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn itest_composefs() -> Result<()> {
|
||||
let sh = xshell::Shell::new()?;
|
||||
let sh = &xshell::Shell::new()?;
|
||||
if !cmd!(sh, "ostree --version").read()?.contains("- composefs") {
|
||||
println!("SKIP no composefs support");
|
||||
return Ok(());
|
||||
@ -24,27 +150,24 @@ pub(crate) fn itest_composefs() -> Result<()> {
|
||||
}
|
||||
Some(v) => v,
|
||||
};
|
||||
if mark != "1" {
|
||||
anyhow::bail!("Invalid reboot mark: {mark}")
|
||||
let metadata = read_booted_metadata()?;
|
||||
match mark.as_str() {
|
||||
"1" => {
|
||||
verify_composefs_sanity(sh, &metadata)?;
|
||||
prepare_composefs_signed(sh)?;
|
||||
Err(reboot("2"))?;
|
||||
Ok(())
|
||||
}
|
||||
"2" => verify_composefs_signed(sh, &metadata),
|
||||
o => anyhow::bail!("Unrecognized reboot mark {o}"),
|
||||
}
|
||||
}
|
||||
|
||||
let fstype = cmd!(sh, "findmnt -n -o FSTYPE /").read()?;
|
||||
assert_eq!(fstype.as_str(), "overlay");
|
||||
|
||||
let metadata = std::fs::read("/run/ostree-booted")?;
|
||||
let metadata = glib::Variant::from_bytes::<glib::VariantDict>(&glib::Bytes::from(&metadata));
|
||||
let metadata = glib::VariantDict::new(Some(&metadata));
|
||||
|
||||
assert_eq!(metadata.lookup::<bool>("composefs").unwrap(), Some(true));
|
||||
|
||||
let private_dir = Path::new("/run/ostree/.private");
|
||||
assert_eq!(
|
||||
std::fs::symlink_metadata(private_dir)?.mode() & !libc::S_IFMT,
|
||||
0
|
||||
);
|
||||
assert!(std::fs::read_dir(private_dir.join("cfsroot-lower"))?
|
||||
.next()
|
||||
.is_none());
|
||||
|
||||
#[test]
|
||||
fn gen_keypair() -> Result<()> {
|
||||
let sh = &xshell::Shell::new()?;
|
||||
let keypair = generate_raw_ed25519_keypair(sh).unwrap();
|
||||
assert_eq!(keypair.public.len(), 32);
|
||||
assert_eq!(keypair.private.len(), 64);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -169,12 +169,19 @@ pub(crate) fn get_reboot_mark() -> Result<Option<String>> {
|
||||
|
||||
/// Initiate a clean reboot; on next boot get_reboot_mark() will return `mark`.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn reboot<M: AsRef<str>>(mark: M) -> std::io::Error {
|
||||
pub(crate) fn reboot<M: AsRef<str>>(mark: M) -> anyhow::Error {
|
||||
let mark = mark.as_ref();
|
||||
use std::os::unix::process::CommandExt;
|
||||
if let Err(e) = std::io::stderr().flush() {
|
||||
return e.into();
|
||||
}
|
||||
if let Err(e) = std::io::stdout().flush() {
|
||||
return e.into();
|
||||
}
|
||||
std::process::Command::new("/tmp/autopkgtest-reboot")
|
||||
.arg(mark)
|
||||
.exec()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Prepare a reboot - you should then initiate a reboot however you like.
|
||||
|
Loading…
x
Reference in New Issue
Block a user