Merge branch 'configurable-mistrust' into 'main'

Make file permissions configurable via regular config mechanisms

Closes #465

See merge request tpo/core/arti!515
This commit is contained in:
Nick Mathewson 2022-05-24 15:19:20 +00:00
commit b055cc27f1
22 changed files with 887 additions and 228 deletions

5
Cargo.lock generated
View File

@ -1200,10 +1200,15 @@ name = "fs-mistrust"
version = "0.1.0"
dependencies = [
"anyhow",
"derive_builder_fork_arti",
"educe",
"libc",
"once_cell",
"serde",
"serde_json",
"tempfile",
"thiserror",
"toml",
"users",
"walkdir",
]

View File

@ -363,8 +363,7 @@ fn main() -> Result<()> {
// TODO really we ought to get this from the arti configuration, or something.
// But this is OK for now since we are a benchmarking tool.
let mut mistrust = fs_mistrust::Mistrust::new();
mistrust.dangerously_trust_everyone();
let mistrust = fs_mistrust::Mistrust::new_dangerously_trust_everyone();
config_sources.set_mistrust(mistrust);
let cfg = config_sources.load()?;

View File

@ -31,7 +31,7 @@ error_detail = []
experimental-api = []
[dependencies]
fs-mistrust = { path = "../fs-mistrust", version = "0.1.0" }
fs-mistrust = { path = "../fs-mistrust", version = "0.1.0", features = ["serde"] }
safelog = { path = "../safelog", version = "0.1.0" }
tor-basic-utils = { path = "../tor-basic-utils", version = "0.3.0"}
tor-circmgr = { path = "../tor-circmgr", version = "0.3.0"}

View File

@ -3,6 +3,7 @@
#![allow(missing_docs, clippy::missing_docs_in_private_items)]
use crate::{err::ErrorDetail, BootstrapBehavior, Result, TorClient, TorClientConfig};
use fs_mistrust::Mistrust;
use std::sync::Arc;
use tor_dirmgr::DirMgrConfig;
use tor_rtcompat::Runtime;
@ -38,6 +39,18 @@ impl<R: Runtime> DirProviderBuilder<R> for DirMgrBuilder {
}
}
/// Rules about whether to replace the `Mistrust` from the configuration.
#[derive(Clone, Debug)]
enum FsMistrustOverride {
/// Disable the mistrust in the configuration if the the environment
/// variable `ARTI_FS_DISABLE_PERMISSION_CHECKS` is set.
FromEnvironment,
/// Disable the mistrust in the configuration unconditionally.
Disable,
/// Always use the mistrust in the configuration.
None,
}
/// An object for constructing a [`TorClient`].
///
/// Returned by [`TorClient::builder()`].
@ -52,7 +65,7 @@ pub struct TorClientBuilder<R: Runtime> {
/// network before `bootstrap()` is called.
bootstrap_behavior: BootstrapBehavior,
/// How the client should decide which file permissions to trust.
fs_mistrust: Option<fs_mistrust::Mistrust>,
fs_mistrust_override: FsMistrustOverride,
/// Optional object to construct a DirProvider.
///
/// Wrapped in an Arc so that we don't need to force DirProviderBuilder to
@ -72,7 +85,7 @@ impl<R: Runtime> TorClientBuilder<R> {
runtime,
config: TorClientConfig::default(),
bootstrap_behavior: BootstrapBehavior::default(),
fs_mistrust: None,
fs_mistrust_override: FsMistrustOverride::FromEnvironment,
dirmgr_builder: Arc::new(DirMgrBuilder {}),
#[cfg(feature = "dirfilter")]
dirfilter: None,
@ -96,28 +109,32 @@ impl<R: Runtime> TorClientBuilder<R> {
self
}
/// Build an [`TorClient`] that will not validate permissions and
/// ownership on the filesystem.
/// Build an [`TorClient`] that will not validate permissions and ownership
/// on the filesystem.
///
/// By default, these checks are enabled, unless the
/// `ARTI_FS_DISABLE_PERMISSION_CHECKS` environment variable has been set or
/// this method has been called.
/// By default, these checks are configured with the `storage.permissions`
/// field of the configuration, and can be overridden with the
/// `ARTI_FS_DISABLE_PERMISSION_CHECKS` environment variable.
pub fn disable_fs_permission_checks(mut self) -> Self {
let mut mistrust = fs_mistrust::Mistrust::new();
mistrust.dangerously_trust_everyone();
self.fs_mistrust = Some(mistrust);
self.fs_mistrust_override = FsMistrustOverride::Disable;
self
}
/// Build an [`TorClient`] that will always validate permissions and
/// ownership on the filesystem.
/// Build a [`TorClient`] that will follow the permissions checks in
/// the `storage.permissions` field of the configuration, regardless of how the
/// environment is set.
pub fn ignore_fs_permission_checks_env_var(mut self) -> Self {
self.fs_mistrust_override = FsMistrustOverride::None;
self
}
/// Build a [`TorClient`] that will follow the permissions checks in the
/// `storage.permissions` field of the configuration, unless the
/// `ARTI_FS_DISABLE_PERMISSION_CHECKS` environment variable is set.
///
/// By default, these checks are enabled, unless the
/// `ARTI_FS_DISABLE_PERMISSION_CHECKS` environment variable has been set or
/// [`disable_fs_permission_checks`](Self::disable_fs_permission_checks)
/// method has been called.
pub fn enable_fs_permission_checks(mut self) -> Self {
self.fs_mistrust = Some(fs_mistrust::Mistrust::new());
/// This is the default.
pub fn obey_fs_permission_checks_env_var(mut self) -> Self {
self.fs_mistrust_override = FsMistrustOverride::FromEnvironment;
self
}
@ -170,12 +187,21 @@ impl<R: Runtime> TorClientBuilder<R> {
dirmgr_extensions.filter = self.dirfilter;
}
let override_mistrust: Option<Mistrust> = match self.fs_mistrust_override {
FsMistrustOverride::FromEnvironment
if crate::config::fs_permissions_checks_disabled_via_env() =>
{
Some(Mistrust::new_dangerously_trust_everyone())
}
FsMistrustOverride::Disable => Some(Mistrust::new_dangerously_trust_everyone()),
_ => None,
};
TorClient::create_inner(
self.runtime,
self.config,
self.bootstrap_behavior,
self.fs_mistrust
.unwrap_or_else(crate::config::default_fs_mistrust),
override_mistrust,
self.dirmgr_builder.as_ref(),
dirmgr_extensions,
)

View File

@ -353,10 +353,11 @@ impl<R: Runtime> TorClient<R> {
runtime: R,
config: TorClientConfig,
autobootstrap: BootstrapBehavior,
mistrust: fs_mistrust::Mistrust,
mistrust: Option<fs_mistrust::Mistrust>,
dirmgr_builder: &dyn crate::builder::DirProviderBuilder<R>,
dirmgr_extensions: tor_dirmgr::config::DirMgrExtensions,
) -> StdResult<Self, ErrorDetail> {
let mistrust = mistrust.unwrap_or_else(|| config.storage.permissions().clone());
let dir_cfg = {
let mut c: tor_dirmgr::DirMgrConfig = config.dir_mgr_config(mistrust.clone())?;
c.extensions = dirmgr_extensions;

View File

@ -13,6 +13,7 @@
use derive_builder::Builder;
use derive_more::AsRef;
use fs_mistrust::{Mistrust, MistrustBuilder};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
@ -106,6 +107,26 @@ fn default_dns_resolve_ptr_timeout() -> Duration {
Duration::new(10, 0)
}
/// Extension trait for `MistrustBuilder` to convert the error type on
/// build.
trait BuilderExt {
/// Type that this builder provides.
type Built;
/// Run this builder and convert its error type (if any)
fn build_for_arti(&self) -> Result<Self::Built, ConfigBuildError>;
}
impl BuilderExt for MistrustBuilder {
type Built = Mistrust;
fn build_for_arti(&self) -> Result<Self::Built, ConfigBuildError> {
self.build().map_err(|e| ConfigBuildError::Invalid {
field: "permissions".to_string(),
problem: e.to_string(),
})
}
}
/// Configuration for where information should be stored on disk.
///
/// By default, cache information will be stored in `${ARTI_CACHE}`, and
@ -124,6 +145,7 @@ fn default_dns_resolve_ptr_timeout() -> Duration {
#[derive(Debug, Clone, Builder, Eq, PartialEq)]
#[builder(build_fn(error = "ConfigBuildError"))]
#[builder(derive(Debug, Serialize, Deserialize))]
#[non_exhaustive]
pub struct StorageConfig {
/// Location on disk for cached directory information.
#[builder(setter(into), default = "default_cache_dir()")]
@ -131,6 +153,10 @@ pub struct StorageConfig {
/// Location on disk for less-sensitive persistent state information.
#[builder(setter(into), default = "default_state_dir()")]
state_dir: CfgPath,
/// Filesystem state to
#[builder(sub_builder(fn_name = "build_for_arti"))]
#[builder_field_attr(serde(default))]
permissions: Mistrust,
}
impl_standard_builder! { StorageConfig }
@ -163,6 +189,10 @@ impl StorageConfig {
problem: e.to_string(),
})
}
/// Return the FS permissions to use for state and cache directories.
pub(crate) fn permissions(&self) -> &Mistrust {
&self.permissions
}
}
/// A configuration used to bootstrap a [`TorClient`](crate::TorClient).
@ -194,6 +224,7 @@ impl StorageConfig {
#[derive(Clone, Builder, Debug, Eq, PartialEq, AsRef)]
#[builder(build_fn(error = "ConfigBuildError"))]
#[builder(derive(Serialize, Deserialize, Debug))]
#[non_exhaustive]
pub struct TorClientConfig {
/// Information about the Tor network we want to connect to.
#[builder(sub_builder)]
@ -289,7 +320,25 @@ impl TorClientConfig {
override_net_params: self.override_net_params.clone(),
extensions: Default::default(),
})
}
/// Return a reference to the [`fs_mistrust::Mistrust`] object that we'll
/// use to check permissions on files and directories by default.
///
/// # Usage notes
///
/// In the future, specific files or directories may have stricter or looser
/// permissions checks applied to them than this default. Callers shouldn't
/// use this [`Mistrust`] to predict what Arti will accept for a specific
/// file or directory. Rather, you should use this if you have some file or
/// directory of your own on which you'd like to enforce the same rules as
/// Arti uses.
//
// NOTE: The presence of this accessor is _NOT_ in any form a commitment to
// expose every field from the configuration as an accessor. We explicitly
// reject that slippery slope argument.
pub fn fs_mistrust(&self) -> &Mistrust {
self.storage.permissions()
}
}
@ -311,26 +360,21 @@ impl TorClientConfigBuilder {
}
}
/// Return a default value for our fs_mistrust configuration.
///
/// This is based on the environment rather on the configuration, since we may
/// want to use it to determine whether configuration files are safe to read.
//
// TODO: This function should probably go away and become part of our storage
// configuration code, once we have made Mistrust configurable via Deserialize.
pub(crate) fn default_fs_mistrust() -> fs_mistrust::Mistrust {
let mut mistrust = fs_mistrust::Mistrust::new();
if std::env::var_os("ARTI_FS_DISABLE_PERMISSION_CHECKS").is_some() {
mistrust.dangerously_trust_everyone();
}
mistrust
}
/// Return a filename for the default user configuration file.
pub fn default_config_file() -> Result<PathBuf, CfgPathError> {
CfgPath::new("${ARTI_CONFIG}/arti.toml".into()).path()
}
/// Return true if the environment has been set up to disable FS permissions
/// checking.
///
/// This function is exposed so that other tools can use the same checking rules
/// as `arti-client`. For more information, see
/// [`TorClientBuilder`](crate::TorClientBuilder).
pub fn fs_permissions_checks_disabled_via_env() -> bool {
std::env::var_os("ARTI_FS_DISABLE_PERMISSION_CHECKS").is_some()
}
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]

View File

@ -65,6 +65,30 @@
#cache_dir = "${ARTI_CACHE}"
#state_dir = "${ARTI_LOCAL_DATA}"
# Describe how to enforce permissions on the filesystem when accessing the cache
# and state directories. (This does not apply to configuration files)
[storage.permissions]
# If set to true, we ignore all filesystem permissions.
#dangerously_trust_everyone = false
# What user (if any) is trusted to own files and directories? ":current" means
# to trust the current user.
#trust_user = ":current"
# What group (if any) is trusted to have read/write access to files and
# directories? ":selfnamed" means to trust the group with the same name as the
# current user, if that user is a member.
#trust_group = ":username"
# If set, gives a path prefix that will always be trusted. For example, if this
# option is set to "/home/", and we are checking "/home/username/.cache", then
# we always accept the permissions on "/" and "/home", but we check the
# permissions on "/home/username" and "/home/username/.cache".
#
# (This is not the default.)
#
# ignore_prefix = "/home/"
# Replacement values for consensus parameters. This is an advanced option
# and you probably should leave it alone. Not all parameters are supported.
# These are case-sensitive.

View File

@ -115,7 +115,7 @@ pub struct ArtiConfig {
/// Configuration of the actual Tor client
#[builder(sub_builder)]
#[builder_field_attr(serde(flatten))]
tor: TorClientConfig,
pub(crate) tor: TorClientConfig,
}
impl_standard_builder! { ArtiConfig }

View File

@ -130,7 +130,7 @@ pub use cfg::{
};
pub use logging::{LoggingConfig, LoggingConfigBuilder};
use arti_client::config::default_config_file;
use arti_client::config::{default_config_file, fs_permissions_checks_disabled_via_env};
use arti_client::{TorClient, TorClientConfig};
use safelog::with_safe_logging_suppressed;
use tor_config::ConfigurationSources;
@ -199,8 +199,6 @@ pub async fn run<R: Runtime>(
.bootstrap_behavior(OnDemand);
if fs_mistrust_disabled {
client_builder = client_builder.disable_fs_permission_checks();
} else {
client_builder = client_builder.enable_fs_permission_checks();
}
let client = client_builder.create_unbootstrapped()?;
if arti_config.application().watch_configuration {
@ -247,15 +245,6 @@ pub async fn run<R: Runtime>(
)
}
/// Return true if the environment has been set up to disable FS mistrust.
//
// TODO(nickm): This is duplicate logic from arti_client config. When we make
// fs_mistrust configurable via deserialize, as a real part of our configuration
// logic, we should unify all this code.
fn fs_mistrust_disabled_via_env() -> bool {
std::env::var_os("ARTI_FS_DISABLE_PERMISSION_CHECKS").is_some()
}
/// Inner function to allow convenient error handling
///
/// # Panics
@ -360,18 +349,15 @@ pub fn main_main() -> Result<()> {
.setting(AppSettings::SubcommandRequiredElseHelp)
.get_matches();
let fs_mistrust_disabled =
fs_mistrust_disabled_via_env() | matches.is_present("disable-fs-permission-checks");
let fs_mistrust_disabled = fs_permissions_checks_disabled_via_env()
| matches.is_present("disable-fs-permission-checks");
let mistrust = {
// TODO: This is duplicate code from arti_client::config. When we make
// fs_mistrust configurable via deserialize, as a real part of our configuration
// logic, we should unify this check.
let mut mistrust = fs_mistrust::Mistrust::new();
if fs_mistrust_disabled {
mistrust.dangerously_trust_everyone();
}
mistrust
// A Mistrust object to use for loading our configuration. Elsewhere, we
// use the value _from_ the configuration.
let cfg_mistrust = if fs_mistrust_disabled {
fs_mistrust::Mistrust::new_dangerously_trust_everyone()
} else {
fs_mistrust::Mistrust::new()
};
let cfg_sources = {
@ -380,7 +366,7 @@ pub fn main_main() -> Result<()> {
matches.values_of_os("config-files").unwrap_or_default(),
matches.values_of("option").unwrap_or_default(),
);
cfg_sources.set_mistrust(mistrust);
cfg_sources.set_mistrust(cfg_mistrust);
cfg_sources
};
@ -388,7 +374,16 @@ pub fn main_main() -> Result<()> {
let config: ArtiConfig = cfg.try_into().context("read configuration")?;
let _log_guards = logging::setup_logging(config.logging(), matches.value_of("loglevel"))?;
let log_mistrust = if fs_mistrust_disabled {
fs_mistrust::Mistrust::new_dangerously_trust_everyone()
} else {
config.tor.fs_mistrust().clone()
};
let _log_guards = logging::setup_logging(
config.logging(),
&log_mistrust,
matches.value_of("loglevel"),
)?;
if let Some(proxy_matches) = matches.subcommand_matches("proxy") {
let socks_port = match (

View File

@ -2,6 +2,7 @@
use anyhow::{anyhow, Context, Result};
use derive_builder::Builder;
use fs_mistrust::Mistrust;
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::str::FromStr;
@ -197,6 +198,7 @@ where
/// dropped when the program exits, to flush buffered messages.
fn logfile_layer<S>(
config: &LogfileConfig,
mistrust: &Mistrust,
) -> Result<(impl Layer<S> + Send + Sync + Sized, WorkerGuard)>
where
S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span> + Send + Sync,
@ -214,6 +216,7 @@ where
};
let path = config.path.path()?;
let directory = path.parent().unwrap_or_else(|| Path::new("."));
mistrust.make_directory(directory)?;
let fname = path
.file_name()
.ok_or_else(|| anyhow!("No path for log file"))
@ -229,7 +232,10 @@ where
///
/// On success, return that layer along with a list of [`WorkerGuard`]s that
/// need to be dropped when the program exits.
fn logfile_layers<S>(config: &LoggingConfig) -> Result<(impl Layer<S>, Vec<WorkerGuard>)>
fn logfile_layers<S>(
config: &LoggingConfig,
mistrust: &Mistrust,
) -> Result<(impl Layer<S>, Vec<WorkerGuard>)>
where
S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span> + Send + Sync,
{
@ -240,7 +246,7 @@ where
return Ok((None, guards));
}
let (layer, guard) = logfile_layer(&config.files[0])?;
let (layer, guard) = logfile_layer(&config.files[0], mistrust)?;
guards.push(guard);
// We have to use a dyn pointer here so we can build up linked list of
@ -248,7 +254,7 @@ where
let mut layer: Box<dyn Layer<S> + Send + Sync + 'static> = Box::new(layer);
for logfile in &config.files[1..] {
let (new_layer, guard) = logfile_layer(logfile)?;
let (new_layer, guard) = logfile_layer(logfile, mistrust)?;
layer = Box::new(layer.and_then(new_layer));
guards.push(guard);
}
@ -272,7 +278,11 @@ pub struct LogGuards {
///
/// Note that the returned LogGuard must be dropped precisely when the program
/// quits; they're used to ensure that all the log messages are flushed.
pub fn setup_logging(config: &LoggingConfig, cli: Option<&str>) -> Result<LogGuards> {
pub fn setup_logging(
config: &LoggingConfig,
mistrust: &Mistrust,
cli: Option<&str>,
) -> Result<LogGuards> {
// Important: We have to make sure that the individual layers we add here
// are not filters themselves. That means, for example, that we can't add
// an `EnvFilter` layer unless we want it to apply globally to _all_ layers.
@ -286,7 +296,7 @@ pub fn setup_logging(config: &LoggingConfig, cli: Option<&str>) -> Result<LogGua
#[cfg(feature = "journald")]
let registry = registry.with(journald_layer(config)?);
let (layer, guards) = logfile_layers(config)?;
let (layer, guards) = logfile_layers(config, mistrust)?;
let registry = registry.with(layer);
registry.init();

View File

@ -14,8 +14,11 @@ repository = "https://gitlab.torproject.org/tpo/core/arti.git/"
default = ["walkdir"]
[dependencies]
derive_builder = { version = "0.11", package = "derive_builder_fork_arti" }
educe = "0.4.6"
thiserror = "1"
walkdir = { version = "2", optional = true }
serde = { version = "1.0.103", features = ["derive"], optional = true }
[target.'cfg(unix)'.dependencies]
libc = "0.2"
@ -24,4 +27,6 @@ once_cell = "1"
[dev-dependencies]
anyhow = "1.0.23"
serde_json = "1.0.50"
tempfile = "3"
toml = "0.5"

View File

@ -52,7 +52,7 @@ impl CheckedDir {
// TODO:
// * If `path` is a prefix of the original ignored path, this will
// make us ignore _less_.
mistrust.ignore_prefix(path)?;
mistrust.ignore_prefix = crate::canonicalize_opt_prefix(&Some(Some(path.to_path_buf())))?;
Ok(CheckedDir {
mistrust,
location: path.to_path_buf(),
@ -268,8 +268,10 @@ mod test {
d.chmod("a/b/c/f2", 0o666);
d.chmod("a/b/d/f3", 0o600);
let mut m = Mistrust::new();
m.ignore_prefix(d.canonical_root()).unwrap();
let m = Mistrust::builder()
.ignore_prefix(d.canonical_root())
.build()
.unwrap();
let sd = m.verifier().secure_dir(d.path("a/b")).unwrap();
@ -314,8 +316,10 @@ mod test {
d.dir("a");
d.chmod("a", 0o700);
let mut m = Mistrust::new();
m.ignore_prefix(d.canonical_root()).unwrap();
let m = Mistrust::builder()
.ignore_prefix(d.canonical_root())
.build()
.unwrap();
let sd = m.verifier().secure_dir(d.path("a")).unwrap();
@ -332,8 +336,10 @@ mod test {
let d = Dir::new();
d.dir("a");
d.chmod("a", 0o700);
let mut m = Mistrust::new();
m.ignore_prefix(d.canonical_root()).unwrap();
let m = Mistrust::builder()
.ignore_prefix(d.canonical_root())
.build()
.unwrap();
let checked = m.verifier().secure_dir(d.path("a")).unwrap();

View File

@ -95,6 +95,19 @@ pub enum Error {
/// file.
#[error("Could not name temporary file for {0}")]
NoTempFile(PathBuf),
/// A field was missing when we tried to construct a
/// [`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 {
@ -134,6 +147,9 @@ impl Error {
Error::InvalidSubdirectory => return None,
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(),
)
@ -156,7 +172,10 @@ impl Error {
| Error::Listing(_)
| Error::InvalidSubdirectory
| Error::Io(_, _)
| Error::NoTempFile(_) => false,
| Error::NoTempFile(_)
| 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(),

View File

@ -42,7 +42,7 @@ impl<'a> super::Verifier<'a> {
// to the code. It's not urgent, since the allocations won't cost much
// compared to the filesystem access.
pub(crate) fn check_errors(&self, path: &Path) -> impl Iterator<Item = Error> + '_ {
if self.mistrust.disable_ownership_and_permission_checks {
if self.mistrust.dangerously_trust_everyone {
// We don't want to walk the path in this case at all: we'll just
// look at the last element.
@ -88,7 +88,7 @@ impl<'a> super::Verifier<'a> {
pub(crate) fn check_content_errors(&self, path: &Path) -> impl Iterator<Item = Error> + '_ {
use std::sync::Arc;
if !self.check_contents || self.mistrust.disable_ownership_and_permission_checks {
if !self.check_contents || self.mistrust.dangerously_trust_everyone {
return boxed(std::iter::empty());
}
@ -179,7 +179,7 @@ impl<'a> super::Verifier<'a> {
// about a directory, the owner cah change the permissions and owner
// of anything in the directory.)
let uid = meta.uid();
if uid != 0 && Some(uid) != self.mistrust.trust_uid {
if uid != 0 && Some(uid) != self.mistrust.trust_user {
errors.push(Error::BadOwner(path.into(), uid));
}
let mut forbidden_bits = if !self.readable_okay && path_type == PathType::Final {
@ -211,7 +211,7 @@ impl<'a> super::Verifier<'a> {
}
};
// If we trust the GID, then we allow even more bits to be set.
if self.mistrust.trust_gid == Some(meta.gid()) {
if self.mistrust.trust_group == Some(meta.gid()) {
forbidden_bits &= !0o070;
}
let bad_bits = meta.mode() & forbidden_bits;

View File

@ -113,13 +113,12 @@
//! # fn example() -> fs_mistrust::Result<()> {
//! use fs_mistrust::Mistrust;
//!
//! let mut my_mistrust = Mistrust::new();
//!
//! // Assume that our home directory and its parents are all well-configured.
//! my_mistrust.ignore_prefix("/home/doze/")?;
//!
//! // Assume that a given group will only contain trusted users.
//! my_mistrust.trust_group_id(413);
//! let my_mistrust = Mistrust::builder()
//! // Assume that our home directory and its parents are all well-configured.
//! .ignore_prefix("/home/doze/")
//! // Assume that a given group will only contain trusted users.
//! .trust_group(413)
//! .build()?;
//! # Ok(())
//! # }
//! ```
@ -284,6 +283,8 @@ mod user;
pub(crate) mod testing;
pub mod walk;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::{
fs::DirBuilder,
path::{Path, PathBuf},
@ -296,6 +297,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
@ -312,35 +316,113 @@ pub type Result<T> = std::result::Result<T, Error>;
///
/// * support more kinds of trust configuration, including more trusted users,
/// trusted groups, multiple trusted directories, etc?
#[derive(Debug, Clone)]
#[derive(Debug, Clone, derive_builder::Builder, Eq, PartialEq)]
#[cfg_attr(feature = "serde", builder(derive(Debug, Serialize, Deserialize)))]
#[cfg_attr(not(feature = "serde"), builder(derive(Debug)))]
#[builder(build_fn(error = "Error"))]
#[cfg_attr(feature = "serde", builder_struct_attr(serde(default)))]
pub struct Mistrust {
/// If the user called [`Mistrust::ignore_prefix`], what did they give us?
/// If the user called [`MistrustBuilder::ignore_prefix`], what did they give us?
///
/// (This is stored in canonical form.)
#[builder(
setter(into, strip_option),
field(build = "canonicalize_opt_prefix(&self.ignore_prefix)?")
)]
ignore_prefix: Option<PathBuf>,
/// Are we configured to enable all permission and ownership tests?
disable_ownership_and_permission_checks: bool,
#[builder(default, setter(custom))]
dangerously_trust_everyone: bool,
/// What user ID do we trust by default (if any?)
#[cfg(target_family = "unix")]
trust_uid: Option<u32>,
#[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")]
trust_gid: Option<u32>,
#[builder(
setter(into),
field(type = "TrustedGroup", build = "self.trust_group.get_gid()?")
)]
trust_group: Option<u32>,
}
/// Compute the canonical prefix for a given path prefix.
///
/// The funny types here are used to please derive_builder.
#[allow(clippy::option_option)]
fn canonicalize_opt_prefix(prefix: &Option<Option<PathBuf>>) -> Result<Option<PathBuf>> {
match prefix {
Some(Some(path)) => Ok(Some(
path.canonicalize()
.map_err(|e| Error::inspecting(e, path))?,
)),
_ => Ok(None),
}
// TODO: Permit "not found?" .
}
impl MistrustBuilder {
/// Configure this `Mistrust` to trust only the admin (root) user.
///
/// By default, both the currently running user and the root user will be
/// trusted.
///
/// 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 = TrustedUser::None;
self.trust_group = TrustedGroup::None;
self
}
/// Configure this `Mistrust` to trust no groups at all.
///
/// By default, we trust the group (if any) with the same name as the
/// current user if we are currently running as a member of that group.
///
/// With this option set, no group is trusted, and and any group-readable or
/// group-writable objects are treated the same as world-readable and
/// world-writable objects respectively.
#[cfg(target_family = "unix")]
pub fn trust_no_group_id(&mut self) -> &mut Self {
self.trust_group = TrustedGroup::None;
self
}
/// Configure this `Mistrust` to trust every user and every group.
///
/// With this option set, every file and directory is treated as having
/// valid permissions: even world-writeable files are allowed. File-type
/// checks are still performed.
///
/// This option is mainly useful to handle cases where you want to make
/// these checks optional, and still use [`CheckedDir`] without having to
/// implement separate code paths for the "checking on" and "checking off"
/// cases.
pub fn dangerously_trust_everyone(&mut self) -> &mut Self {
self.dangerously_trust_everyone = Some(true);
self
}
/// Remove any ignored prefix, restoring this [`MistrustBuilder`] to a state
/// as if [`MistrustBuilder::ignore_prefix`] had not been called.
pub fn remove_ignored_prefix(&mut self) -> &mut Self {
self.ignore_prefix = Some(None);
self
}
}
impl Default for Mistrust {
fn default() -> Self {
Self {
ignore_prefix: None,
disable_ownership_and_permission_checks: false,
#[cfg(target_family = "unix")]
trust_uid: Some(unsafe { libc::getuid() }),
#[cfg(target_family = "unix")]
trust_gid: user::get_self_named_gid(),
}
MistrustBuilder::default()
.build()
.expect("Could not build default")
}
}
@ -383,6 +465,11 @@ enum Type {
}
impl Mistrust {
/// Return a new [`MistrustBuilder`].
pub fn builder() -> MistrustBuilder {
MistrustBuilder::default()
}
/// Initialize a new default `Mistrust`.
///
/// By default:
@ -391,83 +478,16 @@ impl Mistrust {
Self::default()
}
/// Set a path as an "ignored prefix" for all of our checks.
/// Construct a new `Mistrust` that trusts all users and all groups.
///
/// Any path that is a part of this prefix will be _assumed_ to have valid
/// permissions and ownership. For example, if you call
/// `ignore_prefix("/u1/users")`, then we will not check `/`, `/u1`, or
/// `/u1/users`.
///
/// A typical use of this function is to ignore `${HOME}/..`.
///
/// If this directory cannot be found or resolved, this function will return
/// an error.
pub fn ignore_prefix<P: AsRef<Path>>(&mut self, directory: P) -> Result<&mut Self> {
let directory = directory
.as_ref()
.canonicalize()
.map_err(|e| Error::inspecting(e, directory.as_ref()))?;
// TODO: Permit "not found?" . Use "walkdir" to do a more tolerant canonicalization?
self.ignore_prefix = Some(directory);
Ok(self)
}
/// Configure this `Mistrust` to trust only the admin (root) user.
///
/// By default, both the currently running user and the root user will be
/// trusted.
///
/// 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_uid = None;
self.trust_gid = None;
self
}
/// Configure this `Mistrust` to trust no groups at all.
///
/// By default, we trust the group (if any) with the same name as the
/// current user if we are currently running as a member of that group.
///
/// With this option set, no group is trusted, and and any group-readable or
/// group-writable objects are treated the same as world-readable and
/// world-writable objects respectively.
#[cfg(target_family = "unix")]
pub fn trust_no_group_id(&mut self) -> &mut Self {
self.trust_gid = None;
self
}
#[cfg(target_family = "unix")]
/// Configure a trusted group ID for this `Mistrust`.
///
/// If a group ID is considered "trusted", then any file or directory we
/// inspect is allowed to be readable and writable by that group.
///
/// By default, we trust the group (if any) with the same name as the
/// current user, if we are currently running as a member of that group.
///
/// Anybody who is a member (or becomes a member) of the provided group will
/// be allowed to read and modify the verified files.
pub fn trust_group_id(&mut self, gid: u32) -> &mut Self {
self.trust_gid = Some(gid);
self
}
/// Configure this `Mistrust` to trust every user and every group.
///
/// With this option set, every file and directory is treated as having
/// valid permissions: even world-writeable files are allowed. File-type
/// checks are still performed.
///
/// This option is mainly useful to handle cases where you want to make
/// these checks optional, and still use [`CheckedDir`] without having to
/// implement separate code paths for the "checking on" and "checking off"
/// cases.
pub fn dangerously_trust_everyone(&mut self) -> &mut Self {
self.disable_ownership_and_permission_checks = true;
self
/// (In effect, this `Mistrust` will have all of its permissions checks
/// disabled, since if all users and groups are trusted, it doesn't matter
/// what the permissions on any file and directory are.)
pub fn new_dangerously_trust_everyone() -> Self {
Self::builder()
.dangerously_trust_everyone()
.build()
.expect("Could not construct a Mistrust")
}
/// Create a new [`Verifier`] with this configuration, to perform a single check.
@ -687,10 +707,12 @@ mod test {
d.chmod("e", 0o755);
d.chmod("e/f", 0o777);
let mut m = Mistrust::new();
m.trust_no_group_id();
// Ignore the permissions on /tmp/whatever-tempdir-gave-us
m.ignore_prefix(d.canonical_root()).unwrap();
let m = Mistrust::builder()
.trust_no_group_id()
// Ignore the permissions on /tmp/whatever-tempdir-gave-us
.ignore_prefix(d.canonical_root())
.build()
.unwrap();
// /a/b/c should be fine...
m.check_directory(d.path("a/b/c")).unwrap();
// /e/f/g should not.
@ -714,13 +736,20 @@ mod test {
return;
}
let mut m = Mistrust::new();
m.ignore_prefix(d.canonical_root()).unwrap();
// With normal settings should be okay...
let m = Mistrust::builder()
.ignore_prefix(d.canonical_root())
.build()
.unwrap();
m.check_directory(d.path("a/b")).unwrap();
// With admin_only, it'll fail.
m.trust_admin_only();
let m = Mistrust::builder()
.ignore_prefix(d.canonical_root())
.trust_admin_only()
.build()
.unwrap();
let err = m.check_directory(d.path("a/b")).unwrap_err();
assert!(matches!(err, Error::BadOwner(_, _)));
assert_eq!(err.path().unwrap(), d.path("a").canonicalize().unwrap());
@ -734,9 +763,11 @@ mod test {
d.chmod("a", 0o700);
d.chmod("b", 0o600);
let mut m = Mistrust::new();
m.ignore_prefix(d.canonical_root()).unwrap();
m.trust_no_group_id();
let m = Mistrust::builder()
.ignore_prefix(d.canonical_root())
.trust_no_group_id()
.build()
.unwrap();
// If we insist stuff is its own type, it works fine.
m.verifier().require_directory().check(d.path("a")).unwrap();
@ -767,9 +798,11 @@ mod test {
d.chmod("a/b", 0o750);
d.chmod("a/b/c", 0o640);
let mut m = Mistrust::new();
m.ignore_prefix(d.canonical_root()).unwrap();
m.trust_no_group_id();
let m = Mistrust::builder()
.ignore_prefix(d.canonical_root())
.trust_no_group_id()
.build()
.unwrap();
// These will fail, since the file or directory is readable.
let e = m.verifier().check(d.path("a/b")).unwrap_err();
@ -794,9 +827,11 @@ mod test {
d.chmod("a", 0o700);
d.chmod("a/b", 0o700);
let mut m = Mistrust::new();
m.ignore_prefix(d.canonical_root()).unwrap();
m.trust_no_group_id();
let m = Mistrust::builder()
.ignore_prefix(d.canonical_root())
.trust_no_group_id()
.build()
.unwrap();
// Only one error occurs, so we get that error.
let e = m
@ -830,8 +865,10 @@ mod test {
d.chmod("a/b", 0o755);
d.chmod("a/b/c", 0o700);
let mut m = Mistrust::new();
m.ignore_prefix(d.canonical_root()).unwrap();
let m = Mistrust::builder()
.ignore_prefix(d.canonical_root())
.build()
.unwrap();
// `a` is world-writable, so the first check will fail.
m.check_directory(d.path("a/b/c")).unwrap_err();
@ -857,9 +894,11 @@ mod test {
d.chmod("a", 0o770);
d.chmod("a/b", 0o770);
let mut m = Mistrust::new();
m.ignore_prefix(d.canonical_root()).unwrap();
m.trust_no_group_id();
let m = Mistrust::builder()
.ignore_prefix(d.canonical_root())
.trust_no_group_id()
.build()
.unwrap();
// By default, we shouldn't be accept this directory, since it is
// group-writable.
@ -869,12 +908,21 @@ mod test {
// But we can make the group trusted, which will make it okay for the
// directory to be group-writable.
let gid = d.path("a/b").metadata().unwrap().gid();
m.trust_group_id(gid);
let m = Mistrust::builder()
.ignore_prefix(d.canonical_root())
.trust_group(gid)
.build()
.unwrap();
m.check_directory(d.path("a/b")).unwrap();
// OTOH, if we made a _different_ group trusted, it'll fail.
let m = Mistrust::builder()
.ignore_prefix(d.canonical_root())
.trust_group(gid ^ 1)
.build()
.unwrap();
m.trust_group_id(gid ^ 1);
let e = m.check_directory(d.path("a/b")).unwrap_err();
assert!(matches!(e, Error::BadPermission(_, _)));
}
@ -884,8 +932,10 @@ mod test {
let d = Dir::new();
d.dir("a/b");
let mut m = Mistrust::new();
m.ignore_prefix(d.canonical_root()).unwrap();
let m = Mistrust::builder()
.ignore_prefix(d.canonical_root())
.build()
.unwrap();
#[cfg(target_family = "unix")]
{
@ -919,8 +969,10 @@ mod test {
d.chmod("a/b/c", 0o755);
d.chmod("a/b/c/d", 0o666);
let mut m = Mistrust::new();
m.ignore_prefix(d.canonical_root()).unwrap();
let m = Mistrust::builder()
.ignore_prefix(d.canonical_root())
.build()
.unwrap();
// A check should work...
m.check_directory(d.path("a/b")).unwrap();
@ -949,8 +1001,10 @@ mod test {
d.chmod("a/b/c", 0o777);
d.chmod("a/b/c/d", 0o666);
let mut m = Mistrust::new();
m.dangerously_trust_everyone();
let m = Mistrust::builder()
.dangerously_trust_everyone()
.build()
.unwrap();
// This is fine.
m.check_directory(d.path("a/b/c")).unwrap();
@ -965,6 +1019,12 @@ mod test {
.unwrap();
}
#[test]
fn default_mistrust() {
// we can't test a mistrust without ignore_prefix, but we should make sure that we can build one.
let _m = Mistrust::default();
}
// TODO: Write far more tests.
// * Can there be a test for a failed readlink()? I can't see an easy way
// to provoke that without trying to make a time-of-check/time-of-use race

View File

@ -1,7 +1,14 @@
//! Code to inspect user db information on unix.
#[cfg(feature = "serde")]
mod serde_support;
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,15 +17,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()));
/// 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)?;
@ -84,6 +82,170 @@ fn cur_groups() -> Vec<u32> {
buf
}
/// A user that we can be configured to trust.
///
/// # Serde support
///
/// If this crate is build with the `serde1` feature enabled, you can serialize
/// and deserialize this type from any of the following:
///
/// * `false` and the string `":none"` correspond to `TrustedUser::None`.
/// * The string `":current"` and the map `{ special = ":current" }` correspond
/// to `TrustedUser::Current`.
/// * A numeric value (e.g., `413`) and the map `{ id = 413 }` correspond to
/// `TrustedUser::Id(413)`.
/// * A string not starting with `:` (e.g., "jane") and the map `{ name = "jane" }`
/// correspond to `TrustedUser::Name("jane".into())`.
#[derive(Clone, Debug, educe::Educe, Eq, PartialEq)]
#[educe(Default)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(try_from = "serde_support::Serde", into = "serde_support::Serde")
)]
#[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.
///
/// # Serde support
///
/// See the `serde support` section in [`TrustedUser`]. Additionally,
/// you can represent `TrustedGroup::SelfNamed` with the string `":username"`
/// or the map `{ special = ":username" }`.
#[derive(Clone, Debug, educe::Educe, Eq, PartialEq)]
#[educe(Default)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(try_from = "serde_support::Serde", into = "serde_support::Serde")
)]
#[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)]
@ -204,4 +366,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());
}
}

View File

@ -0,0 +1,274 @@
//! Serde support for [`TrustedUser`] and [`TrustedGroup`].
use super::{TrustedGroup, TrustedUser};
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, ffi::OsString};
/// Helper type: when encoding or decoding a group or user, we do so as one of
/// these.
///
/// It's an `untagged` enumeration, so every case must be uniquely identifiable
/// by type or by keywords.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub(super) enum Serde {
/// A boolean value.
///
/// "false" means "no user", and is the same as "none".
///
/// "true" is not allowed.
Bool(bool),
/// A string given in quotes.
///
/// If this starts with ":" it will be interpreted as a special entity (e.g.
/// ":current" or ":username"). Otherwise, it will be interpreted as a name.
///
Str(String),
/// An integer provided without any identification.
///
/// This will be interpreted as a UID or GID.
Num(u32),
/// A name, explicitly qualified as such.
Name {
/// The name in question.
///
/// Even if this begins with ":", it is still interpreted as a name.
name: String,
},
/// A username that cannot be represented as a String.
Raw {
/// The username in question.
raw_name: OsString,
},
/// A special entity.
Special {
/// The name of the special entity. Starts with ":".
special: String,
},
/// A UID or GID, explicitly qualified as such.
Id {
/// The UID or GID.
id: u32,
},
}
impl Serde {
/// Convert this [`Serde`] into a less ambiguous form.
///
/// Removes all Num and Str cases from the output, replacing them with
/// Special/Name/Id as appropriate.
fn disambiguate(self) -> Self {
match self {
Serde::Str(s) if s.starts_with(':') => Self::Special { special: s },
Serde::Str(s) => Self::Name { name: s },
Serde::Num(id) => Self::Id { id },
other => other,
}
}
}
/// Helper: declare
macro_rules! implement_serde {
{ $struct:ident { $( $case:ident => $str:expr, )* [ $errcase:ident ] } } => {
impl $struct {
/// Try to decode a "special-user" string from `s`, for serde.
fn from_special_str(s: &str) -> Result<Self, crate::Error> {
match s {
$( $str => Ok($struct::$case), )*
_ => Err(crate::Error::$errcase(s.to_owned())),
}
}
fn from_boolean(b: bool) -> Result<Self, crate::Error> {
if b {
Err(crate::Error::$errcase("'true'".into()))
} else {
Self::from_special_str(":none")
}
}
}
impl From<$struct> for Serde {
fn from(value: $struct) -> Self {
match value {
$struct::Id(id) => Self::Num(id),
$struct::Name(name) => {
if let Some(name) = name.to_str() {
let name = name.to_string();
if name.starts_with(':') {
Self::Name { name }
} else {
Self::Str(name)
}
} else {
Self::Raw { raw_name: name }
}
}
$(
$struct::$case => Self::Str($str.to_owned())
),*
}
}
}
impl TryFrom<Serde> for $struct {
type Error = crate::Error;
fn try_from(ent: Serde) -> Result<Self, Self::Error> {
Ok(match ent.disambiguate() {
Serde::Str(_) | Serde::Num(_) => {
panic!("These should have been caught by disambiguate.")
}
Serde::Bool(b) => $struct::from_boolean(b)?,
Serde::Name { name } => $struct::Name(name.into()),
Serde::Raw { raw_name } => $struct::Name(raw_name),
Serde::Special { special } => {
$struct::from_special_str(special.as_ref())?
}
Serde::Id { id } => $struct::Id(id),
})
}
}
}}
implement_serde! { TrustedUser {
None => ":none",
Current => ":current",
[NoSuchUser]
}}
implement_serde! { TrustedGroup {
None => ":none",
SelfNamed => ":username",
[NoSuchGroup]
}}
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]
use super::*;
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
struct Chum {
handle: TrustedUser,
team: TrustedGroup,
}
#[test]
fn round_trips() {
let examples: Vec<(&'static str, &'static str, Chum)> = vec![
(
r#"handle = "gardenGnostic"
team = 413
"#,
r#"{ "handle": "gardenGnostic", "team": 413 }"#,
Chum {
handle: TrustedUser::Name("gardenGnostic".into()),
team: TrustedGroup::Id(413),
},
),
(
r#"handle = "413"
team = false
"#,
r#"{ "handle": "413", "team": false }"#,
Chum {
handle: TrustedUser::Name("413".into()),
team: TrustedGroup::None,
},
),
(
r#"handle = { id = 8 }
team = { name = "flarp" }
"#,
r#"{ "handle": { "id": 8 }, "team" : { "name" : "flarp" } }"#,
Chum {
handle: TrustedUser::Id(8),
team: TrustedGroup::Name("flarp".into()),
},
),
(
r#"handle = ":current"
team = ":username"
"#,
r#"{ "handle": ":current", "team" : ":username" }"#,
Chum {
handle: TrustedUser::Current,
team: TrustedGroup::SelfNamed,
},
),
(
r#"handle = { special = ":none" }
team = { special = ":none" }
"#,
r#"{ "handle": {"special" : ":none"}, "team" : { "special" : ":none"} }"#,
Chum {
handle: TrustedUser::None,
team: TrustedGroup::None,
},
),
(
r#"handle = { name = ":none" }
team = { name = ":none" }
"#,
r#"{ "handle": {"name" : ":none"}, "team" : { "name" : ":none"} }"#,
Chum {
handle: TrustedUser::Name(":none".into()),
team: TrustedGroup::Name(":none".into()),
},
),
];
for (toml_string, json_string, chum) in examples {
let toml_obj: Chum = toml::from_str(toml_string).unwrap();
let json_obj: Chum = serde_json::from_str(json_string).unwrap();
assert_eq!(&toml_obj, &chum);
assert_eq!(&json_obj, &chum);
let s = toml::to_string(&chum).unwrap();
let toml_obj2: Chum = toml::from_str(&s).unwrap();
assert_eq!(&toml_obj2, &chum);
let s = serde_json::to_string(&chum).unwrap();
let json_obj2: Chum = serde_json::from_str(&s).unwrap();
assert_eq!(&json_obj2, &chum);
}
}
#[cfg(target_family = "unix")]
#[test]
fn os_string() {
// Try round-tripping a username that isn't UTF8.
use std::os::unix::ffi::OsStringExt as _;
let not_utf8 = OsString::from_vec(vec![1, 0, 0, 1]);
let chum = Chum {
handle: TrustedUser::Name(not_utf8.clone()),
team: TrustedGroup::Name(not_utf8),
};
let s = toml::to_string(&chum).unwrap();
let toml_obj: Chum = toml::from_str(&s).unwrap();
assert_eq!(&toml_obj, &chum);
let s = serde_json::to_string(&chum).unwrap();
let toml_obj: Chum = serde_json::from_str(&s).unwrap();
assert_eq!(&toml_obj, &chum);
}
#[test]
fn bad_names() {
let s = r#"handle = 413
team = false"#;
let r: Result<Chum, _> = toml::from_str(s);
assert!(r.is_ok());
let s = r#"handle = true
team = false"#;
let r: Result<Chum, _> = toml::from_str(s);
assert!(r.is_err());
let s = r#"handle = ":foo"
team = false"#;
let r: Result<Chum, _> = toml::from_str(s);
assert!(r.is_err());
}
}

View File

@ -198,8 +198,8 @@ friends = 4242
files: &[(P, MustRead)],
opts: &[String],
) -> Result<config::Config, config::ConfigError> {
let mut mistrust = fs_mistrust::Mistrust::new();
mistrust.dangerously_trust_everyone();
let mistrust = fs_mistrust::Mistrust::new_dangerously_trust_everyone();
add_sources(config::Config::builder(), &mistrust, files, opts)
.unwrap()
.build()

View File

@ -1254,7 +1254,7 @@ mod test {
let store = crate::storage::SqliteStore::from_path_and_mistrust(
tempdir.path(),
fs_mistrust::Mistrust::new().dangerously_trust_everyone(),
&fs_mistrust::Mistrust::new_dangerously_trust_everyone(),
false,
)
.unwrap();

View File

@ -861,8 +861,10 @@ mod test {
let tmp_dir = tempdir().unwrap();
let sql_path = tmp_dir.path().join("db.sql");
let conn = rusqlite::Connection::open(&sql_path)?;
let blob_dir = fs_mistrust::Mistrust::new()
let blob_dir = fs_mistrust::Mistrust::builder()
.dangerously_trust_everyone()
.build()
.unwrap()
.verifier()
.secure_dir(&tmp_dir)
.unwrap();
@ -874,8 +876,10 @@ mod test {
#[test]
fn init() -> Result<()> {
let tmp_dir = tempdir().unwrap();
let blob_dir = fs_mistrust::Mistrust::new()
let blob_dir = fs_mistrust::Mistrust::builder()
.dangerously_trust_everyone()
.build()
.unwrap()
.verifier()
.secure_dir(&tmp_dir)
.unwrap();
@ -1188,11 +1192,7 @@ mod test {
#[test]
fn from_path_rw() -> Result<()> {
let tmp = tempdir().unwrap();
let mistrust = {
let mut m = fs_mistrust::Mistrust::new();
m.dangerously_trust_everyone();
m
};
let mistrust = fs_mistrust::Mistrust::new_dangerously_trust_everyone();
// Nothing there: can't open read-only
let r = SqliteStore::from_path_and_mistrust(tmp.path(), &mistrust, true);

View File

@ -87,7 +87,7 @@ impl FsStateMgr {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
Self::from_path_and_mistrust(
path,
fs_mistrust::Mistrust::new().dangerously_trust_everyone(),
&fs_mistrust::Mistrust::new_dangerously_trust_everyone(),
)
}

View File

@ -39,6 +39,7 @@ DEPRECATION: arti-config is to be abolished. Currently it is merely an empty to
### fs-mistrust
MODIFIED: New APIs for CachedDir, Error.
BREAKING: New builder API.
### tor-dirclient