mirror of
https://github.com/ostreedev/ostree.git
synced 2025-02-25 21:57:42 +03:00
tests: Add an integration test for composefs signatures
Ensure we have some automated test coverage for this.
This commit is contained in:
parent
cd606aa6fe
commit
372cbd7a64
@ -1,12 +1,138 @@
|
|||||||
|
use std::io::Write;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use ostree_ext::glib;
|
use ostree_ext::{gio, glib};
|
||||||
use xshell::cmd;
|
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<()> {
|
pub(crate) fn itest_composefs() -> Result<()> {
|
||||||
let sh = xshell::Shell::new()?;
|
let sh = &xshell::Shell::new()?;
|
||||||
if !cmd!(sh, "ostree --version").read()?.contains("- composefs") {
|
if !cmd!(sh, "ostree --version").read()?.contains("- composefs") {
|
||||||
println!("SKIP no composefs support");
|
println!("SKIP no composefs support");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -24,27 +150,24 @@ pub(crate) fn itest_composefs() -> Result<()> {
|
|||||||
}
|
}
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
};
|
};
|
||||||
if mark != "1" {
|
let metadata = read_booted_metadata()?;
|
||||||
anyhow::bail!("Invalid reboot mark: {mark}")
|
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()?;
|
#[test]
|
||||||
assert_eq!(fstype.as_str(), "overlay");
|
fn gen_keypair() -> Result<()> {
|
||||||
|
let sh = &xshell::Shell::new()?;
|
||||||
let metadata = std::fs::read("/run/ostree-booted")?;
|
let keypair = generate_raw_ed25519_keypair(sh).unwrap();
|
||||||
let metadata = glib::Variant::from_bytes::<glib::VariantDict>(&glib::Bytes::from(&metadata));
|
assert_eq!(keypair.public.len(), 32);
|
||||||
let metadata = glib::VariantDict::new(Some(&metadata));
|
assert_eq!(keypair.private.len(), 64);
|
||||||
|
|
||||||
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(())
|
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`.
|
/// Initiate a clean reboot; on next boot get_reboot_mark() will return `mark`.
|
||||||
#[allow(dead_code)]
|
#[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();
|
let mark = mark.as_ref();
|
||||||
use std::os::unix::process::CommandExt;
|
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")
|
std::process::Command::new("/tmp/autopkgtest-reboot")
|
||||||
.arg(mark)
|
.arg(mark)
|
||||||
.exec()
|
.exec()
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepare a reboot - you should then initiate a reboot however you like.
|
/// Prepare a reboot - you should then initiate a reboot however you like.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user