Implement the user and group options for etc files
This commit is contained in:
parent
744a170b65
commit
daf3584e66
5 changed files with 80 additions and 11 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -411,6 +411,7 @@ dependencies = [
|
||||||
"itertools",
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
"nix",
|
"nix",
|
||||||
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ im = { version = "15.1.0", features = ["serde"] }
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
nix = { version = "0.29.0", features = ["hostname", "user"] }
|
nix = { version = "0.29.0", features = ["hostname", "user"] }
|
||||||
|
regex = "1.11.1"
|
||||||
serde = { version = "1.0.152", features = ["derive"] }
|
serde = { version = "1.0.152", features = ["derive"] }
|
||||||
serde_json = "1.0.91"
|
serde_json = "1.0.91"
|
||||||
thiserror = "2.0.0"
|
thiserror = "2.0.0"
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@
|
||||||
source = "/run/systemd/system/";
|
source = "/run/systemd/system/";
|
||||||
};
|
};
|
||||||
|
|
||||||
test_perms = {
|
with_ownership = {
|
||||||
text = ''
|
text = ''
|
||||||
This is just a test!
|
This is just a test!
|
||||||
'';
|
'';
|
||||||
|
|
@ -63,6 +63,14 @@
|
||||||
uid = 5;
|
uid = 5;
|
||||||
gid = 6;
|
gid = 6;
|
||||||
};
|
};
|
||||||
|
with_ownership2 = {
|
||||||
|
text = ''
|
||||||
|
This is just a test!
|
||||||
|
'';
|
||||||
|
mode = "0755";
|
||||||
|
user = "nobody";
|
||||||
|
group = "users";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,14 @@ mod etc_tree;
|
||||||
|
|
||||||
use im::HashMap;
|
use im::HashMap;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fs::{DirBuilder, Permissions};
|
use std::fs::{DirBuilder, Permissions};
|
||||||
use std::os::unix::fs as unixfs;
|
use std::os::unix::fs as unixfs;
|
||||||
use std::os::unix::prelude::PermissionsExt;
|
use std::os::unix::prelude::PermissionsExt;
|
||||||
use std::path;
|
use std::path;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::OnceLock;
|
||||||
use std::{fs, io};
|
use std::{fs, io};
|
||||||
|
|
||||||
use self::etc_tree::FileStatus;
|
use self::etc_tree::FileStatus;
|
||||||
|
|
@ -22,6 +24,12 @@ pub use etc_tree::FileTree;
|
||||||
|
|
||||||
type EtcActivationResult = ActivationResult<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)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct EtcFile {
|
struct EtcFile {
|
||||||
|
|
@ -352,9 +360,7 @@ fn create_etc_entry(
|
||||||
match copy_file(
|
match copy_file(
|
||||||
&entry.source.store_path.join(&entry.target),
|
&entry.source.store_path.join(&entry.target),
|
||||||
&target_path,
|
&target_path,
|
||||||
&entry.mode,
|
entry,
|
||||||
entry.uid,
|
|
||||||
entry.gid,
|
|
||||||
old_state,
|
old_state,
|
||||||
) {
|
) {
|
||||||
Ok(_) => Ok(new_state.register_managed_entry(&target_path)),
|
Ok(_) => Ok(new_state.register_managed_entry(&target_path)),
|
||||||
|
|
@ -414,8 +420,54 @@ fn create_dir_recursively(dir: &Path, state: FileTree) -> EtcActivationResult {
|
||||||
new_state
|
new_state
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_file(source: &Path, target: &Path, mode: &str,
|
fn find_uid(entry: &EtcFile) -> anyhow::Result<u32> {
|
||||||
uid: u32, gid: u32, old_state: &FileTree) -> anyhow::Result<()> {
|
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()?;
|
let exists = target.try_exists()?;
|
||||||
if !exists || old_state.is_managed(target) {
|
if !exists || old_state.is_managed(target) {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
|
|
@ -424,9 +476,9 @@ fn copy_file(source: &Path, target: &Path, mode: &str,
|
||||||
target.display()
|
target.display()
|
||||||
);
|
);
|
||||||
fs::copy(source, target)?;
|
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))?;
|
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(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
anyhow::bail!("File {} already exists, ignoring.", target.display());
|
anyhow::bail!("File {} already exists, ignoring.", target.display());
|
||||||
|
|
|
||||||
|
|
@ -175,10 +175,17 @@ forEachUbuntuImage "example" {
|
||||||
vm.succeed("grep -F 'launch_the_rockets = true' /etc/foo.conf")
|
vm.succeed("grep -F 'launch_the_rockets = true' /etc/foo.conf")
|
||||||
vm.fail("grep -F 'launch_the_rockets = false' /etc/foo.conf")
|
vm.fail("grep -F 'launch_the_rockets = false' /etc/foo.conf")
|
||||||
|
|
||||||
uid = vm.succeed("stat -c %u /etc/test_perms").strip()
|
uid = vm.succeed("stat -c %u /etc/with_ownership").strip()
|
||||||
gid = vm.succeed("stat -c %g /etc/test_perms").strip()
|
gid = vm.succeed("stat -c %g /etc/with_ownership").strip()
|
||||||
assert uid == "5", f"uid was {uid}, expected 5"
|
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/system-manager")
|
||||||
vm.succeed("test -d /var/tmp/sample")
|
vm.succeed("test -d /var/tmp/sample")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue