diff --git a/common/topology/src/gateway.rs b/common/topology/src/gateway.rs index 476e33d8e6..9d10b8d2a0 100644 --- a/common/topology/src/gateway.rs +++ b/common/topology/src/gateway.rs @@ -189,15 +189,6 @@ impl<'a> TryFrom<&'a DescribedGateway> for Node { }); } - let ips = ips - .iter() - .map(|ip| ip.parse()) - .collect::, _>>() - .map_err(|err| GatewayConversionError::MalformedIpAddress { - gateway: value.bond.gateway.identity_key.clone(), - err, - })?; - let host = match &self_described.host_information.hostname { None => NetworkAddress::IpAddr(ips[0]), Some(hostname) => NetworkAddress::Hostname(hostname.clone()), diff --git a/gateway/src/commands/helpers.rs b/gateway/src/commands/helpers.rs index f8b9e326bd..514e699824 100644 --- a/gateway/src/commands/helpers.rs +++ b/gateway/src/commands/helpers.rs @@ -41,7 +41,7 @@ pub(crate) struct OverrideConfig { impl OverrideConfig { pub(crate) fn do_override(self, mut config: Config) -> Result { config = config - .with_optional(Config::with_listening_address, self.host) + .with_optional(Config::with_host, self.host) .with_optional(Config::with_mix_port, self.mix_port) .with_optional(Config::with_clients_port, self.clients_port) .with_optional_custom_env( diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index 3ee6a7d8d4..c8728a50ad 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -13,9 +13,9 @@ use nym_config::{ }; use nym_network_defaults::mainnet; use nym_node::config; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use std::io; -use std::net::IpAddr; +use std::net::{IpAddr, SocketAddr}; use std::path::{Path, PathBuf}; use std::time::Duration; use url::Url; @@ -40,6 +40,18 @@ const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000; const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16; const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; +fn de_maybe_port<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let port = u16::deserialize(deserializer)?; + if port == 0 { + Ok(None) + } else { + Ok(Some(port)) + } +} + /// Derive default path to gateway's config directory. /// It should get resolved to `$HOME/.nym/gateways//config` pub fn default_config_directory>(id: P) -> PathBuf { @@ -73,11 +85,17 @@ pub struct Config { #[serde(skip)] pub(crate) save_path: Option, + pub host: config::Host, + #[serde(default)] pub http: config::Http, pub gateway: Gateway, + #[serde(default)] + // currently not really used for anything useful + pub wireguard: config::Wireguard, + pub storage_paths: GatewayPaths, pub network_requester: NetworkRequester, @@ -97,10 +115,16 @@ impl NymConfigTemplate for Config { impl Config { pub fn new>(id: S) -> Self { + let default_gateway = Gateway::new_default(id.as_ref()); Config { save_path: None, + host: config::Host { + public_ips: vec![default_gateway.listening_address], + ..Default::default() + }, http: Default::default(), - gateway: Gateway::new_default(id.as_ref()), + gateway: default_gateway, + wireguard: Default::default(), storage_paths: GatewayPaths::new_default(id.as_ref()), network_requester: Default::default(), logging: Default::default(), @@ -188,8 +212,16 @@ impl Config { self } - pub fn with_listening_address(mut self, listening_address: IpAddr) -> Self { + pub fn with_host(mut self, listening_address: IpAddr) -> Self { self.gateway.listening_address = listening_address; + + // temporary workaround + self.host.public_ips = vec![listening_address]; + let http_port = self.http.bind_address.port(); + self.http.bind_address = SocketAddr::new(listening_address, http_port); + let wg_port = self.wireguard.bind_address.port(); + self.wireguard.bind_address = SocketAddr::new(listening_address, wg_port); + self } @@ -251,6 +283,11 @@ pub struct Gateway { /// (default: 9000) pub clients_port: u16, + /// If applicable, announced port for listening for secure websocket client traffic. + /// (default: None) + #[serde(deserialize_with = "de_maybe_port")] + pub clients_wss_port: Option, + /// Whether gateway collects and sends anonymized statistics pub enabled_statistics: bool, @@ -285,6 +322,7 @@ impl Gateway { listening_address: inaddr_any(), mix_port: DEFAULT_MIX_LISTENING_PORT, clients_port: DEFAULT_CLIENT_LISTENING_PORT, + clients_wss_port: None, enabled_statistics: false, statistics_service_url: mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS .parse() diff --git a/gateway/src/config/old_config_v1_1_29.rs b/gateway/src/config/old_config_v1_1_29.rs index 49f305f0e9..1694afe42a 100644 --- a/gateway/src/config/old_config_v1_1_29.rs +++ b/gateway/src/config/old_config_v1_1_29.rs @@ -110,6 +110,13 @@ impl From for Config { Config { save_path: value.save_path, + // \/ ADDED + host: nym_node::config::Host { + public_ips: vec![value.gateway.listening_address], + ..Default::default() + }, + // /\ ADDED + // \/ ADDED http: Default::default(), // /\ ADDED @@ -120,12 +127,19 @@ impl From for Config { listening_address: value.gateway.listening_address, mix_port: value.gateway.mix_port, clients_port: value.gateway.clients_port, + + // \/ ADDED + clients_wss_port: None, + // /\ ADDED enabled_statistics: value.gateway.enabled_statistics, nym_api_urls: value.gateway.nym_api_urls, nyxd_urls: value.gateway.nyxd_urls, statistics_service_url: value.gateway.statistics_service_url, cosmos_mnemonic: value.gateway.cosmos_mnemonic, }, + // \/ ADDED + wireguard: Default::default(), + // /\ ADDED storage_paths: GatewayPaths { keys: KeysPaths { private_identity_key_file: value.storage_paths.keys.private_identity_key_file, diff --git a/gateway/src/config/template.rs b/gateway/src/config/template.rs index 630780fdc9..cf06399e74 100644 --- a/gateway/src/config/template.rs +++ b/gateway/src/config/template.rs @@ -9,7 +9,18 @@ pub(crate) const CONFIG_TEMPLATE: &str = r#" # This is a TOML config file. # For more information, see https://github.com/toml-lang/toml -##### main base mixnode config options ##### +##### main base gateway config options ##### + +[host] +# Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections. +public_ips = [ + {{#each host.public_ips }} + '{{this}}', + {{/each}} +] + +# (temporary) Optional hostname of this node, for example nymtech.net. +hostname = '{{ host.hostname }}' [gateway] # Version of the gateway for which this configuration was created. @@ -33,6 +44,10 @@ mix_port = {{ gateway.mix_port }} # (default: 9000) clients_port = {{ gateway.clients_port }} +# If applicable, announced port for listening for secure websocket client traffic. +# (default: 0 - disabled) +clients_wss_port ={{#if gateway.clients_wss_port }} {{ gateway.clients_wss_port }} {{else}} 0 {{/if}} + # Wheather gateway collects and sends anonymized statistics enabled_statistics = {{ gateway.enabled_statistics }} diff --git a/gateway/src/http/mod.rs b/gateway/src/http/mod.rs index 1eed00e92f..3dc2f1d705 100644 --- a/gateway/src/http/mod.rs +++ b/gateway/src/http/mod.rs @@ -19,12 +19,21 @@ use tokio::sync::RwLock; fn load_gateway_details( config: &Config, ) -> Result { + let wireguard = if config.wireguard.enabled { + Some(api_requests::v1::gateway::models::Wireguard { + port: config.wireguard.announced_port, + public_key: "placeholder key value".to_string(), + }) + } else { + None + }; + Ok(api_requests::v1::gateway::models::Gateway { client_interfaces: api_requests::v1::gateway::models::ClientInterfaces { - wireguard: None, + wireguard, mixnet_websockets: Some(api_requests::v1::gateway::models::WebSockets { ws_port: config.gateway.clients_port, - wss_port: None, + wss_port: config.gateway.clients_wss_port, }), }, }) @@ -37,8 +46,8 @@ fn load_host_details( ) -> Result { let host_info = api_requests::v1::node::models::HostInformation { // TODO: this should be extracted differently, i.e. it's the issue of the public/private address - ip_address: vec![config.gateway.listening_address.to_string()], - hostname: None, + ip_address: config.host.public_ips.clone(), + hostname: config.host.hostname.clone(), keys: api_requests::v1::node::models::HostKeys { ed25519: identity_keypair.public_key().to_base58_string(), x25519: sphinx_key.to_base58_string(), diff --git a/nym-node/nym-node-requests/src/api/v1/node/models.rs b/nym-node/nym-node-requests/src/api/v1/node/models.rs index 9e2fb3902b..260752436b 100644 --- a/nym-node/nym-node-requests/src/api/v1/node/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/node/models.rs @@ -3,6 +3,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::net::IpAddr; pub use crate::api::SignedHostInformation; pub use nym_bin_common::build_information::BinaryBuildInformationOwned; @@ -19,9 +20,11 @@ pub struct NodeRoles { #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub struct HostInformation { /// Ip address(es) of this host, such as `1.1.1.1`. - pub ip_address: Vec, + #[cfg_attr(feature = "openapi", schema(value_type = Vec, format = Byte, example = json!(["1.1.1.1"])))] + pub ip_address: Vec, /// Optional hostname of this node, for example `nymtech.net`. + #[cfg_attr(feature = "openapi", schema(example = "nymtech.net"))] pub hostname: Option, /// Public keys associated with this node. diff --git a/nym-node/src/config/mod.rs b/nym-node/src/config/mod.rs index 3edf0294fe..e7f4206419 100644 --- a/nym-node/src/config/mod.rs +++ b/nym-node/src/config/mod.rs @@ -1,24 +1,43 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Serialize}; +use serde_helpers::*; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::PathBuf; -fn de_maybe_path<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let path = PathBuf::deserialize(deserializer)?; - if path.as_os_str().is_empty() { - Ok(None) - } else { - Ok(Some(path)) +pub mod persistence; +mod serde_helpers; + +pub const DEFAULT_WIREGUARD_PORT: u16 = 51820; + +// TODO: this is very much a WIP. we need proper ssl certificate support here +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct Host { + /// Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections. + pub public_ips: Vec, + + /// Optional hostname of this node, for example nymtech.net. + // TODO: this is temporary. to be replaced by pulling the data directly from the certs. + #[serde(deserialize_with = "de_maybe_string")] + pub hostname: Option, +} + +#[allow(clippy::derivable_impls)] +impl Default for Host { + fn default() -> Self { + Host { + public_ips: vec![], + hostname: None, + } } } #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(default)] +#[serde(deny_unknown_fields)] pub struct Http { /// Socket address this node will use for binding its http API. /// default: `0.0.0.0:80` @@ -37,3 +56,36 @@ impl Default for Http { } } } + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(default)] +#[serde(deny_unknown_fields)] +pub struct Wireguard { + /// Specifies whether the wireguard service is enabled on this node. + pub enabled: bool, + + /// Socket address this node will use for binding its wireguard interface. + /// default: `0.0.0.0:51820` + pub bind_address: SocketAddr, + + /// Port announced to external clients wishing to connect to the wireguard interface. + /// Useful in the instances where the node is behind a proxy. + pub announced_port: u16, + + /// Paths for wireguard keys, client registries, etc. + pub storage_paths: persistence::WireguardPaths, +} + +impl Default for Wireguard { + fn default() -> Self { + Wireguard { + enabled: false, + bind_address: SocketAddr::new( + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + DEFAULT_WIREGUARD_PORT, + ), + announced_port: DEFAULT_WIREGUARD_PORT, + storage_paths: persistence::WireguardPaths {}, + } + } +} diff --git a/nym-node/src/config/persistence.rs b/nym-node/src/config/persistence.rs new file mode 100644 index 0000000000..2ceed4b3cc --- /dev/null +++ b/nym-node/src/config/persistence.rs @@ -0,0 +1,10 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct WireguardPaths { + // pub keys: +} diff --git a/nym-node/src/config/serde_helpers.rs b/nym-node/src/config/serde_helpers.rs new file mode 100644 index 0000000000..df0f42bb44 --- /dev/null +++ b/nym-node/src/config/serde_helpers.rs @@ -0,0 +1,29 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use serde::{Deserialize, Deserializer}; +use std::path::PathBuf; + +pub(super) fn de_maybe_path<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let path = PathBuf::deserialize(deserializer)?; + if path.as_os_str().is_empty() { + Ok(None) + } else { + Ok(Some(path)) + } +} + +pub(super) fn de_maybe_string<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let path = String::deserialize(deserializer)?; + if path.is_empty() { + Ok(None) + } else { + Ok(Some(path)) + } +} diff --git a/nym-node/src/http/router/mod.rs b/nym-node/src/http/router/mod.rs index c496fc5880..421aad982a 100644 --- a/nym-node/src/http/router/mod.rs +++ b/nym-node/src/http/router/mod.rs @@ -7,7 +7,7 @@ use crate::http::middleware::logging; use crate::http::state::AppState; use crate::http::NymNodeHTTPServer; use axum::Router; -use nym_node_requests::api::v1::gateway::models::Gateway; +use nym_node_requests::api::v1::gateway::models::{Gateway, Wireguard}; use nym_node_requests::api::v1::mixnode::models::Mixnode; use nym_node_requests::api::v1::network_requester::models::NetworkRequester; use nym_node_requests::api::v1::node::models; @@ -15,6 +15,7 @@ use nym_node_requests::api::SignedHostInformation; use nym_node_requests::routes; use std::net::SocketAddr; use std::path::Path; +use tracing::warn; pub mod api; pub mod landing_page; @@ -48,6 +49,18 @@ impl Config { } } + pub fn with_wireguard_interface(mut self, wireguard: Wireguard) -> Self { + match &mut self.api.v1_config.gateway.details { + Some(gw) => gw.client_interfaces.wireguard = Some(wireguard), + None => { + warn!( + "can't add wireguard interface information as the gateway role is not enabled." + ); + } + } + self + } + #[must_use] pub fn with_landing_page_assets>(mut self, assets_path: Option

) -> Self { self.landing.assets_path = assets_path.map(|p| p.as_ref().to_path_buf());