test(namespace): generate hostnames through a shared helper
The hostname tests reimplemented the generation algorithm inline, so they kept passing if the real code drifted; check the actual generate_hostname path instead, and drop the tautological check_user_namespace test. Extract utils::random_suffix and replace the misleading HOSTNAME_SUFFIX_BITS constant (it held the 2^24 entropy space, not a bit count, and the suffix length was derived through a log2()/4 round-trip) with HOSTNAME_SUFFIX_LEN. The QEMU VM hostname generator duplicated the algorithm with different mixing; both callers now share the helper.
This commit is contained in:
+26
-100
@@ -381,35 +381,21 @@ fn get_subgid_range(gid: nix::unistd::Gid) -> Result<(u32, u32)> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a unique hostname for a chroot run:
|
||||||
|
/// `ecr-<distro>-<random suffix>` (see [`crate::utils::random_suffix`]).
|
||||||
|
fn generate_hostname(distro: &str) -> String {
|
||||||
|
format!(
|
||||||
|
"ecr-{}-{}",
|
||||||
|
distro,
|
||||||
|
crate::utils::random_suffix(crate::utils::HOSTNAME_SUFFIX_LEN)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Set hostname in UTS namespace
|
/// Set hostname in UTS namespace
|
||||||
pub fn set_hostname(distro: &str) -> Result<()> {
|
pub fn set_hostname(distro: &str) -> Result<()> {
|
||||||
use nix::unistd::sethostname;
|
use nix::unistd::sethostname;
|
||||||
use std::collections::hash_map::DefaultHasher;
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
|
|
||||||
// Seed from both the current time and the PID so each invocation gets a
|
let hostname = generate_hostname(distro);
|
||||||
// distinct suffix even when called in rapid succession. Time alone is
|
|
||||||
// not sufficient: truncating nanoseconds to u8 in a tight loop produces
|
|
||||||
// the same byte every iteration.
|
|
||||||
let mut hasher = DefaultHasher::new();
|
|
||||||
std::time::SystemTime::now().hash(&mut hasher);
|
|
||||||
std::process::id().hash(&mut hasher);
|
|
||||||
let mut state = hasher.finish();
|
|
||||||
|
|
||||||
let chars = b"abcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
// Use HOSTNAME_SUFFIX_BITS for entropy (6 hex chars = 24 bits)
|
|
||||||
let suffix_len = (crate::utils::HOSTNAME_SUFFIX_BITS as f64).log2() as usize / 4;
|
|
||||||
let random_suffix: String = (0..suffix_len)
|
|
||||||
.map(|_| {
|
|
||||||
// Knuth multiplicative LCG — each step advances the full 64-bit state.
|
|
||||||
state = state
|
|
||||||
.wrapping_mul(6364136223846793005)
|
|
||||||
.wrapping_add(1442695040888963407);
|
|
||||||
chars[(state >> 33) as usize % chars.len()] as char
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let hostname = format!("ecr-{}-{}", distro, random_suffix);
|
|
||||||
|
|
||||||
sethostname(&hostname).with_context(|| format!("Failed to set hostname to {}", hostname))?;
|
sethostname(&hostname).with_context(|| format!("Failed to set hostname to {}", hostname))?;
|
||||||
|
|
||||||
@@ -419,90 +405,30 @@ pub fn set_hostname(distro: &str) -> Result<()> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_check_user_namespace_returns_ok() {
|
fn hostname_format() {
|
||||||
// This test verifies the function runs without panicking
|
for _ in 0..10 {
|
||||||
// On most modern Linux systems with user namespaces enabled, this should pass
|
let hostname = generate_hostname("test");
|
||||||
let result = check_user_namespace();
|
|
||||||
// We can't assert success because it depends on system configuration
|
|
||||||
// But we can verify it doesn't panic and returns a Result
|
|
||||||
assert!(result.is_ok() || result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_hostname_format() {
|
|
||||||
// Test that hostname generation produces valid format
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
let mut hostnames = HashSet::new();
|
|
||||||
for _ in 0..100 {
|
|
||||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
|
||||||
std::time::SystemTime::now().hash(&mut hasher);
|
|
||||||
std::process::id().hash(&mut hasher);
|
|
||||||
let mut state = hasher.finish();
|
|
||||||
|
|
||||||
let chars = b"abcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
let suffix_len = (crate::utils::HOSTNAME_SUFFIX_BITS as f64).log2() as usize / 4;
|
|
||||||
let random_suffix: String = (0..suffix_len)
|
|
||||||
.map(|_| {
|
|
||||||
state = state
|
|
||||||
.wrapping_mul(6364136223846793005)
|
|
||||||
.wrapping_add(1442695040888963407);
|
|
||||||
chars[(state >> 33) as usize % chars.len()] as char
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let hostname = format!("ecr-test-{}", random_suffix);
|
|
||||||
|
|
||||||
// Verify hostname format
|
|
||||||
assert!(hostname.starts_with("ecr-test-"));
|
assert!(hostname.starts_with("ecr-test-"));
|
||||||
assert!(hostname.len() > 9); // "ecr-test-" + at least 1 char
|
assert_eq!(
|
||||||
|
hostname.len(),
|
||||||
|
"ecr-test-".len() + crate::utils::HOSTNAME_SUFFIX_LEN
|
||||||
|
);
|
||||||
assert!(hostname
|
assert!(hostname
|
||||||
.chars()
|
.chars()
|
||||||
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'));
|
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'));
|
||||||
|
|
||||||
hostnames.insert(hostname);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// With 100 iterations and good entropy, we should get many unique hostnames
|
|
||||||
assert!(
|
|
||||||
hostnames.len() > 50,
|
|
||||||
"Expected many unique hostnames, got {}",
|
|
||||||
hostnames.len()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_set_hostname_uniqueness() {
|
fn hostname_generation_is_unique_across_calls() {
|
||||||
// Verify that rapid consecutive calls produce different hostnames
|
let hostnames: HashSet<String> = (0..100).map(|_| generate_hostname("test")).collect();
|
||||||
use std::collections::HashSet;
|
assert!(
|
||||||
|
hostnames.len() > 50,
|
||||||
let mut hostnames = Vec::new();
|
"expected mostly unique hostnames, got {} distinct out of 100",
|
||||||
for _ in 0..10 {
|
hostnames.len()
|
||||||
// Simulate the hostname generation logic
|
);
|
||||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
|
||||||
std::time::SystemTime::now().hash(&mut hasher);
|
|
||||||
std::process::id().hash(&mut hasher);
|
|
||||||
let mut state = hasher.finish();
|
|
||||||
|
|
||||||
let chars = b"abcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
let suffix_len = (crate::utils::HOSTNAME_SUFFIX_BITS as f64).log2() as usize / 4;
|
|
||||||
let random_suffix: String = (0..suffix_len)
|
|
||||||
.map(|_| {
|
|
||||||
state = state
|
|
||||||
.wrapping_mul(6364136223846793005)
|
|
||||||
.wrapping_add(1442695040888963407);
|
|
||||||
chars[(state >> 33) as usize % chars.len()] as char
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
hostnames.push(format!("ecr-test-{}", random_suffix));
|
|
||||||
}
|
|
||||||
|
|
||||||
let unique: HashSet<_> = hostnames.iter().collect();
|
|
||||||
// Most hostnames should be unique (high entropy)
|
|
||||||
assert!(unique.len() >= 8, "Expected mostly unique hostnames");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,17 +76,10 @@ pub fn launch_qemu(config: QemuConfig, progress: &dyn Progress) -> Result<()> {
|
|||||||
let shell = crate::utils::detect_shell(&config.rootfs_path);
|
let shell = crate::utils::detect_shell(&config.rootfs_path);
|
||||||
|
|
||||||
// Generate a unique hostname like "ecr-vm-a1b2c3"
|
// Generate a unique hostname like "ecr-vm-a1b2c3"
|
||||||
// Use VM_HOSTNAME_SUFFIX_BITS constant for entropy
|
let hostname = format!(
|
||||||
let hostname_suffix = format!(
|
"ecr-vm-{}",
|
||||||
"{:x}",
|
crate::utils::random_suffix(crate::utils::HOSTNAME_SUFFIX_LEN)
|
||||||
(std::process::id() as u64).wrapping_mul(
|
|
||||||
std::time::SystemTime::now()
|
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.as_nanos() as u64
|
|
||||||
) % crate::utils::VM_HOSTNAME_SUFFIX_BITS
|
|
||||||
);
|
);
|
||||||
let hostname = format!("ecr-vm-{}", hostname_suffix);
|
|
||||||
|
|
||||||
// Build kernel command line
|
// Build kernel command line
|
||||||
// For initramfs boot, use rdinit= instead of init=
|
// For initramfs boot, use rdinit= instead of init=
|
||||||
|
|||||||
+26
-4
@@ -11,11 +11,33 @@ pub const CHILD_STACK_SIZE: usize = 1024 * 1024;
|
|||||||
/// Buffer size for reading error messages from pipes (4 KiB, typical page size)
|
/// Buffer size for reading error messages from pipes (4 KiB, typical page size)
|
||||||
pub const ERROR_BUFFER_SIZE: usize = 4096;
|
pub const ERROR_BUFFER_SIZE: usize = 4096;
|
||||||
|
|
||||||
/// Maximum entropy bits for hostname suffix (24 bits = 6 hex chars)
|
/// Length of the random hostname suffix (36⁶ ≈ 2×10⁹ distinct values)
|
||||||
pub const HOSTNAME_SUFFIX_BITS: u64 = 0x1000000;
|
pub const HOSTNAME_SUFFIX_LEN: usize = 6;
|
||||||
|
|
||||||
/// Maximum entropy bits for VM hostname suffix (24 bits = 6 hex chars)
|
/// Random lowercase-alphanumeric suffix for hostnames. Seeded from the
|
||||||
pub const VM_HOSTNAME_SUFFIX_BITS: u64 = 0x1000000;
|
/// current time and the PID so calls in rapid succession — including from
|
||||||
|
/// separate processes — produce different values; time alone truncated to
|
||||||
|
/// the suffix space repeats within a tight loop.
|
||||||
|
pub fn random_suffix(len: usize) -> String {
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
std::time::SystemTime::now().hash(&mut hasher);
|
||||||
|
std::process::id().hash(&mut hasher);
|
||||||
|
let mut state = hasher.finish();
|
||||||
|
|
||||||
|
const CHARS: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
// Knuth multiplicative LCG — each step advances the full 64-bit state.
|
||||||
|
(0..len)
|
||||||
|
.map(|_| {
|
||||||
|
state = state
|
||||||
|
.wrapping_mul(6364136223846793005)
|
||||||
|
.wrapping_add(1442695040888963407);
|
||||||
|
CHARS[(state >> 33) as usize % CHARS.len()] as char
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Architecture handling
|
// Architecture handling
|
||||||
|
|||||||
Reference in New Issue
Block a user