feat: key rotation (#5777)
* wip * wip: wrap node's sphinx key with a manager * wip: choosing correct key for packet processing * further propagation of key rotation information * attaching key rotation information to reply surbs * added basic key rotation information to mixnet contract * wip: introducing cached queries for key rotation info from nym api * unified nym-api contract cache refreshing * finish packet decoding * multi api client + retrieving rotation id * rotating sphinx key files * logic for migrating config file * wip: putting new sphinx keys to self described endpoints * processing loop of KeyRotationController * fixed sphinx key loading * rotating bloomfilters * wired up KeyRotationController * flushing bloomfilters to disk and loading * most of nym-node changes * post rebase fixes * fixes due to backwards compatible hostkeys * split http state.rs file * dont use deprecated fields * fixed backwards compatible deserialisation of host information * split up node describe cache * added a dedicated CacheRefresher listener to perform full refresh outside the set interval * controlling announced sphinx keys within nym-api * retrieving rotation id when pulling topology * split nym-nodes http handlers * v2 nym-api endpoints to retrieve nodes with additional metadata information * bug fixes... * additional bugfixes and guards against stuck epoch * testnet manager: set first nym-api as the rewarder * fixed host information deserialisation * fixed panic during first key rotation * post rebase fixes * clippy * more guards against stuck epochs * added helper method to reset node's sphinx key * instantiate mixnet contract with custom key rotation validity * additional bugfixes and debugging nym-api deadlock * passing shutdown to nym apis client * remove dead test * post rebasing fixes * missing MixnetQueryClient variants * remove usage of deprecated methods in sdk example * fix: incorrect method signature * post rebasing fixes * attempt to retrieve key rotation id before doing any config migration work * ignore tests relying on networking behaviour * allow networking failures in certain tests
This commit is contained in:
committed by
GitHub
parent
adbe0392ca
commit
d8c84cc4d6
@@ -1,38 +1,4 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::NymNodeError;
|
||||
use crate::node::http::api::api_requests;
|
||||
use crate::node::http::error::NymNodeHttpError;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_node_requests::api::SignedHostInformation;
|
||||
|
||||
pub mod system_info;
|
||||
|
||||
pub(crate) fn sign_host_details(
|
||||
config: &Config,
|
||||
x22519_sphinx: &x25519::PublicKey,
|
||||
x25519_noise: &x25519::PublicKey,
|
||||
ed22519_identity: &ed25519::KeyPair,
|
||||
) -> Result<SignedHostInformation, NymNodeError> {
|
||||
let x25519_noise = if config.mixnet.debug.unsafe_disable_noise {
|
||||
None
|
||||
} else {
|
||||
Some(*x25519_noise)
|
||||
};
|
||||
|
||||
let host_info = api_requests::v1::node::models::HostInformation {
|
||||
ip_address: config.host.public_ips.clone(),
|
||||
hostname: config.host.hostname.clone(),
|
||||
keys: api_requests::v1::node::models::HostKeys {
|
||||
ed25519_identity: *ed22519_identity.public_key(),
|
||||
x25519_sphinx: *x22519_sphinx,
|
||||
x25519_noise,
|
||||
},
|
||||
};
|
||||
|
||||
let signed_info = SignedHostInformation::new(host_info, ed22519_identity.private_key())
|
||||
.map_err(NymNodeHttpError::from)?;
|
||||
Ok(signed_info)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use axum::extract::Query;
|
||||
use crate::node::http::api::api_requests;
|
||||
use crate::node::http::state::AppState;
|
||||
use axum::extract::{Query, State};
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use nym_node_requests::api::{v1::node::models::SignedHostInformation, SignedDataHostInfo};
|
||||
|
||||
@@ -20,11 +22,54 @@ use nym_node_requests::api::{v1::node::models::SignedHostInformation, SignedData
|
||||
params(OutputParams)
|
||||
)]
|
||||
pub(crate) async fn host_information(
|
||||
host_information: SignedHostInformation,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
) -> HostInformationResponse {
|
||||
let output = output.output.unwrap_or_default();
|
||||
output.to_response(host_information)
|
||||
|
||||
let primary_key = state.x25519_sphinx_keys.primary();
|
||||
let pre_announced = match state.x25519_sphinx_keys.secondary() {
|
||||
None => None,
|
||||
Some(secondary_key) => {
|
||||
if secondary_key.rotation_id() == primary_key.rotation_id() + 1 {
|
||||
Some(api_requests::v1::node::models::SphinxKey {
|
||||
rotation_id: secondary_key.rotation_id(),
|
||||
public_key: secondary_key.x25519_pubkey(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let primary_pubkey = primary_key.x25519_pubkey();
|
||||
|
||||
#[allow(deprecated)]
|
||||
let host_info = api_requests::v1::node::models::HostInformation {
|
||||
ip_address: state.static_information.ip_addresses.clone(),
|
||||
hostname: state.static_information.hostname.clone(),
|
||||
keys: api_requests::v1::node::models::HostKeys {
|
||||
ed25519_identity: *state.static_information.ed25519_identity_keys.public_key(),
|
||||
x25519_sphinx: primary_pubkey,
|
||||
primary_x25519_sphinx_key: api_requests::v1::node::models::SphinxKey {
|
||||
rotation_id: primary_key.rotation_id(),
|
||||
public_key: primary_pubkey,
|
||||
},
|
||||
x25519_noise: state.static_information.x25519_noise_key,
|
||||
pre_announced_x25519_sphinx_key: pre_announced,
|
||||
},
|
||||
};
|
||||
|
||||
// SAFETY: the only way for this call to fail is if serialisation of HostInformation fails.
|
||||
// however, that conversion is stable and infallible
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let signed_info = SignedHostInformation::new(
|
||||
host_info,
|
||||
state.static_information.ed25519_identity_keys.private_key(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
output.to_response(signed_info)
|
||||
}
|
||||
|
||||
pub type HostInformationResponse = FormattedResponse<SignedHostInformation>;
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::node::http::api::v1::node::description::description;
|
||||
use crate::node::http::api::v1::node::hardware::host_system;
|
||||
use crate::node::http::api::v1::node::host_information::host_information;
|
||||
use crate::node::http::api::v1::node::roles::roles;
|
||||
use crate::node::http::state::AppState;
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use nym_node_requests::api::v1::node::models;
|
||||
@@ -22,14 +23,13 @@ pub mod roles;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
pub build_information: models::BinaryBuildInformationOwned,
|
||||
pub host_information: models::SignedHostInformation,
|
||||
pub system_info: Option<models::HostSystem>,
|
||||
pub roles: models::NodeRoles,
|
||||
pub description: models::NodeDescription,
|
||||
pub auxiliary_details: models::AuxiliaryDetails,
|
||||
}
|
||||
|
||||
pub(super) fn routes<S: Send + Sync + 'static + Clone>(config: Config) -> Router<S> {
|
||||
pub(super) fn routes(config: Config) -> Router<AppState> {
|
||||
Router::new()
|
||||
.route(
|
||||
v1::BUILD_INFO,
|
||||
@@ -45,13 +45,7 @@ pub(super) fn routes<S: Send + Sync + 'static + Clone>(config: Config) -> Router
|
||||
move |query| roles(node_roles, query)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
v1::HOST_INFO,
|
||||
get({
|
||||
let host_info = config.host_information;
|
||||
move |query| host_information(host_info, query)
|
||||
}),
|
||||
)
|
||||
.route(v1::HOST_INFO, get(host_information))
|
||||
.route(
|
||||
v1::SYSTEM_INFO,
|
||||
get({
|
||||
|
||||
@@ -16,7 +16,6 @@ use nym_node_requests::api::v1::mixnode::models::Mixnode;
|
||||
use nym_node_requests::api::v1::network_requester::exit_policy::models::UsedExitPolicy;
|
||||
use nym_node_requests::api::v1::network_requester::models::NetworkRequester;
|
||||
use nym_node_requests::api::v1::node::models::{AuxiliaryDetails, HostSystem, NodeDescription};
|
||||
use nym_node_requests::api::SignedHostInformation;
|
||||
use nym_node_requests::routes;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
@@ -34,14 +33,13 @@ pub struct HttpServerConfig {
|
||||
}
|
||||
|
||||
impl HttpServerConfig {
|
||||
pub fn new(host_information: SignedHostInformation) -> Self {
|
||||
pub fn new() -> Self {
|
||||
HttpServerConfig {
|
||||
landing: Default::default(),
|
||||
api: api::Config {
|
||||
v1_config: api::v1::Config {
|
||||
node: api::v1::node::Config {
|
||||
build_information: bin_info_owned!(),
|
||||
host_information,
|
||||
system_info: None,
|
||||
roles: Default::default(),
|
||||
description: Default::default(),
|
||||
@@ -118,6 +116,7 @@ impl HttpServerConfig {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_prometheus_bearer_token(mut self, bearer_token: Option<String>) -> Self {
|
||||
self.api.v1_config.metrics.bearer_token = bearer_token.map(|b| Arc::new(Zeroizing::new(b)));
|
||||
self
|
||||
|
||||
@@ -1,20 +1,35 @@
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2023-2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::http::state::load::CachedNodeLoad;
|
||||
use crate::node::http::state::metrics::MetricsAppState;
|
||||
use crate::node::key_rotation::active_keys::ActiveSphinxKeys;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_node_metrics::NymNodeMetrics;
|
||||
use nym_verloc::measurements::SharedVerlocStats;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::time::Instant;
|
||||
|
||||
pub mod load;
|
||||
pub mod metrics;
|
||||
|
||||
pub(crate) struct StaticNodeInformation {
|
||||
pub(crate) ed25519_identity_keys: Arc<ed25519::KeyPair>,
|
||||
pub(crate) x25519_noise_key: Option<x25519::PublicKey>,
|
||||
pub(crate) ip_addresses: Vec<IpAddr>,
|
||||
pub(crate) hostname: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub(crate) struct AppState {
|
||||
pub(crate) startup_time: Instant,
|
||||
|
||||
pub(crate) static_information: Arc<StaticNodeInformation>,
|
||||
|
||||
pub(crate) x25519_sphinx_keys: ActiveSphinxKeys,
|
||||
|
||||
pub(crate) cached_load: CachedNodeLoad,
|
||||
|
||||
pub(crate) metrics: MetricsAppState,
|
||||
@@ -22,12 +37,17 @@ pub struct AppState {
|
||||
|
||||
impl AppState {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new(
|
||||
pub(crate) fn new(
|
||||
static_information: StaticNodeInformation,
|
||||
x25519_sphinx_keys: ActiveSphinxKeys,
|
||||
metrics: NymNodeMetrics,
|
||||
verloc: SharedVerlocStats,
|
||||
load_cache_ttl: Duration,
|
||||
) -> Self {
|
||||
AppState {
|
||||
static_information: Arc::new(static_information),
|
||||
x25519_sphinx_keys,
|
||||
|
||||
// is it 100% accurate?
|
||||
// no.
|
||||
// does it have to be?
|
||||
|
||||
Reference in New Issue
Block a user