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.
5

            
6
use std::borrow::Cow;
7
use std::path::PathBuf;
8

            
9
use derive_builder::Builder;
10
use derive_more::AsRef;
11

            
12
use directories::ProjectDirs;
13
use fs_mistrust::{Mistrust, MistrustBuilder};
14
use once_cell::sync::Lazy;
15
use serde::{Deserialize, Serialize};
16
use tor_chanmgr::{ChannelConfig, ChannelConfigBuilder};
17
use tor_config::{impl_standard_builder, mistrust::BuilderExt, ConfigBuildError};
18
use tor_config_path::{CfgPath, CfgPathError, CfgPathResolver};
19
use tor_keymgr::config::{ArtiKeystoreConfig, ArtiKeystoreConfigBuilder};
20
use tracing::metadata::Level;
21
use tracing_subscriber::filter::EnvFilter;
22

            
23
/// Paths used for default configuration files.
24
2
pub(crate) fn default_config_paths() -> Result<Vec<PathBuf>, CfgPathError> {
25
2
    // the base path resolver includes the 'ARTI_RELAY_CONFIG' variable
26
2
    let resolver = base_resolver();
27
2
    [
28
2
        "${ARTI_RELAY_CONFIG}/arti-relay.toml",
29
2
        "${ARTI_RELAY_CONFIG}/arti-relay.d/",
30
2
    ]
31
2
    .into_iter()
32
5
    .map(|f| CfgPath::new(f.into()).path(&resolver))
33
2
    .collect()
34
2
}
35

            
36
/// 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.
63
4
pub(crate) fn base_resolver() -> CfgPathResolver {
64
6
    let arti_relay_cache = project_dirs().map(|x| Cow::Owned(x.cache_dir().to_owned()));
65
6
    let arti_relay_config = project_dirs().map(|x| Cow::Owned(x.config_dir().to_owned()));
66
6
    let arti_relay_shared_data = project_dirs().map(|x| Cow::Owned(x.data_dir().to_owned()));
67
6
    let arti_relay_local_data = project_dirs().map(|x| Cow::Owned(x.data_local_dir().to_owned()));
68
4
    let program_dir = get_program_dir().map(Cow::Owned);
69
4
    let user_home = tor_config_path::home().map(Cow::Borrowed);
70
4

            
71
4
    let mut resolver = CfgPathResolver::default();
72
4

            
73
4
    resolver.set_var("ARTI_RELAY_CACHE", arti_relay_cache);
74
4
    resolver.set_var("ARTI_RELAY_CONFIG", arti_relay_config);
75
4
    resolver.set_var("ARTI_RELAY_SHARED_DATA", arti_relay_shared_data);
76
4
    resolver.set_var("ARTI_RELAY_LOCAL_DATA", arti_relay_local_data);
77
4
    resolver.set_var("PROGRAM_DIR", program_dir);
78
4
    resolver.set_var("USER_HOME", user_home);
79
4

            
80
4
    resolver
81
4
}
82

            
83
/// The directory holding the currently executing program.
84
6
fn get_program_dir() -> Result<PathBuf, CfgPathError> {
85
6
    let binary = std::env::current_exe().map_err(|_| CfgPathError::NoProgramPath)?;
86
6
    let directory = binary.parent().ok_or(CfgPathError::NoProgramDir)?;
87
6
    Ok(directory.to_owned())
88
6
}
89

            
90
/// A `ProjectDirs` object for Arti relays.
91
18
fn project_dirs() -> Result<&'static ProjectDirs, CfgPathError> {
92
    /// lazy cell holding the ProjectDirs object.
93
    static PROJECT_DIRS: Lazy<Option<ProjectDirs>> =
94
2
        Lazy::new(|| ProjectDirs::from("org", "torproject", "Arti-Relay"));
95

            
96
18
    PROJECT_DIRS.as_ref().ok_or(CfgPathError::NoProjectDirs)
97
18
}
98

            
99
/// 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
22
#[derive(Clone, Builder, Debug, Eq, PartialEq, AsRef)]
107
#[builder(build_fn(error = "ConfigBuildError"))]
108
#[builder(derive(Serialize, Deserialize, Debug))]
109
#[non_exhaustive]
110
pub(crate) struct TorRelayConfig {
111
    /// Logging configuration
112
    #[builder(sub_builder)]
113
    #[builder_field_attr(serde(default))]
114
    pub(crate) logging: LoggingConfig,
115

            
116
    /// Directories for storing information on disk
117
    #[builder(sub_builder)]
118
    #[builder_field_attr(serde(default))]
119
    pub(crate) storage: StorageConfig,
120

            
121
    /// Information about how to build paths through the network.
122
    #[builder(sub_builder)]
123
    #[builder_field_attr(serde(default))]
124
    pub(crate) channel: ChannelConfig,
125
}
126
impl_standard_builder! { TorRelayConfig }
127

            
128
impl tor_config::load::TopLevel for TorRelayConfig {
129
    type Builder = TorRelayConfigBuilder;
130
}
131

            
132
/// Default log level.
133
pub(crate) const DEFAULT_LOG_LEVEL: Level = Level::INFO;
134

            
135
/// Logging configuration options.
136
24
#[derive(Debug, Clone, Builder, Eq, PartialEq)]
137
#[builder(build_fn(error = "ConfigBuildError", validate = "Self::validate"))]
138
#[builder(derive(Debug, Serialize, Deserialize))]
139
#[non_exhaustive]
140
pub(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))]
148
    pub(crate) console: String,
149
}
150

            
151
impl LoggingConfigBuilder {
152
    /// Validate the options provided to the builder.
153
12
    fn validate(&self) -> Result<(), ConfigBuildError> {
154
12
        if 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
12
        }
162
12
        Ok(())
163
12
    }
164
}
165

            
166
/// 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
32
#[derive(Debug, Clone, Builder, Eq, PartialEq)]
182
#[builder(build_fn(error = "ConfigBuildError"))]
183
#[builder(derive(Debug, Serialize, Deserialize))]
184
#[non_exhaustive]
185
pub(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()")]
202
    cache_dir: CfgPath,
203

            
204
    /// 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()")]
207
    state_dir: CfgPath,
208

            
209
    /// Location on disk for the Arti keystore.
210
    #[builder(sub_builder)]
211
    #[builder_field_attr(serde(default))]
212
    keystore: ArtiKeystoreConfig,
213

            
214
    /// 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))]
217
    permissions: Mistrust,
218
}
219
impl_standard_builder! { StorageConfig }
220

            
221
impl StorageConfig {
222
    /// Return the FS permissions to use for state and cache directories.
223
    pub(crate) fn permissions(&self) -> &Mistrust {
224
        &self.permissions
225
    }
226

            
227
    /// Return the fully expanded path of the keystore directory.
228
    pub(crate) fn keystore_dir(
229
        &self,
230
        resolver: &CfgPathResolver,
231
    ) -> Result<PathBuf, ConfigBuildError> {
232
        Ok(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
}
242

            
243
/// Return the default cache directory.
244
14
fn default_cache_dir() -> CfgPath {
245
14
    CfgPath::new("${ARTI_RELAY_CACHE}".to_owned())
246
14
}
247

            
248
/// Return the default state directory.
249
14
fn default_state_dir() -> CfgPath {
250
14
    CfgPath::new("${ARTI_RELAY_LOCAL_DATA}".to_owned())
251
14
}
252

            
253
#[cfg(test)]
254
mod 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 @@ -->
268

            
269
    use super::*;
270

            
271
    #[test]
272
    fn defaults() {
273
        let dflt = TorRelayConfig::default();
274
        let b2 = TorRelayConfigBuilder::default();
275
        let dflt2 = b2.build().unwrap();
276
        assert_eq!(&dflt, &dflt2);
277
    }
278

            
279
    #[test]
280
    fn builder() {
281
        let 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()));
285

            
286
        let val = bld.build().unwrap();
287

            
288
        assert_ne!(val, TorRelayConfig::default());
289
    }
290

            
291
    fn cfg_variables() -> impl IntoIterator<Item = (&'static str, PathBuf)> {
292
        let project_dirs = project_dirs().unwrap();
293
        let 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
        ];
301

            
302
        list.into_iter()
303
            .map(|(a, b)| (a, b.to_owned()))
304
            .collect::<Vec<_>>()
305
    }
306

            
307
    #[cfg(not(target_family = "windows"))]
308
    #[test]
309
    fn expand_variables() {
310
        let path_resolver = base_resolver();
311

            
312
        for (var, val) in cfg_variables() {
313
            let p = CfgPath::new(format!("${{{var}}}/example"));
314
            assert_eq!(p.to_string(), format!("${{{var}}}/example"));
315

            
316
            let expected = val.join("example");
317
            assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
318
        }
319

            
320
        let p = CfgPath::new("${NOT_A_REAL_VAR}/example".to_string());
321
        assert!(p.path(&path_resolver).is_err());
322
    }
323

            
324
    #[cfg(target_family = "windows")]
325
    #[test]
326
    fn expand_variables() {
327
        let path_resolver = base_resolver();
328

            
329
        for (var, val) in cfg_variables() {
330
            let p = CfgPath::new(format!("${{{var}}}\\example"));
331
            assert_eq!(p.to_string(), format!("${{{var}}}\\example"));
332

            
333
            let expected = val.join("example");
334
            assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
335
        }
336

            
337
        let p = CfgPath::new("${NOT_A_REAL_VAR}\\example".to_string());
338
        assert!(p.path(&path_resolver).is_err());
339
    }
340
}