diff --git a/nym-api/nym-api-requests/src/models.rs b/nym-api/nym-api-requests/src/models.rs index c9c076896e..c187dea2f6 100644 --- a/nym-api/nym-api-requests/src/models.rs +++ b/nym-api/nym-api-requests/src/models.rs @@ -605,6 +605,9 @@ pub struct NymNodeDescription { #[serde(default)] pub ip_packet_router: Option, + #[serde(default)] + pub authenticator: Option, + // for now we only care about their ws/wss situation, nothing more pub mixnet_websockets: WebSockets, } @@ -639,6 +642,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, diff --git a/nym-api/src/node_describe_cache/mod.rs b/nym-api/src/node_describe_cache/mod.rs index 7518a07402..2ac61af3b9 100644 --- a/nym-api/src/node_describe_cache/mod.rs +++ b/nym-api/src/node_describe_cache/mod.rs @@ -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_config::defaults::{mainnet, DEFAULT_NYM_NODE_HTTP_PORT}; use nym_contracts_common::IdentityKey; @@ -186,12 +186,21 @@ async fn get_gateway_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, }; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/authenticator/mod.rs b/nym-node/nym-node-http-api/src/router/api/v1/authenticator/mod.rs new file mode 100644 index 0000000000..ba86dfd272 --- /dev/null +++ b/nym-node/nym-node-http-api/src/router/api/v1/authenticator/mod.rs @@ -0,0 +1,23 @@ +// Copyright 2024 - Nym Technologies SA +// 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, +} + +pub(crate) fn routes(config: Config) -> Router { + Router::new().route( + "/", + get({ + let authenticator_details = config.details; + move |query| root::root_authenticator(authenticator_details, query) + }), + ) +} diff --git a/nym-node/nym-node-http-api/src/router/api/v1/authenticator/root.rs b/nym-node/nym-node-http-api/src/router/api/v1/authenticator/root.rs new file mode 100644 index 0000000000..9fb2104206 --- /dev/null +++ b/nym-node/nym-node-http-api/src/router/api/v1/authenticator/root.rs @@ -0,0 +1,33 @@ +// Copyright 2024 - Nym Technologies SA +// 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, + Query(output): Query, +) -> Result { + let details = details.ok_or(StatusCode::NOT_IMPLEMENTED)?; + let output = output.output.unwrap_or_default(); + Ok(output.to_response(details)) +} + +pub type AuthenticatorResponse = FormattedResponse; diff --git a/nym-node/nym-node-http-api/src/router/api/v1/mod.rs b/nym-node/nym-node-http-api/src/router/api/v1/mod.rs index c84a489ced..f25b7cc801 100644 --- a/nym-node/nym-node-http-api/src/router/api/v1/mod.rs +++ b/nym-node/nym-node-http-api/src/router/api/v1/mod.rs @@ -6,6 +6,7 @@ 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; @@ -23,6 +24,7 @@ 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) -> Router { @@ -39,6 +41,10 @@ pub(super) fn routes(config: Config) -> Router { 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()) } diff --git a/nym-node/nym-node-http-api/src/router/mod.rs b/nym-node/nym-node-http-api/src/router/mod.rs index 973d1a9505..5721274174 100644 --- a/nym-node/nym-node-http-api/src/router/mod.rs +++ b/nym-node/nym-node-http-api/src/router/mod.rs @@ -8,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; @@ -53,6 +54,7 @@ impl Config { mixnode: Default::default(), network_requester: Default::default(), ip_packet_router: Default::default(), + authenticator: Default::default(), }, }, } @@ -147,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 { diff --git a/nym-node/nym-node-requests/src/api/client.rs b/nym-node/nym-node-requests/src/api/client.rs index 0a4ad38aec..7600886588 100644 --- a/nym-node/nym-node-requests/src/api/client.rs +++ b/nym-node/nym-node-requests/src/api/client.rs @@ -9,6 +9,7 @@ use async_trait::async_trait; use nym_bin_common::build_information::BinaryBuildInformationOwned; use nym_http_api_client::{ApiClient, HttpClientError}; +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; @@ -63,6 +64,11 @@ pub trait NymNodeApiClientExt: ApiClient { self.get_json_from(routes::api::v1::ip_packet_router_absolute()) .await } + + async fn get_authenticator(&self) -> Result { + self.get_json_from(routes::api::v1::authenticator_absolute()) + .await + } } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] diff --git a/nym-node/nym-node-requests/src/api/v1/authenticator/mod.rs b/nym-node/nym-node-requests/src/api/v1/authenticator/mod.rs new file mode 100644 index 0000000000..5c45219d84 --- /dev/null +++ b/nym-node/nym-node-requests/src/api/v1/authenticator/mod.rs @@ -0,0 +1,4 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod models; diff --git a/nym-node/nym-node-requests/src/api/v1/authenticator/models.rs b/nym-node/nym-node-requests/src/api/v1/authenticator/models.rs new file mode 100644 index 0000000000..dafbdd9003 --- /dev/null +++ b/nym-node/nym-node-requests/src/api/v1/authenticator/models.rs @@ -0,0 +1,18 @@ +// Copyright 2024 - Nym Technologies SA +// 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, +} diff --git a/nym-node/nym-node-requests/src/api/v1/mod.rs b/nym-node/nym-node-requests/src/api/v1/mod.rs index 1c6e698940..9705e4e4eb 100644 --- a/nym-node/nym-node-requests/src/api/v1/mod.rs +++ b/nym-node/nym-node-requests/src/api/v1/mod.rs @@ -1,6 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +pub mod authenticator; pub mod gateway; pub mod health; pub mod ip_packet_router; diff --git a/nym-node/nym-node-requests/src/lib.rs b/nym-node/nym-node-requests/src/lib.rs index eb59b0a280..30e2036343 100644 --- a/nym-node/nym-node-requests/src/lib.rs +++ b/nym-node/nym-node-requests/src/lib.rs @@ -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 { diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index 614a5849b4..d026a7bd1b 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -106,6 +106,9 @@ pub struct ExitGatewayData { ipr_ed25519: ed25519::PublicKey, ipr_x25519: x25519::PublicKey, + + auth_ed25519: ed25519::PublicKey, + auth_x25519: x25519::PublicKey, } impl ExitGatewayData { @@ -236,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, }) } } @@ -411,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() } @@ -575,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, @@ -594,6 +625,7 @@ impl NymNode { .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);