Implement the user and group options for etc files

This commit is contained in:
r-vdp 2025-02-04 16:00:54 +01:00
parent 744a170b65
commit daf3584e66
No known key found for this signature in database
5 changed files with 80 additions and 11 deletions

1
Cargo.lock generated
View file

@ -411,6 +411,7 @@ dependencies = [
"itertools",
"log",
"nix",
"regex",
"serde",
"serde_json",
"thiserror",

View file

@ -27,6 +27,7 @@ im = { version = "15.1.0", features = ["serde"] }
itertools = "0.14.0"
log = "0.4.17"
nix = { version = "0.29.0", features = ["hostname", "user"] }
regex = "1.11.1"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.91"
thiserror = "2.0.0"

View file

@ -55,7 +55,7 @@
source = "/run/systemd/system/";
};
test_perms = {
with_ownership = {
text = ''
This is just a test!
'';
@ -63,6 +63,14 @@
uid = 5;
gid = 6;
};
with_ownership2 = {
text = ''
This is just a test!
'';
mode = "0755";
user = "nobody";
group = "users";
};
};
};

View file

@ -2,12 +2,14 @@ mod etc_tree;
use im::HashMap;
use itertools::Itertools;
use regex;
use serde::{Deserialize, Serialize};
use std::fs::{DirBuilder, Permissions};
use std::os::unix::fs as unixfs;
use std::os::unix::prelude::PermissionsExt;
use std::path;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use std::{fs, io};
use self::etc_tree::FileStatus;
@ -22,6 +24,12 @@ pub use etc_tree::FileTree;
type EtcActivationResult = ActivationResult<FileTree>;
static UID_GID_REGEX: OnceLock<regex::Regex> = OnceLock::new();
fn get_uid_gid_regex() -> &'static regex::Regex {
UID_GID_REGEX.get_or_init(|| regex::Regex::new(r"^\+[0-9]+$").expect("could not compile regex"))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct EtcFile {
@ -352,9 +360,7 @@ fn create_etc_entry(
match copy_file(
&entry.source.store_path.join(&entry.target),
&target_path,
&entry.mode,
entry.uid,
entry.gid,
entry,
old_state,
) {
Ok(_) => Ok(new_state.register_managed_entry(&target_path)),
@ -414,8 +420,54 @@ fn create_dir_recursively(dir: &Path, state: FileTree) -> EtcActivationResult {
new_state
}
fn copy_file(source: &Path, target: &Path, mode: &str,
uid: u32, gid: u32, old_state: &FileTree) -> anyhow::Result<()> {
fn find_uid(entry: &EtcFile) -> anyhow::Result<u32> {
if !get_uid_gid_regex().is_match(&entry.user) {
nix::unistd::User::from_name(&entry.user)
.map(|maybe_user| {
maybe_user.map_or_else(
|| {
log::warn!(
"Specified user {} not found, defaulting to root",
&entry.user
);
0
},
|user| user.uid.as_raw(),
)
})
.map_err(|err| anyhow::anyhow!(err).context("Failed to determine user"))
} else {
Ok(entry.uid)
}
}
fn find_gid(entry: &EtcFile) -> anyhow::Result<u32> {
if !get_uid_gid_regex().is_match(&entry.group) {
nix::unistd::Group::from_name(&entry.group)
.map(|maybe_group| {
maybe_group.map_or_else(
|| {
log::warn!(
"Specified group {} not found, defaulting to root",
&entry.group
);
0
},
|group| group.gid.as_raw(),
)
})
.map_err(|err| anyhow::anyhow!(err).context("Failed to determine group"))
} else {
Ok(entry.gid)
}
}
fn copy_file(
source: &Path,
target: &Path,
entry: &EtcFile,
old_state: &FileTree,
) -> anyhow::Result<()> {
let exists = target.try_exists()?;
if !exists || old_state.is_managed(target) {
log::debug!(
@ -424,9 +476,9 @@ fn copy_file(source: &Path, target: &Path, mode: &str,
target.display()
);
fs::copy(source, target)?;
let mode_int = u32::from_str_radix(mode, 8)?;
let mode_int = u32::from_str_radix(&entry.mode, 8)?;
fs::set_permissions(target, Permissions::from_mode(mode_int))?;
unixfs::chown(target, Some(uid), Some(gid))?;
unixfs::chown(target, Some(find_uid(entry)?), Some(find_gid(entry)?))?;
Ok(())
} else {
anyhow::bail!("File {} already exists, ignoring.", target.display());

View file

@ -175,10 +175,17 @@ forEachUbuntuImage "example" {
vm.succeed("grep -F 'launch_the_rockets = true' /etc/foo.conf")
vm.fail("grep -F 'launch_the_rockets = false' /etc/foo.conf")
uid = vm.succeed("stat -c %u /etc/test_perms").strip()
gid = vm.succeed("stat -c %g /etc/test_perms").strip()
uid = vm.succeed("stat -c %u /etc/with_ownership").strip()
gid = vm.succeed("stat -c %g /etc/with_ownership").strip()
assert uid == "5", f"uid was {uid}, expected 5"
assert gid == "6", f"uid was {gid}, expected 6"
assert gid == "6", f"gid was {gid}, expected 6"
print(vm.succeed("cat /etc/passwd"))
user = vm.succeed("stat -c %U /etc/with_ownership2").strip()
group = vm.succeed("stat -c %G /etc/with_ownership2").strip()
assert user == "nobody", f"user was {user}, expected nobody"
assert group == "users", f"group was {group}, expected users"
vm.succeed("test -d /var/tmp/system-manager")
vm.succeed("test -d /var/tmp/sample")