Skip to content

Commit

Permalink
Replay protection
Browse files Browse the repository at this point in the history
  • Loading branch information
dynco-nym committed Nov 19, 2024
1 parent 5c6d568 commit a5cac0f
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 3 deletions.
10 changes: 9 additions & 1 deletion nym-node-status-api/src/http/api/testruns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ async fn request_testrun(
// TODO dz log agent's network probe version

authenticate(&request, &state)?;
state
.check_last_request_time(
&request.payload.agent_public_key,
&request.payload.timestamp,
)
.await?;

let agent_pubkey = request.payload.agent_public_key;

tracing::debug!("Agent {} requested testrun", agent_pubkey);
Expand All @@ -58,7 +65,7 @@ async fn request_testrun(
);
Ok(Json(testrun))
} else {
tracing::debug!("No testruns available for agent");
tracing::debug!("No testruns available for agent {}", agent_pubkey);
Err(HttpError::no_testruns_available())
}
}
Expand Down Expand Up @@ -164,6 +171,7 @@ async fn submit_testrun(
}

// TODO dz this should be middleware
#[tracing::instrument(level = "debug", skip_all)]
fn authenticate(request: &get_testrun::GetTestrunRequest, state: &AppState) -> HttpResult<()> {
if !state.is_registered(&request.payload.agent_public_key) {
tracing::warn!("Public key not registered with NS API, rejecting");
Expand Down
43 changes: 41 additions & 2 deletions nym-node-status-api/src/http/state.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
use std::{sync::Arc, time::Duration};
use std::{collections::HashMap, sync::Arc, time::Duration};

use moka::{future::Cache, Entry};
use nym_crypto::asymmetric::ed25519::PublicKey;
use tokio::sync::RwLock;

use crate::{
db::DbPool,
http::models::{DailyStats, Gateway, Mixnode, SummaryHistory},
http::{
error::{HttpError, HttpResult},
models::{DailyStats, Gateway, Mixnode, SummaryHistory},
},
};

#[derive(Debug, Clone)]
pub(crate) struct AppState {
db_pool: DbPool,
cache: HttpCache,
agent_key_list: Vec<PublicKey>,
/// last time agent requested a testrun
agent_last_request_times: Arc<RwLock<HashMap<String, i64>>>,
}

impl AppState {
Expand All @@ -22,6 +27,7 @@ impl AppState {
db_pool,
cache: HttpCache::new(cache_ttl),
agent_key_list,
agent_last_request_times: Arc::new(RwLock::new(HashMap::new())),
}
}

Expand All @@ -36,6 +42,39 @@ impl AppState {
pub(crate) fn is_registered(&self, agent_pubkey: &PublicKey) -> bool {
self.agent_key_list.contains(agent_pubkey)
}

/// Only updates if request time is valid. Otherwise return error
pub(crate) async fn check_last_request_time(
&self,
agent_key: &PublicKey,
request_time: &i64,
) -> HttpResult<()> {
// if entry exists with a newer time than this request's submit time,
// it's a repeated request
let agent_key = agent_key.to_base58_string();
let request_times = self.agent_last_request_times.read().await;
if let Some(previous_request_time) = request_times.get(&agent_key) {
if request_time <= previous_request_time {
tracing::warn!(
"Request has timestamp {} but previous request was at {}, rejecting",
request_time,
previous_request_time
);
return Err(HttpError::unauthorized());
}
}
drop(request_times);

// otherwise this is a newer (or a first) request
self.agent_last_request_times
.write()
.await
.entry(agent_key)
.and_modify(|value| *value = *request_time)
.or_insert(*request_time);

Ok(())
}
}

static GATEWAYS_LIST_KEY: &str = "gateways";
Expand Down

0 comments on commit a5cac0f

Please sign in to comment.