// Copyright 2023-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only use crate::config::Config; use crate::error::GatewayError; use crate::helpers::load_public_key; use nym_bin_common::bin_info_owned; use nym_crypto::asymmetric::{encryption, identity}; use nym_network_requester::RequestFilter; use nym_node_http_api::api::api_requests; use nym_node_http_api::api::api_requests::v1::network_requester::exit_policy::models::UsedExitPolicy; use nym_node_http_api::api::api_requests::SignedHostInformation; use nym_node_http_api::NymNodeHttpError; use nym_sphinx::addressing::clients::Recipient; use nym_task::TaskClient; use std::sync::Arc; use tracing::{debug, error, warn}; fn load_gateway_details( config: &Config, ) -> Result { Ok(api_requests::v1::gateway::models::Gateway { enforces_zk_nyms: config.gateway.only_coconut_credentials, client_interfaces: api_requests::v1::gateway::models::ClientInterfaces { wireguard: None, mixnet_websockets: Some(api_requests::v1::gateway::models::WebSockets { ws_port: config.gateway.clients_port, wss_port: config.gateway.clients_wss_port, }), }, }) } fn load_host_details( config: &Config, sphinx_key: &encryption::PublicKey, identity_keypair: &identity::KeyPair, ) -> Result { let host_info = api_requests::v1::node::models::HostInformation { ip_address: config.host.public_ips.clone(), hostname: config.host.hostname.clone(), keys: api_requests::v1::node::models::HostKeys { ed25519_identity: *identity_keypair.public_key(), x25519_sphinx: *sphinx_key, x25519_noise: None, }, }; let signed_info = SignedHostInformation::new(host_info, identity_keypair.private_key()) .map_err(NymNodeHttpError::from)?; Ok(signed_info) } fn load_network_requester_details( config: &Config, network_requester_config: &nym_network_requester::Config, ) -> Result { let identity_public_key: identity::PublicKey = load_public_key( &network_requester_config .storage_paths .common_paths .keys .public_identity_key_file, "network requester identity", )?; let dh_public_key: encryption::PublicKey = load_public_key( &network_requester_config .storage_paths .common_paths .keys .public_encryption_key_file, "network requester diffie hellman", )?; let gateway_identity_public_key: identity::PublicKey = load_public_key( &config.storage_paths.keys.public_identity_key_file, "gateway identity", )?; Ok( api_requests::v1::network_requester::models::NetworkRequester { encoded_identity_key: identity_public_key.to_base58_string(), encoded_x25519_key: dh_public_key.to_base58_string(), address: Recipient::new( identity_public_key, dh_public_key, gateway_identity_public_key, ) .to_string(), }, ) } fn load_ip_packet_router_details( config: &Config, ip_packet_router_config: &nym_ip_packet_router::Config, ) -> Result { let identity_public_key: identity::PublicKey = load_public_key( &ip_packet_router_config .storage_paths .common_paths .keys .public_identity_key_file, "ip packet router identity", )?; let dh_public_key: encryption::PublicKey = load_public_key( &ip_packet_router_config .storage_paths .common_paths .keys .public_encryption_key_file, "ip packet router diffie hellman", )?; let gateway_identity_public_key: identity::PublicKey = load_public_key( &config.storage_paths.keys.public_identity_key_file, "gateway identity", )?; Ok(api_requests::v1::ip_packet_router::models::IpPacketRouter { encoded_identity_key: identity_public_key.to_base58_string(), encoded_x25519_key: dh_public_key.to_base58_string(), address: Recipient::new( identity_public_key, dh_public_key, gateway_identity_public_key, ) .to_string(), }) } pub(crate) struct HttpApiBuilder<'a> { gateway_config: &'a Config, network_requester_config: Option<&'a nym_network_requester::Config>, exit_policy: Option, ip_packet_router_config: Option<&'a nym_ip_packet_router::Config>, identity_keypair: &'a identity::KeyPair, // TODO: this should be a wg specific key and not re-used sphinx sphinx_keypair: Arc, } impl<'a> HttpApiBuilder<'a> { pub(crate) fn new( gateway_config: &'a Config, identity_keypair: &'a identity::KeyPair, sphinx_keypair: Arc, ) -> Self { HttpApiBuilder { gateway_config, network_requester_config: None, ip_packet_router_config: None, exit_policy: None, identity_keypair, sphinx_keypair, } } #[must_use] pub(crate) fn with_maybe_network_requester( mut self, network_requester_config: Option<&'a nym_network_requester::Config>, ) -> Self { self.network_requester_config = network_requester_config; self } #[must_use] pub(crate) fn with_maybe_network_request_filter( mut self, request_filter: Option, ) -> Self { let Some(request_filter) = request_filter else { warn!("no valid request filter has been passed. no changes will be made"); return self; }; // we can cheat here a bit since we're not refreshing the exit policy // thus: // - we can ignore the Arc pointer and clone the inner value // - we can set the last refresh time to the current time // // once we start refreshing it, we'll have to change it, but at that point // the allow list will be probably be completely removed and thus the pointer management // will be much easier let upstream = request_filter.current_exit_policy_filter().upstream(); // if there's no upstream (i.e. open proxy), we couldn't have possibly updated it : ) let last_updated = if upstream.is_some() { #[allow(clippy::expect_used)] std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .expect("system clock is set to before the unix epoch") .as_secs() } else { 0 }; self.exit_policy = Some(UsedExitPolicy { enabled: true, upstream_source: upstream.map(|u| u.to_string()).unwrap_or_default(), last_updated, policy: Some(request_filter.current_exit_policy_filter().policy().clone()), }); self } #[must_use] pub(crate) fn with_maybe_ip_packet_router( mut self, ip_packet_router_config: Option<&'a nym_ip_packet_router::Config>, ) -> Self { self.ip_packet_router_config = ip_packet_router_config; self } pub(crate) fn start(self, task_client: TaskClient) -> Result<(), GatewayError> { debug!("starting http API"); // is it suboptimal to load all the keys, etc for the second time after they've already been // retrieved during startup of the rest of the components? // yes, a bit. // but in the grand scheme of things performance penalty is negligible since it's only happening on startup // and makes the code a bit nicer to manage. on top of it, all of it will refactored anyway at some point // (famous last words, eh? - 22.09.23) let mut config = nym_node_http_api::Config::new( bin_info_owned!(), load_host_details( self.gateway_config, self.sphinx_keypair.public_key(), self.identity_keypair, )?, ) .with_gateway(load_gateway_details(self.gateway_config)?) .with_landing_page_assets(self.gateway_config.http.landing_page_assets_path.as_ref()); if let Some(nr_config) = self.network_requester_config { config = config.with_network_requester(load_network_requester_details( self.gateway_config, nr_config, )?); if let Some(exit_policy) = self.exit_policy { config = config.with_used_exit_policy(exit_policy) } } if let Some(ipr_config) = self.ip_packet_router_config { config = config.with_ip_packet_router(load_ip_packet_router_details( self.gateway_config, ipr_config, )?); } let bind_address = self.gateway_config.http.bind_address; let router = nym_node_http_api::NymNodeRouter::new(config, None); tokio::spawn(async move { let server = match router.build_server(&bind_address).await { Ok(server) => server.with_task_client(task_client), Err(err) => { error!("failed to create http server: {err}"); return; } }; server.run().await }); Ok(()) } }