Found in review of #370.
lnvps_api_common/src/host/marketplace_pki.rs:91-104:
fn copy_private_if_changed(from: &Path, to: &Path) -> Result<bool> {
let contents = fs::read(from)?;
if !write_if_changed(to, &contents)? { return Ok(false); } // early return skips the chmod
fs::set_permissions(to, Permissions::from_mode(0o600))?;
Two problems with the key that drives every marketplace node's libvirtd:
- The mode is only ever applied when the contents change. A
clientkey.pem that already exists with identical bytes but a looser mode — written by an earlier build without the chmod, restored from a backup or volume, or left behind by a crash between the write and the set_permissions — is never repaired. materialise runs on every poll and short-circuits every time.
- The write is world-readable first.
write_if_changed uses fs::write, which creates with 0666 & ~umask (typically 0644), so there is a window where any process on the API host can read the key.
Fix both with OpenOptions::new().mode(0o600).create(true) and applying set_permissions unconditionally rather than only on change.
lnvps_node/src/libvirt.rs:612-620 (write_private_if_changed) has the identical shape for the node's own server key and needs the same treatment.
marketplace_pki/tests.rs:66-80 only covers the fresh-write case, which is why neither gap was caught — a test that pre-creates the file 0644 with matching contents and asserts the mode afterwards would fail today.
Found in review of #370.
lnvps_api_common/src/host/marketplace_pki.rs:91-104:Two problems with the key that drives every marketplace node's libvirtd:
clientkey.pemthat already exists with identical bytes but a looser mode — written by an earlier build without the chmod, restored from a backup or volume, or left behind by a crash between the write and theset_permissions— is never repaired.materialiseruns on every poll and short-circuits every time.write_if_changedusesfs::write, which creates with0666 & ~umask(typically0644), so there is a window where any process on the API host can read the key.Fix both with
OpenOptions::new().mode(0o600).create(true)and applyingset_permissionsunconditionally rather than only on change.lnvps_node/src/libvirt.rs:612-620(write_private_if_changed) has the identical shape for the node's own server key and needs the same treatment.marketplace_pki/tests.rs:66-80only covers the fresh-write case, which is why neither gap was caught — a test that pre-creates the file0644with matching contents and asserts the mode afterwards would fail today.