fs-mistrust: Add Group and User types.

This will help make the actual configuration more serializable,
I hope.
This commit is contained in:
Nick Mathewson 2022-05-18 14:36:24 -04:00
parent 95200383b5
commit 330582a142
5 changed files with 197 additions and 24 deletions

1
Cargo.lock generated
View File

@ -1201,6 +1201,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"derive_builder_fork_arti", "derive_builder_fork_arti",
"educe",
"libc", "libc",
"once_cell", "once_cell",
"tempfile", "tempfile",

View File

@ -15,6 +15,7 @@ default = ["walkdir"]
[dependencies] [dependencies]
derive_builder = { version = "0.11", package = "derive_builder_fork_arti" } derive_builder = { version = "0.11", package = "derive_builder_fork_arti" }
educe = "0.4.6"
thiserror = "1" thiserror = "1"
walkdir = { version = "2", optional = true } walkdir = { version = "2", optional = true }

View File

@ -100,6 +100,14 @@ pub enum Error {
/// [`Mistrust`](crate::Mistrust). /// [`Mistrust`](crate::Mistrust).
#[error("Missing field: {0}")] #[error("Missing field: {0}")]
MissingField(#[from] derive_builder::UninitializedFieldError), 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 { impl Error {
@ -140,6 +148,8 @@ impl Error {
Error::Content(e) => return e.path(), Error::Content(e) => return e.path(),
Error::Listing(e) => return e.path(), Error::Listing(e) => return e.path(),
Error::MissingField(_) => return None, Error::MissingField(_) => return None,
Error::NoSuchGroup(_) => return None,
Error::NoSuchUser(_) => return None,
} }
.as_path(), .as_path(),
) )
@ -163,7 +173,9 @@ impl Error {
| Error::InvalidSubdirectory | Error::InvalidSubdirectory
| Error::Io(_, _) | Error::Io(_, _)
| Error::NoTempFile(_) | Error::NoTempFile(_)
| Error::MissingField(_) => false, | Error::MissingField(_)
| Error::NoSuchGroup(_)
| Error::NoSuchUser(_) => false,
Error::Multiple(errs) => errs.iter().any(|e| e.is_bad_permission()), Error::Multiple(errs) => errs.iter().any(|e| e.is_bad_permission()),
Error::Content(err) => err.is_bad_permission(), Error::Content(err) => err.is_bad_permission(),

View File

@ -295,6 +295,9 @@ pub use err::Error;
/// A result type as returned by this crate /// A result type as returned by this crate
pub type Result<T> = std::result::Result<T, Error>; 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". /// Configuration for verifying that a file or directory is really "private".
/// ///
/// By default, we mistrust everything that we can: we assume that every /// 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?) /// What user ID do we trust by default (if any?)
#[cfg(target_family = "unix")] #[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>, trust_user: Option<u32>,
/// What group ID do we trust by default (if any?) /// What group ID do we trust by default (if any?)
#[cfg(target_family = "unix")] #[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>, trust_group: Option<u32>,
} }
@ -362,8 +371,8 @@ impl MistrustBuilder {
/// This option disables the default group-trust behavior as well. /// This option disables the default group-trust behavior as well.
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
pub fn trust_admin_only(&mut self) -> &mut Self { pub fn trust_admin_only(&mut self) -> &mut Self {
self.trust_user = Some(None); self.trust_user = TrustedUser::None;
self.trust_group = Some(None); self.trust_group = TrustedGroup::None;
self self
} }
@ -377,7 +386,7 @@ impl MistrustBuilder {
/// world-writable objects respectively. /// world-writable objects respectively.
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
pub fn trust_no_group_id(&mut self) -> &mut Self { pub fn trust_no_group_id(&mut self) -> &mut Self {
self.trust_group = Some(None); self.trust_group = TrustedGroup::None;
self self
} }

View File

@ -1,7 +1,11 @@
//! Code to inspect user db information on unix. //! Code to inspect user db information on unix.
use crate::Error;
use once_cell::sync::Lazy; 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. /// 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. /// 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())); 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. /// 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> { fn get_self_named_gid_impl<U: users::Groups + users::Users>(userdb: &U) -> Option<u32> {
let username = get_own_username(userdb)?; let username = get_own_username(userdb)?;
@ -92,6 +79,141 @@ fn cur_groups() -> Vec<u32> {
buf 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)] #[cfg(test)]
mod test { mod test {
#![allow(clippy::unwrap_used)] #![allow(clippy::unwrap_used)]
@ -212,4 +334,32 @@ mod test {
let found = get_self_named_gid_impl(&db); let found = get_self_named_gid_impl(&db);
assert_eq!(found, Some(cur_groups[0])); 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());
}
} }