Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
feat(config): add config option node.externalAddressFiles
Archived did:key:z6Mkpszi...nsaA opened 8 months ago

with externalAddressFile you can configure the external address or parts of it through a file(s) instead of putting the host/port into the config file. Motivation: this makes it easier for users to: - hide their host from their config - reuse their config - configure their externalAddress via an external program Example:

    "externalAddressesFiles": [
      {
        "type": "split",
        "host": {
          "file": "/var/lib/tor/onion/radicle/host"
        },
        "port": {
          "string": "42069"
        }
      },
      {
        "type": "full",
        "file": "/run/secrets/ext_full_host"
      }
    ]

for more see: https://radicle.zulipchat.com/#narrow/channel/383670-patches/topic/read.20externalAddresses.20from.20external.20file.20instead.20of.20config/with/532591796

9 files changed +100 -11 54fd8c40 7561c869
modified crates/radicle-cli/src/commands/node.rs
@@ -295,7 +295,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
        Operation::Config { addresses } => {
            if addresses {
                let cfg = node.config()?;
-
                for addr in cfg.external_addresses {
+
                for addr in cfg.get_external_addresses() {
                    term::print(ConnectAddress::from((*profile.id(), addr)).to_string());
                }
            } else {
modified crates/radicle-cli/tests/util/environment.rs
@@ -161,7 +161,7 @@ impl Environment {
                &alias,
                &UserAgent::default(),
                LocalTime::now().into(),
-
                config.node.external_addresses.iter(),
+
                config.node.get_external_addresses().iter(),
            )
            .unwrap();

modified crates/radicle-node/src/runtime.rs
@@ -176,7 +176,7 @@ impl Runtime {
            .and_then(|ann| {
                if config.features() == ann.features
                    && config.alias == ann.alias
-
                    && config.external_addresses == ann.addresses.as_ref()
+
                    && config.get_external_addresses() == ann.addresses.as_ref()
                {
                    Some(ann)
                } else {
modified crates/radicle-node/src/test/environment.rs
@@ -137,7 +137,7 @@ impl Environment {
                &Alias::new(alias),
                &UserAgent::default(),
                now.into(),
-
                config.node.external_addresses.iter(),
+
                config.node.get_external_addresses().iter(),
            )
            .unwrap();

modified crates/radicle-node/src/test/peer.rs
@@ -168,7 +168,10 @@ where
        let inventory = storage.repositories().unwrap();

        // Make sure the peer address is advertized.
-
        config.config.external_addresses.push(local_addr.into());
+
        config
+
            .config
+
            .get_external_addresses()
+
            .push(local_addr.into());
        for repo in &inventory {
            policies.seed(&repo.rid, Scope::Followed).unwrap();
        }
@@ -181,7 +184,7 @@ where
                &config.config.alias,
                &UserAgent::default(),
                config.local_time.into(),
-
                config.config.external_addresses.iter(),
+
                config.config.get_external_addresses().iter(),
            )
            .unwrap()
            .into();
modified crates/radicle-protocol/src/service.rs
@@ -2476,7 +2476,12 @@ where
                    .filter(|entry| !entry.address.banned)
                    .filter(|entry| !entry.penalty.is_connect_threshold_reached())
                    .filter(|entry| !self.sessions.contains_key(&entry.node))
-
                    .filter(|entry| !self.config.external_addresses.contains(&entry.address.addr))
+
                    .filter(|entry| {
+
                        !self
+
                            .config
+
                            .get_external_addresses()
+
                            .contains(&entry.address.addr)
+
                    })
                    .filter(|entry| &entry.node != self.nid())
                    .fold(HashMap::new(), |mut acc, entry| {
                        acc.entry(entry.node)
modified crates/radicle-protocol/src/service/gossip.rs
@@ -20,7 +20,7 @@ pub fn node(config: &Config, timestamp: Timestamp) -> NodeAnnouncement {
    let features = config.features();
    let alias = config.alias.clone();
    let addresses: BoundedVec<_, ADDRESS_LIMIT> = config
-
        .external_addresses
+
        .get_external_addresses()
        .clone()
        .try_into()
        .expect("external addresses are within the limit");
modified crates/radicle/src/node/config.rs
@@ -1,7 +1,7 @@
use std::collections::HashSet;
use std::ops::Deref;
use std::str::FromStr;
-
use std::{fmt, net};
+
use std::{fmt, fs, net};

use cyphernet::addr::PeerAddr;
use localtime::LocalDuration;
@@ -371,6 +371,70 @@ pub enum Relay {
    Auto,
}

+
/// The actual FilePath (maybe use some std struct instead)
+
#[derive(Clone, Eq, PartialEq, Debug, Hash, Serialize, Deserialize)]
+
#[serde(rename_all = "camelCase")]
+
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
+
pub struct FilePath(String);
+
impl FilePath {
+
    // maybe implement caching if needed
+
    // NOTE: permission changes might CRASH radicle-node
+
    fn get(&self) -> String {
+
        fs::read_to_string(&self.0)
+
            .expect(format!("Couldnt read from file: {}", &self.0).as_str())
+
            .trim()
+
            .to_string()
+
    }
+
}
+

+
/// Part of a address. Either a file path or a string
+
/// this allows the user to point to /var/lib/tor for the hostname only for example
+
#[derive(Clone, Eq, PartialEq, Debug, Hash, From, Serialize, Deserialize)]
+
#[serde(rename_all = "camelCase")]
+
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
+
pub enum AddressFilePart {
+
    File(FilePath),
+
    String(String),
+
}
+

+
/// encapsulate differences between path and string into a function, encouraging DRY & least knowledge
+
impl ToString for AddressFilePart {
+
    fn to_string(&self) -> String {
+
        match self {
+
            AddressFilePart::File(file_path) => file_path.get(),
+
            AddressFilePart::String(string) => string.clone(),
+
        }
+
    }
+
}
+

+
/// The external Address from a file or multiple files
+
#[derive(Clone, Eq, PartialEq, Debug, Hash, From, Serialize, Deserialize)]
+
#[serde(rename_all = "camelCase", tag = "type")]
+
#[cfg_attr(
+
    feature = "schemars",
+
    derive(schemars::JsonSchema),
+
    schemars(description = "An file path in which the address is stored.")
+
)]
+
pub enum AddressFile {
+
    /// read the entire host from one file
+
    Full(AddressFilePart),
+
    /// split host and port
+
    Split {
+
        host: AddressFilePart,
+
        port: AddressFilePart,
+
    },
+
}
+

+
/// encapsulate Full or Split files into a common $host:$port string
+
impl ToString for AddressFile {
+
    fn to_string(&self) -> String {
+
        match self {
+
            AddressFile::Full(path) => path.to_string(),
+
            AddressFile::Split { host, port } => host.to_string() + ":" + port.to_string().as_str(),
+
        }
+
    }
+
}
+

/// Proxy configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "mode")]
@@ -448,6 +512,9 @@ pub struct Config {
    /// Specify the node's public addresses
    #[serde(default)]
    pub external_addresses: Vec<Address>,
+
    /// Specify the node's public addresses which should not be in the config file
+
    #[serde(default, skip_serializing_if = "Option::is_none")]
+
    pub external_addresses_files: Option<Vec<AddressFile>>,
    /// Global proxy.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub proxy: Option<net::SocketAddr>,
@@ -497,6 +564,7 @@ impl Config {
            listen: vec![],
            connect: HashSet::default(),
            external_addresses: vec![],
+
            external_addresses_files: None,
            network: Network::default(),
            proxy: None,
            onion: None,
@@ -529,7 +597,7 @@ impl Config {
        match self.relay {
            // In "auto" mode, we only relay if we're a public seed node.
            // This reduces traffic for private nodes, as well as message redundancy.
-
            Relay::Auto => !self.external_addresses.is_empty(),
+
            Relay::Auto => !self.get_external_addresses().is_empty(),
            Relay::Never => false,
            Relay::Always => true,
        }
@@ -538,6 +606,19 @@ impl Config {
    pub fn features(&self) -> node::Features {
        node::Features::SEED
    }
+

+
    pub fn get_external_addresses(&self) -> Vec<Address> {
+
        let mut out: Vec<Address> = self.external_addresses.clone();
+
        for i in &self.external_addresses_files.clone().unwrap_or(Vec::new()) {
+
            let addr = i.to_string();
+
            let addr_str = addr.as_str();
+
            out.push(
+
                Address::from_str(&addr_str)
+
                    .expect(format!("Invalid Address: {}", &addr_str).as_str()),
+
            );
+
        }
+
        return out;
+
    }
}

/// Defaults as functions, for serde.
modified crates/radicle/src/profile.rs
@@ -231,7 +231,7 @@ impl Profile {
                &config.node.alias,
                &UserAgent::default(),
                LocalTime::now().into(),
-
                config.node.external_addresses.iter(),
+
                config.node.get_external_addresses().iter(),
            )?;

        // Migrate COBs cache.