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:
commit
b055cc27f1
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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()?;
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue