fs-mistrust: Add Group and User types.
This will help make the actual configuration more serializable, I hope.
This commit is contained in:
parent
95200383b5
commit
330582a142
|
@ -1201,6 +1201,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"derive_builder_fork_arti",
|
||||
"educe",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"tempfile",
|
||||
|
|
|
@ -15,6 +15,7 @@ default = ["walkdir"]
|
|||
|
||||
[dependencies]
|
||||
derive_builder = { version = "0.11", package = "derive_builder_fork_arti" }
|
||||
educe = "0.4.6"
|
||||
thiserror = "1"
|
||||
walkdir = { version = "2", optional = true }
|
||||
|
||||
|
|
|
@ -100,6 +100,14 @@ pub enum Error {
|
|||
/// [`Mistrust`](crate::Mistrust).
|
||||
#[error("Missing field: {0}")]
|
||||
MissingField(#[from] derive_builder::UninitializedFieldError),
|
||||
|
||||
/// A group that we were configured to trust could not be found.
|
||||
#[error("No such group: {0}")]
|
||||
NoSuchGroup(String),
|
||||
|
||||
/// A user that we were configured to trust could not be found.
|
||||
#[error("No such user: {0}")]
|
||||
NoSuchUser(String),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
|
@ -140,6 +148,8 @@ impl Error {
|
|||
Error::Content(e) => return e.path(),
|
||||
Error::Listing(e) => return e.path(),
|
||||
Error::MissingField(_) => return None,
|
||||
Error::NoSuchGroup(_) => return None,
|
||||
Error::NoSuchUser(_) => return None,
|
||||
}
|
||||
.as_path(),
|
||||
)
|
||||
|
@ -163,7 +173,9 @@ impl Error {
|
|||
| Error::InvalidSubdirectory
|
||||
| Error::Io(_, _)
|
||||
| Error::NoTempFile(_)
|
||||
| Error::MissingField(_) => false,
|
||||
| Error::MissingField(_)
|
||||
| Error::NoSuchGroup(_)
|
||||
| Error::NoSuchUser(_) => false,
|
||||
|
||||
Error::Multiple(errs) => errs.iter().any(|e| e.is_bad_permission()),
|
||||
Error::Content(err) => err.is_bad_permission(),
|
||||
|
|
|
@ -295,6 +295,9 @@ pub use err::Error;
|
|||
/// A result type as returned by this crate
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
pub use user::{TrustedGroup, TrustedUser};
|
||||
|
||||
/// Configuration for verifying that a file or directory is really "private".
|
||||
///
|
||||
/// By default, we mistrust everything that we can: we assume that every
|
||||
|
@ -329,12 +332,18 @@ pub struct Mistrust {
|
|||
|
||||
/// What user ID do we trust by default (if any?)
|
||||
#[cfg(target_family = "unix")]
|
||||
#[builder(setter(strip_option), default = "user::this_uid()")]
|
||||
#[builder(
|
||||
setter(into),
|
||||
field(type = "TrustedUser", build = "self.trust_user.get_uid()?")
|
||||
)]
|
||||
trust_user: Option<u32>,
|
||||
|
||||
/// What group ID do we trust by default (if any?)
|
||||
#[cfg(target_family = "unix")]
|
||||
#[builder(setter(strip_option), default = "user::get_self_named_gid()")]
|
||||
#[builder(
|
||||
setter(into),
|
||||
field(type = "TrustedGroup", build = "self.trust_group.get_gid()?")
|
||||
)]
|
||||
trust_group: Option<u32>,
|
||||
}
|
||||
|
||||
|
@ -362,8 +371,8 @@ impl MistrustBuilder {
|
|||
/// This option disables the default group-trust behavior as well.
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn trust_admin_only(&mut self) -> &mut Self {
|
||||
self.trust_user = Some(None);
|
||||
self.trust_group = Some(None);
|
||||
self.trust_user = TrustedUser::None;
|
||||
self.trust_group = TrustedGroup::None;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -377,7 +386,7 @@ impl MistrustBuilder {
|
|||
/// world-writable objects respectively.
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn trust_no_group_id(&mut self) -> &mut Self {
|
||||
self.trust_group = Some(None);
|
||||
self.trust_group = TrustedGroup::None;
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
//! Code to inspect user db information on unix.
|
||||
|
||||
use crate::Error;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{ffi::OsString, sync::Mutex};
|
||||
use std::{
|
||||
ffi::{OsStr, OsString},
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
/// Cached values of user db entries we've looked up.
|
||||
///
|
||||
|
@ -10,23 +14,6 @@ use std::{ffi::OsString, sync::Mutex};
|
|||
/// Though this type has interior mutability, it isn't Sync, so we need to add a mutex.
|
||||
static CACHE: Lazy<Mutex<users::UsersCache>> = Lazy::new(|| Mutex::new(users::UsersCache::new()));
|
||||
|
||||
/// Return the UID for this user.
|
||||
///
|
||||
/// The return type is funny here, to please derive_builder.
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub(crate) fn this_uid() -> Option<u32> {
|
||||
Some(unsafe { libc::getuid() })
|
||||
}
|
||||
|
||||
/// Look for a group with the same name as our username.
|
||||
///
|
||||
/// If there is one, and we belong to it, return its gid. Otherwise
|
||||
/// return None.
|
||||
pub(crate) fn get_self_named_gid() -> Option<u32> {
|
||||
let cache = CACHE.lock().expect("Poisoned lock");
|
||||
get_self_named_gid_impl(&*cache)
|
||||
}
|
||||
|
||||
/// Like get_self_named_gid(), but use a provided user database.
|
||||
fn get_self_named_gid_impl<U: users::Groups + users::Users>(userdb: &U) -> Option<u32> {
|
||||
let username = get_own_username(userdb)?;
|
||||
|
@ -92,6 +79,141 @@ fn cur_groups() -> Vec<u32> {
|
|||
buf
|
||||
}
|
||||
|
||||
/// A user that we can be configured to trust.
|
||||
#[derive(Clone, Debug, educe::Educe, Eq, PartialEq)]
|
||||
#[educe(Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum TrustedUser {
|
||||
/// We won't treat any user as trusted.
|
||||
None,
|
||||
/// Treat the current user as trusted.
|
||||
#[educe(Default)]
|
||||
Current,
|
||||
/// Treat the user with a particular UID as trusted.
|
||||
Id(u32),
|
||||
/// Treat a user with a particular name as trusted.
|
||||
///
|
||||
/// If there is no such user, we'll report an error.
|
||||
Name(OsString),
|
||||
}
|
||||
|
||||
impl From<u32> for TrustedUser {
|
||||
fn from(val: u32) -> Self {
|
||||
TrustedUser::Id(val)
|
||||
}
|
||||
}
|
||||
impl From<OsString> for TrustedUser {
|
||||
fn from(val: OsString) -> Self {
|
||||
TrustedUser::Name(val)
|
||||
}
|
||||
}
|
||||
impl From<&OsStr> for TrustedUser {
|
||||
fn from(val: &OsStr) -> Self {
|
||||
val.to_owned().into()
|
||||
}
|
||||
}
|
||||
impl From<String> for TrustedUser {
|
||||
fn from(val: String) -> Self {
|
||||
OsString::from(val).into()
|
||||
}
|
||||
}
|
||||
impl From<&str> for TrustedUser {
|
||||
fn from(val: &str) -> Self {
|
||||
val.to_owned().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TrustedUser {
|
||||
/// Try to convert this `User` into an optional UID.
|
||||
pub(crate) fn get_uid(&self) -> Result<Option<u32>, Error> {
|
||||
let userdb = CACHE.lock().expect("poisoned lock");
|
||||
self.get_uid_impl(&*userdb)
|
||||
}
|
||||
/// As `get_uid`, but take a userdb.
|
||||
fn get_uid_impl<U: users::Users>(&self, userdb: &U) -> Result<Option<u32>, Error> {
|
||||
match self {
|
||||
TrustedUser::None => Ok(None),
|
||||
TrustedUser::Current => Ok(Some(userdb.get_current_uid())),
|
||||
TrustedUser::Id(id) => Ok(Some(*id)),
|
||||
TrustedUser::Name(name) => userdb
|
||||
.get_user_by_name(&name)
|
||||
.map(|u| Some(u.uid()))
|
||||
.ok_or_else(|| Error::NoSuchUser(name.to_string_lossy().into_owned())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A group that we can be configured to trust.
|
||||
#[derive(Clone, Debug, educe::Educe, Eq, PartialEq)]
|
||||
#[educe(Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum TrustedGroup {
|
||||
/// We won't treat any group as trusted
|
||||
None,
|
||||
/// We'll treat any group with same name as the current user as trusted.
|
||||
///
|
||||
/// If there is no such group, we trust no group.
|
||||
///
|
||||
/// (This is the default.)
|
||||
#[educe(Default)]
|
||||
SelfNamed,
|
||||
/// We'll treat a specific group ID as trusted.
|
||||
Id(u32),
|
||||
/// We'll treat a group with a specific name as trusted.
|
||||
///
|
||||
/// If there is no such group, we'll report an error.
|
||||
Name(OsString),
|
||||
}
|
||||
|
||||
impl From<u32> for TrustedGroup {
|
||||
fn from(val: u32) -> Self {
|
||||
TrustedGroup::Id(val)
|
||||
}
|
||||
}
|
||||
impl From<OsString> for TrustedGroup {
|
||||
fn from(val: OsString) -> TrustedGroup {
|
||||
TrustedGroup::Name(val)
|
||||
}
|
||||
}
|
||||
impl From<&OsStr> for TrustedGroup {
|
||||
fn from(val: &OsStr) -> TrustedGroup {
|
||||
val.to_owned().into()
|
||||
}
|
||||
}
|
||||
impl From<String> for TrustedGroup {
|
||||
fn from(val: String) -> TrustedGroup {
|
||||
OsString::from(val).into()
|
||||
}
|
||||
}
|
||||
impl From<&str> for TrustedGroup {
|
||||
fn from(val: &str) -> TrustedGroup {
|
||||
val.to_owned().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TrustedGroup {
|
||||
/// Try to convert this `Group` into an optional GID.
|
||||
pub(crate) fn get_gid(&self) -> Result<Option<u32>, Error> {
|
||||
let userdb = CACHE.lock().expect("poisoned lock");
|
||||
self.get_gid_impl(&*userdb)
|
||||
}
|
||||
/// Like `get_gid`, but take a user db as an argument.
|
||||
fn get_gid_impl<U: users::Users + users::Groups>(
|
||||
&self,
|
||||
userdb: &U,
|
||||
) -> Result<Option<u32>, Error> {
|
||||
match self {
|
||||
TrustedGroup::None => Ok(None),
|
||||
TrustedGroup::SelfNamed => Ok(get_self_named_gid_impl(userdb)),
|
||||
TrustedGroup::Id(id) => Ok(Some(*id)),
|
||||
TrustedGroup::Name(name) => userdb
|
||||
.get_group_by_name(&name)
|
||||
.map(|u| Some(u.gid()))
|
||||
.ok_or_else(|| Error::NoSuchGroup(name.to_string_lossy().into_owned())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
@ -212,4 +334,32 @@ mod test {
|
|||
let found = get_self_named_gid_impl(&db);
|
||||
assert_eq!(found, Some(cur_groups[0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_id() {
|
||||
let mut db = MockUsers::with_current_uid(413);
|
||||
db.add_user(User::new(413, "aranea", 413413));
|
||||
db.add_group(Group::new(33, "nepeta"));
|
||||
|
||||
assert_eq!(TrustedUser::None.get_uid_impl(&db).unwrap(), None);
|
||||
assert_eq!(TrustedUser::Current.get_uid_impl(&db).unwrap(), Some(413));
|
||||
assert_eq!(TrustedUser::Id(413).get_uid_impl(&db).unwrap(), Some(413));
|
||||
assert_eq!(
|
||||
TrustedUser::Name("aranea".into())
|
||||
.get_uid_impl(&db)
|
||||
.unwrap(),
|
||||
Some(413)
|
||||
);
|
||||
assert!(TrustedUser::Name("ac".into()).get_uid_impl(&db).is_err());
|
||||
|
||||
assert_eq!(TrustedGroup::None.get_gid_impl(&db).unwrap(), None);
|
||||
assert_eq!(TrustedGroup::Id(33).get_gid_impl(&db).unwrap(), Some(33));
|
||||
assert_eq!(
|
||||
TrustedGroup::Name("nepeta".into())
|
||||
.get_gid_impl(&db)
|
||||
.unwrap(),
|
||||
Some(33)
|
||||
);
|
||||
assert!(TrustedGroup::Name("ac".into()).get_gid_impl(&db).is_err());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue