Files
core/test/dever-cli-tests/native_release/bootstrap.rs
T

455 lines
17 KiB
Rust

//! Bootstrap package validation and recovery use only owned fixture directories.
use super::*;
use dever_cli::toolchain::bootstrap;
use sha2::{Digest, Sha256};
#[test]
fn bootstrap_service_recovery_orders_actions_around_file_restoration() {
use bootstrap::RecoveryAction::{Reload, Restart, Stop};
for active in [false, true] {
for loaded in [false, true] {
assert_eq!(
bootstrap::recovery_actions(false, active, loaded),
(vec![], vec![])
);
}
}
assert_eq!(
bootstrap::recovery_actions(true, false, false),
(vec![], vec![Reload])
);
assert_eq!(
bootstrap::recovery_actions(true, false, true),
(vec![Stop], vec![Reload])
);
assert_eq!(
bootstrap::recovery_actions(true, true, true),
(vec![Stop], vec![Reload, Restart])
);
assert_eq!(
bootstrap::recovery_actions(true, true, false),
(vec![], vec![Reload, Restart])
);
}
fn add_bootstrap(author: &mut Author) {
let input = |name: &str, bytes: &[u8]| {
let relative = format!("inputs/{name}");
fs::write(author.root.join(&relative), bytes).unwrap();
json!({"source":relative,"sha256":sha256_file(&author.root.join(&relative)).unwrap()})
};
let launcher = input(
"dever",
&native_elf::compiler(&["libgcc_s.so.1", "libc.so.6"]),
);
let daemon = input(
"deverd",
&native_elf::compiler(&["libgcc_s.so.1", "libc.so.6"]),
);
let mut gcc = input("gcc", &native_elf::library("libgcc_s.so.1", &["libc.so.6"]));
gcc["path"] = json!("libgcc_s.so.1");
author.settings["bootstrap"] = json!({"launcher":launcher,"daemon":daemon,"libraries":[gcc]});
}
#[test]
fn bootstrap_payload_has_a_separate_signed_complete_loader_closure() {
let mut author = Author::new();
add_bootstrap(&mut author);
author.save();
let release = author.output("complete");
let manifest = packaging::create(&author.root, &release).unwrap();
assert_signed(&author, &release);
for path in [
"bootstrap/dever",
"bootstrap/deverd",
"bootstrap/lib/libgcc_s.so.1",
"bootstrap/deverd.service",
] {
let artifact = manifest
.artifacts
.iter()
.find(|artifact| artifact.path == path)
.unwrap();
assert_eq!(artifact.sha256, sha256_file(&release.join(path)).unwrap());
}
let unit = fs::read_to_string(release.join("bootstrap/deverd.service")).unwrap();
assert!(unit.contains("ExecStart=/opt/dever/bin/deverd --root /opt/dever"));
assert!(unit.contains("RuntimeDirectory=dever"));
assert!(!unit.contains("Environment"));
assert!(!unit.contains("PATH"));
assert!(!release.join("bootstrap/dever-process").exists());
assert!(
!manifest
.artifacts
.iter()
.any(|artifact| artifact.path.contains("bwrap") || artifact.path.contains("apparmor"))
);
}
#[test]
fn bootstrap_authoring_rejects_missing_libraries_wrong_sonames_and_search_paths() {
let mut author = Author::new();
add_bootstrap(&mut author);
author.settings["bootstrap"]["libraries"] = json!([]);
assert!(author.reject().contains("libgcc_s.so.1"));
let mut author = Author::new();
add_bootstrap(&mut author);
fs::write(
author.root.join("inputs/gcc"),
native_elf::library("libwrong.so", &["libc.so.6"]),
)
.unwrap();
author.settings["bootstrap"]["libraries"][0]["sha256"] =
json!(sha256_file(&author.root.join("inputs/gcc")).unwrap());
assert!(author.reject().contains("SONAME"));
let mut author = Author::new();
add_bootstrap(&mut author);
fs::write(
author.root.join("inputs/dever"),
native_elf::elf(None, &["libgcc_s.so.1"], Some("/usr/lib")),
)
.unwrap();
author.settings["bootstrap"]["launcher"]["sha256"] =
json!(sha256_file(&author.root.join("inputs/dever")).unwrap());
assert!(author.reject().contains("RPATH"));
}
#[test]
fn signed_installed_versions_keep_their_original_bootstrap_service_template() {
use dever_cli::toolchain::{Layout, MachineManager, Version};
let mut author = Author::new();
add_bootstrap(&mut author);
author.save();
let machine = TemporaryDirectory::new();
let layout = Layout::new(machine.path().join("machine"));
layout.initialize().unwrap();
let version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
let installed = layout.versions().join(version.as_str());
let mut manifest = packaging::create(&author.root, &installed).unwrap();
let service = installed.join("bootstrap/deverd.service");
let current = fs::read_to_string(&service).unwrap();
let old = current.replace("RestartSec=1", "RestartSec=2");
assert_ne!(current, old);
fs::write(&service, old).unwrap();
let artifact = manifest
.artifacts
.iter_mut()
.find(|artifact| artifact.path == "bootstrap/deverd.service")
.unwrap();
artifact.bytes = fs::metadata(&service).unwrap().len();
artifact.sha256 = sha256_file(&service).unwrap();
let encoded = serde_json::to_vec(&manifest).unwrap();
let key =
Ed25519KeyPair::from_pkcs8(&fs::read(author.root.join("signing.pk8")).unwrap()).unwrap();
fs::write(installed.join("manifest.json"), &encoded).unwrap();
fs::write(
installed.join("manifest.sig"),
key.sign(&encoded)
.as_ref()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<String>(),
)
.unwrap();
fs::write(
layout.state().join("trusted-release-key"),
author
.public_key
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<String>(),
)
.unwrap();
let manager = MachineManager::new(layout);
assert_eq!(
manager.resolve_core(&version).unwrap(),
installed.join("dever-core")
);
fs::write(service, b"unsigned modification").unwrap();
assert!(
manager
.resolve_core(&version)
.unwrap_err()
.contains("integrity verification")
);
}
#[test]
fn historical_bootstrap_profiles_are_rejected_regardless_of_version_number() {
use dever_cli::toolchain::{Artifact, Layout, MachineManager, Version};
for release_version in ["0.0.1", "0.1.4", "0.1.6"] {
let mut author = Author::new();
author.settings["version"] = json!(release_version);
add_bootstrap(&mut author);
author.save();
let machine = TemporaryDirectory::new();
let layout = Layout::new(machine.path().join("machine"));
layout.initialize().unwrap();
let version = Version::parse(release_version).unwrap();
let installed = layout.versions().join(release_version);
let mut manifest = packaging::create(&author.root, &installed).unwrap();
let profile = installed.join("bootstrap/dever-sandbox");
fs::write(&profile, b"historical profile fixture\n").unwrap();
manifest.artifacts.push(Artifact {
path: "bootstrap/dever-sandbox".into(),
bytes: fs::metadata(&profile).unwrap().len(),
sha256: sha256_file(&profile).unwrap(),
});
let encoded = serde_json::to_vec(&manifest).unwrap();
let key = Ed25519KeyPair::from_pkcs8(&fs::read(author.root.join("signing.pk8")).unwrap())
.unwrap();
fs::write(installed.join("manifest.json"), &encoded).unwrap();
fs::write(
installed.join("manifest.sig"),
key.sign(&encoded)
.as_ref()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<String>(),
)
.unwrap();
fs::write(
layout.state().join("trusted-release-key"),
author
.public_key
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<String>(),
)
.unwrap();
let manager = MachineManager::new(layout);
assert!(
manager
.resolve_core(&version)
.unwrap_err()
.contains("unsupported artifact")
);
}
}
#[test]
#[ignore = "root-only installer trust rejection; never touches the actual machine installation"]
fn bootstrap_installer_rejects_untrusted_inputs_before_publication() {
assert_eq!(rustix::process::geteuid().as_raw(), 0);
assert!(
bootstrap::install(Path::new("."))
.unwrap_err()
.contains("absolute paths")
);
assert!(
bootstrap::recover(Path::new("."))
.unwrap_err()
.contains("absolute paths")
);
let mut author = Author::new();
add_bootstrap(&mut author);
author.save();
let package = author.output("release");
packaging::create(&author.root, &package).unwrap();
let installer = TemporaryDirectory::new();
let system = TemporaryDirectory::new();
let key = installer.path().join("trusted.pub");
fs::write(
&key,
author
.public_key
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<String>(),
)
.unwrap();
fs::create_dir(installer.path().join("config")).unwrap();
let save = |key: &Path, activate: bool| {
fs::write(installer.path().join("config/setting.json"), serde_json::to_vec(&json!({"release":package,"system_root":system.path(),"trusted_key":key,"activate_service":activate})).unwrap()).unwrap()
};
save(&key, true);
assert!(
bootstrap::install(installer.path())
.unwrap_err()
.contains("actual system root")
);
let inside = package.join("trusted.pub");
fs::copy(&key, &inside).unwrap();
save(&inside, false);
assert!(
bootstrap::install(installer.path())
.unwrap_err()
.contains("independently")
);
save(&key, false);
fs::set_permissions(&key, fs::Permissions::from_mode(0o666)).unwrap();
assert!(
bootstrap::install(installer.path())
.unwrap_err()
.contains("root-owned")
);
fs::set_permissions(&key, fs::Permissions::from_mode(0o644)).unwrap();
let signature = fs::read(package.join("manifest.sig")).unwrap();
fs::write(package.join("manifest.sig"), "00".repeat(64)).unwrap();
assert!(
bootstrap::install(installer.path())
.unwrap_err()
.contains("signature verification")
);
fs::write(package.join("manifest.sig"), signature).unwrap();
// The key is pinned before the intentionally non-executable synthetic core
// fails its health check. Empty/partial temporary files model interrupted
// writes without injecting product failpoints or touching real services.
let state = system.path().join("opt/dever/state");
let temporary_key = state.join(".trusted-release-key.next");
let pinned_key = state.join("trusted-release-key");
for partial in ["", "abc"] {
fs::write(&temporary_key, partial).unwrap();
let error = bootstrap::install(installer.path()).unwrap_err();
assert!(!error.contains("temporary release key"), "{error}");
assert_eq!(
fs::read_to_string(&pinned_key).unwrap().trim(),
fs::read_to_string(&key).unwrap()
);
assert!(!temporary_key.exists());
fs::remove_file(&pinned_key).unwrap();
}
fs::write(&pinned_key, "11".repeat(32)).unwrap();
assert!(
bootstrap::install(installer.path())
.unwrap_err()
.contains("pinned release key")
);
assert_eq!(fs::read_to_string(&pinned_key).unwrap(), "11".repeat(32));
fs::remove_file(&pinned_key).unwrap();
fs::write(package.join("bootstrap/lib/libgcc_s.so.1"), b"tampered").unwrap();
assert!(
bootstrap::install(installer.path())
.unwrap_err()
.contains("integrity verification")
);
fs::remove_file(package.join("bootstrap/lib/libgcc_s.so.1")).unwrap();
assert!(bootstrap::install(installer.path()).is_err());
assert!(!system.path().join("usr/local/bin/dever").exists());
assert!(
!system
.path()
.join("opt/dever/state/active-version")
.exists()
);
}
// Independent construction of a one-node journal identity lets recovery tests
// simulate interruption at publication boundaries without product failpoints.
fn node_hash(kind: &str, bytes: &[u8]) -> String {
let mut hash = Sha256::new();
hash.update(0u64.to_le_bytes());
hash.update(
if kind == "directory" {
0o755u32
} else {
0o644u32
}
.to_le_bytes(),
);
hash.update(kind.as_bytes());
if kind == "file" {
hash.update((bytes.len() as u64).to_le_bytes());
hash.update(bytes);
}
format!("{:x}", hash.finalize())
}
#[test]
#[ignore = "root-only crash-recovery fixture; all journal targets are below an owned image root"]
fn bootstrap_recovery_is_idempotent_and_rejects_unrecorded_targets() {
assert_eq!(rustix::process::geteuid().as_raw(), 0);
let system = TemporaryDirectory::new();
let root = system.path();
let layout = dever_cli::toolchain::Layout::new(root.join("opt/dever"));
layout.initialize().unwrap();
for directory in [
"usr/local/bin",
"etc/systemd/system/multi-user.target.wants",
] {
fs::create_dir_all(root.join(directory)).unwrap();
}
let service = root.join("etc/systemd/system/deverd.service");
fs::write(&service, b"new service").unwrap();
let entry = root.join("usr/local/bin/dever");
symlink("/opt/dever/bin/dever", &entry).unwrap();
let enable_stage = root
.join("etc/systemd/system/multi-user.target.wants/.dever-bootstrap-deverd.service.next");
symlink("../deverd.service", &enable_stage).unwrap();
let active_stage = layout.state().join(".dever-bootstrap-active-version.next");
fs::write(&active_stage, b"new active").unwrap();
let sentinel = root.join("etc/systemd/system/unrelated.service");
fs::write(&sentinel, b"keep").unwrap();
let link_hash =
|target: &str| format!("{:x}", Sha256::digest(format!("link\0{target}").as_bytes()));
let journal = json!({"format":"dever-bootstrap-transaction-v1","phase":"prepared","service_was_active":null,"service_start_requested":false,"changes":[
{"target":"bin","before":node_hash("directory",b""),"after":node_hash("directory",b"")},
{"target":"service","before":null,"after":node_hash("file",b"new service")},
{"target":"entry","before":null,"after":link_hash("/opt/dever/bin/dever")},
{"target":"enable","before":null,"after":link_hash("../deverd.service")},
{"target":"active","before":null,"after":node_hash("file",b"new active")}
]});
let path = layout.state().join("bootstrap-install.json");
fs::write(&path, serde_json::to_vec(&journal).unwrap()).unwrap();
let mut invalid = journal.clone();
invalid["changes"][0]["target"] = json!("../../unrelated");
fs::write(&path, serde_json::to_vec(&invalid).unwrap()).unwrap();
assert!(bootstrap::recover(root).is_err());
assert_eq!(fs::read(&sentinel).unwrap(), b"keep");
fs::write(&path, serde_json::to_vec(&journal).unwrap()).unwrap();
fs::write(&service, b"not this transaction").unwrap();
assert!(
bootstrap::recover(root)
.unwrap_err()
.contains("outside this transaction")
);
assert_eq!(fs::read(&service).unwrap(), b"not this transaction");
fs::write(&service, b"new service").unwrap();
bootstrap::recover(root).unwrap();
bootstrap::recover(root).unwrap();
assert!(!service.exists());
assert!(fs::symlink_metadata(&entry).is_err());
assert!(!active_stage.exists());
assert!(fs::symlink_metadata(&enable_stage).is_err());
assert_eq!(fs::read(&sentinel).unwrap(), b"keep");
assert!(!path.exists());
// A committed publication cleans only its old staging copies; it must not
// undo the new active version, including after a failed cleanup retry.
fs::write(&service, b"new service").unwrap();
symlink("/opt/dever/bin/dever", &entry).unwrap();
symlink(
"../deverd.service",
root.join("etc/systemd/system/multi-user.target.wants/deverd.service"),
)
.unwrap();
fs::write(layout.state().join("active-version"), b"new active").unwrap();
fs::write(&active_stage, b"old active").unwrap();
let mut committed = journal;
committed["phase"] = json!("committed");
fs::write(&path, serde_json::to_vec(&committed).unwrap()).unwrap();
fs::write(&service, b"unexpected modification").unwrap();
assert!(
bootstrap::recover(root)
.unwrap_err()
.contains("committed bootstrap destination changed")
);
assert!(path.exists());
assert_eq!(
fs::read(layout.state().join("active-version")).unwrap(),
b"new active"
);
fs::write(&service, b"new service").unwrap();
bootstrap::recover(root).unwrap();
bootstrap::recover(root).unwrap();
assert_eq!(
fs::read(layout.state().join("active-version")).unwrap(),
b"new active"
);
assert!(!active_stage.exists());
assert!(!path.exists());
assert_eq!(fs::read(sentinel).unwrap(), b"keep");
}