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 @@
#![deny(clippy::missing_docs_in_private_items)]
use tor_chanmgr::ChanMgr;
use tor_netdir::{fallback::FallbackSet, NetDir};
use tor_netdir::{fallback::FallbackDir, NetDir};
use tor_netdoc::types::policy::PortPolicy;
use tor_proto::circuit::{CircParameters, ClientCirc, UniqId};
use tor_retry::RetryError;
......@@ -82,12 +82,12 @@ static NEXT_PENDING_ID: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Copy, Clone)]
pub enum DirInfo<'a> {
/// A list of fallbacks, for use when we don't know a network directory.
Fallbacks(&'a FallbackSet),
Fallbacks(&'a [FallbackDir]),
/// A complete network directory
Directory(&'a NetDir),
}
impl<'a> Into<DirInfo<'a>> for &'a FallbackSet {
impl<'a> Into<DirInfo<'a>> for &'a [FallbackDir] {
fn into(self) -> DirInfo<'a> {
DirInfo::Fallbacks(self)
}
......
......@@ -3,6 +3,8 @@ use super::*;
use crate::{DirInfo, Error};
use tor_netdir::{Relay, WeightRole};
use rand::seq::SliceRandom;
/// A PathBuilder that can connect to a directory.
pub struct DirPathBuilder {}
......@@ -24,7 +26,7 @@ impl DirPathBuilder {
// TODO: this will need to learn about directory guards.
match netdir {
DirInfo::Fallbacks(f) => {
let relay = f.pick(rng);
let relay = f.choose(rng);
if let Some(r) = relay {
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};
use futures::stream::StreamExt;
use log::{error, info, warn, LevelFilter};
use std::net::{Ipv4Addr, Ipv6Addr};
use std::path::PathBuf;
use std::sync::Arc;
use tor_chanmgr::transport::nativetls::NativeTlsTransport;
use tor_circmgr::TargetPort;
use tor_dirmgr::DirMgr;
use tor_dirmgr::{DirMgr, NetworkConfig};
use tor_proto::circuit::IPVersionPreference;
use tor_socksproto::{SocksCmd, SocksRequest};
......@@ -34,7 +33,11 @@ struct Args {
}
/// 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
/// configuration file or the command line.
......@@ -43,14 +46,14 @@ const ARTI_DEFAULTS: &str = include_str!("./arti_defaults.toml");
/// Expect NO stability here.
#[derive(Deserialize, Debug, Clone)]
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
/// connections.
socks_port: Option<u16>,
/// Whether to log at trace level.
trace: bool,
/// Information about the Tor network we want to connect to.
network: NetworkConfig,
}
fn ip_preference(req: &SocksRequest, addr: &str) -> IPVersionPreference {
......@@ -253,14 +256,8 @@ fn main() -> Result<()> {
simple_logging::log_to_stderr(filt);
let mut dircfg = tor_dirmgr::NetDirConfigBuilder::new();
if let Some(chutney_dir) = config.chutney_dir.as_ref() {
dircfg
.configure_from_chutney(chutney_dir)
.context("Can't extract Chutney network configuration")?;
} else {
dircfg.add_default_authorities();
}
let dircfg = dircfg.finalize();
dircfg.set_network_config(config.network.clone());
let dircfg = dircfg.finalize()?;
tor_rtcompat::task::block_on(async {
let transport = NativeTlsTransport::new();
......
......@@ -36,6 +36,7 @@ log = "0.4.14"
memmap = { version="0.7.0", optional=true }
rand = "0.7.3"
rusqlite = { version = "0.24.2", features = ["chrono"] }
serde = {version="1.0.123", features = ["derive"] }
thiserror = "1.0.23"
[dev-dependencies]
......
......@@ -3,11 +3,12 @@
//! From a client's point of view, an authority's role is to to sign the
//! consensus directory.
use serde::Deserialize;
use tor_llcrypto::pk::rsa::RSAIdentity;
use tor_netdoc::doc::authcert::{AuthCert, AuthCertKeyIds};
/// A single authority that signs a consensus directory.
#[derive(Debug, Clone)]
#[derive(Deserialize, Debug, Clone)]
pub struct Authority {
/// A memorable nickname for this authority.
name: String,
......@@ -30,6 +31,7 @@ impl Authority {
pub fn matches_cert(&self, cert: &AuthCert) -> bool {
&self.v3ident == cert.id_fingerprint()
}
/// Return true if this authority matches a given key ID.
pub fn matches_keyid(&self, id: &AuthCertKeyIds) -> bool {
self.v3ident == id.id_fingerprint
......
......@@ -8,16 +8,29 @@ use crate::storage::legacy::LegacyStore;
use crate::storage::sqlite::SqliteStore;
use crate::Authority;
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 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.
///
/// To create a directory configuration, create one of these,
......@@ -42,7 +55,7 @@ pub struct NetDirConfigBuilder {
/// The fallback directories to use when downloading directory
/// information
fallbacks: Option<FallbackSet>,
fallbacks: Vec<FallbackDir>,
}
/// Configuration type for network directory operations.
......@@ -71,7 +84,7 @@ pub struct NetDirConfig {
/// A set of directories to use for fetching directory info when we
/// don't have any directories yet.
fallbacks: FallbackSet,
fallbacks: Vec<FallbackDir>,
}
impl NetDirConfigBuilder {
......@@ -84,136 +97,14 @@ impl NetDirConfigBuilder {
authorities: Vec::new(),
legacy_cache_path: None,
cache_path: None,
fallbacks: None,
fallbacks: Vec::new(),
}
}
/// Add a single directory authority to this configuration.
///
/// The authority's name is `name`; its identity is given as a
/// hex-encoded RSA identity fingrprint in `ident`.
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(())
/// Set the network information (authorities and fallbacks) from `config`.
pub fn set_network_config(&mut self, config: NetworkConfig) {
self.authorities = config.authority;
self.fallbacks = config.fallback_cache;
}
/// Use `path` as the directory to search for legacy directory files.
......@@ -231,7 +122,7 @@ impl NetDirConfigBuilder {
/// Consume this builder and return a NetDirConfig that can be used
/// to load directories
pub fn finalize(mut self) -> NetDirConfig {
pub fn finalize(mut self) -> Result<NetDirConfig> {
if self.legacy_cache_path.is_none() {
// XXXX use dirs crate?
let mut pb: PathBuf = std::env::var_os("HOME").unwrap().into();
......@@ -247,17 +138,18 @@ impl NetDirConfigBuilder {
}
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);
NetDirConfig {
Ok(NetDirConfig {
authorities: self.authorities,
legacy_cache_path: self.legacy_cache_path,
cache_path: self.cache_path.unwrap(),
fallbacks,
}
fallbacks: self.fallbacks,
})
}
}
......@@ -289,13 +181,7 @@ impl NetDirConfig {
}
/// Return the configured set of fallback directories
pub fn fallbacks(&self) -> &FallbackSet {
&self.fallbacks
}
}
impl From<NetDirConfigBuilder> for NetDirConfig {
fn from(builder: NetDirConfigBuilder) -> NetDirConfig {
builder.finalize()
pub fn fallbacks(&self) -> &[FallbackDir] {
&self.fallbacks[..]
}
}
......@@ -24,4 +24,7 @@ pub enum Error {
/// An updater no longer has anything to update.
#[error("directory updater has shut down")]
UpdaterShutdown,
/// We couldn't configure the network.
#[error("bad network configuration")]
BadNetworkConfig(&'static str),
}
......@@ -43,7 +43,7 @@ use std::sync::Arc;
use std::time::SystemTime;
pub use authority::Authority;
pub use config::{NetDirConfig, NetDirConfigBuilder};
pub use config::{NetDirConfig, NetDirConfigBuilder, NetworkConfig};
pub use err::Error;
pub use updater::DirectoryUpdater;
......
......@@ -20,6 +20,7 @@ hex = "0.4.2"
once_cell = "1.5.2"
rand = "0.7.3"
rusqlite = { version = "0.24.2", features = ["chrono"] }
serde = { version = "1.0.123", features = ["derive"] }
signature = "1.3.0"
thiserror = "1.0.23"
......
......@@ -11,15 +11,13 @@
use tor_llcrypto::pk::ed25519::Ed25519Identity;
use tor_llcrypto::pk::rsa::RSAIdentity;
use once_cell::sync::Lazy;
use serde::Deserialize;
use std::net::SocketAddr;
mod pregen;
/// A directory whose location ships with Tor (or arti), and which we
/// can use for bootstrapping when we don't know anything else about
/// the network.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Deserialize)]
pub struct FallbackDir {
/// RSA identity for the directory relay
rsa_identity: RSAIdentity,
......@@ -55,57 +53,3 @@ impl tor_linkspec::ChanTarget for FallbackDir {
&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
pub fn from_fallbacks<T>(fallbacks: T) -> Self
where
T: IntoIterator<Item = FallbackDir>,
{
let fallbacks = fallbacks.into_iter().collect();
FallbackSet {
fallbacks: Some(fallbacks),
}
}
/// Choose a fallback directory at random.
///
/// TODO: In theory, it would be a good idea to have weights for these.
pub fn pick<'a, R>(&'a self, rng: &mut R) -> Option<&'a FallbackDir>
where
R: rand::RngCore,
{
use rand::seq::SliceRandom;
let slice = self.as_ref();
slice.choose(rng)
}
}
impl Default for FallbackSet {
fn default() -> Self {
FallbackSet::new()
}
}
impl AsRef<[FallbackDir]> for FallbackSet {
fn as_ref(&self) -> &[FallbackDir] {
self.fallbacks.as_ref().unwrap_or(&FALLBACK_DIRS)
}
}
This diff is collapsed.
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment