rust/treefile: add support for check-passwd/groups data entries

This adds treefile support for both `check-passwd` and `check-groups`
entries with "type: data".
This commit is contained in:
Luca BRUNO 2021-02-28 09:59:35 +00:00 committed by OpenShift Merge Robot
parent a7e333925e
commit 6b13f2596c
2 changed files with 174 additions and 79 deletions

View File

@ -5,7 +5,7 @@
use crate::cxxrsutil::*; use crate::cxxrsutil::*;
use crate::ffiutil; use crate::ffiutil;
use crate::nameservice; use crate::nameservice;
use crate::treefile::{CheckPasswdType, Treefile}; use crate::treefile::{CheckGroups, CheckPasswd, Treefile};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use gio::prelude::InputStreamExtManual; use gio::prelude::InputStreamExtManual;
use gio::FileExt; use gio::FileExt;
@ -331,17 +331,17 @@ fn data_from_json(
// Migrate the check data from the specified file to /etc. // Migrate the check data from the specified file to /etc.
let mut src_file = if target == "passwd" { let mut src_file = if target == "passwd" {
let check_passwd_cfg = treefile.get_check_passwd(); let check_passwd_file = match treefile.parsed.get_check_passwd() {
if check_passwd_cfg.variant != CheckPasswdType::File { CheckPasswd::File(cfg) => cfg,
return Ok(false); _ => return Ok(false),
}; };
treefile.passwd_file_mut().context("missing passwd file")? treefile.externals.passwd_file_mut(&check_passwd_file)?
} else if target == "group" { } else if target == "group" {
let check_groups_cfg = treefile.get_check_groups(); let check_groups_file = match treefile.parsed.get_check_groups() {
if check_groups_cfg.variant != CheckPasswdType::File { CheckGroups::File(cfg) => cfg,
return Ok(false); _ => return Ok(false),
}; };
treefile.group_file_mut().context("missing group file")? treefile.externals.group_file_mut(&check_groups_file)?
} else { } else {
unreachable!("impossible merge target '{}'", target); unreachable!("impossible merge target '{}'", target);
}; };

View File

@ -43,7 +43,7 @@ const DEFAULT_RPMDB_BACKEND: RpmdbBackend = RpmdbBackend::Sqlite;
/// This struct holds file descriptors for any external files/data referenced by /// This struct holds file descriptors for any external files/data referenced by
/// a TreeComposeConfig. /// a TreeComposeConfig.
struct TreefileExternals { pub(crate) struct TreefileExternals {
postprocess_script: Option<fs::File>, postprocess_script: Option<fs::File>,
add_files: collections::BTreeMap<String, fs::File>, add_files: collections::BTreeMap<String, fs::File>,
passwd: Option<fs::File>, passwd: Option<fs::File>,
@ -61,7 +61,7 @@ pub struct Treefile {
rojig_name: Option<String>, rojig_name: Option<String>,
rojig_spec: Option<String>, rojig_spec: Option<String>,
serialized: CUtf8Buf, serialized: CUtf8Buf,
externals: TreefileExternals, pub(crate) externals: TreefileExternals,
} }
// We only use this while parsing // We only use this while parsing
@ -178,18 +178,11 @@ fn take_archful_pkgs(
Ok(archful_pkgs) Ok(archful_pkgs)
} }
// If a passwd/group file is provided explicitly, load it as a fd /// If a passwd/group file is provided explicitly, load it as a fd.
fn load_passwd_file<P: AsRef<Path>>( fn load_passwd_file<P: AsRef<Path>>(basedir: P, cfg: &CheckFile) -> Result<Option<fs::File>> {
basedir: P, let basedir = basedir.as_ref();
v: &Option<CheckPasswd>, let file = utils::open_file(basedir.join(&cfg.filename))?;
) -> Result<Option<fs::File>> { Ok(Some(file))
if let Some(ref v) = *v {
let basedir = basedir.as_ref();
if let Some(ref path) = v.filename {
return Ok(Some(utils::open_file(basedir.join(path))?));
}
}
Ok(None)
} }
type IncludeMap = collections::BTreeMap<(u64, u64), String>; type IncludeMap = collections::BTreeMap<(u64, u64), String>;
@ -237,8 +230,15 @@ fn treefile_parse<P: AsRef<Path>>(
} }
} }
let parent = utils::parent_dir(filename).unwrap(); let parent = utils::parent_dir(filename).unwrap();
let passwd = load_passwd_file(&parent, &tf.check_passwd)?; let passwd = match tf.get_check_passwd() {
let group = load_passwd_file(&parent, &tf.check_groups)?; CheckPasswd::File(ref f) => load_passwd_file(&parent, f)?,
_ => None,
};
let group = match tf.get_check_groups() {
CheckGroups::File(ref f) => load_passwd_file(&parent, f)?,
_ => None,
};
Ok(ConfigAndExternals { Ok(ConfigAndExternals {
config: tf, config: tf,
externals: TreefileExternals { externals: TreefileExternals {
@ -481,24 +481,10 @@ impl Treefile {
) )
} }
pub(crate) fn passwd_file_mut(&mut self) -> Option<&mut fs::File> {
self.externals
.passwd
.as_mut()
.and_then(|f| f.seek(io::SeekFrom::Start(0)).ok().map(|_| f))
}
pub(crate) fn get_passwd_fd(&mut self) -> i32 { pub(crate) fn get_passwd_fd(&mut self) -> i32 {
self.externals.passwd.as_mut().map_or(-1, raw_seeked_fd) self.externals.passwd.as_mut().map_or(-1, raw_seeked_fd)
} }
pub(crate) fn group_file_mut(&mut self) -> Option<&mut fs::File> {
self.externals
.group
.as_mut()
.and_then(|f| f.seek(io::SeekFrom::Start(0)).ok().map(|_| f))
}
pub(crate) fn get_group_fd(&mut self) -> i32 { pub(crate) fn get_group_fd(&mut self) -> i32 {
self.externals.group.as_mut().map_or(-1, raw_seeked_fd) self.externals.group.as_mut().map_or(-1, raw_seeked_fd)
} }
@ -566,22 +552,6 @@ impl Treefile {
files_to_remove files_to_remove
} }
pub(crate) fn get_check_passwd(&self) -> &CheckPasswd {
static DEFAULT: CheckPasswd = CheckPasswd {
variant: CheckPasswdType::Previous,
filename: None,
};
self.parsed.check_passwd.as_ref().unwrap_or(&DEFAULT)
}
pub(crate) fn get_check_groups(&self) -> &CheckPasswd {
static DEFAULT: CheckPasswd = CheckPasswd {
variant: CheckPasswdType::Previous,
filename: None,
};
self.parsed.check_groups.as_ref().unwrap_or(&DEFAULT)
}
/// Do some upfront semantic checks we can do beyond just the type safety serde provides. /// Do some upfront semantic checks we can do beyond just the type safety serde provides.
fn validate_config(config: &TreeComposeConfig) -> Result<()> { fn validate_config(config: &TreeComposeConfig) -> Result<()> {
// check add-files // check add-files
@ -747,6 +717,24 @@ fn hash_file(hasher: &mut glib::Checksum, mut f: &fs::File) -> Result<()> {
} }
impl TreefileExternals { impl TreefileExternals {
pub(crate) fn group_file_mut(&mut self, _sentinel: &CheckFile) -> Result<&mut fs::File> {
let group_file = self
.group
.as_mut()
.ok_or_else(|| anyhow::anyhow!("missing passwd file"))?;
group_file.seek(io::SeekFrom::Start(0))?;
Ok(group_file)
}
pub(crate) fn passwd_file_mut(&mut self, _sentinel: &CheckFile) -> Result<&mut fs::File> {
let passwd_file = self
.passwd
.as_mut()
.ok_or_else(|| anyhow::anyhow!("missing passwd file"))?;
passwd_file.seek(io::SeekFrom::Start(0))?;
Ok(passwd_file)
}
fn hasher_update(&self, hasher: &mut glib::Checksum) -> Result<()> { fn hasher_update(&self, hasher: &mut glib::Checksum) -> Result<()> {
if let Some(ref f) = self.postprocess_script { if let Some(ref f) = self.postprocess_script {
hash_file(hasher, f)?; hash_file(hasher, f)?;
@ -819,25 +807,70 @@ impl Default for BootLocation {
} }
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq)]
pub(crate) enum CheckPasswdType { #[serde(tag = "type")]
pub(crate) enum CheckGroups {
#[serde(rename = "none")] #[serde(rename = "none")]
None, None,
#[serde(rename = "previous")] #[serde(rename = "previous")]
Previous, Previous,
#[serde(rename = "file")] #[serde(rename = "file")]
File, File(CheckFile),
#[serde(rename = "data")] #[serde(rename = "data")]
Data, Data(CheckGroupsData),
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, PartialEq)]
pub(crate) struct CheckPasswd { pub(crate) struct CheckFile {
#[serde(rename = "type")] filename: String,
pub(crate) variant: CheckPasswdType, }
filename: Option<String>,
// Skip this for now, a separate file is easier #[derive(Serialize, Deserialize, Debug, PartialEq)]
// and anyways we want to switch to sysusers pub(crate) struct CheckGroupsData {
// entries: Option<Map<>String>, entries: HashMap<String, u64>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(tag = "type")]
pub(crate) enum CheckPasswd {
#[serde(rename = "none")]
None,
#[serde(rename = "previous")]
Previous,
#[serde(rename = "file")]
File(CheckFile),
#[serde(rename = "data")]
Data(CheckPasswdData),
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub(crate) struct CheckPasswdData {
entries: HashMap<String, CheckPasswdDataEntries>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(untagged)]
pub(crate) enum CheckPasswdDataEntries {
IdValue(u64),
IdTuple([u64; 1]),
UidGid((u64, u64)),
}
impl From<u64> for CheckPasswdDataEntries {
fn from(item: u64) -> Self {
Self::IdValue(item)
}
}
impl From<[u64; 1]> for CheckPasswdDataEntries {
fn from(item: [u64; 1]) -> Self {
Self::IdTuple(item)
}
}
impl From<(u64, u64)> for CheckPasswdDataEntries {
fn from(item: (u64, u64)) -> Self {
Self::UidGid(item)
}
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -974,7 +1007,7 @@ pub(crate) struct TreeComposeConfig {
check_passwd: Option<CheckPasswd>, check_passwd: Option<CheckPasswd>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "check-groups")] #[serde(rename = "check-groups")]
check_groups: Option<CheckPasswd>, check_groups: Option<CheckGroups>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "ignore-removed-users")] #[serde(rename = "ignore-removed-users")]
ignore_removed_users: Option<Vec<String>>, ignore_removed_users: Option<Vec<String>>,
@ -1091,6 +1124,16 @@ impl TreeComposeConfig {
hasher.update(serde_json::to_vec(self)?.as_slice()); hasher.update(serde_json::to_vec(self)?.as_slice());
Ok(()) Ok(())
} }
pub(crate) fn get_check_passwd(&self) -> &CheckPasswd {
static DEFAULT: CheckPasswd = CheckPasswd::Previous;
self.check_passwd.as_ref().unwrap_or(&DEFAULT)
}
pub(crate) fn get_check_groups(&self) -> &CheckGroups {
static DEFAULT: CheckGroups = CheckGroups::Previous;
self.check_groups.as_ref().unwrap_or(&DEFAULT)
}
} }
#[cfg(test)] #[cfg(test)]
@ -1532,9 +1575,32 @@ etc-group-members:
{ {
let workdir = tempfile::tempdir().unwrap(); let workdir = tempfile::tempdir().unwrap();
let tf = new_test_treefile(workdir.path(), VALID_PRELUDE, None).unwrap(); let tf = new_test_treefile(workdir.path(), VALID_PRELUDE, None).unwrap();
let default_cfg = tf.get_check_passwd(); let default_cfg = tf.parsed.get_check_passwd();
assert_eq!(default_cfg.variant, CheckPasswdType::Previous); assert_eq!(default_cfg, &CheckPasswd::Previous);
assert_eq!(default_cfg.filename, None); }
{
let input = VALID_PRELUDE.to_string() + r#"check-passwd: { "type": "none" }"#;
let workdir = tempfile::tempdir().unwrap();
let tf = new_test_treefile(workdir.path(), &input, None).unwrap();
let custom_cfg = tf.parsed.get_check_passwd();
assert_eq!(custom_cfg, &CheckPasswd::None);
}
{
let input = VALID_PRELUDE.to_string()
+ r#"check-passwd: { "type": "data", "entries": { "bin": 1, "adm": [3, 4], "foo" : [2] } }"#;
let workdir = tempfile::tempdir().unwrap();
let tf = new_test_treefile(workdir.path(), &input, None).unwrap();
let custom_cfg = tf.parsed.get_check_passwd();
assert_eq!(
custom_cfg,
&CheckPasswd::Data(CheckPasswdData {
entries: maplit::hashmap!(
"bin".into() => 1.into(),
"adm".into() => (3, 4).into(),
"foo".into() => [2].into(),
),
})
);
} }
{ {
let input = VALID_PRELUDE.to_string() let input = VALID_PRELUDE.to_string()
@ -1545,9 +1611,13 @@ etc-group-members:
.write_file_contents("local-file", 0o755, "") .write_file_contents("local-file", 0o755, "")
.unwrap(); .unwrap();
let tf = new_test_treefile(workdir.path(), &input, None).unwrap(); let tf = new_test_treefile(workdir.path(), &input, None).unwrap();
let custom_cfg = tf.get_check_passwd(); let custom_cfg = tf.parsed.get_check_passwd();
assert_eq!(custom_cfg.variant, CheckPasswdType::File); assert_eq!(
assert_eq!(custom_cfg.filename, Some("local-file".to_string())); custom_cfg,
&CheckPasswd::File(CheckFile {
filename: "local-file".to_string()
})
);
} }
} }
@ -1556,9 +1626,30 @@ etc-group-members:
{ {
let workdir = tempfile::tempdir().unwrap(); let workdir = tempfile::tempdir().unwrap();
let tf = new_test_treefile(workdir.path(), VALID_PRELUDE, None).unwrap(); let tf = new_test_treefile(workdir.path(), VALID_PRELUDE, None).unwrap();
let default_cfg = tf.get_check_groups(); let default_cfg = tf.parsed.get_check_groups();
assert_eq!(default_cfg.variant, CheckPasswdType::Previous); assert_eq!(default_cfg, &CheckGroups::Previous);
assert_eq!(default_cfg.filename, None); }
{
let input = VALID_PRELUDE.to_string() + r#"check-groups: { "type": "none" }"#;
let workdir = tempfile::tempdir().unwrap();
let tf = new_test_treefile(workdir.path(), &input, None).unwrap();
let custom_cfg = tf.parsed.get_check_groups();
assert_eq!(custom_cfg, &CheckGroups::None);
}
{
let input = VALID_PRELUDE.to_string()
+ r#"check-groups: { "type": "data", "entries": { "bin": 1 } }"#;
let workdir = tempfile::tempdir().unwrap();
let tf = new_test_treefile(workdir.path(), &input, None).unwrap();
let custom_cfg = tf.parsed.get_check_groups();
assert_eq!(
custom_cfg,
&CheckGroups::Data(CheckGroupsData {
entries: maplit::hashmap!(
"bin".into() => 1,
),
})
);
} }
{ {
let input = VALID_PRELUDE.to_string() let input = VALID_PRELUDE.to_string()
@ -1569,9 +1660,13 @@ etc-group-members:
.write_file_contents("local-file", 0o755, "") .write_file_contents("local-file", 0o755, "")
.unwrap(); .unwrap();
let tf = new_test_treefile(workdir.path(), &input, None).unwrap(); let tf = new_test_treefile(workdir.path(), &input, None).unwrap();
let custom_cfg = tf.get_check_groups(); let custom_cfg = tf.parsed.get_check_groups();
assert_eq!(custom_cfg.variant, CheckPasswdType::File); assert_eq!(
assert_eq!(custom_cfg.filename, Some("local-file".to_string())); custom_cfg,
&CheckGroups::File(CheckFile {
filename: "local-file".to_string()
})
);
} }
} }
} }