1//! Types and functions to configure a Tor Relay.
2//!
3//! NOTE: At the moment, only StorageConfig is implemented but as we ramp up arti relay
4//! implementation, more configurations will show up.
56use std::borrow::Cow;
7use std::path::PathBuf;
89use derive_builder::Builder;
10use derive_more::AsRef;
1112use directories::ProjectDirs;
13use fs_mistrust::{Mistrust, MistrustBuilder};
14use once_cell::sync::Lazy;
15use serde::{Deserialize, Serialize};
16use tor_chanmgr::{ChannelConfig, ChannelConfigBuilder};
17use tor_config::{impl_standard_builder, mistrust::BuilderExt, ConfigBuildError};
18use tor_config_path::{CfgPath, CfgPathError, CfgPathResolver};
19use tor_keymgr::config::{ArtiKeystoreConfig, ArtiKeystoreConfigBuilder};
20use tracing::metadata::Level;
21use tracing_subscriber::filter::EnvFilter;
2223/// Paths used for default configuration files.
24pub(crate) fn default_config_paths() -> Result<Vec<PathBuf>, CfgPathError> {
25// the base path resolver includes the 'ARTI_RELAY_CONFIG' variable
26let resolver = base_resolver();
27 [
28"${ARTI_RELAY_CONFIG}/arti-relay.toml",
29"${ARTI_RELAY_CONFIG}/arti-relay.d/",
30 ]
31 .into_iter()
32 .map(|f| CfgPath::new(f.into()).path(&resolver))
33 .collect()
34}
3536/// A [`CfgPathResolver`] with the base variables configured for a Tor relay.
37///
38/// A relay should have a single `CfgPathResolver` that is passed around where needed to ensure that
39/// all parts of the relay are resolving paths consistently using the same variables.
40/// If you need to resolve a path,
41/// you likely want a reference to the existing resolver,
42/// and not to create a new one here.
43///
44/// The supported variables are:
45/// - `ARTI_RELAY_CACHE`:
46/// An arti-specific cache directory.
47/// - `ARTI_RELAY_CONFIG`:
48/// An arti-specific configuration directory.
49/// - `ARTI_RELAY_SHARED_DATA`:
50/// An arti-specific directory in the user's "shared data" space.
51/// - `ARTI_RELAY_LOCAL_DATA`:
52/// An arti-specific directory in the user's "local data" space.
53/// - `PROGRAM_DIR`:
54/// The directory of the currently executing binary.
55/// See documentation for [`std::env::current_exe`] for security notes.
56/// - `USER_HOME`:
57/// The user's home directory.
58///
59/// These variables are implemented using the [`directories`] crate,
60/// and so should use appropriate system-specific overrides under the hood.
61/// (Some of those overrides are based on environment variables.)
62/// For more information, see that crate's documentation.
63pub(crate) fn base_resolver() -> CfgPathResolver {
64let arti_relay_cache = project_dirs().map(|x| Cow::Owned(x.cache_dir().to_owned()));
65let arti_relay_config = project_dirs().map(|x| Cow::Owned(x.config_dir().to_owned()));
66let arti_relay_shared_data = project_dirs().map(|x| Cow::Owned(x.data_dir().to_owned()));
67let arti_relay_local_data = project_dirs().map(|x| Cow::Owned(x.data_local_dir().to_owned()));
68let program_dir = get_program_dir().map(Cow::Owned);
69let user_home = tor_config_path::home().map(Cow::Borrowed);
7071let mut resolver = CfgPathResolver::default();
7273 resolver.set_var("ARTI_RELAY_CACHE", arti_relay_cache);
74 resolver.set_var("ARTI_RELAY_CONFIG", arti_relay_config);
75 resolver.set_var("ARTI_RELAY_SHARED_DATA", arti_relay_shared_data);
76 resolver.set_var("ARTI_RELAY_LOCAL_DATA", arti_relay_local_data);
77 resolver.set_var("PROGRAM_DIR", program_dir);
78 resolver.set_var("USER_HOME", user_home);
7980 resolver
81}
8283/// The directory holding the currently executing program.
84fn get_program_dir() -> Result<PathBuf, CfgPathError> {
85let binary = std::env::current_exe().map_err(|_| CfgPathError::NoProgramPath)?;
86let directory = binary.parent().ok_or(CfgPathError::NoProgramDir)?;
87Ok(directory.to_owned())
88}
8990/// A `ProjectDirs` object for Arti relays.
91fn project_dirs() -> Result<&'static ProjectDirs, CfgPathError> {
92/// lazy cell holding the ProjectDirs object.
93static PROJECT_DIRS: Lazy<Option<ProjectDirs>> =
94 Lazy::new(|| ProjectDirs::from("org", "torproject", "Arti-Relay"));
9596 PROJECT_DIRS.as_ref().ok_or(CfgPathError::NoProjectDirs)
97}
9899/// A configuration used by a TorRelay.
100///
101/// Most users will create a TorRelayConfig by running
102/// [`TorRelayConfig::default`].
103///
104/// Finally, you can get fine-grained control over the members of a
105/// TorRelayConfig using [`TorRelayConfigBuilder`].
106#[derive(Clone, Builder, Debug, Eq, PartialEq, AsRef)]
107#[builder(build_fn(error = "ConfigBuildError"))]
108#[builder(derive(Serialize, Deserialize, Debug))]
109#[non_exhaustive]
110pub(crate) struct TorRelayConfig {
111/// Logging configuration
112#[builder(sub_builder)]
113 #[builder_field_attr(serde(default))]
114pub(crate) logging: LoggingConfig,
115116/// Directories for storing information on disk
117#[builder(sub_builder)]
118 #[builder_field_attr(serde(default))]
119pub(crate) storage: StorageConfig,
120121/// Information about how to build paths through the network.
122#[builder(sub_builder)]
123 #[builder_field_attr(serde(default))]
124pub(crate) channel: ChannelConfig,
125}
126impl_standard_builder! { TorRelayConfig }
127128impl tor_config::load::TopLevel for TorRelayConfig {
129type Builder = TorRelayConfigBuilder;
130}
131132/// Default log level.
133pub(crate) const DEFAULT_LOG_LEVEL: Level = Level::INFO;
134135/// Logging configuration options.
136#[derive(Debug, Clone, Builder, Eq, PartialEq)]
137#[builder(build_fn(error = "ConfigBuildError", validate = "Self::validate"))]
138#[builder(derive(Debug, Serialize, Deserialize))]
139#[non_exhaustive]
140pub(crate) struct LoggingConfig {
141/// Filtering directives that determine tracing levels as described at
142 /// <https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/targets/struct.Targets.html#impl-FromStr-for-Targets>
143 ///
144 /// You can override this setting with the `-l`, `--log-level` command line parameter.
145 ///
146 /// Example: "info,tor_proto::channel=trace"
147#[builder(default = "DEFAULT_LOG_LEVEL.to_string()", setter(into))]
148pub(crate) console: String,
149}
150151impl LoggingConfigBuilder {
152/// Validate the options provided to the builder.
153fn validate(&self) -> Result<(), ConfigBuildError> {
154if let Some(console) = &self.console {
155 EnvFilter::builder()
156 .parse(console)
157 .map_err(|e| ConfigBuildError::Invalid {
158 field: "console".to_string(),
159 problem: e.to_string(),
160 })?;
161 }
162Ok(())
163 }
164}
165166/// Configuration for where information should be stored on disk.
167///
168/// By default, cache information will be stored in `${ARTI_RELAY_CACHE}`, and
169/// persistent state will be stored in `${ARTI_RELAY_LOCAL_DATA}`. That means that
170/// _all_ programs using these defaults will share their cache and state data.
171/// If that isn't what you want, you'll need to override these directories.
172///
173/// On unix, the default directories will typically expand to `~/.cache/arti`
174/// and `~/.local/share/arti/` respectively, depending on the user's
175/// environment. Other platforms will also use suitable defaults. For more
176/// information, see the documentation for [`CfgPath`].
177///
178/// This section is for read/write storage.
179///
180/// You cannot change this section on a running relay.
181#[derive(Debug, Clone, Builder, Eq, PartialEq)]
182#[builder(build_fn(error = "ConfigBuildError"))]
183#[builder(derive(Debug, Serialize, Deserialize))]
184#[non_exhaustive]
185pub(crate) struct StorageConfig {
186/// Location on disk for cached information.
187 ///
188 /// This follows the rules for `/var/cache`: "sufficiently old" filesystem objects
189 /// in it may be deleted outside of the control of Arti,
190 /// and Arti will continue to function properly.
191 /// It is also fine to delete the directory as a whole, while Arti is not running.
192//
193 // Usage note, for implementations of Arti components:
194 //
195 // When files in this directory are to be used by a component, the cache_dir
196 // value should be passed through to the component as-is, and the component is
197 // then responsible for constructing an appropriate sub-path (for example,
198 // tor-dirmgr receives cache_dir, and appends components such as "dir_blobs".
199 //
200 // (This consistency rule is not current always followed by every component.)
201#[builder(setter(into), default = "default_cache_dir()")]
202cache_dir: CfgPath,
203204/// Location on disk for less-sensitive persistent state information.
205// Usage note: see the note for `cache_dir`, above.
206#[builder(setter(into), default = "default_state_dir()")]
207state_dir: CfgPath,
208209/// Location on disk for the Arti keystore.
210#[builder(sub_builder)]
211 #[builder_field_attr(serde(default))]
212keystore: ArtiKeystoreConfig,
213214/// Configuration about which permissions we want to enforce on our files.
215#[builder(sub_builder(fn_name = "build_for_arti"))]
216 #[builder_field_attr(serde(default))]
217permissions: Mistrust,
218}
219impl_standard_builder! { StorageConfig }
220221impl StorageConfig {
222/// Return the FS permissions to use for state and cache directories.
223pub(crate) fn permissions(&self) -> &Mistrust {
224&self.permissions
225 }
226227/// Return the fully expanded path of the keystore directory.
228pub(crate) fn keystore_dir(
229&self,
230 resolver: &CfgPathResolver,
231 ) -> Result<PathBuf, ConfigBuildError> {
232Ok(self
233.state_dir
234 .path(resolver)
235 .map_err(|e| ConfigBuildError::Invalid {
236 field: "state_dir".to_owned(),
237 problem: e.to_string(),
238 })?
239.join("keystore"))
240 }
241}
242243/// Return the default cache directory.
244fn default_cache_dir() -> CfgPath {
245 CfgPath::new("${ARTI_RELAY_CACHE}".to_owned())
246}
247248/// Return the default state directory.
249fn default_state_dir() -> CfgPath {
250 CfgPath::new("${ARTI_RELAY_LOCAL_DATA}".to_owned())
251}
252253#[cfg(test)]
254mod test {
255// @@ begin test lint list maintained by maint/add_warning @@
256#![allow(clippy::bool_assert_comparison)]
257 #![allow(clippy::clone_on_copy)]
258 #![allow(clippy::dbg_macro)]
259 #![allow(clippy::mixed_attributes_style)]
260 #![allow(clippy::print_stderr)]
261 #![allow(clippy::print_stdout)]
262 #![allow(clippy::single_char_pattern)]
263 #![allow(clippy::unwrap_used)]
264 #![allow(clippy::unchecked_duration_subtraction)]
265 #![allow(clippy::useless_vec)]
266 #![allow(clippy::needless_pass_by_value)]
267//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
268269use super::*;
270271#[test]
272fn defaults() {
273let dflt = TorRelayConfig::default();
274let b2 = TorRelayConfigBuilder::default();
275let dflt2 = b2.build().unwrap();
276assert_eq!(&dflt, &dflt2);
277 }
278279#[test]
280fn builder() {
281let mut bld = TorRelayConfigBuilder::default();
282 bld.storage()
283 .cache_dir(CfgPath::new("/var/tmp/foo".to_owned()))
284 .state_dir(CfgPath::new("/var/tmp/bar".to_owned()));
285286let val = bld.build().unwrap();
287288assert_ne!(val, TorRelayConfig::default());
289 }
290291fn cfg_variables() -> impl IntoIterator<Item = (&'static str, PathBuf)> {
292let project_dirs = project_dirs().unwrap();
293let list = [
294 ("ARTI_RELAY_CACHE", project_dirs.cache_dir()),
295 ("ARTI_RELAY_CONFIG", project_dirs.config_dir()),
296 ("ARTI_RELAY_SHARED_DATA", project_dirs.data_dir()),
297 ("ARTI_RELAY_LOCAL_DATA", project_dirs.data_local_dir()),
298 ("PROGRAM_DIR", &get_program_dir().unwrap()),
299 ("USER_HOME", tor_config_path::home().unwrap()),
300 ];
301302 list.into_iter()
303 .map(|(a, b)| (a, b.to_owned()))
304 .collect::<Vec<_>>()
305 }
306307#[cfg(not(target_family = "windows"))]
308 #[test]
309fn expand_variables() {
310let path_resolver = base_resolver();
311312for (var, val) in cfg_variables() {
313let p = CfgPath::new(format!("${{{var}}}/example"));
314assert_eq!(p.to_string(), format!("${{{var}}}/example"));
315316let expected = val.join("example");
317assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
318 }
319320let p = CfgPath::new("${NOT_A_REAL_VAR}/example".to_string());
321assert!(p.path(&path_resolver).is_err());
322 }
323324#[cfg(target_family = "windows")]
325 #[test]
326fn expand_variables() {
327let path_resolver = base_resolver();
328329for (var, val) in cfg_variables() {
330let p = CfgPath::new(format!("${{{var}}}\\example"));
331assert_eq!(p.to_string(), format!("${{{var}}}\\example"));
332333let expected = val.join("example");
334assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
335 }
336337let p = CfgPath::new("${NOT_A_REAL_VAR}\\example".to_string());
338assert!(p.path(&path_resolver).is_err());
339 }
340}