1
//! Types and functions to configure a Tor Relay.
2

            
3
// TODO: It would be nice to remove the builder aspect of these config objects, as we don't need
4
// them for arti-relay. But I don't think we can do so while still using tor-config. See:
5
// https://gitlab.torproject.org/tpo/core/arti/-/issues/2253
6

            
7
mod listen;
8

            
9
use std::borrow::Cow;
10
use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
11
use std::path::PathBuf;
12

            
13
use derive_deftly::Deftly;
14
use derive_more::AsRef;
15
use directories::ProjectDirs;
16
use fs_mistrust::{Mistrust, MistrustBuilder};
17
use serde::{Deserialize, Serialize};
18
use std::sync::LazyLock;
19
use tor_chanmgr::{ChannelConfig, ChannelConfigBuilder};
20
use tor_circmgr::{CircuitTiming, PathConfig, PreemptiveCircuitConfig};
21
use tor_config::derive::prelude::*;
22
use tor_config::{
23
    ConfigBuildError, ExplicitOrAuto, extend_builder::extend_with_replace, mistrust::BuilderExt,
24
};
25
use tor_config_path::{CfgPath, CfgPathError, CfgPathResolver};
26
use tor_config_shared::metrics::{MetricsConfig, MetricsConfigBuilder};
27
use tor_dircommon::config::{NetworkConfig, NetworkConfigBuilder};
28
use tor_dircommon::fallback::FallbackList;
29
use tor_guardmgr::bridge::BridgeConfig;
30
use tor_guardmgr::{VanguardConfig, VanguardConfigBuilder, VanguardMode};
31
use tor_keymgr::config::{ArtiKeystoreConfig, ArtiKeystoreConfigBuilder};
32
use tracing::metadata::Level;
33
use tracing_subscriber::filter::EnvFilter;
34

            
35
use crate::util::NonEmptyList;
36

            
37
use self::listen::Listen;
38

            
39
/// Paths used for default configuration files.
40
2
pub(crate) fn default_config_paths() -> Result<Vec<PathBuf>, CfgPathError> {
41
    // the base path resolver includes the 'ARTI_RELAY_CONFIG' variable
42
2
    let resolver = base_resolver();
43
2
    [
44
2
        "${ARTI_RELAY_CONFIG}/arti-relay.toml",
45
2
        "${ARTI_RELAY_CONFIG}/arti-relay.d/",
46
2
    ]
47
2
    .into_iter()
48
5
    .map(|f| CfgPath::new(f.into()).path(&resolver))
49
2
    .collect()
50
2
}
51

            
52
/// A [`CfgPathResolver`] with the base variables configured for a Tor relay.
53
///
54
/// A relay should have a single `CfgPathResolver` that is passed around where needed to ensure that
55
/// all parts of the relay are resolving paths consistently using the same variables.
56
/// If you need to resolve a path,
57
/// you likely want a reference to the existing resolver,
58
/// and not to create a new one here.
59
///
60
/// The supported variables are:
61
///   - `ARTI_RELAY_CACHE`:
62
///     An arti-specific cache directory.
63
///   - `ARTI_RELAY_CONFIG`:
64
///     An arti-specific configuration directory.
65
///   - `ARTI_RELAY_LOCAL_DATA`:
66
///     An arti-specific directory in the user's "local data" space.
67
///   - `PROGRAM_DIR`:
68
///     The directory of the currently executing binary.
69
///     See documentation for [`std::env::current_exe`] for security notes.
70
///   - `USER_HOME`:
71
///     The user's home directory.
72
///
73
/// These variables are implemented using the [`directories`] crate,
74
/// and so should use appropriate system-specific overrides under the hood.
75
/// (Some of those overrides are based on environment variables.)
76
/// For more information, see that crate's documentation.
77
//
78
// NOTE: We intentionally don't expose an `ARTI_RELAY_SHARED_DATA`
79
// (analogous to `ARTI_SHARED_DATA` in arti).
80
// This is almost certainly never intended over `ARTI_RELAY_LOCAL_DATA`,
81
// so by removing it we don't need to worry about bugs from mixing them up.
82
// We can introduce it later if really needed.
83
4
pub(crate) fn base_resolver() -> CfgPathResolver {
84
6
    let arti_relay_cache = project_dirs().map(|x| Cow::Owned(x.cache_dir().to_owned()));
85
6
    let arti_relay_config = project_dirs().map(|x| Cow::Owned(x.config_dir().to_owned()));
86
6
    let arti_relay_local_data = project_dirs().map(|x| Cow::Owned(x.data_local_dir().to_owned()));
87
4
    let program_dir = get_program_dir().map(Cow::Owned);
88
4
    let user_home = tor_config_path::home().map(Cow::Borrowed);
89

            
90
4
    let mut resolver = CfgPathResolver::default();
91

            
92
4
    resolver.set_var("ARTI_RELAY_CACHE", arti_relay_cache);
93
4
    resolver.set_var("ARTI_RELAY_CONFIG", arti_relay_config);
94
4
    resolver.set_var("ARTI_RELAY_LOCAL_DATA", arti_relay_local_data);
95
4
    resolver.set_var("PROGRAM_DIR", program_dir);
96
4
    resolver.set_var("USER_HOME", user_home);
97

            
98
4
    resolver
99
4
}
100

            
101
/// The directory holding the currently executing program.
102
6
fn get_program_dir() -> Result<PathBuf, CfgPathError> {
103
6
    let binary = std::env::current_exe().map_err(|_| CfgPathError::NoProgramPath)?;
104
6
    let directory = binary.parent().ok_or(CfgPathError::NoProgramDir)?;
105
6
    Ok(directory.to_owned())
106
6
}
107

            
108
/// A `ProjectDirs` object for Arti relays.
109
14
fn project_dirs() -> Result<&'static ProjectDirs, CfgPathError> {
110
    /// lazy lock holding the ProjectDirs object.
111
    static PROJECT_DIRS: LazyLock<Option<ProjectDirs>> =
112
2
        LazyLock::new(|| ProjectDirs::from("org", "torproject", "Arti-Relay"));
113

            
114
14
    PROJECT_DIRS.as_ref().ok_or(CfgPathError::NoProjectDirs)
115
14
}
116

            
117
/// A configuration used by a TorRelay.
118
///
119
/// This is a builder so that it works with tor-config.
120
/// We don't expect to ever use it as a builder since we don't provide this as a public rust API.
121
#[derive(Clone, Deftly, Debug, Eq, PartialEq, AsRef)]
122
#[derive_deftly(TorConfig)]
123
#[deftly(tor_config(no_default_trait))]
124
#[non_exhaustive]
125
pub(crate) struct TorRelayConfig {
126
    /// Configuration for the "relay" part of the relay.
127
    // TODO: Add a better doc comment here once we figure out exactly how we want the config to be
128
    // structured.
129
    #[deftly(tor_config(sub_builder))]
130
    pub(crate) relay: RelayConfig,
131

            
132
    /// Information about the Tor network we want to connect to.
133
    #[deftly(tor_config(sub_builder))]
134
    pub(crate) tor_network: NetworkConfig,
135

            
136
    /// Logging configuration
137
    #[deftly(tor_config(sub_builder))]
138
    pub(crate) logging: LoggingConfig,
139

            
140
    /// Metrics configuration
141
    #[deftly(tor_config(sub_builder))]
142
    pub(crate) metrics: MetricsConfig,
143

            
144
    /// Directories for storing information on disk
145
    #[deftly(tor_config(sub_builder))]
146
    pub(crate) storage: StorageConfig,
147

            
148
    /// Information about how to build paths through the network.
149
    #[deftly(tor_config(sub_builder))]
150
    pub(crate) channel: ChannelConfig,
151

            
152
    /// Configuration for system resources
153
    #[deftly(tor_config(sub_builder))]
154
    pub(crate) system: SystemConfig,
155

            
156
    /// Information about how to build paths through the network.
157
    // We don't expose this field in the config.
158
    #[deftly(tor_config(skip, build = "|_| Default::default()"))]
159
    // Needed to implement `CircMgrConfig`.
160
    #[as_ref]
161
    pub(crate) path_rules: PathConfig,
162

            
163
    /// Information about vanguards.
164
    // We don't expose this field in the config.
165
    #[deftly(tor_config(
166
        skip,
167
        build = r#"|_|
168
        VanguardConfigBuilder::default()
169
            .mode(ExplicitOrAuto::Explicit(VanguardMode::Disabled))
170
            .build()
171
            .expect("Could not build a disabled `VanguardConfig`")"#
172
    ))]
173
    // Needed to implement `CircMgrConfig`.
174
    #[as_ref]
175
    pub(crate) vanguards: VanguardConfig,
176

            
177
    /// Information about how to retry and expire circuits and request for circuits.
178
    // We don't expose this field in the config.
179
    #[deftly(tor_config(skip, build = "|_| Default::default()"))]
180
    // Needed to implement `CircMgrConfig`.
181
    #[as_ref]
182
    pub(crate) circuit_timing: CircuitTiming,
183

            
184
    /// Information about preemptive circuits.
185
    // We don't expose this field in the config.
186
    #[deftly(tor_config(skip, build = "|_| Default::default()"))]
187
    // Needed to implement `CircMgrConfig`.
188
    #[as_ref]
189
    pub(crate) preemptive_circuits: PreemptiveCircuitConfig,
190
}
191

            
192
impl tor_config::load::TopLevel for TorRelayConfig {
193
    type Builder = TorRelayConfigBuilder;
194
}
195

            
196
impl tor_circmgr::CircMgrConfig for TorRelayConfig {}
197

            
198
// Needed to implement `GuardMgrConfig`.
199
impl AsRef<FallbackList> for TorRelayConfig {
200
    fn as_ref(&self) -> &FallbackList {
201
        self.tor_network.fallback_caches()
202
    }
203
}
204

            
205
// Needed to implement `GuardMgrConfig`.
206
impl AsRef<[BridgeConfig]> for TorRelayConfig {
207
    fn as_ref(&self) -> &[BridgeConfig] {
208
        // Relays don't use bridges.
209
        &[]
210
    }
211
}
212

            
213
impl tor_guardmgr::GuardMgrConfig for TorRelayConfig {
214
    fn bridges_enabled(&self) -> bool {
215
        // Relays don't use bridges.
216
        false
217
    }
218
}
219

            
220
/// Configuration for the "relay" part of the relay.
221
///
222
/// TODO: I'm not really sure what to call this yet. I'm expecting that we'll rename and reorganize
223
/// things as we add more options. But we should come back to this and update the name and/or doc
224
/// comment.
225
///
226
/// TODO: There's a high-level issue for discussing these options:
227
/// <https://gitlab.torproject.org/tpo/core/arti/-/issues/2252>
228
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
229
#[derive_deftly(TorConfig)]
230
#[deftly(tor_config(no_default_trait))]
231
pub(crate) struct RelayConfig {
232
    /// Addresses to listen on for incoming OR connections.
233
    #[deftly(tor_config(no_default))]
234
    pub(crate) listen: Listen,
235

            
236
    /// Addresses to advertise on the network for receiving OR connections.
237
    // For now, we've decided that we don't want to include any IP address auto-detection in
238
    // arti-relay, so we require users to provide the addresses to advertise. (So no `Option` and
239
    // `builder(default)` here).
240
    #[deftly(tor_config(no_default))]
241
    pub(crate) advertise: Advertise,
242
}
243

            
244
/// The address(es) to advertise on the network.
245
// TODO: We'll want to make sure we check that the addresses are valid before uploading them in a
246
// server descriptor (for example no `INADDR_ANY`, multicast, etc). We can't do that validation here
247
// during parsing, since we don't know exactly which addresses are valid or not. For example we
248
// don't know if local addresses are allowed as we don't know here whether the user plans to run a
249
// testing tor network. We also don't want to do the validation too late (for example when uploading
250
// the server descriptor) as it's better to validate at startup. A better place might be to perform
251
// the validation in the `RelayConfig` builder validate.
252
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
253
pub(crate) struct Advertise {
254
    /// All relays must advertise an IPv4 address.
255
    ipv4: NonEmptyList<SocketAddrV4>,
256
    /// Relays may optionally advertise an IPv6 address.
257
    ipv6: Vec<SocketAddrV6>,
258
}
259

            
260
impl Advertise {
261
    /// Return all addresses (both IPv4 and IPv6) as in IP + Port ([`SocketAddr`]).
262
    pub(crate) fn all_addr(&self) -> Vec<SocketAddr> {
263
        self.ipv4
264
            .iter()
265
            .map(|s| (*s).into())
266
            .chain(self.ipv6.iter().map(|s| (*s).into()))
267
            .collect()
268
    }
269
}
270

            
271
/// Default log level.
272
pub(crate) const DEFAULT_LOG_LEVEL: Level = Level::INFO;
273

            
274
/// Logging configuration options.
275
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
276
#[derive_deftly(TorConfig)]
277
#[deftly(tor_config(pre_build = "Self::validate"))]
278
#[non_exhaustive]
279
pub(crate) struct LoggingConfig {
280
    /// Filtering directives that determine tracing levels as described at
281
    /// <https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/targets/struct.Targets.html#impl-FromStr-for-Targets>
282
    ///
283
    /// You can override this setting with the `-l`, `--log-level` command line parameter.
284
    ///
285
    /// Example: "info,tor_proto::channel=trace"
286
    #[deftly(tor_config(default = "DEFAULT_LOG_LEVEL.to_string()"))]
287
    pub(crate) console: String,
288

            
289
    /// If set to false, we avoid logging sensitive information at level `info` or higher.
290
    /// This `info` level distinction is not enforced through any technical means,
291
    /// but is according to our `doc/dev/Safelogging.md` guidelines.
292
    ///
293
    /// If set to true, we disable safe logging on all logs,
294
    /// and store potentially sensitive information at all log levels.
295
    ///
296
    /// This can be useful for debugging, but it increases the value of your logs to an attacker.
297
    /// Do not turn this on in production unless you have a good log rotation mechanism.
298
    #[deftly(tor_config(default))]
299
    pub(crate) log_sensitive_information: bool,
300

            
301
    /// Configuration for logging spans with OpenTelemetry.
302
    #[deftly(tor_config(
303
        sub_builder,
304
        cfg = r#" feature = "opentelemetry" "#,
305
        cfg_desc = "with opentelemetry support"
306
    ))]
307
    opentelemetry: tor_config_shared::opentelemetry::OpentelemetryConfig,
308
}
309

            
310
impl LoggingConfigBuilder {
311
    /// Validate the options provided to the builder.
312
4
    fn validate(&self) -> Result<(), ConfigBuildError> {
313
4
        if let Some(console) = &self.console {
314
            EnvFilter::builder()
315
                .parse(console)
316
                .map_err(|e| ConfigBuildError::Invalid {
317
                    field: "console".to_string(),
318
                    problem: e.to_string(),
319
                })?;
320
4
        }
321
4
        Ok(())
322
4
    }
323
}
324

            
325
/// Configuration for where information should be stored on disk.
326
///
327
/// By default, cache information will be stored in `${ARTI_RELAY_CACHE}`, and
328
/// persistent state will be stored in `${ARTI_RELAY_LOCAL_DATA}`. That means that
329
/// _all_ programs using these defaults will share their cache and state data.
330
/// If that isn't what you want, you'll need to override these directories.
331
///
332
/// On unix, the default directories will typically expand to `~/.cache/arti`
333
/// and `~/.local/share/arti/` respectively, depending on the user's
334
/// environment. Other platforms will also use suitable defaults. For more
335
/// information, see the documentation for [`CfgPath`].
336
///
337
/// This section is for read/write storage.
338
///
339
/// You cannot change this section on a running relay.
340
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
341
#[derive_deftly(TorConfig)]
342
#[non_exhaustive]
343
pub(crate) struct StorageConfig {
344
    /// Location on disk for cached information.
345
    ///
346
    /// This follows the rules for `/var/cache`: "sufficiently old" filesystem objects
347
    /// in it may be deleted outside of the control of Arti,
348
    /// and Arti will continue to function properly.
349
    /// It is also fine to delete the directory as a whole, while Arti is not running.
350
    ///
351
    /// Should be accessed through the `cache_dir()` getter to provide better error messages when
352
    /// resolving the path.
353
    //
354
    // Usage note, for implementations of Arti components:
355
    //
356
    // When files in this directory are to be used by a component, the cache_dir
357
    // value should be passed through to the component as-is, and the component is
358
    // then responsible for constructing an appropriate sub-path (for example,
359
    // tor-dirmgr receives cache_dir, and appends components such as "dir_blobs".
360
    //
361
    // (This consistency rule is not current always followed by every component.)
362
    #[deftly(tor_config(default = "default_cache_dir()", setter(into)))]
363
    cache_dir: CfgPath,
364

            
365
    /// Location on disk for less-sensitive persistent state information.
366
    ///
367
    /// Should be accessed through the `state_dir()` getter to provide better error messages when
368
    /// resolving the path.
369
    // Usage note: see the note for `cache_dir`, above.
370
    #[deftly(tor_config(default = "default_state_dir()", setter(into)))]
371
    state_dir: CfgPath,
372

            
373
    /// Location on disk for the Arti keystore.
374
    #[deftly(tor_config(sub_builder))]
375
    keystore: ArtiKeystoreConfig,
376

            
377
    /// Configuration about which permissions we want to enforce on our files.
378
    // NOTE: This 'build_for_arti()' hard-codes the config field name as `permissions` and the
379
    // environment variable as `ARTI_FS_DISABLE_PERMISSION_CHECKS`. These things should be
380
    // configured by the application, not lower-level libraries, but some other lower-level
381
    // libraries like `tor-hsservice` also use 'build_for_arti()'. So we're stuck with it for now.
382
    // It might be confusing in the future if relays use some environment variables prefixed with
383
    // "ARTI_" and others with "ARTI_RELAY_", so we should probably stick to just "ARTI_".
384
    #[deftly(tor_config(
385
        sub_builder(build_fn = "build_for_arti"),
386
        extend_with = "extend_with_replace"
387
    ))]
388
    permissions: Mistrust,
389
}
390

            
391
impl StorageConfig {
392
    /// Return the FS permissions to use for state and cache directories.
393
    pub(crate) fn permissions(&self) -> &Mistrust {
394
        &self.permissions
395
    }
396

            
397
    /// Return the fully expanded path of the state directory.
398
    pub(crate) fn state_dir(
399
        &self,
400
        resolver: &CfgPathResolver,
401
    ) -> Result<PathBuf, ConfigBuildError> {
402
        resolve_cfg_path(&self.state_dir, "state_dir", resolver)
403
    }
404

            
405
    /// Return the fully expanded path of the cache directory.
406
    pub(crate) fn cache_dir(
407
        &self,
408
        resolver: &CfgPathResolver,
409
    ) -> Result<PathBuf, ConfigBuildError> {
410
        resolve_cfg_path(&self.cache_dir, "cache_dir", resolver)
411
    }
412
}
413

            
414
/// Configuration for system resources used by the relay.
415
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
416
#[derive_deftly(TorConfig)]
417
#[non_exhaustive]
418
pub(crate) struct SystemConfig {
419
    /// Memory limits (approximate)
420
    #[deftly(tor_config(sub_builder))]
421
    pub(crate) memory: tor_memquota::Config,
422
}
423

            
424
/// Return the default cache directory.
425
4
fn default_cache_dir() -> CfgPath {
426
4
    CfgPath::new("${ARTI_RELAY_CACHE}".to_owned())
427
4
}
428

            
429
/// Return the default state directory.
430
4
fn default_state_dir() -> CfgPath {
431
4
    CfgPath::new("${ARTI_RELAY_LOCAL_DATA}".to_owned())
432
4
}
433

            
434
/// Helper to return a `ConfigBuildError` if the path could not be resolved.
435
fn resolve_cfg_path(
436
    path: &CfgPath,
437
    name: &str,
438
    resolver: &CfgPathResolver,
439
) -> Result<PathBuf, ConfigBuildError> {
440
    path.path(resolver).map_err(|e| ConfigBuildError::Invalid {
441
        field: name.to_owned(),
442
        problem: e.to_string(),
443
    })
444
}
445

            
446
#[cfg(test)]
447
mod test {
448
    // @@ begin test lint list maintained by maint/add_warning @@
449
    #![allow(clippy::bool_assert_comparison)]
450
    #![allow(clippy::clone_on_copy)]
451
    #![allow(clippy::dbg_macro)]
452
    #![allow(clippy::mixed_attributes_style)]
453
    #![allow(clippy::print_stderr)]
454
    #![allow(clippy::print_stdout)]
455
    #![allow(clippy::single_char_pattern)]
456
    #![allow(clippy::unwrap_used)]
457
    #![allow(clippy::unchecked_time_subtraction)]
458
    #![allow(clippy::useless_vec)]
459
    #![allow(clippy::needless_pass_by_value)]
460
    #![allow(clippy::string_slice)] // See arti#2571
461
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
462

            
463
    use super::*;
464

            
465
    fn cfg_variables() -> impl IntoIterator<Item = (&'static str, PathBuf)> {
466
        let project_dirs = project_dirs().unwrap();
467
        let list = [
468
            ("ARTI_RELAY_CACHE", project_dirs.cache_dir()),
469
            ("ARTI_RELAY_CONFIG", project_dirs.config_dir()),
470
            ("ARTI_RELAY_LOCAL_DATA", project_dirs.data_local_dir()),
471
            ("PROGRAM_DIR", &get_program_dir().unwrap()),
472
            ("USER_HOME", tor_config_path::home().unwrap()),
473
        ];
474

            
475
        list.into_iter()
476
            .map(|(a, b)| (a, b.to_owned()))
477
            .collect::<Vec<_>>()
478
    }
479

            
480
    #[cfg(not(target_family = "windows"))]
481
    #[test]
482
    fn expand_variables() {
483
        let path_resolver = base_resolver();
484

            
485
        for (var, val) in cfg_variables() {
486
            let p = CfgPath::new(format!("${{{var}}}/example"));
487
            assert_eq!(p.to_string(), format!("${{{var}}}/example"));
488

            
489
            let expected = val.join("example");
490
            assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
491
        }
492

            
493
        let p = CfgPath::new("${NOT_A_REAL_VAR}/example".to_string());
494
        assert!(p.path(&path_resolver).is_err());
495
    }
496

            
497
    #[cfg(target_family = "windows")]
498
    #[test]
499
    fn expand_variables() {
500
        let path_resolver = base_resolver();
501

            
502
        for (var, val) in cfg_variables() {
503
            let p = CfgPath::new(format!("${{{var}}}\\example"));
504
            assert_eq!(p.to_string(), format!("${{{var}}}\\example"));
505

            
506
            let expected = val.join("example");
507
            assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
508
        }
509

            
510
        let p = CfgPath::new("${NOT_A_REAL_VAR}\\example".to_string());
511
        assert!(p.path(&path_resolver).is_err());
512
    }
513
}