Merge pull request #4667 from nymtech/feature/authenticator
Add authenticator
This commit is contained in:
Generated
+50
@@ -3950,6 +3950,54 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-authenticator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"bs58 0.5.1",
|
||||
"bytes",
|
||||
"clap 4.5.4",
|
||||
"fastrand 2.1.0",
|
||||
"futures",
|
||||
"ipnetwork 0.16.0",
|
||||
"log",
|
||||
"nym-authenticator-requests",
|
||||
"nym-bin-common",
|
||||
"nym-client-core",
|
||||
"nym-config",
|
||||
"nym-crypto",
|
||||
"nym-id",
|
||||
"nym-network-defaults",
|
||||
"nym-sdk",
|
||||
"nym-service-providers-common",
|
||||
"nym-sphinx",
|
||||
"nym-task",
|
||||
"nym-types",
|
||||
"nym-wireguard",
|
||||
"nym-wireguard-types",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-authenticator-requests"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"nym-sphinx",
|
||||
"nym-wireguard-types",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-bandwidth-controller"
|
||||
version = "0.1.0"
|
||||
@@ -4514,6 +4562,7 @@ dependencies = [
|
||||
"ipnetwork 0.16.0",
|
||||
"log",
|
||||
"nym-api-requests",
|
||||
"nym-authenticator",
|
||||
"nym-bin-common",
|
||||
"nym-config",
|
||||
"nym-credentials",
|
||||
@@ -4964,6 +5013,7 @@ dependencies = [
|
||||
"cupid",
|
||||
"humantime-serde",
|
||||
"ipnetwork 0.16.0",
|
||||
"nym-authenticator",
|
||||
"nym-bin-common",
|
||||
"nym-client-core-config-types",
|
||||
"nym-config",
|
||||
|
||||
@@ -20,6 +20,7 @@ members = [
|
||||
"clients/native",
|
||||
"clients/native/websocket-requests",
|
||||
"clients/socks5",
|
||||
"common/authenticator-requests",
|
||||
"common/async-file-watcher",
|
||||
"common/bandwidth-controller",
|
||||
"common/bin-common",
|
||||
@@ -95,6 +96,7 @@ members = [
|
||||
"mixnode",
|
||||
"sdk/lib/socks5-listener",
|
||||
"sdk/rust/nym-sdk",
|
||||
"service-providers/authenticator",
|
||||
"service-providers/common",
|
||||
"service-providers/ip-packet-router",
|
||||
"service-providers/network-requester",
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "nym-authenticator-requests"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bincode = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-wireguard-types = { path = "../wireguard-types" }
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod v1;
|
||||
|
||||
pub const CURRENT_VERSION: u8 = 1;
|
||||
|
||||
fn make_bincode_serializer() -> impl bincode::Options {
|
||||
use bincode::Options;
|
||||
bincode::DefaultOptions::new()
|
||||
.with_big_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
const VERSION: u8 = 1;
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_wireguard_types::{GatewayClient, InitMessage};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
fn generate_random() -> u64 {
|
||||
use rand::RngCore;
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
rng.next_u64()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AuthenticatorRequest {
|
||||
pub version: u8,
|
||||
pub data: AuthenticatorRequestData,
|
||||
pub reply_to: Recipient,
|
||||
pub request_id: u64,
|
||||
}
|
||||
|
||||
impl AuthenticatorRequest {
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn new_initial_request(init_message: InitMessage, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorRequestData::Initial(init_message),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_final_request(gateway_client: GatewayClient, reply_to: Recipient) -> (Self, u64) {
|
||||
let request_id = generate_random();
|
||||
(
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorRequestData::Final(gateway_client),
|
||||
reply_to,
|
||||
request_id,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum AuthenticatorRequestData {
|
||||
Initial(InitMessage),
|
||||
Final(GatewayClient),
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_wireguard_types::registration::RegistrationData;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::make_bincode_serializer;
|
||||
|
||||
use super::VERSION;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AuthenticatorResponse {
|
||||
pub version: u8,
|
||||
pub data: AuthenticatorResponseData,
|
||||
pub reply_to: Recipient,
|
||||
}
|
||||
|
||||
impl AuthenticatorResponse {
|
||||
pub fn new_pending_registration_success(
|
||||
registration_data: RegistrationData,
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
) -> Self {
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorResponseData::PendingRegistration(PendingRegistrationResponse {
|
||||
reply: registration_data,
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_registered(reply_to: Recipient, request_id: u64) -> Self {
|
||||
Self {
|
||||
version: VERSION,
|
||||
data: AuthenticatorResponseData::Registered(RegisteredResponse {
|
||||
reply_to,
|
||||
request_id,
|
||||
}),
|
||||
reply_to,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recipient(&self) -> Recipient {
|
||||
self.reply_to
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().serialize(self)
|
||||
}
|
||||
|
||||
pub fn from_reconstructed_message(
|
||||
message: &nym_sphinx::receiver::ReconstructedMessage,
|
||||
) -> Result<Self, bincode::Error> {
|
||||
use bincode::Options;
|
||||
make_bincode_serializer().deserialize(&message.message)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Option<u64> {
|
||||
match &self.data {
|
||||
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
|
||||
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum AuthenticatorResponseData {
|
||||
PendingRegistration(PendingRegistrationResponse),
|
||||
Registered(RegisteredResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PendingRegistrationResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
pub reply: RegistrationData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RegisteredResponse {
|
||||
pub request_id: u64,
|
||||
pub reply_to: Recipient,
|
||||
}
|
||||
@@ -14,7 +14,7 @@ bytes = { workspace = true }
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
rand = "0.8.5"
|
||||
rand = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
time = { workspace = true }
|
||||
|
||||
@@ -10,8 +10,7 @@ pub use config::Config;
|
||||
pub use error::Error;
|
||||
pub use public_key::PeerPublicKey;
|
||||
pub use registration::{
|
||||
ClientMac, ClientMessage, ClientRegistrationResponse, GatewayClient, GatewayClientRegistry,
|
||||
InitMessage, Nonce,
|
||||
ClientMac, ClientMessage, GatewayClient, GatewayClientRegistry, InitMessage, Nonce,
|
||||
};
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
|
||||
@@ -7,6 +7,7 @@ use base64::{engine::general_purpose, Engine};
|
||||
use dashmap::DashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::IpAddr;
|
||||
use std::time::SystemTime;
|
||||
use std::{fmt, ops::Deref, str::FromStr};
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
@@ -18,13 +19,13 @@ use sha2::Sha256;
|
||||
|
||||
pub type GatewayClientRegistry = DashMap<PeerPublicKey, GatewayClient>;
|
||||
pub type PendingRegistrations = DashMap<PeerPublicKey, RegistrationData>;
|
||||
pub type PrivateIPs = DashMap<IpAddr, Free>;
|
||||
pub type PrivateIPs = DashMap<IpAddr, Taken>;
|
||||
|
||||
#[cfg(feature = "verify")]
|
||||
pub type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
pub type Nonce = u64;
|
||||
pub type Free = bool;
|
||||
pub type Taken = Option<SystemTime>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
@@ -53,15 +54,6 @@ impl InitMessage {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub enum ClientRegistrationResponse {
|
||||
PendingRegistration(RegistrationData),
|
||||
Registered,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct RegistrationData {
|
||||
pub nonce: u64,
|
||||
pub gateway_data: GatewayClient,
|
||||
|
||||
@@ -58,6 +58,7 @@ zeroize = { workspace = true }
|
||||
|
||||
# internal
|
||||
|
||||
nym-authenticator = { path = "../service-providers/authenticator" }
|
||||
nym-api-requests = { path = "../nym-api/nym-api-requests" }
|
||||
nym-bin-common = { path = "../common/bin-common", features = ["output_format"] }
|
||||
nym-config = { path = "../common/config" }
|
||||
|
||||
+22
-3
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::storage::error::StorageError;
|
||||
use nym_authenticator::error::AuthenticatorError;
|
||||
use nym_ip_packet_router::error::IpPacketRouterError;
|
||||
use nym_network_requester::error::{ClientCoreError, NetworkRequesterError};
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
@@ -60,6 +61,16 @@ pub enum GatewayError {
|
||||
source: io::Error,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"failed to load config file for authenticator (gateway-id: '{id}') using path '{}'. detailed message: {source}",
|
||||
path.display()
|
||||
)]
|
||||
AuthenticatorConfigLoadFailure {
|
||||
id: String,
|
||||
path: PathBuf,
|
||||
source: io::Error,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"failed to load config file for wireguard (gateway-id: '{id}') using path '{}'. detailed message: {source}",
|
||||
path.display()
|
||||
@@ -110,6 +121,9 @@ pub enum GatewayError {
|
||||
#[error("Path to ip packet router configuration file hasn't been specified. Perhaps try to run `setup-ip-packet-router`?")]
|
||||
UnspecifiedIpPacketRouterConfig,
|
||||
|
||||
#[error("Path to authenticator configuration file hasn't been specified. Perhaps try to run `setup-authenticator`?")]
|
||||
UnspecifiedAuthenticatorConfig,
|
||||
|
||||
#[error("there was an issue with the local network requester: {source}")]
|
||||
NetworkRequesterFailure {
|
||||
#[from]
|
||||
@@ -122,6 +136,12 @@ pub enum GatewayError {
|
||||
source: IpPacketRouterError,
|
||||
},
|
||||
|
||||
#[error("there was an issue with the local authenticator: {source}")]
|
||||
AuthenticatorFailure {
|
||||
#[from]
|
||||
source: AuthenticatorError,
|
||||
},
|
||||
|
||||
#[error("failed to startup local network requester")]
|
||||
NetworkRequesterStartupFailure,
|
||||
|
||||
@@ -174,9 +194,8 @@ pub enum GatewayError {
|
||||
#[error("wireguard not set")]
|
||||
WireguardNotSet,
|
||||
|
||||
#[cfg(all(feature = "wireguard", target_os = "linux"))]
|
||||
#[error("failed to catch an interrupt: {source}")]
|
||||
StdError {
|
||||
#[error("failed to start authenticator: {source}")]
|
||||
AuthenticatorStartError {
|
||||
source: Box<dyn std::error::Error + Send + Sync>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ impl<'a> HttpApiBuilder<'a> {
|
||||
}
|
||||
|
||||
let bind_address = self.gateway_config.http.bind_address;
|
||||
let router = nym_node_http_api::NymNodeRouter::new(config, None, None);
|
||||
let router = nym_node_http_api::NymNodeRouter::new(config, None);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let server = match router.build_server(&bind_address).await {
|
||||
|
||||
+40
-15
@@ -89,7 +89,7 @@ pub async fn create_gateway(
|
||||
|
||||
let ip_opts = ip_packet_router_config.map(|config| LocalIpPacketRouterOpts {
|
||||
config,
|
||||
custom_mixnet_path: custom_mixnet,
|
||||
custom_mixnet_path: custom_mixnet.clone(),
|
||||
});
|
||||
|
||||
Gateway::new(config, nr_opts, ip_opts, storage)
|
||||
@@ -109,6 +109,13 @@ pub struct LocalIpPacketRouterOpts {
|
||||
pub custom_mixnet_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalAuthenticatorOpts {
|
||||
pub config: nym_authenticator::Config,
|
||||
|
||||
pub custom_mixnet_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub struct Gateway<St = PersistentStorage> {
|
||||
config: Config,
|
||||
|
||||
@@ -116,6 +123,10 @@ pub struct Gateway<St = PersistentStorage> {
|
||||
|
||||
ip_packet_router_opts: Option<LocalIpPacketRouterOpts>,
|
||||
|
||||
// Use None when wireguard feature is not enabled too
|
||||
#[allow(dead_code)]
|
||||
authenticator_opts: Option<LocalAuthenticatorOpts>,
|
||||
|
||||
/// ed25519 keypair used to assert one's identity.
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
|
||||
@@ -146,6 +157,7 @@ impl<St> Gateway<St> {
|
||||
config,
|
||||
network_requester_opts,
|
||||
ip_packet_router_opts,
|
||||
authenticator_opts: None,
|
||||
#[cfg(all(feature = "wireguard", target_os = "linux"))]
|
||||
wireguard_data: None,
|
||||
run_http_server: true,
|
||||
@@ -157,6 +169,7 @@ impl<St> Gateway<St> {
|
||||
config: Config,
|
||||
network_requester_opts: Option<LocalNetworkRequesterOpts>,
|
||||
ip_packet_router_opts: Option<LocalIpPacketRouterOpts>,
|
||||
authenticator_opts: Option<LocalAuthenticatorOpts>,
|
||||
identity_keypair: Arc<identity::KeyPair>,
|
||||
sphinx_keypair: Arc<encryption::KeyPair>,
|
||||
storage: St,
|
||||
@@ -165,6 +178,7 @@ impl<St> Gateway<St> {
|
||||
config,
|
||||
network_requester_opts,
|
||||
ip_packet_router_opts,
|
||||
authenticator_opts,
|
||||
identity_keypair,
|
||||
sphinx_keypair,
|
||||
storage,
|
||||
@@ -222,11 +236,18 @@ impl<St> Gateway<St> {
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "wireguard", target_os = "linux"))]
|
||||
async fn start_wireguard(
|
||||
async fn start_authenticator(
|
||||
&mut self,
|
||||
opts: &LocalAuthenticatorOpts,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<Arc<nym_wireguard::WgApiWrapper>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
if let Some(wireguard_data) = self.wireguard_data.take() {
|
||||
let authenticator_server = nym_authenticator::Authenticator::new(
|
||||
opts.config.clone(),
|
||||
wireguard_data.inner.clone(),
|
||||
)
|
||||
.with_shutdown(shutdown.fork("authenticator"));
|
||||
tokio::spawn(async move { authenticator_server.run_service_provider().await });
|
||||
nym_wireguard::start_wireguard(shutdown, wireguard_data).await
|
||||
} else {
|
||||
Err(Box::new(GatewayError::WireguardNotSet))
|
||||
@@ -234,8 +255,12 @@ impl<St> Gateway<St> {
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "wireguard", not(target_os = "linux")))]
|
||||
async fn start_wireguard(&self, _shutdown: TaskClient) {
|
||||
nym_wireguard::start_wireguard().await
|
||||
async fn start_authenticator(
|
||||
&self,
|
||||
_opts: &LocalAuthenticatorOpts,
|
||||
_shutdown: TaskClient,
|
||||
) -> Result<Arc<nym_wireguard::WgApiWrapper>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
todo!("Authenticator is currently only supported on Linux");
|
||||
}
|
||||
|
||||
fn start_client_websocket_listener(
|
||||
@@ -538,6 +563,17 @@ impl<St> Gateway<St> {
|
||||
info!("embedded ip packet router is disabled");
|
||||
};
|
||||
|
||||
#[cfg(feature = "wireguard")]
|
||||
let _wg_api = if let Some(opts) = self.authenticator_opts.clone() {
|
||||
Some(
|
||||
self.start_authenticator(&opts, shutdown.fork("wireguard"))
|
||||
.await
|
||||
.map_err(|source| GatewayError::AuthenticatorStartError { source })?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if self.run_http_server {
|
||||
HttpApiBuilder::new(
|
||||
&self.config,
|
||||
@@ -550,17 +586,6 @@ impl<St> Gateway<St> {
|
||||
.start(shutdown.fork("http-api"))?;
|
||||
}
|
||||
|
||||
// Once this is a bit more mature, make this a commandline flag instead of a compile time
|
||||
// flag
|
||||
#[cfg(all(feature = "wireguard", target_os = "linux"))]
|
||||
let _wg_api = self
|
||||
.start_wireguard(shutdown.fork("wireguard"))
|
||||
.await
|
||||
.map_err(|source| GatewayError::StdError { source })?;
|
||||
|
||||
#[cfg(all(feature = "wireguard", not(target_os = "linux")))]
|
||||
self.start_wireguard(shutdown.fork("wireguard")).await;
|
||||
|
||||
info!("Finished nym gateway startup procedure - it should now be able to receive mix and client traffic!");
|
||||
|
||||
info!(
|
||||
|
||||
@@ -103,7 +103,7 @@ impl<'a> HttpApiBuilder<'a> {
|
||||
.with_mixnode(load_mixnode_details(self.mixnode_config)?)
|
||||
.with_landing_page_assets(self.mixnode_config.http.landing_page_assets_path.as_ref());
|
||||
|
||||
let router = nym_node_http_api::NymNodeRouter::new(config, None, None);
|
||||
let router = nym_node_http_api::NymNodeRouter::new(config, None);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let server = match router.build_server(&bind_address).await {
|
||||
|
||||
@@ -606,6 +606,9 @@ pub struct NymNodeDescription {
|
||||
#[serde(default)]
|
||||
pub ip_packet_router: Option<IpPacketRouterDetails>,
|
||||
|
||||
#[serde(default)]
|
||||
pub authenticator: Option<AuthenticatorDetails>,
|
||||
|
||||
// for now we only care about their ws/wss situation, nothing more
|
||||
pub mixnet_websockets: WebSockets,
|
||||
|
||||
@@ -657,6 +660,12 @@ pub struct IpPacketRouterDetails {
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct AuthenticatorDetails {
|
||||
/// address of the embedded authenticator
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct ApiHealthResponse {
|
||||
pub status: ApiStatus,
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::support::config;
|
||||
use crate::support::config::DEFAULT_NODE_DESCRIBE_BATCH_SIZE;
|
||||
use futures::{stream, StreamExt};
|
||||
use nym_api_requests::models::{
|
||||
IpPacketRouterDetails, NetworkRequesterDetails, NymNodeDescription,
|
||||
AuthenticatorDetails, IpPacketRouterDetails, NetworkRequesterDetails, NymNodeDescription,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::NodeRole;
|
||||
use nym_config::defaults::{mainnet, DEFAULT_NYM_NODE_HTTP_PORT};
|
||||
@@ -189,12 +189,21 @@ async fn try_get_description(
|
||||
None
|
||||
};
|
||||
|
||||
let authenticator = if let Ok(auth) = client.get_authenticator().await {
|
||||
Some(AuthenticatorDetails {
|
||||
address: auth.address,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let description = NymNodeDescription {
|
||||
host_information: host_info.data.into(),
|
||||
last_polled: OffsetDateTime::now_utc().into(),
|
||||
build_information: build_info,
|
||||
network_requester,
|
||||
ip_packet_router,
|
||||
authenticator,
|
||||
mixnet_websockets: websockets.into(),
|
||||
auxiliary_details,
|
||||
role: data.role(),
|
||||
|
||||
@@ -55,6 +55,7 @@ nym-wireguard-types = { path = "../common/wireguard-types", default-features = f
|
||||
# nodes:
|
||||
nym-mixnode = { path = "../mixnode" }
|
||||
nym-gateway = { path = "../gateway" }
|
||||
nym-authenticator = { path = "../service-providers/authenticator" }
|
||||
nym-network-requester = { path = "../service-providers/network-requester" }
|
||||
nym-ip-packet-router = { path = "../service-providers/ip-packet-router" }
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::api::v1::gateway::client_interfaces::wireguard::WireguardAppState;
|
||||
use crate::state::AppState;
|
||||
use axum::Router;
|
||||
use nym_node_requests::routes;
|
||||
@@ -17,9 +16,6 @@ pub struct Config {
|
||||
pub v1_config: v1::Config,
|
||||
}
|
||||
|
||||
pub(super) fn routes(config: Config, initial_wg_state: WireguardAppState) -> Router<AppState> {
|
||||
Router::new().nest(
|
||||
routes::api::V1,
|
||||
v1::routes(config.v1_config, initial_wg_state),
|
||||
)
|
||||
pub(super) fn routes(config: Config) -> Router<AppState> {
|
||||
Router::new().nest(routes::api::V1, v1::routes(config.v1_config))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use nym_node_requests::api::v1::authenticator::models;
|
||||
|
||||
pub mod root;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Config {
|
||||
pub details: Option<models::Authenticator>,
|
||||
}
|
||||
|
||||
pub(crate) fn routes<S: Send + Sync + 'static + Clone>(config: Config) -> Router<S> {
|
||||
Router::new().route(
|
||||
"/",
|
||||
get({
|
||||
let authenticator_details = config.details;
|
||||
move |query| root::root_authenticator(authenticator_details, query)
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::router::api::{FormattedResponse, OutputParams};
|
||||
use axum::extract::Query;
|
||||
use axum::http::StatusCode;
|
||||
use nym_node_requests::api::v1::authenticator::models::Authenticator;
|
||||
|
||||
/// Returns root authenticator information
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "",
|
||||
context_path = "/api/v1/authenticator",
|
||||
tag = "Authenticator",
|
||||
responses(
|
||||
(status = 501, description = "the endpoint hasn't been implemented yet"),
|
||||
(status = 200, content(
|
||||
("application/json" = Authenticator),
|
||||
("application/yaml" = Authenticator)
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
pub(crate) async fn root_authenticator(
|
||||
details: Option<Authenticator>,
|
||||
Query(output): Query<OutputParams>,
|
||||
) -> Result<AuthenticatorResponse, StatusCode> {
|
||||
let details = details.ok_or(StatusCode::NOT_IMPLEMENTED)?;
|
||||
let output = output.output.unwrap_or_default();
|
||||
Ok(output.to_response(details))
|
||||
}
|
||||
|
||||
pub type AuthenticatorResponse = FormattedResponse<Authenticator>;
|
||||
@@ -1,20 +1,16 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::api::v1::gateway::client_interfaces::wireguard::WireguardAppState;
|
||||
use crate::api::{FormattedResponse, OutputParams};
|
||||
use axum::extract::Query;
|
||||
use axum::http::StatusCode;
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use nym_node_requests::api::v1::gateway::models::{ClientInterfaces, WebSockets, Wireguard};
|
||||
use nym_node_requests::api::v1::gateway::models::{ClientInterfaces, WebSockets};
|
||||
use nym_node_requests::routes::api::v1::gateway::client_interfaces;
|
||||
|
||||
pub(crate) mod wireguard;
|
||||
|
||||
pub(crate) fn routes<S: Send + Sync + 'static + Clone>(
|
||||
interfaces: Option<ClientInterfaces>,
|
||||
initial_wg_state: WireguardAppState,
|
||||
) -> Router<S> {
|
||||
Router::new()
|
||||
.route(
|
||||
@@ -31,17 +27,6 @@ pub(crate) fn routes<S: Send + Sync + 'static + Clone>(
|
||||
move |query| mixnet_websockets(websockets, query)
|
||||
}),
|
||||
)
|
||||
.nest(
|
||||
client_interfaces::WIREGUARD,
|
||||
wireguard::routes(initial_wg_state),
|
||||
)
|
||||
.route(
|
||||
client_interfaces::WIREGUARD,
|
||||
get({
|
||||
let wireguard_cfg_info = interfaces.and_then(|i| i.wireguard);
|
||||
move |query| wireguard_info(wireguard_cfg_info, query)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns client interfaces supported by this gateway.
|
||||
@@ -70,32 +55,6 @@ pub(crate) async fn client_interfaces(
|
||||
|
||||
pub type ClientInterfacesResponse = FormattedResponse<ClientInterfaces>;
|
||||
|
||||
/// Returns client interfaces supported by this gateway.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/wireguard",
|
||||
context_path = "/api/v1/gateway/client-interfaces",
|
||||
tag = "Gateway",
|
||||
responses(
|
||||
(status = 501, description = "the endpoint hasn't been implemented yet"),
|
||||
(status = 200, content(
|
||||
("application/json" = Wireguard),
|
||||
("application/yaml" = Wireguard)
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
pub(crate) async fn wireguard_info(
|
||||
wireguard: Option<Wireguard>,
|
||||
Query(output): Query<OutputParams>,
|
||||
) -> Result<WireguardResponse, StatusCode> {
|
||||
let wireguard = wireguard.ok_or(StatusCode::NOT_IMPLEMENTED)?;
|
||||
let output = output.output.unwrap_or_default();
|
||||
Ok(output.to_response(wireguard))
|
||||
}
|
||||
|
||||
pub type WireguardResponse = FormattedResponse<Wireguard>;
|
||||
|
||||
/// Returns client interfaces supported by this gateway.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
|
||||
-265
@@ -1,265 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use super::error::WireguardError;
|
||||
use crate::api::v1::gateway::client_interfaces::wireguard::{
|
||||
WireguardAppState, WireguardAppStateInner,
|
||||
};
|
||||
use crate::api::{FormattedResponse, OutputParams};
|
||||
use crate::router::types::RequestError;
|
||||
use axum::extract::{Path, Query, State};
|
||||
use axum::http::StatusCode;
|
||||
use axum::Json;
|
||||
use nym_node_requests::api::v1::gateway::client_interfaces::wireguard::models::{
|
||||
ClientMessage, ClientRegistrationResponse, GatewayClient, InitMessage, PeerPublicKey,
|
||||
};
|
||||
use nym_wireguard_types::registration::RegistrationData;
|
||||
use rand::{prelude::IteratorRandom, thread_rng};
|
||||
|
||||
fn remove_from_registry(
|
||||
state: &WireguardAppStateInner,
|
||||
remote_public: &PeerPublicKey,
|
||||
gateway_client: &GatewayClient,
|
||||
) -> Result<(), RequestError> {
|
||||
state
|
||||
.wireguard_gateway_data
|
||||
.remove_peer(gateway_client)
|
||||
.map_err(|err| RequestError::from_err(err, StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
state
|
||||
.wireguard_gateway_data
|
||||
.client_registry()
|
||||
.remove(remote_public);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn process_final_message(
|
||||
client: GatewayClient,
|
||||
state: &WireguardAppStateInner,
|
||||
) -> Result<ClientRegistrationResponse, RequestError> {
|
||||
let registration_data = state
|
||||
.registration_in_progress
|
||||
.get(&client.pub_key())
|
||||
.ok_or(RequestError::from_err(
|
||||
WireguardError::RegistrationNotInProgress,
|
||||
StatusCode::BAD_REQUEST,
|
||||
))?
|
||||
.value()
|
||||
.clone();
|
||||
|
||||
if client
|
||||
.verify(
|
||||
state.wireguard_gateway_data.keypair().private_key(),
|
||||
registration_data.nonce,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
state
|
||||
.wireguard_gateway_data
|
||||
.add_peer(&client)
|
||||
.map_err(|err| RequestError::from_err(err, StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
state.registration_in_progress.remove(&client.pub_key());
|
||||
state
|
||||
.wireguard_gateway_data
|
||||
.client_registry()
|
||||
.insert(client.pub_key(), client);
|
||||
|
||||
Ok(ClientRegistrationResponse::Registered)
|
||||
} else {
|
||||
Err(RequestError::from_err(
|
||||
WireguardError::MacVerificationFailure,
|
||||
StatusCode::BAD_REQUEST,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_init_message(
|
||||
init_message: InitMessage,
|
||||
state: &WireguardAppStateInner,
|
||||
) -> Result<ClientRegistrationResponse, RequestError> {
|
||||
let remote_public = init_message.pub_key();
|
||||
let nonce: u64 = fastrand::u64(..);
|
||||
if let Some(registration_data) = state.registration_in_progress.get(&remote_public) {
|
||||
return Ok(ClientRegistrationResponse::PendingRegistration(
|
||||
registration_data.value().clone(),
|
||||
));
|
||||
}
|
||||
let gateway_client_opt = if let Some(gateway_client) = state
|
||||
.wireguard_gateway_data
|
||||
.client_registry()
|
||||
.get(&remote_public)
|
||||
{
|
||||
let mut private_ip_ref = state
|
||||
.free_private_network_ips
|
||||
.get_mut(&gateway_client.private_ip)
|
||||
.ok_or(RequestError::new(
|
||||
"Internal data corruption",
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
))?;
|
||||
*private_ip_ref = true;
|
||||
Some(gateway_client.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(gateway_client) = gateway_client_opt {
|
||||
remove_from_registry(state, &remote_public, &gateway_client)?;
|
||||
}
|
||||
let mut private_ip_ref = state
|
||||
.free_private_network_ips
|
||||
.iter_mut()
|
||||
.filter(|r| **r)
|
||||
.choose(&mut thread_rng())
|
||||
.ok_or(RequestError::new(
|
||||
"No more space in the network",
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
))?;
|
||||
// mark it as used, even though it's not final
|
||||
*private_ip_ref = false;
|
||||
let gateway_data = GatewayClient::new(
|
||||
state.wireguard_gateway_data.keypair().private_key(),
|
||||
remote_public.inner(),
|
||||
*private_ip_ref.key(),
|
||||
nonce,
|
||||
);
|
||||
let registration_data = RegistrationData {
|
||||
nonce,
|
||||
gateway_data,
|
||||
wg_port: state.binding_port,
|
||||
};
|
||||
state
|
||||
.registration_in_progress
|
||||
.insert(remote_public, registration_data.clone());
|
||||
Ok(ClientRegistrationResponse::PendingRegistration(
|
||||
registration_data,
|
||||
))
|
||||
}
|
||||
|
||||
/// Perform wireguard client registration.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/client",
|
||||
context_path = "/api/v1/gateway/client-interfaces/wireguard",
|
||||
tag = "Wireguard (EXPERIMENTAL AND UNSTABLE)",
|
||||
request_body(
|
||||
content = ClientMessage,
|
||||
description = "Data used for proceeding with client wireguard registration",
|
||||
content_type = "application/json"
|
||||
),
|
||||
responses(
|
||||
(status = 501, body = ErrorResponse, description = "the endpoint hasn't been implemented yet"),
|
||||
(status = 400, body = ErrorResponse),
|
||||
(status = 200, content(
|
||||
("application/json" = ClientRegistrationResponse),
|
||||
("application/yaml" = ClientRegistrationResponse)
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
pub(crate) async fn register_client(
|
||||
State(state): State<WireguardAppState>,
|
||||
Query(output): Query<OutputParams>,
|
||||
Json(payload): Json<ClientMessage>,
|
||||
) -> Result<RegisterClientResponse, RequestError> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let Some(state) = state.inner() else {
|
||||
return Err(RequestError::new_status(StatusCode::NOT_IMPLEMENTED));
|
||||
};
|
||||
|
||||
let response = match payload {
|
||||
ClientMessage::Initial(init) => process_init_message(init, state).await?,
|
||||
ClientMessage::Final(finalize) => process_final_message(finalize, state).await?,
|
||||
};
|
||||
Ok(output.to_response(response))
|
||||
}
|
||||
|
||||
pub type RegisterClientResponse = FormattedResponse<ClientRegistrationResponse>;
|
||||
|
||||
/// Get public keys of all registered wireguard clients.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/clients",
|
||||
context_path = "/api/v1/gateway/client-interfaces/wireguard",
|
||||
tag = "Wireguard (EXPERIMENTAL AND UNSTABLE)",
|
||||
responses(
|
||||
(status = 501, body = ErrorResponse, description = "the endpoint hasn't been implemented yet"),
|
||||
(status = 200, content(
|
||||
("application/json" = Vec<String>),
|
||||
("application/yaml" = Vec<String>)
|
||||
))
|
||||
),
|
||||
params(OutputParams)
|
||||
)]
|
||||
pub(crate) async fn get_all_clients(
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<WireguardAppState>,
|
||||
) -> Result<AllClientsResponse, RequestError> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let Some(state) = state.inner() else {
|
||||
return Err(RequestError::new_status(StatusCode::NOT_IMPLEMENTED));
|
||||
};
|
||||
|
||||
let clients = state
|
||||
.wireguard_gateway_data
|
||||
.client_registry()
|
||||
.iter()
|
||||
.map(|c| c.pub_key())
|
||||
.collect::<Vec<PeerPublicKey>>();
|
||||
|
||||
Ok(output.to_response(clients))
|
||||
}
|
||||
|
||||
pub type AllClientsResponse = FormattedResponse<Vec<PeerPublicKey>>;
|
||||
|
||||
/// Get client details of the registered wireguard client by its public key.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/client/{pub_key}",
|
||||
context_path = "/api/v1/gateway/client-interfaces/wireguard",
|
||||
tag = "Wireguard (EXPERIMENTAL AND UNSTABLE)",
|
||||
params(
|
||||
("pub_key", Path, description = "The public key of the client"),
|
||||
OutputParams
|
||||
),
|
||||
responses(
|
||||
(status = 501, body = ErrorResponse, description = "the endpoint hasn't been implemented yet"),
|
||||
(status = 404, body = ErrorResponse, description = "there are no clients with the provided public key"),
|
||||
(status = 400, body = ErrorResponse),
|
||||
(status = 200, content(
|
||||
("application/json" = Vec<GatewayClient>),
|
||||
("application/yaml" = Vec<GatewayClient>)
|
||||
))
|
||||
),
|
||||
)]
|
||||
pub(crate) async fn get_client(
|
||||
Path(pub_key): Path<PeerPublicKey>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<WireguardAppState>,
|
||||
) -> Result<ClientResponse, RequestError> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let Some(state) = state.inner() else {
|
||||
return Err(RequestError::new_status(StatusCode::NOT_IMPLEMENTED));
|
||||
};
|
||||
|
||||
let clients = state
|
||||
.wireguard_gateway_data
|
||||
.client_registry()
|
||||
.iter()
|
||||
.filter_map(|c| {
|
||||
if c.pub_key() == pub_key {
|
||||
Some(c.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<GatewayClient>>();
|
||||
|
||||
if clients.is_empty() {
|
||||
return Err(RequestError::new_status(StatusCode::NOT_FOUND));
|
||||
}
|
||||
|
||||
Ok(output.to_response(clients))
|
||||
}
|
||||
|
||||
pub type ClientResponse = FormattedResponse<Vec<GatewayClient>>;
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum WireguardError {
|
||||
#[error("the client is currently not in the process of being registered")]
|
||||
RegistrationNotInProgress,
|
||||
|
||||
#[error("the client mac failed to get verified correctly")]
|
||||
MacVerificationFailure,
|
||||
}
|
||||
-296
@@ -1,296 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::api::v1::gateway::client_interfaces::wireguard::client_registry::{
|
||||
get_all_clients, get_client, register_client,
|
||||
};
|
||||
use crate::error::NymNodeHttpError;
|
||||
use axum::routing::{get, post};
|
||||
use axum::Router;
|
||||
use ipnetwork::IpNetwork;
|
||||
use nym_node_requests::routes::api::v1::gateway::client_interfaces::wireguard;
|
||||
use nym_wireguard::WireguardGatewayData;
|
||||
use nym_wireguard_types::registration::PendingRegistrations;
|
||||
use nym_wireguard_types::registration::PrivateIPs;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) mod client_registry;
|
||||
mod error;
|
||||
|
||||
// I don't see any reason why this state should be accessible to any routes outside /wireguard
|
||||
// if anyone finds compelling reason, it could be moved to the `AppState` struct instead
|
||||
#[derive(Clone, Default)]
|
||||
pub struct WireguardAppState {
|
||||
inner: Option<WireguardAppStateInner>,
|
||||
}
|
||||
|
||||
impl WireguardAppState {
|
||||
pub fn new(
|
||||
wireguard_gateway_data: WireguardGatewayData,
|
||||
registration_in_progress: Arc<PendingRegistrations>,
|
||||
binding_port: u16,
|
||||
private_ip_network: IpNetwork,
|
||||
) -> Result<Self, NymNodeHttpError> {
|
||||
Ok(WireguardAppState {
|
||||
inner: Some(WireguardAppStateInner {
|
||||
wireguard_gateway_data,
|
||||
registration_in_progress,
|
||||
binding_port,
|
||||
free_private_network_ips: Arc::new(
|
||||
private_ip_network.iter().map(|ip| (ip, true)).collect(),
|
||||
),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
// #[allow(dead_code)]
|
||||
// pub(crate) fn dh_keypair(&self) -> Option<&encryption::KeyPair> {
|
||||
// self.inner.as_ref().map(|s| s.dh_keypair.as_ref())
|
||||
// }
|
||||
//
|
||||
// #[allow(dead_code)]
|
||||
// pub(crate) fn client_registry(&self) -> Option<&RwLock<ClientRegistry>> {
|
||||
// self.inner.as_ref().map(|s| s.client_registry.as_ref())
|
||||
// }
|
||||
//
|
||||
// #[allow(dead_code)]
|
||||
// pub(crate) fn registration_in_progress(&self) -> Option<&RwLock<PendingRegistrations>> {
|
||||
// self.inner
|
||||
// .as_ref()
|
||||
// .map(|s| s.registration_in_progress.as_ref())
|
||||
// }
|
||||
|
||||
// not sure what to feel about exposing this method
|
||||
pub(crate) fn inner(&self) -> Option<&WireguardAppStateInner> {
|
||||
self.inner.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
// helper macro to deal with missing wg state (if not being exposed by the node)
|
||||
#[macro_export]
|
||||
macro_rules! get_state {
|
||||
( $state: ident, $field: ident ) => {{
|
||||
let Some(ref inner) = $state.inner else {
|
||||
return ::axum::http::StatusCode::NOT_IMPLEMENTED;
|
||||
};
|
||||
inner.$field.as_ref()
|
||||
}};
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct WireguardAppStateInner {
|
||||
wireguard_gateway_data: WireguardGatewayData,
|
||||
registration_in_progress: Arc<PendingRegistrations>,
|
||||
binding_port: u16,
|
||||
free_private_network_ips: Arc<PrivateIPs>,
|
||||
}
|
||||
|
||||
pub(crate) fn routes<S>(initial_state: WireguardAppState) -> Router<S> {
|
||||
Router::new()
|
||||
// .route("/", get())
|
||||
.route(wireguard::CLIENTS, get(get_all_clients))
|
||||
.route(wireguard::CLIENT, post(register_client))
|
||||
.route(&format!("{}/:pub_key", wireguard::CLIENT), get(get_client))
|
||||
.with_state(initial_state)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::api::v1::gateway::client_interfaces::wireguard::{
|
||||
routes, WireguardAppState, WireguardAppStateInner,
|
||||
};
|
||||
use axum::body::to_bytes;
|
||||
use axum::body::Body;
|
||||
use axum::http::Request;
|
||||
use axum::http::StatusCode;
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use dashmap::DashMap;
|
||||
use hmac::Mac;
|
||||
use ipnetwork::IpNetwork;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
use nym_node_requests::api::v1::gateway::client_interfaces::wireguard::models::{
|
||||
ClientMac, ClientMessage, ClientRegistrationResponse, GatewayClient, InitMessage,
|
||||
PeerPublicKey,
|
||||
};
|
||||
use nym_node_requests::routes::api::v1::gateway::client_interfaces::wireguard;
|
||||
use nym_wireguard::{peer_controller::PeerControlMessage, WireguardGatewayData};
|
||||
use nym_wireguard_types::registration::{HmacSha256, RegistrationData};
|
||||
use std::net::IpAddr;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tower::Service;
|
||||
use tower::ServiceExt;
|
||||
use x25519_dalek::{PublicKey, StaticSecret};
|
||||
|
||||
const PRIVATE_KEY: &str = "AEqXrLFT4qjYq3wmX0456iv94uM6nDj5ugp6Jedcflg=";
|
||||
|
||||
fn decode_base64_key(base64_key: &str) -> [u8; 32] {
|
||||
general_purpose::STANDARD
|
||||
.decode(base64_key)
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn server_static_private_key() -> x25519_dalek::StaticSecret {
|
||||
// TODO: this is a temporary solution for development
|
||||
let static_private_bytes: [u8; 32] = decode_base64_key(PRIVATE_KEY);
|
||||
x25519_dalek::StaticSecret::from(static_private_bytes)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn registration() {
|
||||
// 1. Provision random keys for gateway and client
|
||||
// 2. Generate DH shared secret
|
||||
// 3. Client submits its public key to the gateway to start the handshake process, gateway responds with nonce
|
||||
// 4. Client generates mac digest using DH shared secret, its own public key, socket address and port, and nonce
|
||||
// 5. Client sends its public key, socket address and port, nonce and mac digest to the gateway
|
||||
// 6. Gateway verifies mac digest and nonce, and stores client's public key and socket address and port
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let gateway_private_key =
|
||||
encryption::PrivateKey::from_bytes(server_static_private_key().as_bytes()).unwrap();
|
||||
let gateway_public_key = encryption::PublicKey::from(&gateway_private_key);
|
||||
|
||||
let gateway_key_pair = encryption::KeyPair::from_bytes(
|
||||
&gateway_private_key.to_bytes(),
|
||||
&gateway_public_key.to_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
let client_key_pair = encryption::KeyPair::new(&mut rng);
|
||||
|
||||
let gateway_static_public = PublicKey::from(gateway_key_pair.public_key().to_bytes());
|
||||
|
||||
let client_static_private = StaticSecret::from(client_key_pair.private_key().to_bytes());
|
||||
let client_static_public = PublicKey::from(client_key_pair.public_key().to_bytes());
|
||||
|
||||
let client_dh = client_static_private.diffie_hellman(&gateway_static_public);
|
||||
|
||||
let registration_in_progress = Arc::new(DashMap::new());
|
||||
let free_private_network_ips = Arc::new(
|
||||
IpNetwork::from_str("10.1.0.0/24")
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|ip| (ip, true))
|
||||
.collect(),
|
||||
);
|
||||
let client_private_ip = IpAddr::from_str("10.1.0.42").unwrap();
|
||||
let (wireguard_gateway_data, mut peer_rx) = WireguardGatewayData::new(
|
||||
nym_wireguard_types::Config {
|
||||
bind_address: "0.0.0.0:51822".parse().unwrap(),
|
||||
private_ip: "10.1.0.1".parse().unwrap(),
|
||||
announced_port: 51822,
|
||||
private_network_prefix: 16,
|
||||
},
|
||||
Arc::new(gateway_key_pair),
|
||||
);
|
||||
|
||||
let state = WireguardAppState {
|
||||
inner: Some(WireguardAppStateInner {
|
||||
wireguard_gateway_data: wireguard_gateway_data.clone(),
|
||||
registration_in_progress: Arc::clone(®istration_in_progress),
|
||||
binding_port: 8080,
|
||||
free_private_network_ips,
|
||||
}),
|
||||
};
|
||||
|
||||
// `Router` implements `tower::Service<Request<Body>>` so we can
|
||||
// call it like any tower service, no need to run an HTTP server.
|
||||
let mut app = routes(state);
|
||||
|
||||
let init_message = ClientMessage::Initial(InitMessage {
|
||||
pub_key: PeerPublicKey::new(client_static_public),
|
||||
});
|
||||
|
||||
let init_request = Request::builder()
|
||||
.method("POST")
|
||||
.uri(wireguard::CLIENT)
|
||||
.header("Content-type", "application/json")
|
||||
.body(Body::from(serde_json::to_vec(&init_message).unwrap()))
|
||||
.unwrap();
|
||||
|
||||
let response = ServiceExt::<Request<Body>>::ready(&mut app)
|
||||
.await
|
||||
.unwrap()
|
||||
.call(init_request)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert!(!registration_in_progress.is_empty());
|
||||
|
||||
let ClientRegistrationResponse::PendingRegistration(RegistrationData {
|
||||
nonce,
|
||||
gateway_data,
|
||||
wg_port: 8080,
|
||||
}) = serde_json::from_slice(&to_bytes(response.into_body(), usize::MAX).await.unwrap())
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("invalid response")
|
||||
};
|
||||
assert!(gateway_data
|
||||
.verify(client_key_pair.private_key(), nonce)
|
||||
.is_ok());
|
||||
|
||||
let mut mac = HmacSha256::new_from_slice(client_dh.as_bytes()).unwrap();
|
||||
mac.update(client_static_public.as_bytes());
|
||||
mac.update(client_private_ip.to_string().as_bytes());
|
||||
mac.update(&nonce.to_le_bytes());
|
||||
let mac = mac.finalize().into_bytes();
|
||||
|
||||
let finalized_message = ClientMessage::Final(GatewayClient {
|
||||
pub_key: PeerPublicKey::new(client_static_public),
|
||||
private_ip: client_private_ip,
|
||||
mac: ClientMac::new(mac.as_slice().to_vec()),
|
||||
});
|
||||
|
||||
let final_request = Request::builder()
|
||||
.method("POST")
|
||||
.uri(wireguard::CLIENT)
|
||||
.header("Content-type", "application/json")
|
||||
.body(Body::from(serde_json::to_vec(&finalized_message).unwrap()))
|
||||
.unwrap();
|
||||
|
||||
let response = ServiceExt::<Request<Body>>::ready(&mut app)
|
||||
.await
|
||||
.unwrap()
|
||||
.call(final_request)
|
||||
.await
|
||||
.unwrap();
|
||||
let msg = peer_rx.recv().await.unwrap();
|
||||
|
||||
assert!(matches!(msg, PeerControlMessage::AddPeer(_)));
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert!(!wireguard_gateway_data.client_registry().is_empty());
|
||||
|
||||
let clients_request = Request::builder()
|
||||
.method("GET")
|
||||
.uri(wireguard::CLIENTS)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let response = ServiceExt::<Request<Body>>::ready(&mut app)
|
||||
.await
|
||||
.unwrap()
|
||||
.call(clients_request)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let clients: Vec<PeerPublicKey> =
|
||||
serde_json::from_slice(&to_bytes(response.into_body(), usize::MAX).await.unwrap())
|
||||
.unwrap();
|
||||
|
||||
assert!(!clients.is_empty());
|
||||
|
||||
assert_eq!(
|
||||
wireguard_gateway_data
|
||||
.client_registry()
|
||||
.iter()
|
||||
.map(|c| c.value().pub_key())
|
||||
.collect::<Vec<PeerPublicKey>>(),
|
||||
clients
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::api::v1::gateway::client_interfaces::wireguard::WireguardAppState;
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use nym_node_requests::api::v1::gateway::models;
|
||||
@@ -15,10 +14,7 @@ pub struct Config {
|
||||
pub details: Option<models::Gateway>,
|
||||
}
|
||||
|
||||
pub(crate) fn routes<S: Send + Sync + 'static + Clone>(
|
||||
config: Config,
|
||||
initial_wg_state: WireguardAppState,
|
||||
) -> Router<S> {
|
||||
pub(crate) fn routes<S: Send + Sync + 'static + Clone>(config: Config) -> Router<S> {
|
||||
Router::new()
|
||||
.route(
|
||||
"/",
|
||||
@@ -29,9 +25,6 @@ pub(crate) fn routes<S: Send + Sync + 'static + Clone>(
|
||||
)
|
||||
.nest(
|
||||
gateway::CLIENT_INTERFACES,
|
||||
client_interfaces::routes(
|
||||
config.details.map(|g| g.client_interfaces),
|
||||
initial_wg_state,
|
||||
),
|
||||
client_interfaces::routes(config.details.map(|g| g.client_interfaces)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::api::v1::gateway::client_interfaces::wireguard::WireguardAppState;
|
||||
use crate::state::AppState;
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use nym_node_requests::routes::api::v1;
|
||||
|
||||
pub mod authenticator;
|
||||
pub mod gateway;
|
||||
pub mod health;
|
||||
pub mod ip_packet_router;
|
||||
@@ -24,16 +24,14 @@ pub struct Config {
|
||||
pub mixnode: mixnode::Config,
|
||||
pub network_requester: network_requester::Config,
|
||||
pub ip_packet_router: ip_packet_router::Config,
|
||||
pub authenticator: authenticator::Config,
|
||||
}
|
||||
|
||||
pub(super) fn routes(config: Config, initial_wg_state: WireguardAppState) -> Router<AppState> {
|
||||
pub(super) fn routes(config: Config) -> Router<AppState> {
|
||||
Router::new()
|
||||
.route(v1::HEALTH, get(health::root_health))
|
||||
.nest(v1::METRICS, metrics::routes(config.metrics))
|
||||
.nest(
|
||||
v1::GATEWAY,
|
||||
gateway::routes(config.gateway, initial_wg_state),
|
||||
)
|
||||
.nest(v1::GATEWAY, gateway::routes(config.gateway))
|
||||
.nest(v1::MIXNODE, mixnode::routes(config.mixnode))
|
||||
.nest(
|
||||
v1::NETWORK_REQUESTER,
|
||||
@@ -43,6 +41,10 @@ pub(super) fn routes(config: Config, initial_wg_state: WireguardAppState) -> Rou
|
||||
v1::IP_PACKET_ROUTER,
|
||||
ip_packet_router::routes(config.ip_packet_router),
|
||||
)
|
||||
.nest(
|
||||
v1::AUTHENTICATOR,
|
||||
authenticator::routes(config.authenticator),
|
||||
)
|
||||
.merge(node::routes(config.node))
|
||||
.merge(openapi::route())
|
||||
}
|
||||
|
||||
@@ -26,11 +26,7 @@ use utoipa_swagger_ui::SwaggerUi;
|
||||
api::v1::health::root_health,
|
||||
api::v1::gateway::root::root_gateway,
|
||||
api::v1::gateway::client_interfaces::client_interfaces,
|
||||
api::v1::gateway::client_interfaces::wireguard_info,
|
||||
api::v1::gateway::client_interfaces::mixnet_websockets,
|
||||
api::v1::gateway::client_interfaces::wireguard::client_registry::register_client,
|
||||
api::v1::gateway::client_interfaces::wireguard::client_registry::get_all_clients,
|
||||
api::v1::gateway::client_interfaces::wireguard::client_registry::get_client,
|
||||
api::v1::mixnode::root::root_mixnode,
|
||||
api::v1::network_requester::root::root_network_requester,
|
||||
api::v1::network_requester::exit_policy::node_exit_policy,
|
||||
@@ -67,7 +63,6 @@ use utoipa_swagger_ui::SwaggerUi;
|
||||
api_requests::v1::gateway::client_interfaces::wireguard::models::ClientMessage,
|
||||
api_requests::v1::gateway::client_interfaces::wireguard::models::InitMessage,
|
||||
api_requests::v1::gateway::client_interfaces::wireguard::models::GatewayClient,
|
||||
api_requests::v1::gateway::client_interfaces::wireguard::models::ClientRegistrationResponse,
|
||||
api_requests::v1::mixnode::models::Mixnode,
|
||||
api_requests::v1::network_requester::models::NetworkRequester,
|
||||
api_requests::v1::network_requester::exit_policy::models::AddressPolicy,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub use crate::api::v1::gateway::client_interfaces::wireguard::WireguardAppState;
|
||||
use crate::error::NymNodeHttpError;
|
||||
use crate::middleware::logging;
|
||||
use crate::state::AppState;
|
||||
@@ -9,6 +8,7 @@ use crate::NymNodeHTTPServer;
|
||||
use axum::response::Redirect;
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use nym_node_requests::api::v1::authenticator::models::Authenticator;
|
||||
use nym_node_requests::api::v1::gateway::models::{Gateway, Wireguard};
|
||||
use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter;
|
||||
use nym_node_requests::api::v1::mixnode::models::Mixnode;
|
||||
@@ -54,6 +54,7 @@ impl Config {
|
||||
mixnode: Default::default(),
|
||||
network_requester: Default::default(),
|
||||
ip_packet_router: Default::default(),
|
||||
authenticator: Default::default(),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -148,6 +149,12 @@ impl Config {
|
||||
self.api.v1_config.ip_packet_router.details = Some(ip_packet_router);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_authenticator_details(mut self, authenticator: Authenticator) -> Self {
|
||||
self.api.v1_config.authenticator.details = Some(authenticator);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NymNodeRouter {
|
||||
@@ -156,11 +163,7 @@ pub struct NymNodeRouter {
|
||||
|
||||
impl NymNodeRouter {
|
||||
// TODO: move the wg state to a builder
|
||||
pub fn new(
|
||||
config: Config,
|
||||
app_state: Option<AppState>,
|
||||
initial_wg_state: Option<WireguardAppState>,
|
||||
) -> NymNodeRouter {
|
||||
pub fn new(config: Config, app_state: Option<AppState>) -> NymNodeRouter {
|
||||
let state = app_state.unwrap_or(AppState::new());
|
||||
|
||||
NymNodeRouter {
|
||||
@@ -189,10 +192,7 @@ impl NymNodeRouter {
|
||||
}),
|
||||
)
|
||||
.nest(routes::LANDING_PAGE, landing_page::routes(config.landing))
|
||||
.nest(
|
||||
routes::API,
|
||||
api::routes(config.api, initial_wg_state.unwrap_or_default()),
|
||||
)
|
||||
.nest(routes::API, api::routes(config.api))
|
||||
.layer(axum::middleware::from_fn(logging::logger))
|
||||
.with_state(state),
|
||||
}
|
||||
|
||||
@@ -24,19 +24,6 @@ impl RequestError {
|
||||
status,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_status(status: StatusCode) -> Self {
|
||||
RequestError {
|
||||
inner: ErrorResponse {
|
||||
message: String::new(),
|
||||
},
|
||||
status,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_err<E: std::error::Error>(err: E, status: StatusCode) -> Self {
|
||||
Self::new(err.to_string(), status)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for RequestError {
|
||||
|
||||
@@ -8,8 +8,8 @@ use crate::routes;
|
||||
use async_trait::async_trait;
|
||||
use nym_bin_common::build_information::BinaryBuildInformationOwned;
|
||||
use nym_http_api_client::{ApiClient, HttpClientError};
|
||||
use nym_wireguard_types::{ClientMessage, ClientRegistrationResponse};
|
||||
|
||||
use crate::api::v1::authenticator::models::Authenticator;
|
||||
use crate::api::v1::health::models::NodeHealth;
|
||||
use crate::api::v1::ip_packet_router::models::IpPacketRouter;
|
||||
use crate::api::v1::network_requester::exit_policy::models::UsedExitPolicy;
|
||||
@@ -65,15 +65,9 @@ pub trait NymNodeApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn post_gateway_register_client(
|
||||
&self,
|
||||
client_message: &ClientMessage,
|
||||
) -> Result<ClientRegistrationResponse, NymNodeApiClientError> {
|
||||
self.post_json_data_to(
|
||||
routes::api::v1::gateway::client_interfaces::wireguard::client_absolute(),
|
||||
client_message,
|
||||
)
|
||||
.await
|
||||
async fn get_authenticator(&self) -> Result<Authenticator, NymNodeApiClientError> {
|
||||
self.get_json_from(routes::api::v1::authenticator_absolute())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod models;
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct Authenticator {
|
||||
/// Base58 encoded ed25519 EdDSA public key of the authenticator.
|
||||
pub encoded_identity_key: String,
|
||||
|
||||
/// Base58-encoded x25519 public key used for performing key exchange with remote clients.
|
||||
pub encoded_x25519_key: String,
|
||||
|
||||
/// Nym address of this ip packet router.
|
||||
pub address: String,
|
||||
}
|
||||
@@ -2,6 +2,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub use nym_wireguard_types::{
|
||||
ClientMac, ClientMessage, ClientRegistrationResponse, GatewayClient, InitMessage, Nonce,
|
||||
PeerPublicKey,
|
||||
ClientMac, ClientMessage, GatewayClient, InitMessage, Nonce, PeerPublicKey,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod authenticator;
|
||||
pub mod gateway;
|
||||
pub mod health;
|
||||
pub mod ip_packet_router;
|
||||
|
||||
@@ -42,6 +42,7 @@ pub mod routes {
|
||||
pub const METRICS: &str = "/metrics";
|
||||
pub const NETWORK_REQUESTER: &str = "/network-requester";
|
||||
pub const IP_PACKET_ROUTER: &str = "/ip-packet-router";
|
||||
pub const AUTHENTICATOR: &str = "/authenticator";
|
||||
|
||||
// define helper functions to get absolute routes
|
||||
absolute_route!(health_absolute, v1_absolute(), HEALTH);
|
||||
@@ -57,6 +58,7 @@ pub mod routes {
|
||||
absolute_route!(metrics_absolute, v1_absolute(), METRICS);
|
||||
absolute_route!(network_requester_absolute, v1_absolute(), NETWORK_REQUESTER);
|
||||
absolute_route!(ip_packet_router_absolute, v1_absolute(), IP_PACKET_ROUTER);
|
||||
absolute_route!(authenticator_absolute, v1_absolute(), AUTHENTICATOR);
|
||||
absolute_route!(swagger_absolute, v1_absolute(), SWAGGER);
|
||||
|
||||
pub mod metrics {
|
||||
|
||||
@@ -409,6 +409,9 @@ async fn migrate_gateway(mut args: Args) -> Result<(), NymNodeError> {
|
||||
bind_address: SocketAddr::new(ip, cfg.gateway.clients_port),
|
||||
announce_ws_port: None,
|
||||
announce_wss_port: cfg.gateway.clients_wss_port,
|
||||
authenticator: config::authenticator::Authenticator {
|
||||
debug: Default::default(),
|
||||
},
|
||||
debug: config::entry_gateway::Debug {
|
||||
message_retrieval_limit: cfg.debug.message_retrieval_limit,
|
||||
},
|
||||
@@ -454,6 +457,9 @@ async fn migrate_gateway(mut args: Args) -> Result<(), NymNodeError> {
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
},
|
||||
authenticator: config::authenticator::Authenticator {
|
||||
debug: Default::default(),
|
||||
},
|
||||
}),
|
||||
)
|
||||
.build();
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_client_core_config_types::DebugConfig as ClientDebugConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
pub struct Authenticator {
|
||||
#[serde(default)]
|
||||
pub debug: AuthenticatorDebug,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for Authenticator {
|
||||
fn default() -> Self {
|
||||
Authenticator {
|
||||
debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct AuthenticatorDebug {
|
||||
/// Specifies whether authenticator service is enabled in this process.
|
||||
/// This is only here for debugging purposes as exit gateway should always run
|
||||
/// the authenticator.
|
||||
pub enabled: bool,
|
||||
|
||||
/// Disable Poisson sending rate.
|
||||
/// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true
|
||||
/// (or is it (?))
|
||||
pub disable_poisson_rate: bool,
|
||||
|
||||
/// Shared detailed client configuration options
|
||||
#[serde(flatten)]
|
||||
pub client_debug: ClientDebugConfig,
|
||||
}
|
||||
|
||||
impl Default for AuthenticatorDebug {
|
||||
fn default() -> Self {
|
||||
AuthenticatorDebug {
|
||||
enabled: true,
|
||||
disable_poisson_rate: true,
|
||||
client_debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ use serde::{Deserialize, Serialize};
|
||||
use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
|
||||
use super::authenticator::Authenticator;
|
||||
|
||||
pub const DEFAULT_WS_PORT: u16 = DEFAULT_CLIENT_LISTENING_PORT;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -38,6 +40,8 @@ pub struct EntryGatewayConfig {
|
||||
#[serde(deserialize_with = "de_maybe_port")]
|
||||
pub announce_wss_port: Option<u16>,
|
||||
|
||||
pub authenticator: Authenticator,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: Debug,
|
||||
}
|
||||
@@ -69,6 +73,7 @@ impl EntryGatewayConfig {
|
||||
bind_address: SocketAddr::new(inaddr_any(), DEFAULT_WS_PORT),
|
||||
announce_ws_port: None,
|
||||
announce_wss_port: None,
|
||||
authenticator: Default::default(),
|
||||
debug: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,14 @@ use crate::error::ExitGatewayError;
|
||||
use clap::crate_version;
|
||||
use nym_client_core_config_types::DebugConfig as ClientDebugConfig;
|
||||
use nym_config::defaults::mainnet;
|
||||
use nym_gateway::node::{LocalIpPacketRouterOpts, LocalNetworkRequesterOpts};
|
||||
use nym_gateway::node::{
|
||||
LocalAuthenticatorOpts, LocalIpPacketRouterOpts, LocalNetworkRequesterOpts,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use url::Url;
|
||||
|
||||
use super::LocalWireguardOpts;
|
||||
use super::{authenticator::Authenticator, LocalWireguardOpts};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -30,6 +32,8 @@ pub struct ExitGatewayConfig {
|
||||
pub network_requester: NetworkRequester,
|
||||
|
||||
pub ip_packet_router: IpPacketRouter,
|
||||
|
||||
pub authenticator: Authenticator,
|
||||
}
|
||||
|
||||
impl ExitGatewayConfig {
|
||||
@@ -45,6 +49,7 @@ impl ExitGatewayConfig {
|
||||
.expect("invalid default exit policy URL"),
|
||||
network_requester: Default::default(),
|
||||
ip_packet_router: Default::default(),
|
||||
authenticator: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,6 +143,7 @@ pub struct EphemeralConfig {
|
||||
pub gateway: nym_gateway::config::Config,
|
||||
pub nr_opts: LocalNetworkRequesterOpts,
|
||||
pub ipr_opts: LocalIpPacketRouterOpts,
|
||||
pub auth_opts: LocalAuthenticatorOpts,
|
||||
pub wg_opts: LocalWireguardOpts,
|
||||
}
|
||||
|
||||
@@ -234,6 +240,26 @@ pub fn ephemeral_exit_gateway_config(
|
||||
ipr_opts.config.base.set_no_poisson_process()
|
||||
}
|
||||
|
||||
let auth_opts = LocalAuthenticatorOpts {
|
||||
config: nym_authenticator::Config {
|
||||
base: nym_client_core_config_types::Config {
|
||||
client: base_client_config(&config),
|
||||
debug: config.exit_gateway.authenticator.debug.client_debug,
|
||||
},
|
||||
authenticator: config.wireguard.clone().into(),
|
||||
storage_paths: nym_authenticator::config::AuthenticatorPaths {
|
||||
common_paths: config
|
||||
.exit_gateway
|
||||
.storage_paths
|
||||
.authenticator
|
||||
.to_common_client_paths(),
|
||||
authenticator_description: Default::default(),
|
||||
},
|
||||
logging: config.logging,
|
||||
},
|
||||
custom_mixnet_path: None,
|
||||
};
|
||||
|
||||
let pub_id_path = config
|
||||
.storage_paths
|
||||
.keys
|
||||
@@ -266,6 +292,7 @@ pub fn ephemeral_exit_gateway_config(
|
||||
Ok(EphemeralConfig {
|
||||
nr_opts,
|
||||
ipr_opts,
|
||||
auth_opts,
|
||||
wg_opts,
|
||||
gateway,
|
||||
})
|
||||
|
||||
@@ -25,10 +25,12 @@ use std::time::Duration;
|
||||
use tracing::{debug, error};
|
||||
use url::Url;
|
||||
|
||||
pub mod authenticator;
|
||||
pub mod entry_gateway;
|
||||
pub mod exit_gateway;
|
||||
pub mod helpers;
|
||||
pub mod mixnode;
|
||||
mod old_configs;
|
||||
pub mod persistence;
|
||||
mod template;
|
||||
pub mod upgrade_helpers;
|
||||
@@ -543,6 +545,17 @@ impl From<Wireguard> for nym_wireguard_types::Config {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Wireguard> for nym_authenticator::config::Authenticator {
|
||||
fn from(value: Wireguard) -> Self {
|
||||
nym_authenticator::config::Authenticator {
|
||||
bind_address: value.bind_address,
|
||||
private_ip: value.private_ip,
|
||||
announced_port: value.announced_port,
|
||||
private_network_prefix: value.private_network_prefix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LocalWireguardOpts {
|
||||
pub config: Wireguard,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
mod old_config_1_1_2;
|
||||
mod old_config_1_1_3;
|
||||
|
||||
pub use old_config_1_1_2::try_upgrade_config_1_1_2;
|
||||
pub use old_config_1_1_3::try_upgrade_config_1_1_3;
|
||||
@@ -0,0 +1,978 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#![allow(dead_code)]
|
||||
use crate::config::*;
|
||||
use crate::error::KeyIOFailure;
|
||||
use nym_client_core_config_types::DebugConfig as ClientDebugConfig;
|
||||
use nym_config::serde_helpers::de_maybe_port;
|
||||
use nym_crypto::asymmetric::encryption::KeyPair;
|
||||
use nym_pemstore::store_keypair;
|
||||
use old_configs::old_config_1_1_3::*;
|
||||
use rand::rngs::OsRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WireguardPaths1_1_2 {
|
||||
pub private_diffie_hellman_key_file: PathBuf,
|
||||
pub public_diffie_hellman_key_file: PathBuf,
|
||||
}
|
||||
|
||||
impl WireguardPaths1_1_2 {
|
||||
pub fn new<P: AsRef<Path>>(data_dir: P) -> Self {
|
||||
let data_dir = data_dir.as_ref();
|
||||
WireguardPaths1_1_2 {
|
||||
private_diffie_hellman_key_file: data_dir
|
||||
.join(persistence::DEFAULT_X25519_WG_DH_KEY_FILENAME),
|
||||
public_diffie_hellman_key_file: data_dir
|
||||
.join(persistence::DEFAULT_X25519_WG_PUBLIC_DH_KEY_FILENAME),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn x25519_wireguard_storage_paths(&self) -> nym_pemstore::KeyPairPath {
|
||||
nym_pemstore::KeyPairPath::new(
|
||||
&self.private_diffie_hellman_key_file,
|
||||
&self.public_diffie_hellman_key_file,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Wireguard1_1_2 {
|
||||
/// 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:51822`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
/// Ip address of the private wireguard network.
|
||||
/// default: `10.1.0.0`
|
||||
pub private_network_ip: IpAddr,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard.
|
||||
/// The maximum value for IPv4 is 32 and for IPv6 is 128
|
||||
pub private_network_prefix: u8,
|
||||
|
||||
/// Paths for wireguard keys, client registries, etc.
|
||||
pub storage_paths: WireguardPaths1_1_2,
|
||||
}
|
||||
|
||||
// a temporary solution until all "types" are run at the same time
|
||||
#[derive(Debug, Default, Serialize, Deserialize, ValueEnum, Clone, Copy)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NodeMode1_1_2 {
|
||||
#[default]
|
||||
#[clap(alias = "mix")]
|
||||
Mixnode,
|
||||
|
||||
#[clap(alias = "entry", alias = "gateway")]
|
||||
EntryGateway,
|
||||
|
||||
#[clap(alias = "exit")]
|
||||
ExitGateway,
|
||||
}
|
||||
|
||||
impl From<NodeMode1_1_2> for NodeMode1_1_3 {
|
||||
fn from(config: NodeMode1_1_2) -> Self {
|
||||
match config {
|
||||
NodeMode1_1_2::Mixnode => NodeMode1_1_3::Mixnode,
|
||||
NodeMode1_1_2::EntryGateway => NodeMode1_1_3::EntryGateway,
|
||||
NodeMode1_1_2::ExitGateway => NodeMode1_1_3::ExitGateway,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this is very much a WIP. we need proper ssl certificate support here
|
||||
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Host1_1_2 {
|
||||
/// Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections.
|
||||
/// If no values are provided, when this node gets included in the network,
|
||||
/// its ip addresses will be populated by whatever value is resolved by associated nym-api.
|
||||
pub public_ips: Vec<IpAddr>,
|
||||
|
||||
/// 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_stringified")]
|
||||
pub hostname: Option<String>,
|
||||
|
||||
/// Optional ISO 3166 alpha-2 two-letter country code of the node's **physical** location
|
||||
#[serde(deserialize_with = "de_maybe_stringified")]
|
||||
pub location: Option<Country>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MixnetDebug1_1_2 {
|
||||
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_initial_backoff: Duration,
|
||||
|
||||
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_maximum_backoff: Duration,
|
||||
|
||||
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub initial_connection_timeout: Duration,
|
||||
|
||||
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
|
||||
pub maximum_connection_buffer_size: usize,
|
||||
|
||||
/// Specifies whether this node should **NOT** use noise protocol in the connections (currently not implemented)
|
||||
pub unsafe_disable_noise: bool,
|
||||
}
|
||||
|
||||
impl MixnetDebug1_1_2 {
|
||||
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
|
||||
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
}
|
||||
|
||||
impl Default for MixnetDebug1_1_2 {
|
||||
fn default() -> Self {
|
||||
MixnetDebug1_1_2 {
|
||||
packet_forwarding_initial_backoff: Self::DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: Self::DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: Self::DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
maximum_connection_buffer_size: Self::DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
// to be changed by @SW once the implementation is there
|
||||
unsafe_disable_noise: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Mixnet1_1_2 {
|
||||
fn default() -> Self {
|
||||
// SAFETY:
|
||||
// our hardcoded values should always be valid
|
||||
#[allow(clippy::expect_used)]
|
||||
// is if there's anything set in the environment, otherwise fallback to mainnet
|
||||
let nym_api_urls = if let Ok(env_value) = env::var(var_names::NYM_API) {
|
||||
parse_urls(&env_value)
|
||||
} else {
|
||||
vec![mainnet::NYM_API.parse().expect("Invalid default API URL")]
|
||||
};
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let nyxd_urls = if let Ok(env_value) = env::var(var_names::NYXD) {
|
||||
parse_urls(&env_value)
|
||||
} else {
|
||||
vec![mainnet::NYXD_URL.parse().expect("Invalid default nyxd URL")]
|
||||
};
|
||||
|
||||
Mixnet1_1_2 {
|
||||
bind_address: SocketAddr::new(inaddr_any(), DEFAULT_MIXNET_PORT),
|
||||
nym_api_urls,
|
||||
nyxd_urls,
|
||||
debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Mixnet1_1_2 {
|
||||
/// Address this node will bind to for listening for mixnet packets
|
||||
/// default: `0.0.0.0:1789`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
/// Addresses to nym APIs from which the node gets the view of the network.
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
|
||||
/// Addresses to nyxd which the node uses to interact with the nyx chain.
|
||||
pub nyxd_urls: Vec<Url>,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: MixnetDebug1_1_2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct KeysPaths1_1_2 {
|
||||
/// Path to file containing ed25519 identity private key.
|
||||
pub private_ed25519_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing ed25519 identity public key.
|
||||
pub public_ed25519_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing x25519 sphinx private key.
|
||||
pub private_x25519_sphinx_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing x25519 sphinx public key.
|
||||
pub public_x25519_sphinx_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing x25519 noise private key.
|
||||
pub private_x25519_noise_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing x25519 noise public key.
|
||||
pub public_x25519_noise_key_file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NymNodePaths1_1_2 {
|
||||
pub keys: KeysPaths1_1_2,
|
||||
|
||||
/// Path to a file containing basic node description: human-readable name, website, details, etc.
|
||||
pub description: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Http1_1_2 {
|
||||
/// Socket address this node will use for binding its http API.
|
||||
/// default: `0.0.0.0:8080`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
/// Path to assets directory of custom landing page of this node.
|
||||
#[serde(deserialize_with = "de_maybe_stringified")]
|
||||
pub landing_page_assets_path: Option<PathBuf>,
|
||||
|
||||
/// An optional bearer token for accessing certain http endpoints.
|
||||
/// Currently only used for obtaining mixnode's stats.
|
||||
#[serde(default)]
|
||||
pub access_token: Option<String>,
|
||||
|
||||
/// Specify whether basic system information should be exposed.
|
||||
/// default: true
|
||||
pub expose_system_info: bool,
|
||||
|
||||
/// Specify whether basic system hardware information should be exposed.
|
||||
/// This option is superseded by `expose_system_info`
|
||||
/// default: true
|
||||
pub expose_system_hardware: bool,
|
||||
|
||||
/// Specify whether detailed system crypto hardware information should be exposed.
|
||||
/// This option is superseded by `expose_system_hardware`
|
||||
/// default: true
|
||||
pub expose_crypto_hardware: bool,
|
||||
}
|
||||
|
||||
impl Default for Http1_1_2 {
|
||||
fn default() -> Self {
|
||||
Http1_1_2 {
|
||||
bind_address: SocketAddr::new(inaddr_any(), DEFAULT_HTTP_PORT),
|
||||
landing_page_assets_path: None,
|
||||
access_token: None,
|
||||
expose_system_info: true,
|
||||
expose_system_hardware: true,
|
||||
expose_crypto_hardware: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MixnodePaths1_1_2 {}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Debug1_1_2 {
|
||||
/// Delay between each subsequent node statistics being logged to the console
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub node_stats_logging_delay: Duration,
|
||||
|
||||
/// Delay between each subsequent node statistics being updated
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub node_stats_updating_delay: Duration,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct VerlocDebug1_1_2 {
|
||||
/// Specifies number of echo packets sent to each node during a measurement run.
|
||||
pub packets_per_node: usize,
|
||||
|
||||
/// Specifies maximum amount of time to wait for the connection to get established.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub connection_timeout: Duration,
|
||||
|
||||
/// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_timeout: Duration,
|
||||
|
||||
/// Specifies delay between subsequent test packets being sent (after receiving a reply).
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub delay_between_packets: Duration,
|
||||
|
||||
/// Specifies number of nodes being tested at once.
|
||||
pub tested_nodes_batch_size: usize,
|
||||
|
||||
/// Specifies delay between subsequent test runs.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub testing_interval: Duration,
|
||||
|
||||
/// Specifies delay between attempting to run the measurement again if the previous run failed
|
||||
/// due to being unable to get the list of nodes.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub retry_timeout: Duration,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Verloc1_1_2 {
|
||||
/// Socket address this node will use for binding its verloc API.
|
||||
/// default: `0.0.0.0:1790`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: VerlocDebug1_1_2,
|
||||
}
|
||||
|
||||
impl VerlocDebug1_1_2 {
|
||||
const DEFAULT_PACKETS_PER_NODE: usize = 100;
|
||||
const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000);
|
||||
const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500);
|
||||
const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50);
|
||||
const DEFAULT_BATCH_SIZE: usize = 50;
|
||||
const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12);
|
||||
const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30);
|
||||
}
|
||||
|
||||
impl Default for VerlocDebug1_1_2 {
|
||||
fn default() -> Self {
|
||||
VerlocDebug1_1_2 {
|
||||
packets_per_node: Self::DEFAULT_PACKETS_PER_NODE,
|
||||
connection_timeout: Self::DEFAULT_CONNECTION_TIMEOUT,
|
||||
packet_timeout: Self::DEFAULT_PACKET_TIMEOUT,
|
||||
delay_between_packets: Self::DEFAULT_DELAY_BETWEEN_PACKETS,
|
||||
tested_nodes_batch_size: Self::DEFAULT_BATCH_SIZE,
|
||||
testing_interval: Self::DEFAULT_TESTING_INTERVAL,
|
||||
retry_timeout: Self::DEFAULT_RETRY_TIMEOUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MixnodeConfig1_1_2 {
|
||||
pub storage_paths: MixnodePaths1_1_2,
|
||||
|
||||
pub verloc: Verloc1_1_2,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: Debug1_1_2,
|
||||
}
|
||||
|
||||
impl Debug1_1_2 {
|
||||
const DEFAULT_NODE_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_000);
|
||||
const DEFAULT_NODE_STATS_UPDATING_DELAY: Duration = Duration::from_millis(30_000);
|
||||
}
|
||||
|
||||
impl Default for Debug1_1_2 {
|
||||
fn default() -> Self {
|
||||
Debug1_1_2 {
|
||||
node_stats_logging_delay: Self::DEFAULT_NODE_STATS_LOGGING_DELAY,
|
||||
node_stats_updating_delay: Self::DEFAULT_NODE_STATS_UPDATING_DELAY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct EntryGatewayPaths1_1_2 {
|
||||
/// Path to sqlite database containing all persistent data: messages for offline clients,
|
||||
/// derived shared keys and available client bandwidths.
|
||||
pub clients_storage: PathBuf,
|
||||
|
||||
/// Path to file containing cosmos account mnemonic used for zk-nym redemption.
|
||||
pub cosmos_mnemonic: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct EntryGatewayConfigDebug1_1_2 {
|
||||
/// Number of messages from offline client that can be pulled at once (i.e. with a single SQL query) from the storage.
|
||||
pub message_retrieval_limit: i64,
|
||||
}
|
||||
|
||||
impl EntryGatewayConfigDebug1_1_2 {
|
||||
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
}
|
||||
|
||||
impl Default for EntryGatewayConfigDebug1_1_2 {
|
||||
fn default() -> Self {
|
||||
EntryGatewayConfigDebug1_1_2 {
|
||||
message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct EntryGatewayConfig1_1_2 {
|
||||
pub storage_paths: EntryGatewayPaths1_1_2,
|
||||
|
||||
/// Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet
|
||||
/// or if it also accepts non-paying clients
|
||||
pub enforce_zk_nyms: bool,
|
||||
|
||||
/// Socket address this node will use for binding its client websocket API.
|
||||
/// default: `0.0.0.0:9000`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
/// Custom announced port for listening for websocket client traffic.
|
||||
/// If unspecified, the value from the `bind_address` will be used instead
|
||||
/// default: None
|
||||
#[serde(deserialize_with = "de_maybe_port")]
|
||||
pub announce_ws_port: Option<u16>,
|
||||
|
||||
/// If applicable, announced port for listening for secure websocket client traffic.
|
||||
/// (default: None)
|
||||
#[serde(deserialize_with = "de_maybe_port")]
|
||||
pub announce_wss_port: Option<u16>,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: EntryGatewayConfigDebug1_1_2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NetworkRequesterPaths1_1_2 {
|
||||
/// Path to file containing network requester ed25519 identity private key.
|
||||
pub private_ed25519_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing network requester ed25519 identity public key.
|
||||
pub public_ed25519_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing network requester x25519 diffie hellman private key.
|
||||
pub private_x25519_diffie_hellman_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing network requester x25519 diffie hellman public key.
|
||||
pub public_x25519_diffie_hellman_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing key used for encrypting and decrypting the content of an
|
||||
/// acknowledgement so that nobody besides the client knows which packet it refers to.
|
||||
pub ack_key_file: PathBuf,
|
||||
|
||||
/// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
|
||||
pub reply_surb_database: PathBuf,
|
||||
|
||||
/// Normally this is a path to the file containing information about gateways used by this client,
|
||||
/// i.e. details such as their public keys, owner addresses or the network information.
|
||||
/// but in this case it just has the basic information of "we're using custom gateway".
|
||||
/// Due to how clients are started up, this file has to exist.
|
||||
pub gateway_registrations: PathBuf,
|
||||
// it's possible we might have to add credential storage here for return tickets
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct IpPacketRouterPaths1_1_2 {
|
||||
/// Path to file containing ip packet router ed25519 identity private key.
|
||||
pub private_ed25519_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing ip packet router ed25519 identity public key.
|
||||
pub public_ed25519_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing ip packet router x25519 diffie hellman private key.
|
||||
pub private_x25519_diffie_hellman_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing ip packet router x25519 diffie hellman public key.
|
||||
pub public_x25519_diffie_hellman_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing key used for encrypting and decrypting the content of an
|
||||
/// acknowledgement so that nobody besides the client knows which packet it refers to.
|
||||
pub ack_key_file: PathBuf,
|
||||
|
||||
/// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
|
||||
pub reply_surb_database: PathBuf,
|
||||
|
||||
/// Normally this is a path to the file containing information about gateways used by this client,
|
||||
/// i.e. details such as their public keys, owner addresses or the network information.
|
||||
/// but in this case it just has the basic information of "we're using custom gateway".
|
||||
/// Due to how clients are started up, this file has to exist.
|
||||
pub gateway_registrations: PathBuf,
|
||||
// it's possible we might have to add credential storage here for return tickets
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ExitGatewayPaths1_1_2 {
|
||||
pub network_requester: NetworkRequesterPaths1_1_2,
|
||||
|
||||
pub ip_packet_router: IpPacketRouterPaths1_1_2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct IpPacketRouterDebug1_1_2 {
|
||||
/// Specifies whether ip packet routing service is enabled in this process.
|
||||
/// This is only here for debugging purposes as exit gateway should always run **both**
|
||||
/// network requester and an ip packet router.
|
||||
pub enabled: bool,
|
||||
|
||||
/// Disable Poisson sending rate.
|
||||
/// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true
|
||||
/// (or is it (?))
|
||||
pub disable_poisson_rate: bool,
|
||||
|
||||
/// Shared detailed client configuration options
|
||||
#[serde(flatten)]
|
||||
pub client_debug: ClientDebugConfig,
|
||||
}
|
||||
|
||||
impl Default for IpPacketRouterDebug1_1_2 {
|
||||
fn default() -> Self {
|
||||
IpPacketRouterDebug1_1_2 {
|
||||
enabled: true,
|
||||
disable_poisson_rate: true,
|
||||
client_debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
pub struct IpPacketRouter1_1_2 {
|
||||
#[serde(default)]
|
||||
pub debug: IpPacketRouterDebug1_1_2,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for IpPacketRouter1_1_2 {
|
||||
fn default() -> Self {
|
||||
IpPacketRouter1_1_2 {
|
||||
debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
pub struct NetworkRequesterDebug1_1_2 {
|
||||
/// Specifies whether network requester service is enabled in this process.
|
||||
/// This is only here for debugging purposes as exit gateway should always run **both**
|
||||
/// network requester and an ip packet router.
|
||||
pub enabled: bool,
|
||||
|
||||
/// Disable Poisson sending rate.
|
||||
/// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true
|
||||
/// (or is it (?))
|
||||
pub disable_poisson_rate: bool,
|
||||
|
||||
/// Shared detailed client configuration options
|
||||
#[serde(flatten)]
|
||||
pub client_debug: ClientDebugConfig,
|
||||
}
|
||||
|
||||
impl Default for NetworkRequesterDebug1_1_2 {
|
||||
fn default() -> Self {
|
||||
NetworkRequesterDebug1_1_2 {
|
||||
enabled: true,
|
||||
disable_poisson_rate: true,
|
||||
client_debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
pub struct NetworkRequester1_1_2 {
|
||||
#[serde(default)]
|
||||
pub debug: NetworkRequesterDebug1_1_2,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for NetworkRequester1_1_2 {
|
||||
fn default() -> Self {
|
||||
NetworkRequester1_1_2 {
|
||||
debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ExitGatewayConfig1_1_2 {
|
||||
pub storage_paths: ExitGatewayPaths1_1_2,
|
||||
|
||||
/// specifies whether this exit node should run in 'open-proxy' mode
|
||||
/// and thus would attempt to resolve **ANY** request it receives.
|
||||
pub open_proxy: bool,
|
||||
|
||||
/// Specifies the url for an upstream source of the exit policy used by this node.
|
||||
pub upstream_exit_policy_url: Url,
|
||||
|
||||
pub network_requester: NetworkRequester1_1_2,
|
||||
|
||||
pub ip_packet_router: IpPacketRouter1_1_2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct LoggingSettings1_1_2 {
|
||||
// well, we need to implement something here at some point...
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config1_1_2 {
|
||||
// additional metadata holding on-disk location of this config file
|
||||
#[serde(skip)]
|
||||
pub(crate) save_path: Option<PathBuf>,
|
||||
|
||||
/// Human-readable ID of this particular node.
|
||||
pub id: String,
|
||||
|
||||
/// Current mode of this nym-node.
|
||||
/// Expect this field to be changed in the future to allow running the node in multiple modes (i.e. mixnode + gateway)
|
||||
pub mode: NodeMode1_1_2,
|
||||
|
||||
pub host: Host1_1_2,
|
||||
|
||||
pub mixnet: Mixnet1_1_2,
|
||||
|
||||
/// Storage paths to persistent nym-node data, such as its long term keys.
|
||||
pub storage_paths: NymNodePaths1_1_2,
|
||||
|
||||
#[serde(default)]
|
||||
pub http: Http1_1_2,
|
||||
|
||||
pub wireguard: Wireguard1_1_2,
|
||||
|
||||
pub mixnode: MixnodeConfig1_1_2,
|
||||
|
||||
pub entry_gateway: EntryGatewayConfig1_1_2,
|
||||
|
||||
pub exit_gateway: ExitGatewayConfig1_1_2,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettings1_1_2,
|
||||
}
|
||||
|
||||
impl NymConfigTemplate for Config1_1_2 {
|
||||
fn template(&self) -> &'static str {
|
||||
CONFIG_TEMPLATE
|
||||
}
|
||||
}
|
||||
|
||||
impl Config1_1_2 {
|
||||
pub fn save(&self) -> Result<(), NymNodeError> {
|
||||
let save_location = self.save_location();
|
||||
debug!(
|
||||
"attempting to save config file to '{}'",
|
||||
save_location.display()
|
||||
);
|
||||
save_formatted_config_to_file(self, &save_location).map_err(|source| {
|
||||
NymNodeError::ConfigSaveFailure {
|
||||
id: self.id.clone(),
|
||||
path: save_location,
|
||||
source,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn save_location(&self) -> PathBuf {
|
||||
self.save_path
|
||||
.clone()
|
||||
.unwrap_or(self.default_save_location())
|
||||
}
|
||||
|
||||
pub fn default_save_location(&self) -> PathBuf {
|
||||
default_config_filepath(&self.id)
|
||||
}
|
||||
|
||||
pub fn default_data_directory<P: AsRef<Path>>(config_path: P) -> Result<PathBuf, NymNodeError> {
|
||||
let config_path = config_path.as_ref();
|
||||
|
||||
// we got a proper path to the .toml file
|
||||
let Some(config_dir) = config_path.parent() else {
|
||||
error!(
|
||||
"'{}' does not have a parent directory. Have you pointed to the fs root?",
|
||||
config_path.display()
|
||||
);
|
||||
return Err(NymNodeError::DataDirDerivationFailure);
|
||||
};
|
||||
|
||||
let Some(config_dir_name) = config_dir.file_name() else {
|
||||
error!(
|
||||
"could not obtain parent directory name of '{}'. Have you used relative paths?",
|
||||
config_path.display()
|
||||
);
|
||||
return Err(NymNodeError::DataDirDerivationFailure);
|
||||
};
|
||||
|
||||
if config_dir_name != DEFAULT_CONFIG_DIR {
|
||||
error!(
|
||||
"the parent directory of '{}' ({}) is not {DEFAULT_CONFIG_DIR}. currently this is not supported",
|
||||
config_path.display(), config_dir_name.to_str().unwrap_or("UNKNOWN")
|
||||
);
|
||||
return Err(NymNodeError::DataDirDerivationFailure);
|
||||
}
|
||||
|
||||
let Some(node_dir) = config_dir.parent() else {
|
||||
error!(
|
||||
"'{}' does not have a parent directory. Have you pointed to the fs root?",
|
||||
config_dir.display()
|
||||
);
|
||||
return Err(NymNodeError::DataDirDerivationFailure);
|
||||
};
|
||||
|
||||
Ok(node_dir.join(DEFAULT_DATA_DIR))
|
||||
}
|
||||
|
||||
// simple wrapper that reads config file and assigns path location
|
||||
fn read_from_path<P: AsRef<Path>>(path: P) -> Result<Self, NymNodeError> {
|
||||
let path = path.as_ref();
|
||||
let mut loaded: Config1_1_2 =
|
||||
read_config_from_toml_file(path).map_err(|source| NymNodeError::ConfigLoadFailure {
|
||||
path: path.to_path_buf(),
|
||||
source,
|
||||
})?;
|
||||
loaded.save_path = Some(path.to_path_buf());
|
||||
debug!("loaded config file from {}", path.display());
|
||||
Ok(loaded)
|
||||
}
|
||||
|
||||
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> Result<Self, NymNodeError> {
|
||||
Self::read_from_path(path)
|
||||
}
|
||||
}
|
||||
|
||||
fn initialise(config: &Wireguard1_1_3) -> std::io::Result<()> {
|
||||
let mut rng = OsRng;
|
||||
let x25519_keys = KeyPair::new(&mut rng);
|
||||
|
||||
store_keypair(
|
||||
&x25519_keys,
|
||||
&config.storage_paths.x25519_wireguard_storage_paths(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// currently there are no upgrades
|
||||
pub async fn try_upgrade_config_1_1_2<P: AsRef<Path>>(path: P) -> Result<(), NymNodeError> {
|
||||
let old_cfg = Config1_1_2::read_from_path(&path)?;
|
||||
let wireguard = Wireguard1_1_3 {
|
||||
enabled: old_cfg.wireguard.enabled,
|
||||
bind_address: old_cfg.wireguard.bind_address,
|
||||
private_ip: old_cfg.wireguard.private_network_ip,
|
||||
announced_port: old_cfg.wireguard.announced_port,
|
||||
private_network_prefix: old_cfg.wireguard.private_network_prefix,
|
||||
storage_paths: WireguardPaths1_1_3::new(Config1_1_3::default_data_directory(path)?),
|
||||
};
|
||||
initialise(&wireguard).map_err(|err| KeyIOFailure::KeyPairStoreFailure {
|
||||
keys: "wg-x25519-dh".to_string(),
|
||||
paths: wireguard.storage_paths.x25519_wireguard_storage_paths(),
|
||||
err,
|
||||
})?;
|
||||
let cfg = Config1_1_3 {
|
||||
save_path: old_cfg.save_path,
|
||||
id: old_cfg.id,
|
||||
mode: old_cfg.mode.into(),
|
||||
host: Host1_1_3 {
|
||||
public_ips: old_cfg.host.public_ips,
|
||||
hostname: old_cfg.host.hostname,
|
||||
location: old_cfg.host.location,
|
||||
},
|
||||
mixnet: Mixnet1_1_3 {
|
||||
bind_address: old_cfg.mixnet.bind_address,
|
||||
nym_api_urls: old_cfg.mixnet.nym_api_urls,
|
||||
nyxd_urls: old_cfg.mixnet.nyxd_urls,
|
||||
debug: MixnetDebug1_1_3 {
|
||||
packet_forwarding_initial_backoff: old_cfg
|
||||
.mixnet
|
||||
.debug
|
||||
.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: old_cfg
|
||||
.mixnet
|
||||
.debug
|
||||
.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: old_cfg.mixnet.debug.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: old_cfg.mixnet.debug.maximum_connection_buffer_size,
|
||||
unsafe_disable_noise: old_cfg.mixnet.debug.unsafe_disable_noise,
|
||||
},
|
||||
},
|
||||
storage_paths: NymNodePaths1_1_3 {
|
||||
keys: KeysPaths1_1_3 {
|
||||
private_ed25519_identity_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.private_ed25519_identity_key_file,
|
||||
public_ed25519_identity_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.public_ed25519_identity_key_file,
|
||||
private_x25519_sphinx_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.private_x25519_sphinx_key_file,
|
||||
public_x25519_sphinx_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.public_x25519_sphinx_key_file,
|
||||
private_x25519_noise_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.private_x25519_noise_key_file,
|
||||
public_x25519_noise_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.public_x25519_noise_key_file,
|
||||
},
|
||||
description: old_cfg.storage_paths.description,
|
||||
},
|
||||
http: Http1_1_3 {
|
||||
bind_address: old_cfg.http.bind_address,
|
||||
landing_page_assets_path: old_cfg.http.landing_page_assets_path,
|
||||
access_token: old_cfg.http.access_token,
|
||||
expose_system_info: old_cfg.http.expose_system_info,
|
||||
expose_system_hardware: old_cfg.http.expose_system_hardware,
|
||||
expose_crypto_hardware: old_cfg.http.expose_crypto_hardware,
|
||||
},
|
||||
wireguard,
|
||||
mixnode: MixnodeConfig1_1_3 {
|
||||
storage_paths: MixnodePaths1_1_3 {},
|
||||
verloc: Verloc1_1_3 {
|
||||
bind_address: old_cfg.mixnode.verloc.bind_address,
|
||||
debug: VerlocDebug1_1_3 {
|
||||
packets_per_node: old_cfg.mixnode.verloc.debug.packets_per_node,
|
||||
connection_timeout: old_cfg.mixnode.verloc.debug.connection_timeout,
|
||||
packet_timeout: old_cfg.mixnode.verloc.debug.packet_timeout,
|
||||
delay_between_packets: old_cfg.mixnode.verloc.debug.delay_between_packets,
|
||||
tested_nodes_batch_size: old_cfg.mixnode.verloc.debug.tested_nodes_batch_size,
|
||||
testing_interval: old_cfg.mixnode.verloc.debug.testing_interval,
|
||||
retry_timeout: old_cfg.mixnode.verloc.debug.retry_timeout,
|
||||
},
|
||||
},
|
||||
debug: Debug1_1_3 {
|
||||
node_stats_logging_delay: old_cfg.mixnode.debug.node_stats_logging_delay,
|
||||
node_stats_updating_delay: old_cfg.mixnode.debug.node_stats_updating_delay,
|
||||
},
|
||||
},
|
||||
entry_gateway: EntryGatewayConfig1_1_3 {
|
||||
storage_paths: EntryGatewayPaths1_1_3 {
|
||||
clients_storage: old_cfg.entry_gateway.storage_paths.clients_storage,
|
||||
cosmos_mnemonic: old_cfg.entry_gateway.storage_paths.cosmos_mnemonic,
|
||||
},
|
||||
enforce_zk_nyms: old_cfg.entry_gateway.enforce_zk_nyms,
|
||||
bind_address: old_cfg.entry_gateway.bind_address,
|
||||
announce_ws_port: old_cfg.entry_gateway.announce_ws_port,
|
||||
announce_wss_port: old_cfg.entry_gateway.announce_wss_port,
|
||||
debug: EntryGatewayConfigDebug1_1_3 {
|
||||
message_retrieval_limit: old_cfg.entry_gateway.debug.message_retrieval_limit,
|
||||
},
|
||||
},
|
||||
exit_gateway: ExitGatewayConfig1_1_3 {
|
||||
storage_paths: ExitGatewayPaths1_1_3 {
|
||||
network_requester: NetworkRequesterPaths1_1_3 {
|
||||
private_ed25519_identity_key_file: old_cfg
|
||||
.exit_gateway
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.private_ed25519_identity_key_file,
|
||||
public_ed25519_identity_key_file: old_cfg
|
||||
.exit_gateway
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.public_ed25519_identity_key_file,
|
||||
private_x25519_diffie_hellman_key_file: old_cfg
|
||||
.exit_gateway
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.private_x25519_diffie_hellman_key_file,
|
||||
public_x25519_diffie_hellman_key_file: old_cfg
|
||||
.exit_gateway
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.public_x25519_diffie_hellman_key_file,
|
||||
ack_key_file: old_cfg
|
||||
.exit_gateway
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.ack_key_file,
|
||||
reply_surb_database: old_cfg
|
||||
.exit_gateway
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.reply_surb_database,
|
||||
gateway_registrations: old_cfg
|
||||
.exit_gateway
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.gateway_registrations,
|
||||
},
|
||||
ip_packet_router: IpPacketRouterPaths1_1_3 {
|
||||
private_ed25519_identity_key_file: old_cfg
|
||||
.exit_gateway
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.private_ed25519_identity_key_file,
|
||||
public_ed25519_identity_key_file: old_cfg
|
||||
.exit_gateway
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.public_ed25519_identity_key_file,
|
||||
private_x25519_diffie_hellman_key_file: old_cfg
|
||||
.exit_gateway
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.private_x25519_diffie_hellman_key_file,
|
||||
public_x25519_diffie_hellman_key_file: old_cfg
|
||||
.exit_gateway
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.public_x25519_diffie_hellman_key_file,
|
||||
ack_key_file: old_cfg
|
||||
.exit_gateway
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.ack_key_file,
|
||||
reply_surb_database: old_cfg
|
||||
.exit_gateway
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.reply_surb_database,
|
||||
gateway_registrations: old_cfg
|
||||
.exit_gateway
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.gateway_registrations,
|
||||
},
|
||||
},
|
||||
open_proxy: old_cfg.exit_gateway.open_proxy,
|
||||
upstream_exit_policy_url: old_cfg.exit_gateway.upstream_exit_policy_url,
|
||||
network_requester: NetworkRequester1_1_3 {
|
||||
debug: NetworkRequesterDebug1_1_3 {
|
||||
enabled: old_cfg.exit_gateway.network_requester.debug.enabled,
|
||||
disable_poisson_rate: old_cfg
|
||||
.exit_gateway
|
||||
.network_requester
|
||||
.debug
|
||||
.disable_poisson_rate,
|
||||
client_debug: old_cfg.exit_gateway.network_requester.debug.client_debug,
|
||||
},
|
||||
},
|
||||
ip_packet_router: IpPacketRouter1_1_3 {
|
||||
debug: IpPacketRouterDebug1_1_3 {
|
||||
enabled: old_cfg.exit_gateway.ip_packet_router.debug.enabled,
|
||||
disable_poisson_rate: old_cfg
|
||||
.exit_gateway
|
||||
.ip_packet_router
|
||||
.debug
|
||||
.disable_poisson_rate,
|
||||
client_debug: old_cfg.exit_gateway.ip_packet_router.debug.client_debug,
|
||||
},
|
||||
},
|
||||
},
|
||||
logging: LoggingSettings1_1_3 {},
|
||||
};
|
||||
|
||||
cfg.save()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,766 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#![allow(dead_code)]
|
||||
use crate::config::*;
|
||||
use nym_client_core_config_types::DebugConfig as ClientDebugConfig;
|
||||
use nym_config::serde_helpers::de_maybe_port;
|
||||
use nym_crypto::asymmetric::encryption::KeyPair;
|
||||
use nym_pemstore::store_keypair;
|
||||
use rand::rngs::OsRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WireguardPaths1_1_3 {
|
||||
pub private_diffie_hellman_key_file: PathBuf,
|
||||
pub public_diffie_hellman_key_file: PathBuf,
|
||||
}
|
||||
|
||||
impl WireguardPaths1_1_3 {
|
||||
pub fn new<P: AsRef<Path>>(data_dir: P) -> Self {
|
||||
let data_dir = data_dir.as_ref();
|
||||
WireguardPaths1_1_3 {
|
||||
private_diffie_hellman_key_file: data_dir
|
||||
.join(persistence::DEFAULT_X25519_WG_DH_KEY_FILENAME),
|
||||
public_diffie_hellman_key_file: data_dir
|
||||
.join(persistence::DEFAULT_X25519_WG_PUBLIC_DH_KEY_FILENAME),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn x25519_wireguard_storage_paths(&self) -> nym_pemstore::KeyPairPath {
|
||||
nym_pemstore::KeyPairPath::new(
|
||||
&self.private_diffie_hellman_key_file,
|
||||
&self.public_diffie_hellman_key_file,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Wireguard1_1_3 {
|
||||
/// 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:51822`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
/// Ip address of the private wireguard network.
|
||||
/// default: `10.1.0.0`
|
||||
pub private_ip: IpAddr,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard.
|
||||
/// The maximum value for IPv4 is 32 and for IPv6 is 128
|
||||
pub private_network_prefix: u8,
|
||||
|
||||
/// Paths for wireguard keys, client registries, etc.
|
||||
pub storage_paths: WireguardPaths1_1_3,
|
||||
}
|
||||
|
||||
// a temporary solution until all "types" are run at the same time
|
||||
#[derive(Debug, Default, Serialize, Deserialize, ValueEnum, Clone, Copy)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NodeMode1_1_3 {
|
||||
#[default]
|
||||
#[clap(alias = "mix")]
|
||||
Mixnode,
|
||||
|
||||
#[clap(alias = "entry", alias = "gateway")]
|
||||
EntryGateway,
|
||||
|
||||
#[clap(alias = "exit")]
|
||||
ExitGateway,
|
||||
}
|
||||
|
||||
// TODO: this is very much a WIP. we need proper ssl certificate support here
|
||||
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Host1_1_3 {
|
||||
/// Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections.
|
||||
/// If no values are provided, when this node gets included in the network,
|
||||
/// its ip addresses will be populated by whatever value is resolved by associated nym-api.
|
||||
pub public_ips: Vec<IpAddr>,
|
||||
|
||||
/// 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_stringified")]
|
||||
pub hostname: Option<String>,
|
||||
|
||||
/// Optional ISO 3166 alpha-2 two-letter country code of the node's **physical** location
|
||||
#[serde(deserialize_with = "de_maybe_stringified")]
|
||||
pub location: Option<Country>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MixnetDebug1_1_3 {
|
||||
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_initial_backoff: Duration,
|
||||
|
||||
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
|
||||
/// forwarding sphinx packets.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_forwarding_maximum_backoff: Duration,
|
||||
|
||||
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub initial_connection_timeout: Duration,
|
||||
|
||||
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
|
||||
pub maximum_connection_buffer_size: usize,
|
||||
|
||||
/// Specifies whether this node should **NOT** use noise protocol in the connections (currently not implemented)
|
||||
pub unsafe_disable_noise: bool,
|
||||
}
|
||||
|
||||
impl MixnetDebug1_1_3 {
|
||||
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
|
||||
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
|
||||
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
}
|
||||
|
||||
impl Default for MixnetDebug1_1_3 {
|
||||
fn default() -> Self {
|
||||
MixnetDebug1_1_3 {
|
||||
packet_forwarding_initial_backoff: Self::DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
|
||||
packet_forwarding_maximum_backoff: Self::DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: Self::DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
maximum_connection_buffer_size: Self::DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
// to be changed by @SW once the implementation is there
|
||||
unsafe_disable_noise: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Mixnet1_1_3 {
|
||||
fn default() -> Self {
|
||||
// SAFETY:
|
||||
// our hardcoded values should always be valid
|
||||
#[allow(clippy::expect_used)]
|
||||
// is if there's anything set in the environment, otherwise fallback to mainnet
|
||||
let nym_api_urls = if let Ok(env_value) = env::var(var_names::NYM_API) {
|
||||
parse_urls(&env_value)
|
||||
} else {
|
||||
vec![mainnet::NYM_API.parse().expect("Invalid default API URL")]
|
||||
};
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let nyxd_urls = if let Ok(env_value) = env::var(var_names::NYXD) {
|
||||
parse_urls(&env_value)
|
||||
} else {
|
||||
vec![mainnet::NYXD_URL.parse().expect("Invalid default nyxd URL")]
|
||||
};
|
||||
|
||||
Mixnet1_1_3 {
|
||||
bind_address: SocketAddr::new(inaddr_any(), DEFAULT_MIXNET_PORT),
|
||||
nym_api_urls,
|
||||
nyxd_urls,
|
||||
debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Mixnet1_1_3 {
|
||||
/// Address this node will bind to for listening for mixnet packets
|
||||
/// default: `0.0.0.0:1789`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
/// Addresses to nym APIs from which the node gets the view of the network.
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
|
||||
/// Addresses to nyxd which the node uses to interact with the nyx chain.
|
||||
pub nyxd_urls: Vec<Url>,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: MixnetDebug1_1_3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct KeysPaths1_1_3 {
|
||||
/// Path to file containing ed25519 identity private key.
|
||||
pub private_ed25519_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing ed25519 identity public key.
|
||||
pub public_ed25519_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing x25519 sphinx private key.
|
||||
pub private_x25519_sphinx_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing x25519 sphinx public key.
|
||||
pub public_x25519_sphinx_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing x25519 noise private key.
|
||||
pub private_x25519_noise_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing x25519 noise public key.
|
||||
pub public_x25519_noise_key_file: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NymNodePaths1_1_3 {
|
||||
pub keys: KeysPaths1_1_3,
|
||||
|
||||
/// Path to a file containing basic node description: human-readable name, website, details, etc.
|
||||
pub description: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Http1_1_3 {
|
||||
/// Socket address this node will use for binding its http API.
|
||||
/// default: `0.0.0.0:8080`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
/// Path to assets directory of custom landing page of this node.
|
||||
#[serde(deserialize_with = "de_maybe_stringified")]
|
||||
pub landing_page_assets_path: Option<PathBuf>,
|
||||
|
||||
/// An optional bearer token for accessing certain http endpoints.
|
||||
/// Currently only used for obtaining mixnode's stats.
|
||||
#[serde(default)]
|
||||
pub access_token: Option<String>,
|
||||
|
||||
/// Specify whether basic system information should be exposed.
|
||||
/// default: true
|
||||
pub expose_system_info: bool,
|
||||
|
||||
/// Specify whether basic system hardware information should be exposed.
|
||||
/// This option is superseded by `expose_system_info`
|
||||
/// default: true
|
||||
pub expose_system_hardware: bool,
|
||||
|
||||
/// Specify whether detailed system crypto hardware information should be exposed.
|
||||
/// This option is superseded by `expose_system_hardware`
|
||||
/// default: true
|
||||
pub expose_crypto_hardware: bool,
|
||||
}
|
||||
|
||||
impl Default for Http1_1_3 {
|
||||
fn default() -> Self {
|
||||
Http1_1_3 {
|
||||
bind_address: SocketAddr::new(inaddr_any(), DEFAULT_HTTP_PORT),
|
||||
landing_page_assets_path: None,
|
||||
access_token: None,
|
||||
expose_system_info: true,
|
||||
expose_system_hardware: true,
|
||||
expose_crypto_hardware: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MixnodePaths1_1_3 {}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Debug1_1_3 {
|
||||
/// Delay between each subsequent node statistics being logged to the console
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub node_stats_logging_delay: Duration,
|
||||
|
||||
/// Delay between each subsequent node statistics being updated
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub node_stats_updating_delay: Duration,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct VerlocDebug1_1_3 {
|
||||
/// Specifies number of echo packets sent to each node during a measurement run.
|
||||
pub packets_per_node: usize,
|
||||
|
||||
/// Specifies maximum amount of time to wait for the connection to get established.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub connection_timeout: Duration,
|
||||
|
||||
/// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub packet_timeout: Duration,
|
||||
|
||||
/// Specifies delay between subsequent test packets being sent (after receiving a reply).
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub delay_between_packets: Duration,
|
||||
|
||||
/// Specifies number of nodes being tested at once.
|
||||
pub tested_nodes_batch_size: usize,
|
||||
|
||||
/// Specifies delay between subsequent test runs.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub testing_interval: Duration,
|
||||
|
||||
/// Specifies delay between attempting to run the measurement again if the previous run failed
|
||||
/// due to being unable to get the list of nodes.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub retry_timeout: Duration,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Verloc1_1_3 {
|
||||
/// Socket address this node will use for binding its verloc API.
|
||||
/// default: `0.0.0.0:1790`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: VerlocDebug1_1_3,
|
||||
}
|
||||
|
||||
impl VerlocDebug1_1_3 {
|
||||
const DEFAULT_PACKETS_PER_NODE: usize = 100;
|
||||
const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000);
|
||||
const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500);
|
||||
const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50);
|
||||
const DEFAULT_BATCH_SIZE: usize = 50;
|
||||
const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12);
|
||||
const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30);
|
||||
}
|
||||
|
||||
impl Default for VerlocDebug1_1_3 {
|
||||
fn default() -> Self {
|
||||
VerlocDebug1_1_3 {
|
||||
packets_per_node: Self::DEFAULT_PACKETS_PER_NODE,
|
||||
connection_timeout: Self::DEFAULT_CONNECTION_TIMEOUT,
|
||||
packet_timeout: Self::DEFAULT_PACKET_TIMEOUT,
|
||||
delay_between_packets: Self::DEFAULT_DELAY_BETWEEN_PACKETS,
|
||||
tested_nodes_batch_size: Self::DEFAULT_BATCH_SIZE,
|
||||
testing_interval: Self::DEFAULT_TESTING_INTERVAL,
|
||||
retry_timeout: Self::DEFAULT_RETRY_TIMEOUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MixnodeConfig1_1_3 {
|
||||
pub storage_paths: MixnodePaths1_1_3,
|
||||
|
||||
pub verloc: Verloc1_1_3,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: Debug1_1_3,
|
||||
}
|
||||
|
||||
impl Debug1_1_3 {
|
||||
const DEFAULT_NODE_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_000);
|
||||
const DEFAULT_NODE_STATS_UPDATING_DELAY: Duration = Duration::from_millis(30_000);
|
||||
}
|
||||
|
||||
impl Default for Debug1_1_3 {
|
||||
fn default() -> Self {
|
||||
Debug1_1_3 {
|
||||
node_stats_logging_delay: Self::DEFAULT_NODE_STATS_LOGGING_DELAY,
|
||||
node_stats_updating_delay: Self::DEFAULT_NODE_STATS_UPDATING_DELAY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct EntryGatewayPaths1_1_3 {
|
||||
/// Path to sqlite database containing all persistent data: messages for offline clients,
|
||||
/// derived shared keys and available client bandwidths.
|
||||
pub clients_storage: PathBuf,
|
||||
|
||||
/// Path to file containing cosmos account mnemonic used for zk-nym redemption.
|
||||
pub cosmos_mnemonic: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct EntryGatewayConfigDebug1_1_3 {
|
||||
/// Number of messages from offline client that can be pulled at once (i.e. with a single SQL query) from the storage.
|
||||
pub message_retrieval_limit: i64,
|
||||
}
|
||||
|
||||
impl EntryGatewayConfigDebug1_1_3 {
|
||||
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
}
|
||||
|
||||
impl Default for EntryGatewayConfigDebug1_1_3 {
|
||||
fn default() -> Self {
|
||||
EntryGatewayConfigDebug1_1_3 {
|
||||
message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct EntryGatewayConfig1_1_3 {
|
||||
pub storage_paths: EntryGatewayPaths1_1_3,
|
||||
|
||||
/// Indicates whether this gateway is accepting only coconut credentials for accessing the mixnet
|
||||
/// or if it also accepts non-paying clients
|
||||
pub enforce_zk_nyms: bool,
|
||||
|
||||
/// Socket address this node will use for binding its client websocket API.
|
||||
/// default: `0.0.0.0:9000`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
/// Custom announced port for listening for websocket client traffic.
|
||||
/// If unspecified, the value from the `bind_address` will be used instead
|
||||
/// default: None
|
||||
#[serde(deserialize_with = "de_maybe_port")]
|
||||
pub announce_ws_port: Option<u16>,
|
||||
|
||||
/// If applicable, announced port for listening for secure websocket client traffic.
|
||||
/// (default: None)
|
||||
#[serde(deserialize_with = "de_maybe_port")]
|
||||
pub announce_wss_port: Option<u16>,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: EntryGatewayConfigDebug1_1_3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NetworkRequesterPaths1_1_3 {
|
||||
/// Path to file containing network requester ed25519 identity private key.
|
||||
pub private_ed25519_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing network requester ed25519 identity public key.
|
||||
pub public_ed25519_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing network requester x25519 diffie hellman private key.
|
||||
pub private_x25519_diffie_hellman_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing network requester x25519 diffie hellman public key.
|
||||
pub public_x25519_diffie_hellman_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing key used for encrypting and decrypting the content of an
|
||||
/// acknowledgement so that nobody besides the client knows which packet it refers to.
|
||||
pub ack_key_file: PathBuf,
|
||||
|
||||
/// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
|
||||
pub reply_surb_database: PathBuf,
|
||||
|
||||
/// Normally this is a path to the file containing information about gateways used by this client,
|
||||
/// i.e. details such as their public keys, owner addresses or the network information.
|
||||
/// but in this case it just has the basic information of "we're using custom gateway".
|
||||
/// Due to how clients are started up, this file has to exist.
|
||||
pub gateway_registrations: PathBuf,
|
||||
// it's possible we might have to add credential storage here for return tickets
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct IpPacketRouterPaths1_1_3 {
|
||||
/// Path to file containing ip packet router ed25519 identity private key.
|
||||
pub private_ed25519_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing ip packet router ed25519 identity public key.
|
||||
pub public_ed25519_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing ip packet router x25519 diffie hellman private key.
|
||||
pub private_x25519_diffie_hellman_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing ip packet router x25519 diffie hellman public key.
|
||||
pub public_x25519_diffie_hellman_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing key used for encrypting and decrypting the content of an
|
||||
/// acknowledgement so that nobody besides the client knows which packet it refers to.
|
||||
pub ack_key_file: PathBuf,
|
||||
|
||||
/// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
|
||||
pub reply_surb_database: PathBuf,
|
||||
|
||||
/// Normally this is a path to the file containing information about gateways used by this client,
|
||||
/// i.e. details such as their public keys, owner addresses or the network information.
|
||||
/// but in this case it just has the basic information of "we're using custom gateway".
|
||||
/// Due to how clients are started up, this file has to exist.
|
||||
pub gateway_registrations: PathBuf,
|
||||
// it's possible we might have to add credential storage here for return tickets
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ExitGatewayPaths1_1_3 {
|
||||
pub network_requester: NetworkRequesterPaths1_1_3,
|
||||
|
||||
pub ip_packet_router: IpPacketRouterPaths1_1_3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct IpPacketRouterDebug1_1_3 {
|
||||
/// Specifies whether ip packet routing service is enabled in this process.
|
||||
/// This is only here for debugging purposes as exit gateway should always run **both**
|
||||
/// network requester and an ip packet router.
|
||||
pub enabled: bool,
|
||||
|
||||
/// Disable Poisson sending rate.
|
||||
/// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true
|
||||
/// (or is it (?))
|
||||
pub disable_poisson_rate: bool,
|
||||
|
||||
/// Shared detailed client configuration options
|
||||
#[serde(flatten)]
|
||||
pub client_debug: ClientDebugConfig,
|
||||
}
|
||||
|
||||
impl Default for IpPacketRouterDebug1_1_3 {
|
||||
fn default() -> Self {
|
||||
IpPacketRouterDebug1_1_3 {
|
||||
enabled: true,
|
||||
disable_poisson_rate: true,
|
||||
client_debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
pub struct IpPacketRouter1_1_3 {
|
||||
#[serde(default)]
|
||||
pub debug: IpPacketRouterDebug1_1_3,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for IpPacketRouter1_1_3 {
|
||||
fn default() -> Self {
|
||||
IpPacketRouter1_1_3 {
|
||||
debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
pub struct NetworkRequesterDebug1_1_3 {
|
||||
/// Specifies whether network requester service is enabled in this process.
|
||||
/// This is only here for debugging purposes as exit gateway should always run **both**
|
||||
/// network requester and an ip packet router.
|
||||
pub enabled: bool,
|
||||
|
||||
/// Disable Poisson sending rate.
|
||||
/// This is equivalent to setting client_debug.traffic.disable_main_poisson_packet_distribution = true
|
||||
/// (or is it (?))
|
||||
pub disable_poisson_rate: bool,
|
||||
|
||||
/// Shared detailed client configuration options
|
||||
#[serde(flatten)]
|
||||
pub client_debug: ClientDebugConfig,
|
||||
}
|
||||
|
||||
impl Default for NetworkRequesterDebug1_1_3 {
|
||||
fn default() -> Self {
|
||||
NetworkRequesterDebug1_1_3 {
|
||||
enabled: true,
|
||||
disable_poisson_rate: true,
|
||||
client_debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
pub struct NetworkRequester1_1_3 {
|
||||
#[serde(default)]
|
||||
pub debug: NetworkRequesterDebug1_1_3,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for NetworkRequester1_1_3 {
|
||||
fn default() -> Self {
|
||||
NetworkRequester1_1_3 {
|
||||
debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ExitGatewayConfig1_1_3 {
|
||||
pub storage_paths: ExitGatewayPaths1_1_3,
|
||||
|
||||
/// specifies whether this exit node should run in 'open-proxy' mode
|
||||
/// and thus would attempt to resolve **ANY** request it receives.
|
||||
pub open_proxy: bool,
|
||||
|
||||
/// Specifies the url for an upstream source of the exit policy used by this node.
|
||||
pub upstream_exit_policy_url: Url,
|
||||
|
||||
pub network_requester: NetworkRequester1_1_3,
|
||||
|
||||
pub ip_packet_router: IpPacketRouter1_1_3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct LoggingSettings1_1_3 {
|
||||
// well, we need to implement something here at some point...
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config1_1_3 {
|
||||
// additional metadata holding on-disk location of this config file
|
||||
#[serde(skip)]
|
||||
pub(crate) save_path: Option<PathBuf>,
|
||||
|
||||
/// Human-readable ID of this particular node.
|
||||
pub id: String,
|
||||
|
||||
/// Current mode of this nym-node.
|
||||
/// Expect this field to be changed in the future to allow running the node in multiple modes (i.e. mixnode + gateway)
|
||||
pub mode: NodeMode1_1_3,
|
||||
|
||||
pub host: Host1_1_3,
|
||||
|
||||
pub mixnet: Mixnet1_1_3,
|
||||
|
||||
/// Storage paths to persistent nym-node data, such as its long term keys.
|
||||
pub storage_paths: NymNodePaths1_1_3,
|
||||
|
||||
#[serde(default)]
|
||||
pub http: Http1_1_3,
|
||||
|
||||
pub wireguard: Wireguard1_1_3,
|
||||
|
||||
pub mixnode: MixnodeConfig1_1_3,
|
||||
|
||||
pub entry_gateway: EntryGatewayConfig1_1_3,
|
||||
|
||||
pub exit_gateway: ExitGatewayConfig1_1_3,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettings1_1_3,
|
||||
}
|
||||
|
||||
impl NymConfigTemplate for Config1_1_3 {
|
||||
fn template(&self) -> &'static str {
|
||||
CONFIG_TEMPLATE
|
||||
}
|
||||
}
|
||||
|
||||
impl Config1_1_3 {
|
||||
pub fn save(&self) -> Result<(), NymNodeError> {
|
||||
let save_location = self.save_location();
|
||||
debug!(
|
||||
"attempting to save config file to '{}'",
|
||||
save_location.display()
|
||||
);
|
||||
save_formatted_config_to_file(self, &save_location).map_err(|source| {
|
||||
NymNodeError::ConfigSaveFailure {
|
||||
id: self.id.clone(),
|
||||
path: save_location,
|
||||
source,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn save_location(&self) -> PathBuf {
|
||||
self.save_path
|
||||
.clone()
|
||||
.unwrap_or(self.default_save_location())
|
||||
}
|
||||
|
||||
pub fn default_save_location(&self) -> PathBuf {
|
||||
default_config_filepath(&self.id)
|
||||
}
|
||||
|
||||
pub fn default_data_directory<P: AsRef<Path>>(config_path: P) -> Result<PathBuf, NymNodeError> {
|
||||
let config_path = config_path.as_ref();
|
||||
|
||||
// we got a proper path to the .toml file
|
||||
let Some(config_dir) = config_path.parent() else {
|
||||
error!(
|
||||
"'{}' does not have a parent directory. Have you pointed to the fs root?",
|
||||
config_path.display()
|
||||
);
|
||||
return Err(NymNodeError::DataDirDerivationFailure);
|
||||
};
|
||||
|
||||
let Some(config_dir_name) = config_dir.file_name() else {
|
||||
error!(
|
||||
"could not obtain parent directory name of '{}'. Have you used relative paths?",
|
||||
config_path.display()
|
||||
);
|
||||
return Err(NymNodeError::DataDirDerivationFailure);
|
||||
};
|
||||
|
||||
if config_dir_name != DEFAULT_CONFIG_DIR {
|
||||
error!(
|
||||
"the parent directory of '{}' ({}) is not {DEFAULT_CONFIG_DIR}. currently this is not supported",
|
||||
config_path.display(), config_dir_name.to_str().unwrap_or("UNKNOWN")
|
||||
);
|
||||
return Err(NymNodeError::DataDirDerivationFailure);
|
||||
}
|
||||
|
||||
let Some(node_dir) = config_dir.parent() else {
|
||||
error!(
|
||||
"'{}' does not have a parent directory. Have you pointed to the fs root?",
|
||||
config_dir.display()
|
||||
);
|
||||
return Err(NymNodeError::DataDirDerivationFailure);
|
||||
};
|
||||
|
||||
Ok(node_dir.join(DEFAULT_DATA_DIR))
|
||||
}
|
||||
|
||||
// simple wrapper that reads config file and assigns path location
|
||||
fn read_from_path<P: AsRef<Path>>(path: P) -> Result<Self, NymNodeError> {
|
||||
let path = path.as_ref();
|
||||
let mut loaded: Config1_1_3 =
|
||||
read_config_from_toml_file(path).map_err(|source| NymNodeError::ConfigLoadFailure {
|
||||
path: path.to_path_buf(),
|
||||
source,
|
||||
})?;
|
||||
loaded.save_path = Some(path.to_path_buf());
|
||||
debug!("loaded config file from {}", path.display());
|
||||
Ok(loaded)
|
||||
}
|
||||
|
||||
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> Result<Self, NymNodeError> {
|
||||
Self::read_from_path(path)
|
||||
}
|
||||
}
|
||||
|
||||
fn initialise(config: &Wireguard1_1_3) -> std::io::Result<()> {
|
||||
let mut rng = OsRng;
|
||||
let x25519_keys = KeyPair::new(&mut rng);
|
||||
|
||||
store_keypair(
|
||||
&x25519_keys,
|
||||
&config.storage_paths.x25519_wireguard_storage_paths(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// currently there are no upgrades
|
||||
pub async fn try_upgrade_config_1_1_3<P: AsRef<Path>>(path: P) -> Result<(), NymNodeError> {
|
||||
let old_cfg = Config::read_from_path(&path)?;
|
||||
let cfg = Config {
|
||||
save_path: old_cfg.save_path,
|
||||
id: old_cfg.id,
|
||||
mode: old_cfg.mode,
|
||||
host: old_cfg.host,
|
||||
mixnet: old_cfg.mixnet,
|
||||
storage_paths: old_cfg.storage_paths,
|
||||
http: old_cfg.http,
|
||||
wireguard: old_cfg.wireguard,
|
||||
mixnode: old_cfg.mixnode,
|
||||
entry_gateway: old_cfg.entry_gateway,
|
||||
exit_gateway: old_cfg.exit_gateway,
|
||||
logging: old_cfg.logging,
|
||||
};
|
||||
|
||||
cfg.save()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -43,6 +43,14 @@ pub const DEFAULT_IPR_ACK_KEY_FILENAME: &str = "aes128ctr_ipr_ack";
|
||||
pub const DEFAULT_IPR_REPLY_SURB_DB_FILENAME: &str = "ipr_persistent_reply_store.sqlite";
|
||||
pub const DEFAULT_IPR_GATEWAYS_DB_FILENAME: &str = "ipr_gateways_info_store.sqlite";
|
||||
|
||||
pub const DEFAULT_ED25519_AUTH_PRIVATE_IDENTITY_KEY_FILENAME: &str = "ed25519_auth_identity";
|
||||
pub const DEFAULT_ED25519_AUTH_PUBLIC_IDENTITY_KEY_FILENAME: &str = "ed25519_auth_identity.pub";
|
||||
pub const DEFAULT_X25519_AUTH_PRIVATE_DH_KEY_FILENAME: &str = "x25519_auth_dh";
|
||||
pub const DEFAULT_X25519_AUTH_PUBLIC_DH_KEY_FILENAME: &str = "x25519_auth_dh.pub";
|
||||
pub const DEFAULT_AUTH_ACK_KEY_FILENAME: &str = "aes128ctr_auth_ack";
|
||||
pub const DEFAULT_AUTH_REPLY_SURB_DB_FILENAME: &str = "auth_persistent_reply_store.sqlite";
|
||||
pub const DEFAULT_AUTH_GATEWAYS_DB_FILENAME: &str = "auth_gateways_info_store.sqlite";
|
||||
|
||||
// Wireguard
|
||||
pub const DEFAULT_X25519_WG_DH_KEY_FILENAME: &str = "x25519_wg_dh";
|
||||
pub const DEFAULT_X25519_WG_PUBLIC_DH_KEY_FILENAME: &str = "x25519_wg_dh.pub";
|
||||
@@ -195,6 +203,8 @@ pub struct ExitGatewayPaths {
|
||||
pub network_requester: NetworkRequesterPaths,
|
||||
|
||||
pub ip_packet_router: IpPacketRouterPaths,
|
||||
|
||||
pub authenticator: AuthenticatorPaths,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
@@ -357,12 +367,93 @@ impl IpPacketRouterPaths {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct AuthenticatorPaths {
|
||||
/// Path to file containing authenticator ed25519 identity private key.
|
||||
pub private_ed25519_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing authenticator ed25519 identity public key.
|
||||
pub public_ed25519_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing authenticator x25519 diffie hellman private key.
|
||||
pub private_x25519_diffie_hellman_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing authenticator x25519 diffie hellman public key.
|
||||
pub public_x25519_diffie_hellman_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing key used for encrypting and decrypting the content of an
|
||||
/// acknowledgement so that nobody besides the client knows which packet it refers to.
|
||||
pub ack_key_file: PathBuf,
|
||||
|
||||
/// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
|
||||
pub reply_surb_database: PathBuf,
|
||||
|
||||
/// Normally this is a path to the file containing information about gateways used by this client,
|
||||
/// i.e. details such as their public keys, owner addresses or the network information.
|
||||
/// but in this case it just has the basic information of "we're using custom gateway".
|
||||
/// Due to how clients are started up, this file has to exist.
|
||||
pub gateway_registrations: PathBuf,
|
||||
// it's possible we might have to add credential storage here for return tickets
|
||||
}
|
||||
|
||||
impl AuthenticatorPaths {
|
||||
pub fn new<P: AsRef<Path>>(data_dir: P) -> Self {
|
||||
let data_dir = data_dir.as_ref();
|
||||
AuthenticatorPaths {
|
||||
private_ed25519_identity_key_file: data_dir
|
||||
.join(DEFAULT_ED25519_AUTH_PRIVATE_IDENTITY_KEY_FILENAME),
|
||||
public_ed25519_identity_key_file: data_dir
|
||||
.join(DEFAULT_ED25519_AUTH_PUBLIC_IDENTITY_KEY_FILENAME),
|
||||
private_x25519_diffie_hellman_key_file: data_dir
|
||||
.join(DEFAULT_X25519_AUTH_PRIVATE_DH_KEY_FILENAME),
|
||||
public_x25519_diffie_hellman_key_file: data_dir
|
||||
.join(DEFAULT_X25519_AUTH_PUBLIC_DH_KEY_FILENAME),
|
||||
ack_key_file: data_dir.join(DEFAULT_AUTH_ACK_KEY_FILENAME),
|
||||
reply_surb_database: data_dir.join(DEFAULT_AUTH_REPLY_SURB_DB_FILENAME),
|
||||
gateway_registrations: data_dir.join(DEFAULT_AUTH_GATEWAYS_DB_FILENAME),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_common_client_paths(&self) -> CommonClientPaths {
|
||||
CommonClientPaths {
|
||||
keys: ClientKeysPaths {
|
||||
private_identity_key_file: self.private_ed25519_identity_key_file.clone(),
|
||||
public_identity_key_file: self.public_ed25519_identity_key_file.clone(),
|
||||
private_encryption_key_file: self.private_x25519_diffie_hellman_key_file.clone(),
|
||||
public_encryption_key_file: self.public_x25519_diffie_hellman_key_file.clone(),
|
||||
ack_key_file: self.ack_key_file.clone(),
|
||||
},
|
||||
gateway_registrations: self.gateway_registrations.clone(),
|
||||
|
||||
// not needed for embedded providers
|
||||
credentials_database: Default::default(),
|
||||
reply_surb_database: self.reply_surb_database.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ed25519_identity_storage_paths(&self) -> nym_pemstore::KeyPairPath {
|
||||
nym_pemstore::KeyPairPath::new(
|
||||
&self.private_ed25519_identity_key_file,
|
||||
&self.public_ed25519_identity_key_file,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn x25519_diffie_hellman_storage_paths(&self) -> nym_pemstore::KeyPairPath {
|
||||
nym_pemstore::KeyPairPath::new(
|
||||
&self.private_x25519_diffie_hellman_key_file,
|
||||
&self.public_x25519_diffie_hellman_key_file,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExitGatewayPaths {
|
||||
pub fn new<P: AsRef<Path>>(data_dir: P) -> Self {
|
||||
let data_dir = data_dir.as_ref();
|
||||
ExitGatewayPaths {
|
||||
network_requester: NetworkRequesterPaths::new(data_dir),
|
||||
ip_packet_router: IpPacketRouterPaths::new(data_dir),
|
||||
authenticator: AuthenticatorPaths::new(data_dir),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,151 +1,22 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::persistence::WireguardPaths;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::config::old_configs::*;
|
||||
use crate::config::Config;
|
||||
use crate::error::NymNodeError;
|
||||
use std::path::Path;
|
||||
|
||||
// currently there are no upgrades
|
||||
async fn try_upgrade_config<P: AsRef<Path>>(path: P) -> Result<(), NymNodeError> {
|
||||
use crate::config::*;
|
||||
use crate::error::KeyIOFailure;
|
||||
use nym_crypto::asymmetric::encryption::KeyPair;
|
||||
use nym_pemstore::store_keypair;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct OldWireguardPaths {
|
||||
// pub keys:
|
||||
async fn try_upgrade_config(path: &Path) -> Result<(), NymNodeError> {
|
||||
if try_upgrade_config_1_1_2(path).await.is_ok() {
|
||||
debug!("Updated from 1.1.2 or previous");
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct OldWireguard {
|
||||
/// 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:51822`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
/// Ip address of the private wireguard network.
|
||||
/// default: `10.1.0.0`
|
||||
pub private_network_ip: IpAddr,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard.
|
||||
/// The maximum value for IPv4 is 32 and for IPv6 is 128
|
||||
pub private_network_prefix: u8,
|
||||
|
||||
/// Paths for wireguard keys, client registries, etc.
|
||||
pub storage_paths: OldWireguardPaths,
|
||||
if try_upgrade_config_1_1_3(path).await.is_ok() {
|
||||
debug!("Updated from 1.1.3");
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct OldConfig {
|
||||
// additional metadata holding on-disk location of this config file
|
||||
#[serde(skip)]
|
||||
pub(crate) save_path: Option<PathBuf>,
|
||||
|
||||
/// Human-readable ID of this particular node.
|
||||
pub id: String,
|
||||
|
||||
/// Current mode of this nym-node.
|
||||
/// Expect this field to be changed in the future to allow running the node in multiple modes (i.e. mixnode + gateway)
|
||||
pub mode: NodeMode,
|
||||
|
||||
pub host: Host,
|
||||
|
||||
pub mixnet: Mixnet,
|
||||
|
||||
/// Storage paths to persistent nym-node data, such as its long term keys.
|
||||
pub storage_paths: NymNodePaths,
|
||||
|
||||
#[serde(default)]
|
||||
pub http: Http,
|
||||
|
||||
pub wireguard: OldWireguard,
|
||||
|
||||
pub mixnode: MixnodeConfig,
|
||||
|
||||
pub entry_gateway: EntryGatewayConfig,
|
||||
|
||||
pub exit_gateway: ExitGatewayConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettings,
|
||||
}
|
||||
|
||||
impl NymConfigTemplate for OldConfig {
|
||||
fn template(&self) -> &'static str {
|
||||
CONFIG_TEMPLATE
|
||||
}
|
||||
}
|
||||
|
||||
impl OldConfig {
|
||||
fn read_from_path<P: AsRef<Path>>(path: P) -> Result<Self, NymNodeError> {
|
||||
let path = path.as_ref();
|
||||
let mut loaded: OldConfig = read_config_from_toml_file(path).map_err(|source| {
|
||||
NymNodeError::ConfigLoadFailure {
|
||||
path: path.to_path_buf(),
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
loaded.save_path = Some(path.to_path_buf());
|
||||
debug!("loaded config file from {}", path.display());
|
||||
Ok(loaded)
|
||||
}
|
||||
}
|
||||
|
||||
fn initialise(config: &Wireguard) -> std::io::Result<()> {
|
||||
let mut rng = OsRng;
|
||||
let x25519_keys = KeyPair::new(&mut rng);
|
||||
|
||||
store_keypair(
|
||||
&x25519_keys,
|
||||
&config.storage_paths.x25519_wireguard_storage_paths(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let old_cfg = OldConfig::read_from_path(&path)?;
|
||||
let wireguard = Wireguard {
|
||||
enabled: old_cfg.wireguard.enabled,
|
||||
bind_address: old_cfg.wireguard.bind_address,
|
||||
private_ip: old_cfg.wireguard.private_network_ip,
|
||||
announced_port: old_cfg.wireguard.announced_port,
|
||||
private_network_prefix: old_cfg.wireguard.private_network_prefix,
|
||||
storage_paths: WireguardPaths::new(Config::default_data_directory(path)?),
|
||||
};
|
||||
initialise(&wireguard).map_err(|err| KeyIOFailure::KeyPairStoreFailure {
|
||||
keys: "wg-x25519-dh".to_string(),
|
||||
paths: wireguard.storage_paths.x25519_wireguard_storage_paths(),
|
||||
err,
|
||||
})?;
|
||||
let cfg = Config {
|
||||
save_path: old_cfg.save_path,
|
||||
id: old_cfg.id,
|
||||
mode: old_cfg.mode,
|
||||
host: old_cfg.host,
|
||||
mixnet: old_cfg.mixnet,
|
||||
storage_paths: old_cfg.storage_paths,
|
||||
http: old_cfg.http,
|
||||
wireguard,
|
||||
mixnode: old_cfg.mixnode,
|
||||
entry_gateway: old_cfg.entry_gateway,
|
||||
exit_gateway: old_cfg.exit_gateway,
|
||||
logging: old_cfg.logging,
|
||||
};
|
||||
|
||||
cfg.save()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+37
-26
@@ -8,7 +8,6 @@ use crate::node::helpers::{
|
||||
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;
|
||||
@@ -26,7 +25,6 @@ use nym_node::config::{
|
||||
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};
|
||||
@@ -68,7 +66,6 @@ impl MixnodeData {
|
||||
pub struct EntryGatewayData {
|
||||
mnemonic: Zeroizing<bip39::Mnemonic>,
|
||||
client_storage: nym_gateway::node::PersistentStorage,
|
||||
wireguard_data: WireguardGatewayData,
|
||||
}
|
||||
|
||||
impl EntryGatewayData {
|
||||
@@ -86,10 +83,7 @@ impl EntryGatewayData {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn new(
|
||||
config: &EntryGatewayConfig,
|
||||
wireguard_data: WireguardGatewayData,
|
||||
) -> Result<EntryGatewayData, EntryGatewayError> {
|
||||
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(
|
||||
@@ -98,7 +92,6 @@ impl EntryGatewayData {
|
||||
)
|
||||
.await
|
||||
.map_err(nym_gateway::GatewayError::from)?,
|
||||
wireguard_data: wireguard_data.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -113,6 +106,9 @@ pub struct ExitGatewayData {
|
||||
|
||||
ipr_ed25519: ed25519::PublicKey,
|
||||
ipr_x25519: x25519::PublicKey,
|
||||
|
||||
auth_ed25519: ed25519::PublicKey,
|
||||
auth_x25519: x25519::PublicKey,
|
||||
}
|
||||
|
||||
impl ExitGatewayData {
|
||||
@@ -243,11 +239,24 @@ impl ExitGatewayData {
|
||||
"ip packet router x25519",
|
||||
)?;
|
||||
|
||||
let auth_paths = &config.storage_paths.authenticator;
|
||||
let auth_ed25519 = load_key(
|
||||
&auth_paths.public_ed25519_identity_key_file,
|
||||
"authenticator ed25519",
|
||||
)?;
|
||||
|
||||
let auth_x25519 = load_key(
|
||||
&auth_paths.public_x25519_diffie_hellman_key_file,
|
||||
"authenticator x25519",
|
||||
)?;
|
||||
|
||||
Ok(ExitGatewayData {
|
||||
nr_ed25519,
|
||||
nr_x25519,
|
||||
ipr_ed25519,
|
||||
ipr_x25519,
|
||||
auth_ed25519,
|
||||
auth_x25519,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -386,11 +395,7 @@ impl NymNode {
|
||||
description: load_node_description(&config.storage_paths.description)?,
|
||||
verloc_stats: Default::default(),
|
||||
mixnode: MixnodeData::new(&config.mixnode)?,
|
||||
entry_gateway: EntryGatewayData::new(
|
||||
&config.entry_gateway,
|
||||
wireguard_data.inner.clone(),
|
||||
)
|
||||
.await?,
|
||||
entry_gateway: EntryGatewayData::new(&config.entry_gateway).await?,
|
||||
exit_gateway: ExitGatewayData::new(&config.exit_gateway)?,
|
||||
wireguard: wireguard_data,
|
||||
config,
|
||||
@@ -422,6 +427,14 @@ impl NymNode {
|
||||
)
|
||||
}
|
||||
|
||||
fn exit_authenticator_address(&self) -> Recipient {
|
||||
Recipient::new(
|
||||
self.exit_gateway.auth_ed25519,
|
||||
self.exit_gateway.auth_x25519,
|
||||
*self.ed25519_identity_keys.public_key(),
|
||||
)
|
||||
}
|
||||
|
||||
fn x25519_wireguard_key(&self) -> &x25519::PublicKey {
|
||||
self.wireguard.inner.keypair().public_key()
|
||||
}
|
||||
@@ -487,6 +500,7 @@ impl NymNode {
|
||||
config,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
self.ed25519_identity_keys.clone(),
|
||||
self.x25519_sphinx_keys.clone(),
|
||||
self.entry_gateway.client_storage.clone(),
|
||||
@@ -514,6 +528,7 @@ impl NymNode {
|
||||
config.gateway,
|
||||
Some(config.nr_opts),
|
||||
Some(config.ipr_opts),
|
||||
Some(config.auth_opts),
|
||||
self.ed25519_identity_keys.clone(),
|
||||
self.x25519_sphinx_keys.clone(),
|
||||
self.entry_gateway.client_storage.clone(),
|
||||
@@ -584,6 +599,13 @@ impl NymNode {
|
||||
encoded_x25519_key: self.exit_gateway.ipr_x25519.to_base58_string(),
|
||||
address: self.exit_ip_packet_router_address().to_string(),
|
||||
};
|
||||
|
||||
let auth_details = api_requests::v1::authenticator::models::Authenticator {
|
||||
encoded_identity_key: self.exit_gateway.auth_ed25519.to_base58_string(),
|
||||
encoded_x25519_key: self.exit_gateway.auth_x25519.to_base58_string(),
|
||||
address: self.exit_authenticator_address().to_string(),
|
||||
};
|
||||
|
||||
let exit_policy_details =
|
||||
api_requests::v1::network_requester::exit_policy::models::UsedExitPolicy {
|
||||
enabled: true,
|
||||
@@ -597,24 +619,13 @@ impl NymNode {
|
||||
policy: None,
|
||||
};
|
||||
|
||||
let wireguard_private_network = IpNetwork::new(
|
||||
self.config.wireguard.private_ip,
|
||||
self.config.wireguard.private_network_prefix,
|
||||
)?;
|
||||
|
||||
let wg_state = WireguardAppState::new(
|
||||
self.entry_gateway.wireguard_data.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_authenticator_details(auth_details)
|
||||
.with_used_exit_policy(exit_policy_details)
|
||||
.with_description(self.description.clone())
|
||||
.with_auxiliary_details(auxiliary_details);
|
||||
@@ -640,7 +651,7 @@ impl NymNode {
|
||||
.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))
|
||||
Ok(NymNodeRouter::new(config, Some(app_state))
|
||||
.build_server(&self.config.http.bind_address)
|
||||
.await?)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
[package]
|
||||
name = "nym-authenticator"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
bincode = { workspace = true }
|
||||
bs58 = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
fastrand = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
ipnetwork = { workspace = true }
|
||||
log = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net"] }
|
||||
tokio-stream = { workspace = true }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
url = { workspace = true }
|
||||
|
||||
nym-authenticator-requests = { path = "../../common/authenticator-requests" }
|
||||
nym-bin-common = { path = "../../common/bin-common" }
|
||||
nym-client-core = { path = "../../common/client-core" }
|
||||
nym-config = { path = "../../common/config" }
|
||||
nym-crypto = { path = "../../common/crypto" }
|
||||
nym-id = { path = "../../common/nym-id" }
|
||||
nym-network-defaults = { path = "../../common/network-defaults" }
|
||||
nym-sdk = { path = "../../sdk/rust/nym-sdk" }
|
||||
nym-service-providers-common = { path = "../common" }
|
||||
nym-sphinx = { path = "../../common/nymsphinx" }
|
||||
nym-task = { path = "../../common/task" }
|
||||
nym-types = { path = "../../common/types" }
|
||||
nym-wireguard = { path = "../../common/wireguard" }
|
||||
nym-wireguard-types = { path = "../../common/wireguard-types" }
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use futures::channel::oneshot;
|
||||
use ipnetwork::IpNetwork;
|
||||
use nym_client_core::{HardcodedTopologyProvider, TopologyProvider};
|
||||
use nym_sdk::mixnet::Recipient;
|
||||
use nym_task::{TaskClient, TaskHandle};
|
||||
use nym_wireguard::WireguardGatewayData;
|
||||
|
||||
use crate::{config::Config, error::AuthenticatorError};
|
||||
|
||||
pub struct OnStartData {
|
||||
// to add more fields as required
|
||||
pub address: Recipient,
|
||||
}
|
||||
|
||||
impl OnStartData {
|
||||
pub fn new(address: Recipient) -> Self {
|
||||
Self { address }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Authenticator {
|
||||
#[allow(unused)]
|
||||
config: Config,
|
||||
wait_for_gateway: bool,
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
wireguard_gateway_data: WireguardGatewayData,
|
||||
shutdown: Option<TaskClient>,
|
||||
on_start: Option<oneshot::Sender<OnStartData>>,
|
||||
}
|
||||
|
||||
impl Authenticator {
|
||||
pub fn new(config: Config, wireguard_gateway_data: WireguardGatewayData) -> Self {
|
||||
Self {
|
||||
config,
|
||||
wait_for_gateway: false,
|
||||
custom_topology_provider: None,
|
||||
wireguard_gateway_data,
|
||||
shutdown: None,
|
||||
on_start: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(unused)]
|
||||
pub fn with_shutdown(mut self, shutdown: TaskClient) -> Self {
|
||||
self.shutdown = Some(shutdown);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(unused)]
|
||||
pub fn with_wait_for_gateway(mut self, wait_for_gateway: bool) -> Self {
|
||||
self.wait_for_gateway = wait_for_gateway;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(unused)]
|
||||
pub fn with_on_start(mut self, on_start: oneshot::Sender<OnStartData>) -> Self {
|
||||
self.on_start = Some(on_start);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(unused)]
|
||||
pub fn with_custom_topology_provider(
|
||||
mut self,
|
||||
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
) -> Self {
|
||||
self.custom_topology_provider = Some(topology_provider);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_stored_topology<P: AsRef<Path>>(
|
||||
mut self,
|
||||
file: P,
|
||||
) -> Result<Self, AuthenticatorError> {
|
||||
self.custom_topology_provider =
|
||||
Some(Box::new(HardcodedTopologyProvider::new_from_file(file)?));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub async fn run_service_provider(self) -> Result<(), AuthenticatorError> {
|
||||
// Used to notify tasks to shutdown. Not all tasks fully supports this (yet).
|
||||
|
||||
let task_handle: TaskHandle = self.shutdown.map(Into::into).unwrap_or_default();
|
||||
|
||||
// Connect to the mixnet
|
||||
let mixnet_client = crate::mixnet_client::create_mixnet_client(
|
||||
&self.config.base,
|
||||
task_handle.get_handle().named("nym_sdk::MixnetClient"),
|
||||
None,
|
||||
self.custom_topology_provider,
|
||||
self.wait_for_gateway,
|
||||
&self.config.storage_paths.common_paths,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let self_address = *mixnet_client.nym_address();
|
||||
|
||||
let private_ip_network = IpNetwork::new(
|
||||
self.config.authenticator.private_ip,
|
||||
self.config.authenticator.private_network_prefix,
|
||||
)?;
|
||||
let mixnet_listener = crate::mixnet_listener::MixnetListener::new(
|
||||
self.config,
|
||||
private_ip_network,
|
||||
self.wireguard_gateway_data,
|
||||
mixnet_client,
|
||||
task_handle,
|
||||
);
|
||||
|
||||
log::info!("The address of this client is: {self_address}");
|
||||
log::info!("All systems go. Press CTRL-C to stop the server.");
|
||||
|
||||
if let Some(on_start) = self.on_start {
|
||||
if on_start.send(OnStartData::new(self_address)).is_err() {
|
||||
// the parent has dropped the channel before receiving the response
|
||||
return Err(AuthenticatorError::DisconnectedParent);
|
||||
}
|
||||
}
|
||||
|
||||
mixnet_listener.run().await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::cli::CliAuthenticatorClient;
|
||||
use nym_authenticator::error::AuthenticatorError;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_client_core::cli_helpers::client_add_gateway::{add_gateway, CommonClientAddGatewayArgs};
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
#[command(flatten)]
|
||||
common_args: CommonClientAddGatewayArgs,
|
||||
|
||||
#[arg(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
|
||||
impl AsRef<CommonClientAddGatewayArgs> for Args {
|
||||
fn as_ref(&self) -> &CommonClientAddGatewayArgs {
|
||||
&self.common_args
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> Result<(), AuthenticatorError> {
|
||||
let output = args.output;
|
||||
let res = add_gateway::<CliAuthenticatorClient, _>(args, None).await?;
|
||||
|
||||
println!("{}", output.format(&res));
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Args;
|
||||
use nym_bin_common::bin_info_owned;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
|
||||
#[derive(Args)]
|
||||
pub(crate) struct BuildInfo {
|
||||
#[arg(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
|
||||
pub(crate) fn execute(args: BuildInfo) {
|
||||
println!("{}", args.output.format(&bin_info_owned!()))
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::cli::CliAuthenticatorClient;
|
||||
use nym_authenticator::error::AuthenticatorError;
|
||||
use nym_client_core::cli_helpers::client_import_credential::{
|
||||
import_credential, CommonClientImportCredentialArgs,
|
||||
};
|
||||
|
||||
pub(crate) async fn execute(
|
||||
args: CommonClientImportCredentialArgs,
|
||||
) -> Result<(), AuthenticatorError> {
|
||||
import_credential::<CliAuthenticatorClient, _>(args).await?;
|
||||
println!("successfully imported credential!");
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::cli::{override_config, CliAuthenticatorClient, OverrideConfig};
|
||||
use clap::Args;
|
||||
use nym_authenticator::{
|
||||
config::{default_config_directory, default_config_filepath, default_data_directory, Config},
|
||||
error::AuthenticatorError,
|
||||
};
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_client_core::cli_helpers::client_init::{
|
||||
initialise_client, CommonClientInitArgs, InitResultsWithConfig, InitialisableClient,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::{fmt::Display, fs, path::PathBuf};
|
||||
|
||||
impl InitialisableClient for CliAuthenticatorClient {
|
||||
type InitArgs = Init;
|
||||
|
||||
fn initialise_storage_paths(id: &str) -> Result<(), Self::Error> {
|
||||
fs::create_dir_all(default_data_directory(id))?;
|
||||
fs::create_dir_all(default_config_directory(id))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn default_config_path(id: &str) -> PathBuf {
|
||||
default_config_filepath(id)
|
||||
}
|
||||
|
||||
fn construct_config(init_args: &Self::InitArgs) -> Self::Config {
|
||||
override_config(
|
||||
Config::new(&init_args.common_args.id),
|
||||
OverrideConfig::from(init_args.clone()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub(crate) struct Init {
|
||||
#[command(flatten)]
|
||||
common_args: CommonClientInitArgs,
|
||||
|
||||
#[clap(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
|
||||
impl From<Init> for OverrideConfig {
|
||||
fn from(init_config: Init) -> Self {
|
||||
OverrideConfig {
|
||||
nym_apis: init_config.common_args.nym_apis,
|
||||
nyxd_urls: init_config.common_args.nyxd_urls,
|
||||
enabled_credentials_mode: init_config.common_args.enabled_credentials_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<CommonClientInitArgs> for Init {
|
||||
fn as_ref(&self) -> &CommonClientInitArgs {
|
||||
&self.common_args
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct InitResults {
|
||||
#[serde(flatten)]
|
||||
client_core: nym_client_core::init::types::InitResults,
|
||||
client_address: String,
|
||||
}
|
||||
|
||||
impl InitResults {
|
||||
fn new(res: InitResultsWithConfig<Config>) -> Self {
|
||||
Self {
|
||||
client_address: res.init_results.address.to_string(),
|
||||
client_core: res.init_results,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for InitResults {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "{}", self.client_core)?;
|
||||
write!(f, "Address of this authenticator: {}", self.client_address)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Init) -> Result<(), AuthenticatorError> {
|
||||
eprintln!("Initialising client...");
|
||||
|
||||
let output = args.output;
|
||||
let res = initialise_client::<CliAuthenticatorClient>(args, None).await?;
|
||||
|
||||
let init_results = InitResults::new(res);
|
||||
println!("{}", output.format(&init_results));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::cli::CliAuthenticatorClient;
|
||||
use nym_authenticator::error::AuthenticatorError;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_client_core::cli_helpers::client_list_gateways::{
|
||||
list_gateways, CommonClientListGatewaysArgs,
|
||||
};
|
||||
|
||||
#[derive(clap::Args)]
|
||||
pub(crate) struct Args {
|
||||
#[command(flatten)]
|
||||
common_args: CommonClientListGatewaysArgs,
|
||||
|
||||
#[arg(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
|
||||
impl AsRef<CommonClientListGatewaysArgs> for Args {
|
||||
fn as_ref(&self) -> &CommonClientListGatewaysArgs {
|
||||
&self.common_args
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> Result<(), AuthenticatorError> {
|
||||
let output = args.output;
|
||||
let res = list_gateways::<CliAuthenticatorClient, _>(args).await?;
|
||||
|
||||
println!("{}", output.format(&res));
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{CommandFactory, Parser, Subcommand};
|
||||
use log::error;
|
||||
use nym_authenticator::{
|
||||
config::{helpers::try_upgrade_config, BaseClientConfig, Config},
|
||||
error::AuthenticatorError,
|
||||
};
|
||||
use nym_bin_common::completions::{fig_generate, ArgShell};
|
||||
use nym_bin_common::{bin_info, version_checker};
|
||||
use nym_client_core::cli_helpers::client_import_credential::CommonClientImportCredentialArgs;
|
||||
use nym_client_core::cli_helpers::CliClient;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
mod add_gateway;
|
||||
mod build_info;
|
||||
mod import_credential;
|
||||
mod init;
|
||||
mod list_gateways;
|
||||
mod peer_handler;
|
||||
mod run;
|
||||
mod sign;
|
||||
mod switch_gateway;
|
||||
|
||||
pub(crate) struct CliAuthenticatorClient;
|
||||
|
||||
impl CliClient for CliAuthenticatorClient {
|
||||
const NAME: &'static str = "authenticator";
|
||||
type Error = AuthenticatorError;
|
||||
type Config = Config;
|
||||
|
||||
async fn try_upgrade_outdated_config(id: &str) -> Result<(), Self::Error> {
|
||||
try_upgrade_config(id).await
|
||||
}
|
||||
|
||||
async fn try_load_current_config(id: &str) -> Result<Self::Config, Self::Error> {
|
||||
try_load_current_config(id).await
|
||||
}
|
||||
}
|
||||
|
||||
fn pretty_build_info_static() -> &'static str {
|
||||
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
|
||||
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author = "Nymtech", version, about, long_version = pretty_build_info_static())]
|
||||
pub(crate) struct Cli {
|
||||
/// Path pointing to an env file that configures the client.
|
||||
#[arg(short, long)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
/// Flag used for disabling the printed banner in tty.
|
||||
#[arg(long)]
|
||||
pub(crate) no_banner: bool,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
/// Initialize an authenticator. Do this first!
|
||||
Init(init::Init),
|
||||
|
||||
/// Run the authenticator with the provided configuration and optionally override
|
||||
/// parameters.
|
||||
Run(run::Run),
|
||||
|
||||
/// Import a pre-generated credential
|
||||
ImportCredential(CommonClientImportCredentialArgs),
|
||||
|
||||
/// List all registered with gateways
|
||||
ListGateways(list_gateways::Args),
|
||||
|
||||
/// Add new gateway to this client
|
||||
AddGateway(add_gateway::Args),
|
||||
|
||||
/// Change the currently active gateway. Note that you must have already registered with the new gateway!
|
||||
SwitchGateway(switch_gateway::Args),
|
||||
|
||||
/// Sign to prove ownership of this authenticator
|
||||
Sign(sign::Sign),
|
||||
|
||||
/// Show build information of this binary
|
||||
BuildInfo(build_info::BuildInfo),
|
||||
|
||||
/// Generate shell completions
|
||||
Completions(ArgShell),
|
||||
|
||||
/// Generate Fig specification
|
||||
GenerateFigSpec,
|
||||
}
|
||||
|
||||
// Configuration that can be overridden.
|
||||
pub(crate) struct OverrideConfig {
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
}
|
||||
|
||||
pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
|
||||
config
|
||||
.with_optional_base_custom_env(
|
||||
BaseClientConfig::with_custom_nym_apis,
|
||||
args.nym_apis,
|
||||
nym_network_defaults::var_names::NYM_API,
|
||||
nym_config::parse_urls,
|
||||
)
|
||||
.with_optional_base_custom_env(
|
||||
BaseClientConfig::with_custom_nyxd,
|
||||
args.nyxd_urls,
|
||||
nym_network_defaults::var_names::NYXD,
|
||||
nym_config::parse_urls,
|
||||
)
|
||||
.with_optional_base(
|
||||
BaseClientConfig::with_disabled_credentials,
|
||||
args.enabled_credentials_mode.map(|b| !b),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Cli) -> Result<(), AuthenticatorError> {
|
||||
let bin_name = "nym-authenticator";
|
||||
|
||||
match args.command {
|
||||
Commands::Init(m) => init::execute(m).await?,
|
||||
Commands::Run(m) => run::execute(&m).await?,
|
||||
Commands::ImportCredential(m) => import_credential::execute(m).await?,
|
||||
Commands::ListGateways(args) => list_gateways::execute(args).await?,
|
||||
Commands::AddGateway(args) => add_gateway::execute(args).await?,
|
||||
Commands::SwitchGateway(args) => switch_gateway::execute(args).await?,
|
||||
Commands::Sign(m) => sign::execute(&m).await?,
|
||||
Commands::BuildInfo(m) => build_info::execute(m),
|
||||
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn try_load_current_config(id: &str) -> Result<Config, AuthenticatorError> {
|
||||
// try to load the config as is
|
||||
if let Ok(cfg) = Config::read_from_default_path(id) {
|
||||
return if !cfg.validate() {
|
||||
Err(AuthenticatorError::ConfigValidationFailure)
|
||||
} else {
|
||||
Ok(cfg)
|
||||
};
|
||||
}
|
||||
|
||||
// we couldn't load it - try upgrading it from older revisions
|
||||
try_upgrade_config(id).await?;
|
||||
|
||||
let config = match Config::read_from_default_path(id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
error!("Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})");
|
||||
return Err(AuthenticatorError::FailedToLoadConfig(id.to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
if !config.validate() {
|
||||
return Err(AuthenticatorError::ConfigValidationFailure);
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
// this only checks compatibility between config the binary. It does not take into consideration
|
||||
// network version. It might do so in the future.
|
||||
fn version_check(cfg: &Config) -> bool {
|
||||
let binary_version = env!("CARGO_PKG_VERSION");
|
||||
let config_version = &cfg.base.client.version;
|
||||
if binary_version == config_version {
|
||||
true
|
||||
} else {
|
||||
log::warn!(
|
||||
"The native-client binary has different version than what is specified \
|
||||
in config file! {binary_version} and {config_version}",
|
||||
);
|
||||
if version_checker::is_minor_version_compatible(binary_version, config_version) {
|
||||
log::info!(
|
||||
"but they are still semver compatible. \
|
||||
However, consider running the `upgrade` command"
|
||||
);
|
||||
true
|
||||
} else {
|
||||
log::error!(
|
||||
"and they are semver incompatible! - \
|
||||
please run the `upgrade` command before attempting `run` again"
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sdk::TaskClient;
|
||||
use nym_wireguard::peer_controller::PeerControlMessage;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub struct DummyHandler {
|
||||
peer_rx: mpsc::UnboundedReceiver<PeerControlMessage>,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl DummyHandler {
|
||||
pub fn new(
|
||||
peer_rx: mpsc::UnboundedReceiver<PeerControlMessage>,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
DummyHandler {
|
||||
peer_rx,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(mut self) {
|
||||
while !self.task_client.is_shutdown() {
|
||||
tokio::select! {
|
||||
msg = self.peer_rx.recv() => {
|
||||
if let Some(msg) = msg {
|
||||
match msg {
|
||||
PeerControlMessage::AddPeer(peer) => {
|
||||
log::info!("[DUMMY] Adding peer {:?}", peer);
|
||||
}
|
||||
PeerControlMessage::RemovePeer(key) => {
|
||||
log::info!("[DUMMY] Removing peer {:?}", key);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_ = self.task_client.recv() => {
|
||||
log::trace!("DummyHandler: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
log::debug!("DummyHandler: Exiting");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::cli::peer_handler::DummyHandler;
|
||||
use crate::cli::{override_config, OverrideConfig};
|
||||
use crate::cli::{try_load_current_config, version_check};
|
||||
use clap::Args;
|
||||
use log::error;
|
||||
use nym_authenticator::error::AuthenticatorError;
|
||||
use nym_client_core::cli_helpers::client_run::CommonClientRunArgs;
|
||||
use nym_crypto::asymmetric::x25519::KeyPair;
|
||||
use nym_task::TaskHandle;
|
||||
use nym_wireguard::WireguardGatewayData;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Run {
|
||||
#[command(flatten)]
|
||||
common_args: CommonClientRunArgs,
|
||||
}
|
||||
|
||||
impl From<Run> for OverrideConfig {
|
||||
fn from(run_config: Run) -> Self {
|
||||
OverrideConfig {
|
||||
nym_apis: None,
|
||||
nyxd_urls: run_config.common_args.nyxd_urls,
|
||||
enabled_credentials_mode: run_config.common_args.enabled_credentials_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), AuthenticatorError> {
|
||||
let mut config = try_load_current_config(&args.common_args.id).await?;
|
||||
config = override_config(config, OverrideConfig::from(args.clone()));
|
||||
log::debug!("Using config: {:#?}", config);
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
return Err(AuthenticatorError::FailedLocalVersionCheck);
|
||||
}
|
||||
|
||||
log::info!("Starting authenticator service provider");
|
||||
let (wireguard_gateway_data, peer_rx) = WireguardGatewayData::new(
|
||||
config.authenticator.clone().into(),
|
||||
Arc::new(KeyPair::new(&mut OsRng)),
|
||||
);
|
||||
let task_handler = TaskHandle::default();
|
||||
let handler = DummyHandler::new(peer_rx, task_handler.fork("peer-handler"));
|
||||
tokio::spawn(async move {
|
||||
handler.run().await;
|
||||
});
|
||||
let mut server = nym_authenticator::Authenticator::new(config, wireguard_gateway_data);
|
||||
if let Some(custom_mixnet) = &args.common_args.custom_mixnet {
|
||||
server = server.with_stored_topology(custom_mixnet)?
|
||||
}
|
||||
|
||||
server.run_service_provider().await
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::cli::{try_load_current_config, version_check};
|
||||
use clap::Args;
|
||||
use nym_authenticator::error::AuthenticatorError;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_types::helpers::ConsoleSigningOutput;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Sign {
|
||||
/// The id of the mixnode you want to sign with
|
||||
#[arg(long)]
|
||||
id: String,
|
||||
|
||||
/// Signs a transaction-specific payload, that is going to be sent to the smart contract, with your identity key
|
||||
#[arg(long)]
|
||||
contract_msg: String,
|
||||
|
||||
#[arg(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
|
||||
fn print_signed_contract_msg(
|
||||
private_key: &identity::PrivateKey,
|
||||
raw_msg: &str,
|
||||
output: OutputFormat,
|
||||
) {
|
||||
let trimmed = raw_msg.trim();
|
||||
eprintln!(">>> attempting to sign {trimmed}");
|
||||
|
||||
let Ok(decoded) = bs58::decode(trimmed).into_vec() else {
|
||||
println!("it seems you have incorrectly copied the message to sign. Make sure you didn't accidentally skip any characters");
|
||||
return;
|
||||
};
|
||||
|
||||
eprintln!(">>> decoding the message...");
|
||||
|
||||
// we don't really care about what particular information is embedded inside of it,
|
||||
// we just want to know if user correctly copied the string, i.e. whether it's a valid bs58 encoded json
|
||||
if serde_json::from_slice::<serde_json::Value>(&decoded).is_err() {
|
||||
println!("it seems you have incorrectly copied the message to sign. Make sure you didn't accidentally skip any characters");
|
||||
return;
|
||||
};
|
||||
|
||||
// if this is a valid json, it MUST be a valid string
|
||||
let decoded_string = String::from_utf8(decoded.clone()).unwrap();
|
||||
let signature = private_key.sign(&decoded).to_base58_string();
|
||||
|
||||
let sign_output = ConsoleSigningOutput::new(decoded_string, signature);
|
||||
println!("{}", output.format(&sign_output));
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Sign) -> Result<(), AuthenticatorError> {
|
||||
let config = try_load_current_config(&args.id).await?;
|
||||
|
||||
if !version_check(&config) {
|
||||
log::error!("Failed the local version check");
|
||||
return Err(AuthenticatorError::FailedLocalVersionCheck);
|
||||
}
|
||||
|
||||
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys);
|
||||
let identity_keypair = key_store.load_identity_keypair().map_err(|source| {
|
||||
AuthenticatorError::ClientCoreError(ClientCoreError::KeyStoreError {
|
||||
source: Box::new(source),
|
||||
})
|
||||
})?;
|
||||
|
||||
print_signed_contract_msg(
|
||||
identity_keypair.private_key(),
|
||||
&args.contract_msg,
|
||||
args.output,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::cli::CliAuthenticatorClient;
|
||||
use nym_authenticator::error::AuthenticatorError;
|
||||
use nym_client_core::cli_helpers::client_switch_gateway::{
|
||||
switch_gateway, CommonClientSwitchGatewaysArgs,
|
||||
};
|
||||
|
||||
#[derive(clap::Args, Clone, Debug)]
|
||||
pub struct Args {
|
||||
#[command(flatten)]
|
||||
common_args: CommonClientSwitchGatewaysArgs,
|
||||
}
|
||||
|
||||
impl AsRef<CommonClientSwitchGatewaysArgs> for Args {
|
||||
fn as_ref(&self) -> &CommonClientSwitchGatewaysArgs {
|
||||
&self.common_args
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> Result<(), AuthenticatorError> {
|
||||
switch_gateway::<CliAuthenticatorClient, _>(args).await
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use log::trace;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::error::AuthenticatorError;
|
||||
|
||||
pub async fn try_upgrade_config<P: AsRef<Path>>(_config_path: P) -> Result<(), AuthenticatorError> {
|
||||
trace!("Attempting to upgrade config");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
pub use nym_client_core::config::Config as BaseClientConfig;
|
||||
use nym_client_core::{cli_helpers::CliClientConfig, config::disk_persistence::CommonClientPaths};
|
||||
use nym_config::{
|
||||
must_get_home, save_formatted_config_to_file, NymConfigTemplate, OptionalSet,
|
||||
DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR,
|
||||
};
|
||||
use nym_network_defaults::WG_PORT;
|
||||
use nym_service_providers_common::DEFAULT_SERVICE_PROVIDERS_DIR;
|
||||
pub use persistence::AuthenticatorPaths;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
io,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
use template::CONFIG_TEMPLATE;
|
||||
|
||||
pub mod helpers;
|
||||
pub mod persistence;
|
||||
pub mod template;
|
||||
|
||||
const DEFAULT_AUTHENTICATOR_DIR: &str = "authenticator";
|
||||
|
||||
/// Derive default path to authenticator's config directory.
|
||||
/// It should get resolved to `$HOME/.nym/service-providers/authenticator/<id>/config`
|
||||
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_SERVICE_PROVIDERS_DIR)
|
||||
.join(DEFAULT_AUTHENTICATOR_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_CONFIG_DIR)
|
||||
}
|
||||
|
||||
/// Derive default path to authenticator's config file.
|
||||
/// It should get resolved to `$HOME/.nym/service-providers/authenticator/<id>/config/config.toml`
|
||||
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
|
||||
}
|
||||
|
||||
/// Derive default path to authenticator's data directory where files, such as keys, are stored.
|
||||
/// It should get resolved to `$HOME/.nym/service-providers/authenticator/<id>/data`
|
||||
pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_SERVICE_PROVIDERS_DIR)
|
||||
.join(DEFAULT_AUTHENTICATOR_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_DATA_DIR)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
#[serde(flatten)]
|
||||
pub base: BaseClientConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub authenticator: Authenticator,
|
||||
|
||||
pub storage_paths: AuthenticatorPaths,
|
||||
|
||||
pub logging: LoggingSettings,
|
||||
}
|
||||
|
||||
impl NymConfigTemplate for Config {
|
||||
fn template(&self) -> &'static str {
|
||||
CONFIG_TEMPLATE
|
||||
}
|
||||
}
|
||||
|
||||
impl CliClientConfig for Config {
|
||||
fn common_paths(&self) -> &CommonClientPaths {
|
||||
&self.storage_paths.common_paths
|
||||
}
|
||||
|
||||
fn core_config(&self) -> &BaseClientConfig {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn default_store_location(&self) -> PathBuf {
|
||||
self.default_location()
|
||||
}
|
||||
|
||||
fn save_to<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
||||
save_formatted_config_to_file(self, path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new<S: AsRef<str>>(id: S) -> Self {
|
||||
Config {
|
||||
base: BaseClientConfig::new(id.as_ref(), env!("CARGO_PKG_VERSION")),
|
||||
authenticator: Default::default(),
|
||||
storage_paths: AuthenticatorPaths::new_base(default_data_directory(id.as_ref())),
|
||||
logging: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn with_data_directory<P: AsRef<Path>>(mut self, data_directory: P) -> Self {
|
||||
self.storage_paths = AuthenticatorPaths::new_base(data_directory);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
nym_config::read_config_from_toml_file(path)
|
||||
}
|
||||
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
Self::read_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
|
||||
pub fn default_location(&self) -> PathBuf {
|
||||
default_config_filepath(&self.base.client.id)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn save_to_default_location(&self) -> io::Result<()> {
|
||||
let config_save_location: PathBuf = self.default_location();
|
||||
save_formatted_config_to_file(self, config_save_location)
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> bool {
|
||||
// no other sections have explicit requirements (yet)
|
||||
self.base.validate()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn set_no_poisson_process(&mut self) {
|
||||
self.base.set_no_poisson_process()
|
||||
}
|
||||
|
||||
// poor man's 'builder' method
|
||||
#[allow(unused)]
|
||||
pub fn with_base<F, T>(mut self, f: F, val: T) -> Self
|
||||
where
|
||||
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
|
||||
{
|
||||
self.base = f(self.base, val);
|
||||
self
|
||||
}
|
||||
|
||||
// helper methods to use `OptionalSet` trait. Those are defined due to very... ehm. 'specific' structure of this config
|
||||
// (plz, lets refactor it)
|
||||
pub fn with_optional_base<F, T>(mut self, f: F, val: Option<T>) -> Self
|
||||
where
|
||||
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
|
||||
{
|
||||
self.base = self.base.with_optional(f, val);
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn with_optional_base_env<F, T>(mut self, f: F, val: Option<T>, env_var: &str) -> Self
|
||||
where
|
||||
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
|
||||
T: FromStr,
|
||||
<T as FromStr>::Err: std::fmt::Debug,
|
||||
{
|
||||
self.base = self.base.with_optional_env(f, val, env_var);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_optional_base_custom_env<F, T, G>(
|
||||
mut self,
|
||||
f: F,
|
||||
val: Option<T>,
|
||||
env_var: &str,
|
||||
parser: G,
|
||||
) -> Self
|
||||
where
|
||||
F: Fn(BaseClientConfig, T) -> BaseClientConfig,
|
||||
G: Fn(&str) -> T,
|
||||
{
|
||||
self.base = self.base.with_optional_custom_env(f, val, env_var, parser);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct Authenticator {
|
||||
/// Socket address this node will use for binding its wireguard interface.
|
||||
/// default: `0.0.0.0:51822`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
/// Private IP address of the wireguard gateway.
|
||||
/// default: `10.1.0.1`
|
||||
pub private_ip: IpAddr,
|
||||
|
||||
/// 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,
|
||||
|
||||
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard.
|
||||
/// The maximum value for IPv4 is 32 and for IPv6 is 128
|
||||
pub private_network_prefix: u8,
|
||||
}
|
||||
|
||||
impl Default for Authenticator {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 51822),
|
||||
private_ip: IpAddr::V4(Ipv4Addr::new(10, 1, 0, 1)),
|
||||
announced_port: WG_PORT,
|
||||
private_network_prefix: 16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Authenticator> for nym_wireguard_types::Config {
|
||||
fn from(value: Authenticator) -> Self {
|
||||
nym_wireguard_types::Config {
|
||||
bind_address: value.bind_address,
|
||||
private_ip: value.private_ip,
|
||||
announced_port: value.announced_port,
|
||||
private_network_prefix: value.private_network_prefix,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_client_core::config::disk_persistence::CommonClientPaths;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub const DEFAULT_DESCRIPTION_FILENAME: &str = "description.toml";
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
|
||||
pub struct AuthenticatorPaths {
|
||||
#[serde(flatten)]
|
||||
pub common_paths: CommonClientPaths,
|
||||
|
||||
/// Location of the file containing our description
|
||||
pub authenticator_description: PathBuf,
|
||||
}
|
||||
|
||||
impl AuthenticatorPaths {
|
||||
pub fn new_base<P: AsRef<Path>>(base_data_directory: P) -> Self {
|
||||
let base_dir = base_data_directory.as_ref();
|
||||
|
||||
Self {
|
||||
common_paths: CommonClientPaths::new_base(base_dir),
|
||||
authenticator_description: base_dir.join(DEFAULT_DESCRIPTION_FILENAME),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) const CONFIG_TEMPLATE: &str =
|
||||
// While using normal toml marshalling would have been way simpler with less overhead,
|
||||
// I think it's useful to have comments attached to the saved config file to explain behaviour of
|
||||
// particular fields.
|
||||
// Note: any changes to the template must be reflected in the appropriate structs.
|
||||
r#"
|
||||
# This is a TOML config file.
|
||||
# For more information, see https://github.com/toml-lang/toml
|
||||
|
||||
##### main base client config options #####
|
||||
|
||||
[client]
|
||||
# Version of the client for which this configuration was created.
|
||||
version = '{{ client.version }}'
|
||||
|
||||
# Human readable ID of this particular client.
|
||||
id = '{{ client.id }}'
|
||||
|
||||
# Indicates whether this client is running in a disabled credentials mode, thus attempting
|
||||
# to claim bandwidth without presenting bandwidth credentials.
|
||||
disabled_credentials_mode = {{ client.disabled_credentials_mode }}
|
||||
|
||||
# Addresses to nyxd validators via which the client can communicate with the chain.
|
||||
nyxd_urls = [
|
||||
{{#each client.nyxd_urls }}
|
||||
'{{this}}',
|
||||
{{/each}}
|
||||
]
|
||||
|
||||
# Addresses to APIs running on validator from which the client gets the view of the network.
|
||||
nym_api_urls = [
|
||||
{{#each client.nym_api_urls }}
|
||||
'{{this}}',
|
||||
{{/each}}
|
||||
]
|
||||
|
||||
[storage_paths]
|
||||
|
||||
# Path to file containing private identity key.
|
||||
keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}'
|
||||
|
||||
# Path to file containing public identity key.
|
||||
keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}'
|
||||
|
||||
# Path to file containing private encryption key.
|
||||
keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key_file }}'
|
||||
|
||||
# Path to file containing public encryption key.
|
||||
keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}'
|
||||
|
||||
# Path to file containing key used for encrypting and decrypting the content of an
|
||||
# acknowledgement so that nobody besides the client knows which packet it refers to.
|
||||
keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}'
|
||||
|
||||
# Path to the database containing bandwidth credentials
|
||||
credentials_database = '{{ storage_paths.credentials_database }}'
|
||||
|
||||
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
|
||||
reply_surb_database = '{{ storage_paths.reply_surb_database }}'
|
||||
|
||||
# Path to the file containing information about gateways used by this client,
|
||||
# i.e. details such as their public keys, owner addresses or the network information.
|
||||
gateway_registrations = '{{ storage_paths.gateway_registrations }}'
|
||||
|
||||
# Location of the file containing our allow.list
|
||||
allowed_list_location = '{{ storage_paths.allowed_list_location }}'
|
||||
|
||||
# Location of the file containing our unknown.list
|
||||
unknown_list_location = '{{ storage_paths.unknown_list_location }}'
|
||||
|
||||
# Path to file containing description of this authenticator.
|
||||
authenticator_description = '{{ storage_paths.authenticator_description }}'
|
||||
|
||||
|
||||
##### logging configuration options #####
|
||||
|
||||
[logging]
|
||||
|
||||
# TODO
|
||||
|
||||
|
||||
##### debug configuration options #####
|
||||
# The following options should not be modified unless you know EXACTLY what you are doing
|
||||
# as if set incorrectly, they may impact your anonymity.
|
||||
|
||||
[debug]
|
||||
|
||||
[debug.traffic]
|
||||
average_packet_delay = '{{ debug.traffic.average_packet_delay }}'
|
||||
message_sending_average_delay = '{{ debug.traffic.message_sending_average_delay }}'
|
||||
|
||||
[debug.acknowledgements]
|
||||
average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}'
|
||||
|
||||
[debug.cover_traffic]
|
||||
loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}'
|
||||
|
||||
"#;
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use ipnetwork::IpNetworkError;
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_id::NymIdError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum AuthenticatorError {
|
||||
#[error("client-core error: {0}")]
|
||||
ClientCoreError(#[from] ClientCoreError),
|
||||
|
||||
// TODO: add more details here
|
||||
#[error("failed to validate the loaded config")]
|
||||
ConfigValidationFailure,
|
||||
|
||||
#[error("the entity wrapping the network requester has disconnected")]
|
||||
DisconnectedParent,
|
||||
|
||||
#[error("received empty packet")]
|
||||
EmptyPacket,
|
||||
|
||||
#[error("failed local version check, client and config mismatch")]
|
||||
FailedLocalVersionCheck,
|
||||
|
||||
#[error("failed to connect to mixnet: {source}")]
|
||||
FailedToConnectToMixnet { source: nym_sdk::Error },
|
||||
|
||||
#[error("failed to deserialize tagged packet: {source}")]
|
||||
FailedToDeserializeTaggedPacket { source: bincode::Error },
|
||||
|
||||
#[error("failed to load configuration file: {0}")]
|
||||
FailedToLoadConfig(String),
|
||||
|
||||
#[error("failed to send packet to mixnet: {source}")]
|
||||
FailedToSendPacketToMixnet { source: nym_sdk::Error },
|
||||
|
||||
#[error("failed to serialize response packet: {source}")]
|
||||
FailedToSerializeResponsePacket { source: Box<bincode::ErrorKind> },
|
||||
|
||||
#[error("failed to setup mixnet client: {source}")]
|
||||
FailedToSetupMixnetClient { source: nym_sdk::Error },
|
||||
|
||||
#[error("internal error: {0}")]
|
||||
InternalError(String),
|
||||
|
||||
#[error("received packet has an invalid version: {0}")]
|
||||
InvalidPacketVersion(u8),
|
||||
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[error("{0}")]
|
||||
IpNetworkError(#[from] IpNetworkError),
|
||||
|
||||
#[error("mac does not verify")]
|
||||
MacVerificationFailure,
|
||||
|
||||
#[error("no more space in the network")]
|
||||
NoFreeIp,
|
||||
|
||||
#[error(transparent)]
|
||||
NymIdError(#[from] NymIdError),
|
||||
|
||||
#[error("registration is not in progress for the given key")]
|
||||
RegistrationNotInProgress,
|
||||
|
||||
#[error("internal data corruption: {0}")]
|
||||
InternalDataCorruption(String),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, AuthenticatorError>;
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub use authenticator::{Authenticator, OnStartData};
|
||||
pub use config::Config;
|
||||
|
||||
pub mod authenticator;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod mixnet_client;
|
||||
pub mod mixnet_listener;
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod cli;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
use clap::Parser;
|
||||
|
||||
let args = cli::Cli::parse();
|
||||
nym_bin_common::logging::setup_logging();
|
||||
nym_network_defaults::setup_env(args.config_env_file.as_ref());
|
||||
|
||||
if !args.no_banner {
|
||||
nym_bin_common::logging::maybe_print_banner(clap::crate_name!(), clap::crate_version!());
|
||||
}
|
||||
|
||||
cli::execute(args).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_client_core::{config::disk_persistence::CommonClientPaths, TopologyProvider};
|
||||
use nym_sdk::{GatewayTransceiver, NymNetworkDetails};
|
||||
use nym_task::TaskClient;
|
||||
|
||||
use crate::{config::BaseClientConfig, error::AuthenticatorError};
|
||||
|
||||
// Helper function to create the mixnet client.
|
||||
// This is NOT in the SDK since we don't want to expose any of the client-core config types.
|
||||
// We could however consider moving it to a crate in common in the future.
|
||||
// TODO: refactor this function and its arguments
|
||||
pub(crate) async fn create_mixnet_client(
|
||||
config: &BaseClientConfig,
|
||||
shutdown: TaskClient,
|
||||
custom_transceiver: Option<Box<dyn GatewayTransceiver + Send + Sync>>,
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
wait_for_gateway: bool,
|
||||
paths: &CommonClientPaths,
|
||||
) -> Result<nym_sdk::mixnet::MixnetClient, AuthenticatorError> {
|
||||
let debug_config = config.debug;
|
||||
|
||||
let storage_paths = nym_sdk::mixnet::StoragePaths::from(paths.clone());
|
||||
|
||||
let mut client_builder =
|
||||
nym_sdk::mixnet::MixnetClientBuilder::new_with_default_storage(storage_paths)
|
||||
.await
|
||||
.map_err(|err| AuthenticatorError::FailedToSetupMixnetClient { source: err })?
|
||||
.network_details(NymNetworkDetails::new_from_env())
|
||||
.debug_config(debug_config)
|
||||
.custom_shutdown(shutdown)
|
||||
.with_wait_for_gateway(wait_for_gateway);
|
||||
if !config.get_disabled_credentials_mode() {
|
||||
client_builder = client_builder.enable_credentials_mode();
|
||||
}
|
||||
if let Some(gateway_transceiver) = custom_transceiver {
|
||||
client_builder = client_builder.custom_gateway_transceiver(gateway_transceiver);
|
||||
}
|
||||
if let Some(topology_provider) = custom_topology_provider {
|
||||
client_builder = client_builder.custom_topology_provider(topology_provider);
|
||||
}
|
||||
|
||||
let mixnet_client = client_builder
|
||||
.build()
|
||||
.map_err(|err| AuthenticatorError::FailedToSetupMixnetClient { source: err })?;
|
||||
|
||||
mixnet_client
|
||||
.connect_to_mixnet()
|
||||
.await
|
||||
.map_err(|err| AuthenticatorError::FailedToConnectToMixnet { source: err })
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use crate::error::AuthenticatorError;
|
||||
use futures::StreamExt;
|
||||
use ipnetwork::IpNetwork;
|
||||
use nym_authenticator_requests::v1::{
|
||||
self,
|
||||
request::{AuthenticatorRequest, AuthenticatorRequestData},
|
||||
response::AuthenticatorResponse,
|
||||
};
|
||||
use nym_sdk::mixnet::{InputMessage, MixnetMessageSender, Recipient, TransmissionLane};
|
||||
use nym_sphinx::receiver::ReconstructedMessage;
|
||||
use nym_task::TaskHandle;
|
||||
use nym_wireguard::WireguardGatewayData;
|
||||
use nym_wireguard_types::{
|
||||
registration::{PendingRegistrations, PrivateIPs, RegistrationData},
|
||||
GatewayClient, InitMessage, PeerPublicKey,
|
||||
};
|
||||
use rand::{prelude::IteratorRandom, thread_rng};
|
||||
use tokio_stream::wrappers::IntervalStream;
|
||||
|
||||
use crate::{config::Config, error::*};
|
||||
|
||||
type AuthenticatorHandleResult = Result<AuthenticatorResponse>;
|
||||
const DEFAULT_REGISTRATION_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minute
|
||||
|
||||
pub(crate) struct MixnetListener {
|
||||
// The configuration for the mixnet listener
|
||||
pub(crate) config: Config,
|
||||
|
||||
// The mixnet client that we use to send and receive packets from the mixnet
|
||||
pub(crate) mixnet_client: nym_sdk::mixnet::MixnetClient,
|
||||
|
||||
// The task handle for the main loop
|
||||
pub(crate) task_handle: TaskHandle,
|
||||
|
||||
// Registrations awaiting confirmation
|
||||
pub(crate) registration_in_progres: Arc<PendingRegistrations>,
|
||||
|
||||
pub(crate) wireguard_gateway_data: WireguardGatewayData,
|
||||
|
||||
pub(crate) free_private_network_ips: Arc<PrivateIPs>,
|
||||
|
||||
pub(crate) timeout_check_interval: IntervalStream,
|
||||
}
|
||||
|
||||
impl MixnetListener {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
private_ip_network: IpNetwork,
|
||||
wireguard_gateway_data: WireguardGatewayData,
|
||||
mixnet_client: nym_sdk::mixnet::MixnetClient,
|
||||
task_handle: TaskHandle,
|
||||
) -> Self {
|
||||
let timeout_check_interval =
|
||||
IntervalStream::new(tokio::time::interval(DEFAULT_REGISTRATION_TIMEOUT_CHECK));
|
||||
MixnetListener {
|
||||
config,
|
||||
mixnet_client,
|
||||
task_handle,
|
||||
registration_in_progres: Default::default(),
|
||||
wireguard_gateway_data,
|
||||
free_private_network_ips: Arc::new(
|
||||
private_ip_network.iter().map(|ip| (ip, None)).collect(),
|
||||
),
|
||||
timeout_check_interval,
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_from_registry(
|
||||
&self,
|
||||
remote_public: &PeerPublicKey,
|
||||
gateway_client: &GatewayClient,
|
||||
) -> Result<()> {
|
||||
self.wireguard_gateway_data
|
||||
.remove_peer(gateway_client)
|
||||
.map_err(|err| {
|
||||
AuthenticatorError::InternalError(format!("could not remove peer: {:?}", err))
|
||||
})?;
|
||||
self.wireguard_gateway_data
|
||||
.client_registry()
|
||||
.remove(remote_public);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_stale_registrations(&self) -> Result<()> {
|
||||
for reg in self.registration_in_progres.iter().map(|reg| reg.clone()) {
|
||||
let mut ip = self
|
||||
.free_private_network_ips
|
||||
.get_mut(®.gateway_data.private_ip)
|
||||
.ok_or(AuthenticatorError::InternalDataCorruption(format!(
|
||||
"IP {} should be present",
|
||||
reg.gateway_data.private_ip
|
||||
)))?;
|
||||
|
||||
let timestamp = ip.ok_or(AuthenticatorError::InternalDataCorruption(format!(
|
||||
"timestamp should be set for IP {}",
|
||||
ip.key()
|
||||
)))?;
|
||||
let duration = SystemTime::now().duration_since(timestamp).map_err(|_| {
|
||||
AuthenticatorError::InternalDataCorruption(
|
||||
"set timestamp shouldn't have been set in the future".to_string(),
|
||||
)
|
||||
})?;
|
||||
if duration > DEFAULT_REGISTRATION_TIMEOUT_CHECK {
|
||||
*ip = None;
|
||||
self.registration_in_progres
|
||||
.remove(®.gateway_data.pub_key());
|
||||
log::debug!(
|
||||
"Removed stale registration of {}",
|
||||
reg.gateway_data.pub_key()
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_initial_request(
|
||||
&mut self,
|
||||
init_message: InitMessage,
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
) -> AuthenticatorHandleResult {
|
||||
let remote_public = init_message.pub_key();
|
||||
let nonce: u64 = fastrand::u64(..);
|
||||
if let Some(registration_data) = self.registration_in_progres.get(&remote_public) {
|
||||
return Ok(AuthenticatorResponse::new_pending_registration_success(
|
||||
registration_data.value().clone(),
|
||||
request_id,
|
||||
reply_to,
|
||||
));
|
||||
}
|
||||
let gateway_client_opt = if let Some(gateway_client) = self
|
||||
.wireguard_gateway_data
|
||||
.client_registry()
|
||||
.get(&remote_public)
|
||||
{
|
||||
let mut private_ip_ref = self
|
||||
.free_private_network_ips
|
||||
.get_mut(&gateway_client.private_ip)
|
||||
.ok_or(AuthenticatorError::InternalError(String::from(
|
||||
"could not find private IP",
|
||||
)))?;
|
||||
*private_ip_ref = None;
|
||||
Some(gateway_client.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(gateway_client) = gateway_client_opt {
|
||||
self.remove_from_registry(&remote_public, &gateway_client)?;
|
||||
}
|
||||
let mut private_ip_ref = self
|
||||
.free_private_network_ips
|
||||
.iter_mut()
|
||||
.filter(|r| r.is_none())
|
||||
.choose(&mut thread_rng())
|
||||
.ok_or(AuthenticatorError::NoFreeIp)?;
|
||||
// mark it as used, even though it's not final
|
||||
*private_ip_ref = Some(SystemTime::now());
|
||||
let gateway_data = GatewayClient::new(
|
||||
self.wireguard_gateway_data.keypair().private_key(),
|
||||
remote_public.inner(),
|
||||
*private_ip_ref.key(),
|
||||
nonce,
|
||||
);
|
||||
let registration_data = RegistrationData {
|
||||
nonce,
|
||||
gateway_data,
|
||||
wg_port: self.config.authenticator.announced_port,
|
||||
};
|
||||
self.registration_in_progres
|
||||
.insert(remote_public, registration_data.clone());
|
||||
|
||||
Ok(AuthenticatorResponse::new_pending_registration_success(
|
||||
registration_data,
|
||||
request_id,
|
||||
reply_to,
|
||||
))
|
||||
}
|
||||
|
||||
fn on_final_request(
|
||||
&mut self,
|
||||
gateway_client: GatewayClient,
|
||||
request_id: u64,
|
||||
reply_to: Recipient,
|
||||
) -> AuthenticatorHandleResult {
|
||||
let registration_data = self
|
||||
.registration_in_progres
|
||||
.get(&gateway_client.pub_key())
|
||||
.ok_or(AuthenticatorError::RegistrationNotInProgress)?
|
||||
.value()
|
||||
.clone();
|
||||
|
||||
if gateway_client
|
||||
.verify(
|
||||
self.wireguard_gateway_data.keypair().private_key(),
|
||||
registration_data.nonce,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
self.wireguard_gateway_data
|
||||
.add_peer(&gateway_client)
|
||||
.map_err(|err| {
|
||||
AuthenticatorError::InternalError(format!("could not add peer: {:?}", err))
|
||||
})?;
|
||||
self.registration_in_progres
|
||||
.remove(&gateway_client.pub_key());
|
||||
self.wireguard_gateway_data
|
||||
.client_registry()
|
||||
.insert(gateway_client.pub_key(), gateway_client);
|
||||
|
||||
Ok(AuthenticatorResponse::new_registered(reply_to, request_id))
|
||||
} else {
|
||||
Err(AuthenticatorError::MacVerificationFailure)
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_reconstructed_message(
|
||||
&mut self,
|
||||
reconstructed: ReconstructedMessage,
|
||||
) -> AuthenticatorHandleResult {
|
||||
log::debug!(
|
||||
"Received message with sender_tag: {:?}",
|
||||
reconstructed.sender_tag
|
||||
);
|
||||
|
||||
let request = match deserialize_request(&reconstructed) {
|
||||
Err(AuthenticatorError::InvalidPacketVersion(version)) => {
|
||||
return self.on_version_mismatch(version, &reconstructed);
|
||||
}
|
||||
req => req,
|
||||
}?;
|
||||
|
||||
match request.data {
|
||||
AuthenticatorRequestData::Initial(init_msg) => {
|
||||
self.on_initial_request(init_msg, request.request_id, request.reply_to)
|
||||
}
|
||||
AuthenticatorRequestData::Final(client) => {
|
||||
self.on_final_request(client, request.request_id, request.reply_to)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_version_mismatch(
|
||||
&self,
|
||||
version: u8,
|
||||
_reconstructed: &ReconstructedMessage,
|
||||
) -> AuthenticatorHandleResult {
|
||||
// If it's possible to parse, do so and return back a response, otherwise just drop
|
||||
Err(AuthenticatorError::InvalidPacketVersion(version))
|
||||
}
|
||||
|
||||
// When an incoming mixnet message triggers a response that we send back.
|
||||
async fn handle_response(&self, response: AuthenticatorResponse) -> Result<()> {
|
||||
let recipient = response.recipient();
|
||||
|
||||
let response_packet = response.to_bytes().map_err(|err| {
|
||||
log::error!("Failed to serialize response packet");
|
||||
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
|
||||
})?;
|
||||
|
||||
let input_message =
|
||||
InputMessage::new_regular(recipient, response_packet, TransmissionLane::General, None);
|
||||
self.mixnet_client
|
||||
.send(input_message)
|
||||
.await
|
||||
.map_err(|err| AuthenticatorError::FailedToSendPacketToMixnet { source: err })
|
||||
}
|
||||
|
||||
pub(crate) async fn run(mut self) -> Result<()> {
|
||||
let mut task_client = self.task_handle.fork("main_loop");
|
||||
|
||||
while !task_client.is_shutdown() {
|
||||
tokio::select! {
|
||||
_ = task_client.recv() => {
|
||||
log::debug!("Authenticator [main loop]: received shutdown");
|
||||
},
|
||||
_ = self.timeout_check_interval.next() => {
|
||||
if let Err(e) = self.remove_stale_registrations() {
|
||||
log::error!("Could not clear stale registrations. The registration process might get jammed soon - {:?}", e);
|
||||
}
|
||||
}
|
||||
msg = self.mixnet_client.next() => {
|
||||
if let Some(msg) = msg {
|
||||
match self.on_reconstructed_message(msg).await {
|
||||
Ok(response) => {
|
||||
if let Err(err) = self.handle_response(response).await {
|
||||
log::error!("Mixnet listener failed to handle response: {err}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Error handling reconstructed mixnet message: {err}");
|
||||
}
|
||||
|
||||
};
|
||||
} else {
|
||||
log::trace!("Authenticator [main loop]: stopping since channel closed");
|
||||
break;
|
||||
};
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
log::debug!("Authenticator: stopping");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result<AuthenticatorRequest> {
|
||||
let request_version = *reconstructed
|
||||
.message
|
||||
.first()
|
||||
.ok_or(AuthenticatorError::EmptyPacket)?;
|
||||
|
||||
// Check version of the request and convert to the latest version if necessary
|
||||
match request_version {
|
||||
1 => v1::request::AuthenticatorRequest::from_reconstructed_message(reconstructed)
|
||||
.map_err(|err| AuthenticatorError::FailedToDeserializeTaggedPacket { source: err }),
|
||||
_ => {
|
||||
log::info!("Received packet with invalid version: v{request_version}");
|
||||
Err(AuthenticatorError::InvalidPacketVersion(request_version))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user