Commit 572a857a authored by Nick Mathewson's avatar Nick Mathewson 🥔
Browse files

Option to defer bootstrapping at startup.

This option will primarily be used by integrators who want to modify
the configuration, either directly or via RPC, before launching Arti
completely.
parent cca1e63c
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -33,6 +33,10 @@
# mistake.)
#allow_running_as_root = false

# If true, then we don't connect to the external network until told to do so,
# either by setting this option to `true`, or via the RPC interface.
#defer_bootstrap = false

# Set up the Arti program to run as a proxy.
[proxy]
# Default port to use when listening to SOCKS connections
+10 −0
Original line number Diff line number Diff line
@@ -109,6 +109,15 @@ pub(crate) struct ApplicationConfig {
    /// This has no effect on Windows.
    #[deftly(tor_config(default))]
    pub(crate) allow_running_as_root: bool,

    /// If true, then we do not bootstrap a [`TorClient`](arti_client::TorClient) on startup.
    /// Instead, we defer bootstrapping until _either_ this option is false,
    /// or until an RPC-using application tells us to bootstrap.
    ///
    /// We will still bind to proxy ports at startup, but we won't make any connections
    /// to the network until after we are bootstrapping.
    #[deftly(tor_config(default))]
    pub(crate) defer_bootstrap: bool,
}

/// Configuration for one or more proxy listeners.
@@ -559,6 +568,7 @@ mod test {
                "storage.port_info_file",
                "proxy.socket_send_buf_size",
                "proxy.socket_recv_buf_size",
                "application.defer_bootstrap",
            ],
        );

+17 −0
Original line number Diff line number Diff line
@@ -277,6 +277,17 @@ pub(crate) struct ProxySet<R: Runtime> {
}

impl<R: Runtime> ProxySet<R> {
    /// Create a new empty onion service proxy set.
    ///
    /// We do this when we are running with deferred bootstrapping,
    /// since we can't launch an onion service on an unbootstrapped client.
    pub(crate) fn new_deferred(client: Arc<arti_client::TorClient<R>>) -> Self {
        Self {
            client,
            proxies: Mutex::new(BTreeMap::new()),
        }
    }

    /// Create and launch a set of onion service proxies.
    pub(crate) fn launch_new(
        client: Arc<arti_client::TorClient<R>>,
@@ -372,6 +383,12 @@ impl<R: Runtime> ProxySet<R> {

impl<R: Runtime> crate::reload_cfg::ReconfigurableModule for ProxySet<R> {
    fn reconfigure(&self, new: &crate::ArtiCombinedConfig) -> anyhow::Result<()> {
        if new.0.application().defer_bootstrap {
            // Do not actually launch any onion services unless we are trying
            // to bootstrap the client.
            return Ok(());
        }

        ProxySet::reconfigure(self, new.0.onion_services.clone())?;
        Ok(())
    }
+60 −4
Original line number Diff line number Diff line
//! Code to watch configuration files for any changes.

use std::sync::Weak;
use std::sync::{Arc, Mutex, Weak};
use std::time::Duration;

use anyhow::Context;
@@ -164,6 +164,7 @@ async fn reload_configuration<R: Runtime>(
    modules: &[Weak<dyn ReconfigurableModule>],
    tx: FileEventSender,
) -> anyhow::Result<Option<FileWatcher>> {
    // TODO RPC: Take 'how' as an argument.
    let found_files = if watcher.is_some() {
        let mut new_watcher = FileWatcher::builder(runtime.clone());
        let found_files = prepare(&mut new_watcher, sources)
@@ -203,10 +204,66 @@ async fn reload_configuration<R: Runtime>(
    Ok(watcher)
}

impl<R: Runtime> ReconfigurableModule for TorClient<R> {
/// A TorClient that we may or may not have told to start bootstrapping.
pub(crate) struct LaunchableTorClient<R: Runtime> {
    /// Original value of defer_bootstrap.
    orig_defer_bootstrap: bool,

    /// True if we have launched bootstrapping on the the client.
    have_launched: Mutex<bool>,

    /// The client itself.
    client: Arc<TorClient<R>>,
}

impl<R: Runtime> ReconfigurableModule for LaunchableTorClient<R> {
    #[instrument(level = "trace", skip_all)]
    fn reconfigure(&self, new: &ArtiCombinedConfig) -> anyhow::Result<()> {
        TorClient::reconfigure(self, &new.1, Reconfigure::WarnOnFailures)?;
        // TODO RPC: Take 'how' as an argument.

        if new.0.application().defer_bootstrap && !self.orig_defer_bootstrap {
            warn!("Cannot enable defer_bootstrap while arti is running.");
        }
        if !new.0.application().defer_bootstrap {
            self.ensure_bootstrap_launched()?;
        }

        TorClient::reconfigure(&self.client, &new.1, Reconfigure::WarnOnFailures)?;
        Ok(())
    }
}

impl<R: Runtime> LaunchableTorClient<R> {
    /// Create a new LaunchableTorClient.
    ///
    /// We assume that it has (or has not) been told to bootstrap itself based on `cfg`.
    pub(crate) fn new(client: Arc<TorClient<R>>, cfg: &crate::ApplicationConfig) -> Self {
        Self {
            orig_defer_bootstrap: cfg.defer_bootstrap,
            have_launched: Mutex::new(!cfg.defer_bootstrap),
            client,
        }
    }

    /// If we have not already told this LaunchableTorClient to bootstrap itself, do so.
    fn ensure_bootstrap_launched(&self) -> anyhow::Result<()> {
        let mut have_launched = self.have_launched.lock().expect("lock poisoned");

        if *have_launched {
            return Ok(());
        }

        let client = Arc::clone(&self.client);
        // We spawn this as a new task since `bootstrap` is very much async,
        // but this needs to be called from `reconfigure`, which is not.
        self.client
            .runtime()
            .spawn(async move {
                let _outcome = client.bootstrap().await;
            })
            .context("Launching bootstrap")?;

        *have_launched = true;
        Ok(())
    }
}
@@ -252,7 +309,6 @@ impl ReconfigurableModule for Application {
        if config.application().permit_debugging && !original.application().permit_debugging {
            warn!("Cannot disable application hardening when it has already been enabled.");
        }

        // Note that this is the only config transition we actually perform so far.
        if !config.application().permit_debugging {
            #[cfg(feature = "harden")]
+36 −13
Original line number Diff line number Diff line
@@ -125,32 +125,51 @@ async fn run_proxy<R: ToplevelRuntime>(
) -> Result<()> {
    // Using OnDemand arranges that, while we are bootstrapping, incoming connections wait
    // for bootstrap to complete, rather than getting errors.
    use arti_client::BootstrapBehavior::OnDemand;
    use arti_client::BootstrapBehavior;
    use futures::FutureExt;

    // TODO: We may instead want to provide a way to get these items out of TorClient.
    let fs_mistrust = client_config.fs_mistrust().clone();
    let path_resolver: CfgPathResolver = AsRef::<CfgPathResolver>::as_ref(&client_config).clone();

    let defer_bootstrap = arti_config.application().defer_bootstrap;

    let bootstrap_behavior = match defer_bootstrap {
        true => BootstrapBehavior::Manual,
        false => BootstrapBehavior::OnDemand,
    };

    let client_builder = TorClient::with_runtime(runtime.clone())
        .config(client_config)
        .bootstrap_behavior(OnDemand);
        .bootstrap_behavior(bootstrap_behavior);
    let client = client_builder.create_unbootstrapped_async().await?;

    let launchable_client = Arc::new(reload_cfg::LaunchableTorClient::new(
        Arc::clone(&client),
        arti_config.application(),
    ));

    #[allow(unused_mut)]
    let mut reconfigurable_modules: Vec<Arc<dyn reload_cfg::ReconfigurableModule>> = vec![
        Arc::clone(&client) as _,
        Arc::clone(&launchable_client) as _,
        Arc::new(reload_cfg::Application::new(arti_config.clone())),
    ];

    cfg_if::cfg_if! {
        if #[cfg(feature = "onion-service-service")] {
            let have_onion_svc = if defer_bootstrap {
                let onion_services = onion_proxy::ProxySet::new_deferred(Arc::clone(&client));
                reconfigurable_modules.push(Arc::new(onion_services));
                arti_config.onion_services.values().any(|c| *c.svc_cfg.enabled())
            } else {
                let onion_services =
                    onion_proxy::ProxySet::launch_new(Arc::clone(&client), arti_config.onion_services.clone())?;
            let launched_onion_svc = !onion_services.is_empty();
                let have_onion_svc = !onion_services.is_empty();
                reconfigurable_modules.push(Arc::new(onion_services));
                have_onion_svc
            };
        } else {
            let launched_onion_svc = false;
            let have_onion_svc = false;
        }
    };

@@ -245,7 +264,7 @@ async fn run_proxy<R: ToplevelRuntime>(
    }

    if proxy.is_empty() {
        if !launched_onion_svc {
        if !have_onion_svc {
            // TODO: rename "socks_listen" to "proxy_listen", preserving compat, once http-connect is stable.
            warn!(
                "No proxy address set; \
@@ -288,12 +307,16 @@ async fn run_proxy<R: ToplevelRuntime>(
        r = proxy.fuse()
            => r,
        r = async {
            if defer_bootstrap {
                info!("Bootstrapping deferred.");
            } else {
                client.bootstrap().await?;
                if !socks_listen.is_empty() {
                    info!("Sufficiently bootstrapped; proxy now functional.");
                } else {
                    info!("Sufficiently bootstrapped.");
                }
            }
            futures::future::pending::<Result<()>>().await
        }.fuse()
            => r.context("bootstrap"),