From dd172d9cf8d421d6955decb0c02b6f77f52aaa61 Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 9 Oct 2026 16:22:34 +0200 Subject: [PATCH] hive-c0re, hivectl: make the htpasswd and wg key tests fail on the in-place write The htpasswd test now seeds the temp path at 0600, so only the explicit chmod on the temp yields 0644. The wg test blocks the temp path with a directory and asserts the write fails with the existing key untouched, which an in-place write cannot satisfy. --- hive-c0re/src/gateway_nginx.rs | 13 ++++++++----- hivectl/src/wg.rs | 17 +++++++++++++---- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/hive-c0re/src/gateway_nginx.rs b/hive-c0re/src/gateway_nginx.rs index 1fe2bd8b..4a4b4ca0 100644 --- a/hive-c0re/src/gateway_nginx.rs +++ b/hive-c0re/src/gateway_nginx.rs @@ -406,14 +406,17 @@ mod tests { use super::*; use crate::lifecycle::MANAGER_NAME; - /// The htpasswd file is published `0644` whatever the umask, holds the - /// lines newline-terminated, and leaves no temp file beside it. + /// The htpasswd file is published `0644` even when the temp file it is + /// written through already exists at `0600` (opening an existing file + /// keeps its mode), holds the lines newline-terminated, and leaves no + /// temp file beside it. #[test] - fn htpasswd_is_written_0644_with_no_temp_left() { + fn htpasswd_is_published_0644_over_a_private_leftover_tmp() { let dir = tempfile::tempdir().expect("tempdir"); let path = dir.path().join("htpasswd"); - std::fs::write(&path, "stale\n").expect("seed"); - std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)).expect("chmod"); + let tmp = path.with_extension("htpasswd.tmp"); + std::fs::write(&tmp, "leftover\n").expect("seed tmp"); + std::fs::set_permissions(&tmp, std::fs::Permissions::from_mode(0o600)).expect("chmod"); htpasswd_write_at(&path, &["a:x".to_owned(), "b:y".to_owned()]).expect("write"); diff --git a/hivectl/src/wg.rs b/hivectl/src/wg.rs index 61c436c6..b291a622 100644 --- a/hivectl/src/wg.rs +++ b/hivectl/src/wg.rs @@ -300,16 +300,25 @@ mod tests { } } - /// The key lands at `0400` with its bytes intact, replacing whatever was - /// there, and no temp file is left beside it. + /// The key reaches its path only by a rename from a sibling temp: with + /// the temp path blocked, the write fails and the existing key is left + /// byte-for-byte as it was. Once unblocked, the key lands at `0400` with + /// its bytes intact and no temp file beside it. #[test] - fn key_is_written_0400_with_no_temp_left() { + fn key_is_written_through_a_temp_and_lands_0400() { use std::os::unix::fs::PermissionsExt as _; let dir = std::env::temp_dir().join(format!("hivectl-wg-key-{}", std::process::id())); std::fs::create_dir_all(&dir).expect("mkdir"); let path = dir.join("hive.key"); - std::fs::write(&path, "stale").expect("seed"); + let tmp = dir.join("hive.key.tmp"); + std::fs::write(&path, "old-key\n").expect("seed"); + std::fs::create_dir(&tmp).expect("block tmp"); + + let blocked = write_key_0400(&path, b"key-bytes\n"); + assert!(blocked.is_err(), "a blocked temp must fail the write"); + assert_eq!(std::fs::read(&path).expect("read seed"), b"old-key\n"); + std::fs::remove_dir(&tmp).expect("unblock tmp"); write_key_0400(&path, b"key-bytes\n").expect("write"); let mode = std::fs::metadata(&path).expect("stat").permissions().mode() & 0o777;