Files
nym/nym-node/src/node/mod.rs
T
Jędrzej Stuczyński 73fe7618db removing needless clones
2024-05-21 17:06:15 +01:00

604 lines
22 KiB
Rust

// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::description::{load_node_description, save_node_description};
use crate::node::helpers::{
load_ed25519_identity_keypair, load_key, load_x25519_noise_keypair, load_x25519_sphinx_keypair,
store_ed25519_identity_keypair, store_key, store_keypair, store_x25519_noise_keypair,
store_x25519_sphinx_keypair, DisplayDetails,
};
use crate::node::http::{sign_host_details, system_info::get_system_info};
use ipnetwork::IpNetwork;
use nym_bin_common::bin_info_owned;
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_gateway::Gateway;
use nym_mixnode::MixNode;
use nym_network_requester::{
set_active_gateway, setup_fs_gateways_storage, store_gateway_details, CustomGatewayDetails,
GatewayDetails, GatewayRegistration,
};
use nym_node::config::entry_gateway::ephemeral_entry_gateway_config;
use nym_node::config::exit_gateway::ephemeral_exit_gateway_config;
use nym_node::config::mixnode::ephemeral_mixnode_config;
use nym_node::config::{Config, EntryGatewayConfig, ExitGatewayConfig, MixnodeConfig, NodeMode};
use nym_node::error::{EntryGatewayError, ExitGatewayError, MixnodeError, NymNodeError};
use nym_node_http_api::api::api_requests;
use nym_node_http_api::api::api_requests::v1::node::models::NodeDescription;
use nym_node_http_api::router::WireguardAppState;
use nym_node_http_api::state::metrics::{SharedMixingStats, SharedVerlocStats};
use nym_node_http_api::state::AppState;
use nym_node_http_api::{NymNodeHTTPServer, NymNodeRouter};
use nym_sphinx_acknowledgements::AckKey;
use nym_sphinx_addressing::Recipient;
use nym_task::{TaskClient, TaskManager};
use nym_wireguard_types::registration::GatewayClientRegistry;
use rand::rngs::OsRng;
use rand::{CryptoRng, RngCore};
use std::path::Path;
use std::sync::Arc;
use tracing::{debug, error, info, trace};
use zeroize::Zeroizing;
pub mod bonding_information;
pub mod description;
pub mod helpers;
pub(crate) mod http;
pub struct MixnodeData {
mixing_stats: SharedMixingStats,
}
impl MixnodeData {
pub fn initialise(_config: &MixnodeConfig) -> Result<(), MixnodeError> {
Ok(())
}
fn new(_config: &MixnodeConfig) -> Result<MixnodeData, MixnodeError> {
Ok(MixnodeData {
mixing_stats: SharedMixingStats::new(),
})
}
}
pub struct EntryGatewayData {
mnemonic: Zeroizing<bip39::Mnemonic>,
client_storage: nym_gateway::node::PersistentStorage,
client_registry: Arc<GatewayClientRegistry>,
}
impl EntryGatewayData {
pub fn initialise(
config: &EntryGatewayConfig,
custom_mnemonic: Option<Zeroizing<bip39::Mnemonic>>,
) -> Result<(), EntryGatewayError> {
// SAFETY:
// this unwrap is fine as 24 word count is a valid argument for generating entropy for a new bip39 mnemonic
#[allow(clippy::unwrap_used)]
let mnemonic = custom_mnemonic
.unwrap_or_else(|| Zeroizing::new(bip39::Mnemonic::generate(24).unwrap()));
config.storage_paths.save_mnemonic_to_file(&mnemonic)?;
Ok(())
}
async fn new(config: &EntryGatewayConfig) -> Result<EntryGatewayData, EntryGatewayError> {
Ok(EntryGatewayData {
mnemonic: config.storage_paths.load_mnemonic_from_file()?,
client_storage: nym_gateway::node::PersistentStorage::init(
&config.storage_paths.clients_storage,
config.debug.message_retrieval_limit,
)
.await
.map_err(nym_gateway::GatewayError::from)?,
client_registry: Arc::new(Default::default()),
})
}
}
pub struct ExitGatewayData {
// ideally we'd be storing all the keys here, but unfortunately due to how the service providers
// are currently implemented, they will be loading the data themselves from the provided paths
// those public keys are just convenience wrappers for http builder and details displayer
nr_ed25519: ed25519::PublicKey,
nr_x25519: x25519::PublicKey,
ipr_ed25519: ed25519::PublicKey,
ipr_x25519: x25519::PublicKey,
}
impl ExitGatewayData {
fn initialise_client_keys<R: RngCore + CryptoRng>(
rng: &mut R,
typ: &str,
ed25519_paths: nym_pemstore::KeyPairPath,
x25519_paths: nym_pemstore::KeyPairPath,
ack_key_path: &Path,
) -> Result<(), ExitGatewayError> {
let ed25519_keys = ed25519::KeyPair::new(rng);
let x25519_keys = x25519::KeyPair::new(rng);
let aes128ctr_key = AckKey::new(rng);
store_keypair(
&ed25519_keys,
ed25519_paths,
format!("{typ}-ed25519-identity"),
)?;
store_keypair(&x25519_keys, x25519_paths, format!("{typ}-x25519-dh"))?;
store_key(&aes128ctr_key, ack_key_path, format!("{typ}-ack-key"))?;
Ok(())
}
async fn initialise_client_gateway_storage(
storage_path: &Path,
registration: &GatewayRegistration,
) -> Result<(), ExitGatewayError> {
// insert all required information into the gateways store
// (I hate that we have to do it, but that's currently the simplest thing to do)
let storage = setup_fs_gateways_storage(storage_path).await?;
store_gateway_details(&storage, registration).await?;
set_active_gateway(&storage, &registration.gateway_id().to_base58_string()).await?;
Ok(())
}
pub async fn initialise_network_requester<R: RngCore + CryptoRng>(
rng: &mut R,
config: &ExitGatewayConfig,
registration: &GatewayRegistration,
) -> Result<(), ExitGatewayError> {
trace!("initialising network requester keys");
Self::initialise_client_keys(
rng,
"network-requester",
config
.storage_paths
.network_requester
.ed25519_identity_storage_paths(),
config
.storage_paths
.network_requester
.x25519_diffie_hellman_storage_paths(),
&config.storage_paths.network_requester.ack_key_file,
)?;
Self::initialise_client_gateway_storage(
&config.storage_paths.network_requester.gateway_registrations,
registration,
)
.await
}
pub async fn initialise_ip_packet_router_requester<R: RngCore + CryptoRng>(
rng: &mut R,
config: &ExitGatewayConfig,
registration: &GatewayRegistration,
) -> Result<(), ExitGatewayError> {
trace!("initialising ip packet router keys");
Self::initialise_client_keys(
rng,
"ip-packet-router",
config
.storage_paths
.ip_packet_router
.ed25519_identity_storage_paths(),
config
.storage_paths
.ip_packet_router
.x25519_diffie_hellman_storage_paths(),
&config.storage_paths.ip_packet_router.ack_key_file,
)?;
Self::initialise_client_gateway_storage(
&config.storage_paths.ip_packet_router.gateway_registrations,
registration,
)
.await
}
pub async fn initialise(
config: &ExitGatewayConfig,
public_key: ed25519::PublicKey,
) -> Result<(), ExitGatewayError> {
// generate all the keys for NR and IPR
let mut rng = OsRng;
let gateway_details = GatewayDetails::Custom(CustomGatewayDetails::new(public_key)).into();
// NR:
Self::initialise_network_requester(&mut rng, config, &gateway_details).await?;
// IPR:
Self::initialise_ip_packet_router_requester(&mut rng, config, &gateway_details).await?;
Ok(())
}
fn new(config: &ExitGatewayConfig) -> Result<ExitGatewayData, ExitGatewayError> {
let nr_paths = &config.storage_paths.network_requester;
let nr_ed25519 = load_key(
&nr_paths.public_ed25519_identity_key_file,
"network requester ed25519",
)?;
let nr_x25519 = load_key(
&nr_paths.public_x25519_diffie_hellman_key_file,
"network requester x25519",
)?;
let ipr_paths = &config.storage_paths.ip_packet_router;
let ipr_ed25519 = load_key(
&ipr_paths.public_ed25519_identity_key_file,
"ip packet router ed25519",
)?;
let ipr_x25519 = load_key(
&ipr_paths.public_x25519_diffie_hellman_key_file,
"ip packet router x25519",
)?;
Ok(ExitGatewayData {
nr_ed25519,
nr_x25519,
ipr_ed25519,
ipr_x25519,
})
}
}
pub(crate) struct NymNode {
config: Config,
description: NodeDescription,
// TODO: currently we're only making measurements in 'mixnode' mode; this should be changed
verloc_stats: SharedVerlocStats,
#[allow(dead_code)]
mixnode: MixnodeData,
entry_gateway: EntryGatewayData,
#[allow(dead_code)]
exit_gateway: ExitGatewayData,
ed25519_identity_keys: Arc<ed25519::KeyPair>,
x25519_sphinx_keys: Arc<x25519::KeyPair>,
// to be used when noise is integrated
#[allow(dead_code)]
x25519_noise_keys: Arc<x25519::KeyPair>,
}
impl NymNode {
pub(crate) async fn initialise(
config: &Config,
custom_mnemonic: Option<Zeroizing<bip39::Mnemonic>>,
) -> Result<(), NymNodeError> {
debug!("initialising nym-node with id: {}", config.id);
let mut rng = OsRng;
// global initialisation
let ed25519_identity_keys = ed25519::KeyPair::new(&mut rng);
let x25519_sphinx_keys = x25519::KeyPair::new(&mut rng);
let x25519_noise_keys = x25519::KeyPair::new(&mut rng);
trace!("attempting to store ed25519 identity keypair");
store_ed25519_identity_keypair(
&ed25519_identity_keys,
config.storage_paths.keys.ed25519_identity_storage_paths(),
)?;
trace!("attempting to store x25519 sphinx keypair");
store_x25519_sphinx_keypair(
&x25519_sphinx_keys,
config.storage_paths.keys.x25519_sphinx_storage_paths(),
)?;
trace!("attempting to store x25519 noise keypair");
store_x25519_noise_keypair(
&x25519_noise_keys,
config.storage_paths.keys.x25519_noise_storage_paths(),
)?;
trace!("creating description file");
save_node_description(
&config.storage_paths.description,
&NodeDescription::default(),
)?;
// mixnode initialisation
MixnodeData::initialise(&config.mixnode)?;
// entry gateway initialisation
EntryGatewayData::initialise(&config.entry_gateway, custom_mnemonic)?;
// exit gateway initialisation
ExitGatewayData::initialise(&config.exit_gateway, *ed25519_identity_keys.public_key())
.await?;
config.save()
}
pub(crate) async fn new(config: Config) -> Result<Self, NymNodeError> {
Ok(NymNode {
ed25519_identity_keys: Arc::new(load_ed25519_identity_keypair(
config.storage_paths.keys.ed25519_identity_storage_paths(),
)?),
x25519_sphinx_keys: Arc::new(load_x25519_sphinx_keypair(
config.storage_paths.keys.x25519_sphinx_storage_paths(),
)?),
x25519_noise_keys: Arc::new(load_x25519_noise_keypair(
config.storage_paths.keys.x25519_noise_storage_paths(),
)?),
description: load_node_description(&config.storage_paths.description)?,
verloc_stats: Default::default(),
mixnode: MixnodeData::new(&config.mixnode)?,
entry_gateway: EntryGatewayData::new(&config.entry_gateway).await?,
exit_gateway: ExitGatewayData::new(&config.exit_gateway)?,
config,
})
}
fn exit_network_requester_address(&self) -> Recipient {
Recipient::new(
self.exit_gateway.nr_ed25519,
self.exit_gateway.nr_x25519,
*self.ed25519_identity_keys.public_key(),
)
}
fn exit_ip_packet_router_address(&self) -> Recipient {
Recipient::new(
self.exit_gateway.ipr_ed25519,
self.exit_gateway.ipr_x25519,
*self.ed25519_identity_keys.public_key(),
)
}
pub(crate) fn display_details(&self) -> DisplayDetails {
DisplayDetails {
current_mode: self.config.mode,
description: self.description.clone(),
ed25519_identity_key: self.ed25519_identity_key().to_base58_string(),
x25519_sphinx_key: self.x25519_sphinx_key().to_base58_string(),
x25519_noise_key: self.x25519_noise_key().to_base58_string(),
exit_network_requester_address: self.exit_network_requester_address().to_string(),
exit_ip_packet_router_address: self.exit_ip_packet_router_address().to_string(),
}
}
pub(crate) fn mode(&self) -> NodeMode {
self.config.mode
}
pub(crate) fn ed25519_identity_key(&self) -> &ed25519::PublicKey {
self.ed25519_identity_keys.public_key()
}
pub(crate) fn x25519_sphinx_key(&self) -> &x25519::PublicKey {
self.x25519_sphinx_keys.public_key()
}
pub(crate) fn x25519_noise_key(&self) -> &x25519::PublicKey {
self.x25519_noise_keys.public_key()
}
fn start_mixnode(&self, task_client: TaskClient) -> Result<(), NymNodeError> {
info!("going to start the nym-node in MIXNODE mode");
let config = ephemeral_mixnode_config(self.config.clone())?;
let mut mixnode = MixNode::new_loaded(
config,
Default::default(),
self.ed25519_identity_keys.clone(),
self.x25519_sphinx_keys.clone(),
);
mixnode.disable_http_server();
mixnode.set_task_client(task_client);
mixnode.set_mixing_stats(self.mixnode.mixing_stats.clone());
mixnode.set_verloc_stats(self.verloc_stats.clone());
tokio::spawn(async move {
if let Err(err) = mixnode.run().await {
error!("the mixnode subtask has failed with the following message: {err}")
}
});
Ok(())
}
fn start_entry_gateway(&self, task_client: TaskClient) -> Result<(), NymNodeError> {
info!("going to start the nym-node in ENTRY GATEWAY mode");
let config =
ephemeral_entry_gateway_config(self.config.clone(), &self.entry_gateway.mnemonic)?;
let mut entry_gateway = Gateway::new_loaded(
config,
None,
None,
self.ed25519_identity_keys.clone(),
self.x25519_sphinx_keys.clone(),
self.entry_gateway.client_storage.clone(),
);
entry_gateway.disable_http_server();
entry_gateway.set_task_client(task_client);
entry_gateway.set_wireguard_client_registry(self.entry_gateway.client_registry.clone());
tokio::spawn(async move {
if let Err(err) = entry_gateway.run().await {
error!("the entry gateway subtask has failed with the following message: {err}")
}
});
Ok(())
}
fn start_exit_gateway(&self, task_client: TaskClient) -> Result<(), NymNodeError> {
info!("going to start the nym-node in EXIT GATEWAY mode");
let config =
ephemeral_exit_gateway_config(self.config.clone(), &self.entry_gateway.mnemonic)?;
let mut exit_gateway = Gateway::new_loaded(
config.gateway,
Some(config.nr_opts),
Some(config.ipr_opts),
self.ed25519_identity_keys.clone(),
self.x25519_sphinx_keys.clone(),
self.entry_gateway.client_storage.clone(),
);
exit_gateway.disable_http_server();
exit_gateway.set_task_client(task_client);
exit_gateway.set_wireguard_client_registry(self.entry_gateway.client_registry.clone());
tokio::spawn(async move {
if let Err(err) = exit_gateway.run().await {
error!("the exit gateway subtask has failed with the following message: {err}")
}
});
Ok(())
}
pub(crate) async fn build_http_server(&self) -> Result<NymNodeHTTPServer, NymNodeError> {
let host_details = sign_host_details(
&self.config,
self.x25519_sphinx_keys.public_key(),
self.x25519_noise_keys.public_key(),
&self.ed25519_identity_keys,
)?;
let auxiliary_details = api_requests::v1::node::models::AuxiliaryDetails {
location: self.config.host.location,
};
// mixnode info
let mixnode_details = api_requests::v1::mixnode::models::Mixnode {};
// entry gateway info
let wireguard = if self.config.wireguard.enabled {
Some(api_requests::v1::gateway::models::Wireguard {
port: self.config.wireguard.announced_port,
public_key: "placeholder key value".to_string(),
})
} else {
None
};
let mixnet_websockets = Some(api_requests::v1::gateway::models::WebSockets {
ws_port: self
.config
.entry_gateway
.announce_ws_port
.unwrap_or(self.config.entry_gateway.bind_address.port()),
wss_port: self.config.entry_gateway.announce_wss_port,
});
let gateway_details = api_requests::v1::gateway::models::Gateway {
enforces_zk_nyms: self.config.entry_gateway.enforce_zk_nyms,
client_interfaces: api_requests::v1::gateway::models::ClientInterfaces {
wireguard,
mixnet_websockets,
},
};
// exit gateway info
let nr_details = api_requests::v1::network_requester::models::NetworkRequester {
encoded_identity_key: self.exit_gateway.nr_ed25519.to_base58_string(),
encoded_x25519_key: self.exit_gateway.nr_x25519.to_base58_string(),
address: self.exit_network_requester_address().to_string(),
};
let ipr_details = api_requests::v1::ip_packet_router::models::IpPacketRouter {
encoded_identity_key: self.exit_gateway.ipr_ed25519.to_base58_string(),
encoded_x25519_key: self.exit_gateway.ipr_x25519.to_base58_string(),
address: self.exit_ip_packet_router_address().to_string(),
};
let exit_policy_details =
api_requests::v1::network_requester::exit_policy::models::UsedExitPolicy {
enabled: true,
upstream_source: self
.config
.exit_gateway
.upstream_exit_policy_url
.to_string(),
last_updated: 0,
// TODO: this will require some refactoring to actually retrieve the data from the embedded providers
policy: None,
};
let wireguard_private_network = IpNetwork::new(
self.config.wireguard.private_network_ip,
self.config.wireguard.private_network_prefix,
)?;
let wg_state = WireguardAppState::new(
self.entry_gateway.client_registry.clone(),
Default::default(),
self.config.wireguard.bind_address.port(),
wireguard_private_network,
)?;
let mut config = nym_node_http_api::Config::new(bin_info_owned!(), host_details)
.with_landing_page_assets(self.config.http.landing_page_assets_path.as_ref())
.with_mixnode_details(mixnode_details)
.with_gateway_details(gateway_details)
.with_network_requester_details(nr_details)
.with_ip_packet_router_details(ipr_details)
.with_used_exit_policy(exit_policy_details)
.with_description(self.description.clone())
.with_auxiliary_details(auxiliary_details);
if self.config.http.expose_system_info {
config = config.with_system_info(get_system_info(
self.config.http.expose_system_hardware,
self.config.http.expose_crypto_hardware,
))
}
match self.config.mode {
NodeMode::Mixnode => config.api.v1_config.node.roles.mixnode_enabled = true,
NodeMode::EntryGateway => config.api.v1_config.node.roles.gateway_enabled = true,
NodeMode::ExitGateway => {
config.api.v1_config.node.roles.gateway_enabled = true;
config.api.v1_config.node.roles.network_requester_enabled = true;
config.api.v1_config.node.roles.ip_packet_router_enabled = true;
}
}
let app_state = AppState::new()
.with_mixing_stats(self.mixnode.mixing_stats.clone())
.with_verloc_stats(self.verloc_stats.clone())
.with_metrics_key(self.config.http.access_token.clone());
Ok(NymNodeRouter::new(config, Some(app_state), Some(wg_state))
.build_server(&self.config.http.bind_address)
.await?)
}
pub(crate) async fn run(self) -> Result<(), NymNodeError> {
let mut task_manager = TaskManager::default().named("NymNode");
let http_server = self
.build_http_server()
.await?
.with_task_client(task_manager.subscribe_named("http-server"));
let bind_address = self.config.http.bind_address;
tokio::spawn(async move {
{
info!("Started NymNodeHTTPServer on {bind_address}");
http_server.run().await
}
});
match self.config.mode {
NodeMode::Mixnode => {
self.start_mixnode(task_manager.subscribe_named("mixnode"))?;
let _ = task_manager.catch_interrupt().await;
Ok(())
}
NodeMode::EntryGateway => {
self.start_entry_gateway(task_manager.subscribe_named("entry-gateway"))?;
let _ = task_manager.catch_interrupt().await;
Ok(())
}
NodeMode::ExitGateway => {
self.start_exit_gateway(task_manager.subscribe_named("exit-gateway"))?;
let _ = task_manager.catch_interrupt().await;
Ok(())
}
}
}
}