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,
}
}
/// 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]));
fallbacks: Vec::new(),
}
}
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