Commit aeb410b9 authored by Nick Mathewson's avatar Nick Mathewson 🎨
Browse files

Make authorities and fallbacks configurable.

This commit adds configuration options for these values, with the
right defaults, and uses those options instead of built-in functions
to set them.

We also remove the function to extract information from chutney
directories: now that arti is configurable, it can be chutney's job
to make its own network configurations.
parent 59fb158c
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
#![deny(clippy::missing_docs_in_private_items)] #![deny(clippy::missing_docs_in_private_items)]
use tor_chanmgr::ChanMgr; use tor_chanmgr::ChanMgr;
use tor_netdir::{fallback::FallbackSet, NetDir}; use tor_netdir::{fallback::FallbackDir, NetDir};
use tor_netdoc::types::policy::PortPolicy; use tor_netdoc::types::policy::PortPolicy;
use tor_proto::circuit::{CircParameters, ClientCirc, UniqId}; use tor_proto::circuit::{CircParameters, ClientCirc, UniqId};
use tor_retry::RetryError; use tor_retry::RetryError;
...@@ -82,12 +82,12 @@ static NEXT_PENDING_ID: AtomicUsize = AtomicUsize::new(0); ...@@ -82,12 +82,12 @@ static NEXT_PENDING_ID: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum DirInfo<'a> { pub enum DirInfo<'a> {
/// A list of fallbacks, for use when we don't know a network directory. /// A list of fallbacks, for use when we don't know a network directory.
Fallbacks(&'a FallbackSet), Fallbacks(&'a [FallbackDir]),
/// A complete network directory /// A complete network directory
Directory(&'a NetDir), Directory(&'a NetDir),
} }
impl<'a> Into<DirInfo<'a>> for &'a FallbackSet { impl<'a> Into<DirInfo<'a>> for &'a [FallbackDir] {
fn into(self) -> DirInfo<'a> { fn into(self) -> DirInfo<'a> {
DirInfo::Fallbacks(self) DirInfo::Fallbacks(self)
} }
......
...@@ -3,6 +3,8 @@ use super::*; ...@@ -3,6 +3,8 @@ use super::*;
use crate::{DirInfo, Error}; use crate::{DirInfo, Error};
use tor_netdir::{Relay, WeightRole}; use tor_netdir::{Relay, WeightRole};
use rand::seq::SliceRandom;
/// A PathBuilder that can connect to a directory. /// A PathBuilder that can connect to a directory.
pub struct DirPathBuilder {} pub struct DirPathBuilder {}
...@@ -24,7 +26,7 @@ impl DirPathBuilder { ...@@ -24,7 +26,7 @@ impl DirPathBuilder {
// TODO: this will need to learn about directory guards. // TODO: this will need to learn about directory guards.
match netdir { match netdir {
DirInfo::Fallbacks(f) => { DirInfo::Fallbacks(f) => {
let relay = f.pick(rng); let relay = f.choose(rng);
if let Some(r) = relay { if let Some(r) = relay {
return Ok(TorPath::FallbackOneHop(r)); return Ok(TorPath::FallbackOneHop(r));
} }
......
[[network.authority]]
name = "moria1"
v3ident = "D586D18309DED4CD6D57C18FDB97EFA96D330566"
[[network.authority]]
name = "tor26"
v3ident = "14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4"
[[network.authority]]
name = "dizum"
v3ident = "E8A9C45EDE6D711294FADF8E7951F4DE6CA56B58"
[[network.authority]]
name = "gabelmoo"
v3ident = "ED03BB616EB2F60BEC80151114BB25CEF515B226"
[[network.authority]]
name = "dannenberg"
v3ident = "0232AF901C31A04EE9848595AF9BB7620D4C5B2E"
[[network.authority]]
name = "maatuska"
v3ident = "49015F787433103580E3B66A1707A00E60F2D15B"
[[network.authority]]
name = "Faravahar"
v3ident = "EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97"
[[network.authority]]
name = "longclaw"
v3ident = "23D15D965BC35114467363C165C4F724B64B4F66"
[[network.authority]]
name = "bastet"
v3ident = "27102BC123E7AF1D4741AE047E160C91ADC76B21"
This diff is collapsed.
...@@ -7,12 +7,11 @@ use futures::io::{AsyncReadExt, AsyncWriteExt}; ...@@ -7,12 +7,11 @@ use futures::io::{AsyncReadExt, AsyncWriteExt};
use futures::stream::StreamExt; use futures::stream::StreamExt;
use log::{error, info, warn, LevelFilter}; use log::{error, info, warn, LevelFilter};
use std::net::{Ipv4Addr, Ipv6Addr}; use std::net::{Ipv4Addr, Ipv6Addr};
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use tor_chanmgr::transport::nativetls::NativeTlsTransport; use tor_chanmgr::transport::nativetls::NativeTlsTransport;
use tor_circmgr::TargetPort; use tor_circmgr::TargetPort;
use tor_dirmgr::DirMgr; use tor_dirmgr::{DirMgr, NetworkConfig};
use tor_proto::circuit::IPVersionPreference; use tor_proto::circuit::IPVersionPreference;
use tor_socksproto::{SocksCmd, SocksRequest}; use tor_socksproto::{SocksCmd, SocksRequest};
...@@ -34,7 +33,11 @@ struct Args { ...@@ -34,7 +33,11 @@ struct Args {
} }
/// Default options to use for our configuration. /// Default options to use for our configuration.
const ARTI_DEFAULTS: &str = include_str!("./arti_defaults.toml"); const ARTI_DEFAULTS: &str = concat!(
include_str!("./arti_defaults.toml"),
include_str!("./fallback_caches.toml"),
include_str!("./authorities.toml"),
);
/// Structure to hold our configuration options, whether from a /// Structure to hold our configuration options, whether from a
/// configuration file or the command line. /// configuration file or the command line.
...@@ -43,14 +46,14 @@ const ARTI_DEFAULTS: &str = include_str!("./arti_defaults.toml"); ...@@ -43,14 +46,14 @@ const ARTI_DEFAULTS: &str = include_str!("./arti_defaults.toml");
/// Expect NO stability here. /// Expect NO stability here.
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
struct ArtiConfig { struct ArtiConfig {
/// A location for a chutney network whose authorities we should
/// use instead of the default authorities.
chutney_dir: Option<PathBuf>,
/// Port to listen on (at localhost) for incoming SOCKS /// Port to listen on (at localhost) for incoming SOCKS
/// connections. /// connections.
socks_port: Option<u16>, socks_port: Option<u16>,
/// Whether to log at trace level. /// Whether to log at trace level.
trace: bool, trace: bool,
/// Information about the Tor network we want to connect to.
network: NetworkConfig,
} }
fn ip_preference(req: &SocksRequest, addr: &str) -> IPVersionPreference { fn ip_preference(req: &SocksRequest, addr: &str) -> IPVersionPreference {
...@@ -253,14 +256,8 @@ fn main() -> Result<()> { ...@@ -253,14 +256,8 @@ fn main() -> Result<()> {
simple_logging::log_to_stderr(filt); simple_logging::log_to_stderr(filt);
let mut dircfg = tor_dirmgr::NetDirConfigBuilder::new(); let mut dircfg = tor_dirmgr::NetDirConfigBuilder::new();
if let Some(chutney_dir) = config.chutney_dir.as_ref() { dircfg.set_network_config(config.network.clone());
dircfg let dircfg = dircfg.finalize()?;
.configure_from_chutney(chutney_dir)
.context("Can't extract Chutney network configuration")?;
} else {
dircfg.add_default_authorities();
}
let dircfg = dircfg.finalize();
tor_rtcompat::task::block_on(async { tor_rtcompat::task::block_on(async {
let transport = NativeTlsTransport::new(); let transport = NativeTlsTransport::new();
......
...@@ -36,6 +36,7 @@ log = "0.4.14" ...@@ -36,6 +36,7 @@ log = "0.4.14"
memmap = { version="0.7.0", optional=true } memmap = { version="0.7.0", optional=true }
rand = "0.7.3" rand = "0.7.3"
rusqlite = { version = "0.24.2", features = ["chrono"] } rusqlite = { version = "0.24.2", features = ["chrono"] }
serde = {version="1.0.123", features = ["derive"] }
thiserror = "1.0.23" thiserror = "1.0.23"
[dev-dependencies] [dev-dependencies]
......
...@@ -3,11 +3,12 @@ ...@@ -3,11 +3,12 @@
//! From a client's point of view, an authority's role is to to sign the //! From a client's point of view, an authority's role is to to sign the
//! consensus directory. //! consensus directory.
use serde::Deserialize;
use tor_llcrypto::pk::rsa::RSAIdentity; use tor_llcrypto::pk::rsa::RSAIdentity;
use tor_netdoc::doc::authcert::{AuthCert, AuthCertKeyIds}; use tor_netdoc::doc::authcert::{AuthCert, AuthCertKeyIds};
/// A single authority that signs a consensus directory. /// A single authority that signs a consensus directory.
#[derive(Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct Authority { pub struct Authority {
/// A memorable nickname for this authority. /// A memorable nickname for this authority.
name: String, name: String,
...@@ -30,6 +31,7 @@ impl Authority { ...@@ -30,6 +31,7 @@ impl Authority {
pub fn matches_cert(&self, cert: &AuthCert) -> bool { pub fn matches_cert(&self, cert: &AuthCert) -> bool {
&self.v3ident == cert.id_fingerprint() &self.v3ident == cert.id_fingerprint()
} }
/// Return true if this authority matches a given key ID. /// Return true if this authority matches a given key ID.
pub fn matches_keyid(&self, id: &AuthCertKeyIds) -> bool { pub fn matches_keyid(&self, id: &AuthCertKeyIds) -> bool {
self.v3ident == id.id_fingerprint self.v3ident == id.id_fingerprint
......
...@@ -8,16 +8,29 @@ use crate::storage::legacy::LegacyStore; ...@@ -8,16 +8,29 @@ use crate::storage::legacy::LegacyStore;
use crate::storage::sqlite::SqliteStore; use crate::storage::sqlite::SqliteStore;
use crate::Authority; use crate::Authority;
use crate::{Error, Result}; use crate::{Error, Result};
use tor_netdir::fallback::{FallbackDir, FallbackSet}; use tor_netdir::fallback::FallbackDir;
use tor_llcrypto::pk::ed25519::Ed25519Identity;
use tor_llcrypto::pk::rsa::RSAIdentity;
use log::warn;
use std::fs;
use std::net::SocketAddr;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use serde::Deserialize;
/// Configuration information about the Tor network; used as part of
/// Arti's configuration.
// TODO: move this?
#[derive(Deserialize, Debug, Clone)]
pub struct NetworkConfig {
/// List of locations to look in when downloading directory information,
/// if we don't actually have a directory yet.
///
/// (If we do have a chached directory, we use directory caches
/// listed there instead.)
fallback_cache: Vec<FallbackDir>,
/// List of directory authorities which we expect to sign
/// consensus documents.
authority: Vec<Authority>,
}
/// Builder for a NetDirConfig. /// Builder for a NetDirConfig.
/// ///
/// To create a directory configuration, create one of these, /// To create a directory configuration, create one of these,
...@@ -42,7 +55,7 @@ pub struct NetDirConfigBuilder { ...@@ -42,7 +55,7 @@ pub struct NetDirConfigBuilder {
/// The fallback directories to use when downloading directory /// The fallback directories to use when downloading directory
/// information /// information
fallbacks: Option<FallbackSet>, fallbacks: Vec<FallbackDir>,
} }
/// Configuration type for network directory operations. /// Configuration type for network directory operations.
...@@ -71,7 +84,7 @@ pub struct NetDirConfig { ...@@ -71,7 +84,7 @@ pub struct NetDirConfig {
/// A set of directories to use for fetching directory info when we /// A set of directories to use for fetching directory info when we
/// don't have any directories yet. /// don't have any directories yet.
fallbacks: FallbackSet, fallbacks: Vec<FallbackDir>,
} }
impl NetDirConfigBuilder { impl NetDirConfigBuilder {
...@@ -84,136 +97,14 @@ impl NetDirConfigBuilder { ...@@ -84,136 +97,14 @@ impl NetDirConfigBuilder {
authorities: Vec::new(), authorities: Vec::new(),
legacy_cache_path: None, legacy_cache_path: None,
cache_path: None, cache_path: None,
fallbacks: None, fallbacks: Vec::new(),
} }
} }
/// Add a single directory authority to this configuration. /// Set the network information (authorities and fallbacks) from `config`.
/// pub fn set_network_config(&mut self, config: NetworkConfig) {
/// The authority's name is `name`; its identity is given as a self.authorities = config.authority;
/// hex-encoded RSA identity fingrprint in `ident`. self.fallbacks = config.fallback_cache;
pub fn add_authority(&mut self, name: &str, ident: &str) -> Result<()> {
let ident: Vec<u8> =
hex::decode(ident).map_err(|_| Error::BadArgument("bad hex identity"))?;
let v3ident =
RSAIdentity::from_bytes(&ident).ok_or(Error::BadArgument("wrong identity length"))?;
self.authorities
.push(Authority::new(name.to_string(), v3ident));
Ok(())
}
/// Configure the set of fallback directories to be `fallbacks`, instead
/// of the defaults.
pub fn set_fallback_list(&mut self, fallbacks: FallbackSet) {
self.fallbacks = Some(fallbacks);
}
/// Add the default Tor network directory authorities to this
/// configuration.
///
/// This list is added by default if you try to load() without having
/// configured any authorities.
///
/// (List generated August 2020.)
pub fn add_default_authorities(&mut self) {
self.add_authority("moria1", "D586D18309DED4CD6D57C18FDB97EFA96D330566")
.unwrap();
self.add_authority("tor26", "14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4")
.unwrap();
self.add_authority("dizum", "E8A9C45EDE6D711294FADF8E7951F4DE6CA56B58")
.unwrap();
self.add_authority("gabelmoo", "ED03BB616EB2F60BEC80151114BB25CEF515B226")
.unwrap();
self.add_authority("dannenberg", "0232AF901C31A04EE9848595AF9BB7620D4C5B2E")
.unwrap();
self.add_authority("maatuska", "49015F787433103580E3B66A1707A00E60F2D15B")
.unwrap();
self.add_authority("Faravahar", "EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97")
.unwrap();
self.add_authority("longclaw", "23D15D965BC35114467363C165C4F724B64B4F66")
.unwrap();
self.add_authority("bastet", "27102BC123E7AF1D4741AE047E160C91ADC76B21")
.unwrap();
}
/// Read the authorities from a torrc file in a Chutney directory.
///
/// # Limitations
///
/// This function can handle the format for DirAuthority lines
/// that chutney generates now, but that's it. It isn't careful
/// about line continuations.
pub fn configure_from_chutney<P>(&mut self, path: P) -> Result<()>
where
P: AsRef<Path>,
{
use std::io::{self, BufRead};
let pb = path.as_ref().join("000a/torrc"); // Any node directory will do.
let f = fs::File::open(pb)?;
let mut fbinfo: Vec<(SocketAddr, RSAIdentity)> = Vec::new();
// Find the authorities. These will also be the fallbacks.
for line in io::BufReader::new(f).lines() {
let line = line?;
let line = line.trim();
if !line.starts_with("DirAuthority") {
continue;
}
let elts: Vec<_> = line.split_ascii_whitespace().collect();
let name = elts[1];
let orport = elts[2];
let v3ident = elts[4];
if !v3ident.starts_with("v3ident=") || !orport.starts_with("orport=") {
warn!("Chutney torrc not in expected format.");
}
self.add_authority(name, &v3ident[8..])?;
// XXXX These unwraps should turn into errors.
let dir_addr: SocketAddr = elts[5].parse().unwrap();
let port: u16 = orport[7..].parse().unwrap();
let sockaddr = SocketAddr::new(dir_addr.ip(), port);
let rsaident = hex::decode(elts[6]).unwrap();
let rsaident = RSAIdentity::from_bytes(&rsaident[..]).unwrap();
fbinfo.push((sockaddr, rsaident));
}
// Now find the ed identities so we can configure the fallbacks.
let mut fallbacks = Vec::new();
for entry in fs::read_dir(path)? {
let entry = entry?;
if !entry.metadata()?.is_dir() {
continue;
}
let path = entry.path();
let rsapath = path.join("fingerprint");
let edpath = path.join("fingerprint-ed25519");
if !rsapath.exists() || !edpath.exists() {
continue;
}
// XXXX this is ugly
// XXXX These unwraps can be crashy.
let rsa = std::fs::read_to_string(rsapath)?;
let ed = std::fs::read_to_string(edpath)?;
let rsa = rsa.split_ascii_whitespace().nth(1).unwrap();
let ed = ed.split_ascii_whitespace().nth(1).unwrap();
let rsa = hex::decode(rsa).unwrap();
let ed = base64::decode(ed).unwrap();
let rsa = RSAIdentity::from_bytes(&rsa).unwrap();
let ed = Ed25519Identity::from_bytes(&ed).unwrap();
if let Some((sa, _)) = fbinfo.iter().find(|(_, rsaid)| rsaid == &rsa) {
fallbacks.push(FallbackDir::new(rsa, ed, vec![*sa]));
}
}
let fallbacks = FallbackSet::from_fallbacks(fallbacks);
self.set_fallback_list(fallbacks);
Ok(())
} }
/// Use `path` as the directory to search for legacy directory files. /// Use `path` as the directory to search for legacy directory files.
...@@ -231,7 +122,7 @@ impl NetDirConfigBuilder { ...@@ -231,7 +122,7 @@ impl NetDirConfigBuilder {
/// Consume this builder and return a NetDirConfig that can be used /// Consume this builder and return a NetDirConfig that can be used
/// to load directories /// to load directories
pub fn finalize(mut self) -> NetDirConfig { pub fn finalize(mut self) -> Result<NetDirConfig> {
if self.legacy_cache_path.is_none() { if self.legacy_cache_path.is_none() {
// XXXX use dirs crate? // XXXX use dirs crate?
let mut pb: PathBuf = std::env::var_os("HOME").unwrap().into(); let mut pb: PathBuf = std::env::var_os("HOME").unwrap().into();
...@@ -247,17 +138,18 @@ impl NetDirConfigBuilder { ...@@ -247,17 +138,18 @@ impl NetDirConfigBuilder {
} }
if self.authorities.is_empty() { if self.authorities.is_empty() {
self.add_default_authorities(); return Err(Error::BadNetworkConfig("No authorities configured").into());
}
if self.fallbacks.is_empty() {
return Err(Error::BadNetworkConfig("No fallback caches configured").into());
} }
let fallbacks = self.fallbacks.unwrap_or_else(FallbackSet::default); Ok(NetDirConfig {
NetDirConfig {
authorities: self.authorities, authorities: self.authorities,
legacy_cache_path: self.legacy_cache_path, legacy_cache_path: self.legacy_cache_path,
cache_path: self.cache_path.unwrap(), cache_path: self.cache_path.unwrap(),
fallbacks, fallbacks: self.fallbacks,
} })
} }
} }
...@@ -289,13 +181,7 @@ impl NetDirConfig { ...@@ -289,13 +181,7 @@ impl NetDirConfig {
} }
/// Return the configured set of fallback directories /// Return the configured set of fallback directories
pub fn fallbacks(&self) -> &FallbackSet { pub fn fallbacks(&self) -> &[FallbackDir] {
&self.fallbacks &self.fallbacks[..]
}
}
impl From<NetDirConfigBuilder> for NetDirConfig {
fn from(builder: NetDirConfigBuilder) -> NetDirConfig {
builder.finalize()
} }
} }
...@@ -24,4 +24,7 @@ pub enum Error { ...@@ -24,4 +24,7 @@ pub enum Error {
/// An updater no longer has anything to update. /// An updater no longer has anything to update.
#[error("directory updater has shut down")] #[error("directory updater has shut down")]
UpdaterShutdown, UpdaterShutdown,
/// We couldn't configure the network.
#[error("bad network configuration")]
BadNetworkConfig(&'static str),
} }
...@@ -43,7 +43,7 @@ use std::sync::Arc; ...@@ -43,7 +43,7 @@ use std::sync::Arc;
use std::time::SystemTime; use std::time::SystemTime;
pub use authority::Authority; pub use authority::Authority;
pub use config::{NetDirConfig, NetDirConfigBuilder}; pub use config::{NetDirConfig, NetDirConfigBuilder, NetworkConfig};
pub use err::Error; pub use err::Error;
pub use updater::DirectoryUpdater; pub use updater::DirectoryUpdater;
......
...@@ -20,6 +20,7 @@ hex = "0.4.2" ...@@ -20,6 +20,7 @@ hex = "0.4.2"
once_cell = "1.5.2" once_cell = "1.5.2"
rand = "0.7.3" rand = "0.7.3"
rusqlite = { version = "0.24.2", features = ["chrono"] } rusqlite = { version = "0.24.2", features = ["chrono"] }
serde = { version = "1.0.123", features = ["derive"] }
signature = "1.3.0" signature = "1.3.0"
thiserror = "1.0.23" thiserror = "1.0.23"
......
...@@ -11,15 +11,13 @@ ...@@ -11,15 +11,13 @@
use tor_llcrypto::pk::ed25519::Ed25519Identity; use tor_llcrypto::pk::ed25519::Ed25519Identity;
use tor_llcrypto::pk::rsa::RSAIdentity; use tor_llcrypto::pk::rsa::RSAIdentity;
use once_cell::sync::Lazy; use serde::Deserialize;
use std::net::SocketAddr; use std::net::SocketAddr;
mod pregen;
/// A directory whose location ships with Tor (or arti), and which we /// A directory whose location ships with Tor (or arti), and which we
/// can use for bootstrapping when we don't know anything else about /// can use for bootstrapping when we don't know anything else about
/// the network. /// the network.
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize)]
pub struct FallbackDir { pub struct FallbackDir {
/// RSA identity for the directory relay /// RSA identity for the directory relay
rsa_identity: RSAIdentity, rsa_identity: RSAIdentity,
...@@ -55,57 +53,3 @@ impl tor_linkspec::ChanTarget for FallbackDir { ...@@ -55,57 +53,3 @@ impl tor_linkspec::ChanTarget for FallbackDir {
&self.rsa_identity &self.rsa_identity
} }
} }
/// A list of all the built-in fallbacks that we know.
static FALLBACK_DIRS: Lazy<Vec<FallbackDir>> =
Lazy::new(|| pregen::FALLBACKS.iter().map(FallbackDir::from).collect());
/// A set of fallback directories.
///
/// This can either be the default set, or a set provided at runtime.
#[derive(Debug, Clone)]
pub struct FallbackSet {
/// If present a list of all our fallback directories. If absent,
/// we use the default list.
fallbacks: Option<Vec<FallbackDir>>,
}
impl FallbackSet {
/// Construct the default set of fallback directories.
pub fn new() -> Self {
FallbackSet { fallbacks: None }
}
/// Construct a set of caller-provided fallback directories