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 dns;
8
mod listen;
9

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

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

            
36
use crate::util::NonEmptyList;
37

            
38
pub(crate) use dns::{DnsConfig, DnsConfigBuilder};
39
use listen::Listen;
40

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

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

            
92
2
    let mut resolver = CfgPathResolver::default();
93

            
94
2
    resolver.set_var("ARTI_RELAY_CACHE", arti_relay_cache);
95
2
    resolver.set_var("ARTI_RELAY_CONFIG", arti_relay_config);
96
2
    resolver.set_var("ARTI_RELAY_LOCAL_DATA", arti_relay_local_data);
97
2
    resolver.set_var("PROGRAM_DIR", program_dir);
98
2
    resolver.set_var("USER_HOME", user_home);
99

            
100
2
    resolver
101
2
}
102

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

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

            
116
7
    PROJECT_DIRS.as_ref().ok_or(CfgPathError::NoProjectDirs)
117
7
}
118

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

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

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

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

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

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

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

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

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

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

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

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

            
198
impl tor_circmgr::CircMgrConfig for TorRelayConfig {}
199

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

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

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

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

            
238
    /// The DNS configuration.
239
    #[deftly(tor_config(sub_builder))]
240
    pub(crate) dns: DnsConfig,
241

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

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

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

            
277
/// Default log level.
278
pub(crate) const DEFAULT_LOG_LEVEL: Level = Level::INFO;
279

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

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

            
307
    /// Configuration for logging spans with OpenTelemetry.
308
    #[deftly(tor_config(
309
        sub_builder,
310
        cfg = r#" feature = "opentelemetry" "#,
311
        cfg_desc = "with opentelemetry support"
312
    ))]
313
    opentelemetry: tor_config_shared::opentelemetry::OpentelemetryConfig,
314
}
315

            
316
impl LoggingConfigBuilder {
317
    /// Validate the options provided to the builder.
318
2
    fn validate(&self) -> Result<(), ConfigBuildError> {
319
2
        if let Some(console) = &self.console {
320
            EnvFilter::builder()
321
                .parse(console)
322
                .map_err(|e| ConfigBuildError::Invalid {
323
                    field: "console".to_string(),
324
                    problem: e.to_string(),
325
                })?;
326
2
        }
327
2
        Ok(())
328
2
    }
329
}
330

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

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

            
379
    /// Location on disk for the Arti keystore.
380
    #[deftly(tor_config(sub_builder))]
381
    keystore: ArtiKeystoreConfig,
382

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

            
397
impl StorageConfig {
398
    /// Return the FS permissions to use for state and cache directories.
399
    pub(crate) fn permissions(&self) -> &Mistrust {
400
        &self.permissions
401
    }
402

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

            
411
    /// Return the fully expanded path of the cache directory.
412
    pub(crate) fn cache_dir(
413
        &self,
414
        resolver: &CfgPathResolver,
415
    ) -> Result<PathBuf, ConfigBuildError> {
416
        resolve_cfg_path(&self.cache_dir, "cache_dir", resolver)
417
    }
418
}
419

            
420
/// Configuration for system resources used by the relay.
421
#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
422
#[derive_deftly(TorConfig)]
423
#[non_exhaustive]
424
pub(crate) struct SystemConfig {
425
    /// Memory limits (approximate)
426
    #[deftly(tor_config(sub_builder))]
427
    pub(crate) memory: tor_memquota::Config,
428
}
429

            
430
/// Return the default cache directory.
431
2
fn default_cache_dir() -> CfgPath {
432
2
    CfgPath::new("${ARTI_RELAY_CACHE}".to_owned())
433
2
}
434

            
435
/// Return the default state directory.
436
2
fn default_state_dir() -> CfgPath {
437
2
    CfgPath::new("${ARTI_RELAY_LOCAL_DATA}".to_owned())
438
2
}
439

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

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

            
469
    use super::*;
470

            
471
    fn cfg_variables() -> impl IntoIterator<Item = (&'static str, PathBuf)> {
472
        let project_dirs = project_dirs().unwrap();
473
        let list = [
474
            ("ARTI_RELAY_CACHE", project_dirs.cache_dir()),
475
            ("ARTI_RELAY_CONFIG", project_dirs.config_dir()),
476
            ("ARTI_RELAY_LOCAL_DATA", project_dirs.data_local_dir()),
477
            ("PROGRAM_DIR", &get_program_dir().unwrap()),
478
            ("USER_HOME", tor_config_path::home().unwrap()),
479
        ];
480

            
481
        list.into_iter()
482
            .map(|(a, b)| (a, b.to_owned()))
483
            .collect::<Vec<_>>()
484
    }
485

            
486
    #[cfg(not(target_family = "windows"))]
487
    #[test]
488
    fn expand_variables() {
489
        let path_resolver = base_resolver();
490

            
491
        for (var, val) in cfg_variables() {
492
            let p = CfgPath::new(format!("${{{var}}}/example"));
493
            assert_eq!(p.to_string(), format!("${{{var}}}/example"));
494

            
495
            let expected = val.join("example");
496
            assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
497
        }
498

            
499
        let p = CfgPath::new("${NOT_A_REAL_VAR}/example".to_string());
500
        assert!(p.path(&path_resolver).is_err());
501
    }
502

            
503
    #[cfg(target_family = "windows")]
504
    #[test]
505
    fn expand_variables() {
506
        let path_resolver = base_resolver();
507

            
508
        for (var, val) in cfg_variables() {
509
            let p = CfgPath::new(format!("${{{var}}}\\example"));
510
            assert_eq!(p.to_string(), format!("${{{var}}}\\example"));
511

            
512
            let expected = val.join("example");
513
            assert_eq!(p.path(&path_resolver).unwrap().to_str(), expected.to_str());
514
        }
515

            
516
        let p = CfgPath::new("${NOT_A_REAL_VAR}\\example".to_string());
517
        assert!(p.path(&path_resolver).is_err());
518
    }
519
}