Agents authenticate with NSAPI

This commit is contained in:
dynco-nym
2024-11-13 12:05:36 +01:00
parent b9500aacf3
commit ff53cd6e1d
22 changed files with 394 additions and 167 deletions
Generated
+12 -6
View File
@@ -3822,9 +3822,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.155"
version = "0.2.162"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
[[package]]
name = "libm"
@@ -5010,6 +5010,7 @@ dependencies = [
name = "nym-common-models"
version = "0.1.0"
dependencies = [
"nym-crypto",
"serde",
]
@@ -6055,8 +6056,11 @@ dependencies = [
"clap 4.5.20",
"nym-bin-common",
"nym-common-models",
"nym-crypto",
"rand",
"reqwest 0.12.4",
"serde_json",
"tempfile",
"tokio",
"tokio-util",
"tracing",
@@ -6077,6 +6081,7 @@ dependencies = [
"moka",
"nym-bin-common",
"nym-common-models",
"nym-crypto",
"nym-explorer-client",
"nym-network-defaults",
"nym-node-requests",
@@ -8277,9 +8282,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.34"
version = "0.38.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
dependencies = [
"bitflags 2.5.0",
"errno",
@@ -9514,12 +9519,13 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.10.1"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
dependencies = [
"cfg-if",
"fastrand 2.1.1",
"once_cell",
"rustix",
"windows-sys 0.52.0",
]
+1 -1
View File
@@ -329,7 +329,7 @@ syn = "1"
sysinfo = "0.30.13"
tap = "1.0.1"
tar = "0.4.42"
tempfile = "3.5.0"
tempfile = "3.14"
thiserror = "1.0.64"
time = "0.3.30"
tokio = "1.39"
+1
View File
@@ -12,3 +12,4 @@ readme.workspace = true
[dependencies]
serde = { workspace = true, features = ["derive"] }
nym-crypto = { path = "../crypto", features = ["asymmetric", "serde"] }
+8
View File
@@ -1,3 +1,4 @@
use nym_crypto::asymmetric::ed25519::{PublicKey, Signature};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
@@ -5,3 +6,10 @@ pub struct TestrunAssignment {
pub testrun_id: i64,
pub gateway_identity_key: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SubmitResults {
pub message: String,
pub signature: Signature,
pub public_key: PublicKey,
}
+1
View File
@@ -1 +1,2 @@
nym-gateway-probe
keys/
+5
View File
@@ -19,9 +19,14 @@ anyhow = { workspace = true}
clap = { workspace = true, features = ["derive", "env"] }
nym-bin-common = { path = "../common/bin-common", features = ["models"]}
nym-common-models = { path = "../common/models" }
nym-crypto = { path = "../common/crypto", features = ["asymmetric", "rand"] }
rand = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "process"] }
tokio-util = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
reqwest = { workspace = true, features = ["json"] }
serde_json = { workspace = true }
[dev-dependencies]
tempfile = { workspace = true }
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
mkdir -p keys
cargo run --package nym-node-status-agent -- generate-keypair --path keys/private
+7 -6
View File
@@ -4,7 +4,7 @@ set -eu
environment="qa"
source ../envs/${environment}.env
probe_git_ref="0dd5dacdda92b1ddd51cd30a3399515e45613371"
export RUST_LOG="debug"
@@ -13,13 +13,17 @@ gateway_probe_src=$(dirname $(dirname "$crate_root"))/nym-vpn-client/nym-vpn-cor
echo "gateway_probe_src=$gateway_probe_src"
echo "crate_root=$crate_root"
export RUST_LOG="debug"
export NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1"
export NODE_STATUS_AGENT_SERVER_PORT="8000"
export NODE_STATUS_AGENT_PROBE_PATH="$crate_root/nym-gateway-probe"
export NODE_STATUS_AGENT_AUTH_KEY="BjyC9SsHAZUzPRkQR4sPTvVrp4GgaquTh5YfSJksvvWT"
# build & copy over GW probe
function copy_gw_probe() {
pushd $gateway_probe_src
git switch main
git pull
git fetch -a
git checkout $probe_git_ref
cargo build --release --package nym-gateway-probe
cp target/release/nym-gateway-probe "$crate_root"
$crate_root/nym-gateway-probe --version
@@ -45,9 +49,6 @@ function swarm() {
echo "All agents completed"
}
export NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1"
export NODE_STATUS_AGENT_SERVER_PORT="8000"
copy_gw_probe
swarm 8
-118
View File
@@ -1,118 +0,0 @@
use anyhow::bail;
use clap::{Parser, Subcommand};
use nym_bin_common::bin_info;
use nym_common_models::ns_api::TestrunAssignment;
use std::sync::OnceLock;
use tracing::instrument;
use crate::probe::GwProbe;
// Helper for passing LONG_VERSION to clap
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, Debug)]
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
pub(crate) struct Args {
#[command(subcommand)]
pub(crate) command: Command,
#[arg(short, long, env = "NODE_STATUS_AGENT_SERVER_ADDRESS")]
pub(crate) server_address: String,
#[arg(short = 'p', long, env = "NODE_STATUS_AGENT_SERVER_PORT")]
pub(crate) server_port: u16,
// TODO dz accept keypair for identification / auth
}
#[derive(Subcommand, Debug)]
pub(crate) enum Command {
RunProbe {
/// path of binary to run
#[arg(long, env = "NODE_STATUS_AGENT_PROBE_PATH")]
probe_path: String,
},
}
impl Args {
pub(crate) async fn execute(&self) -> anyhow::Result<()> {
match &self.command {
Command::RunProbe { probe_path } => self.run_probe(probe_path).await?,
}
Ok(())
}
async fn run_probe(&self, probe_path: &str) -> anyhow::Result<()> {
let server_address = format!("{}:{}", &self.server_address, self.server_port);
let probe = GwProbe::new(probe_path.to_string());
let version = probe.version().await;
tracing::info!("Probe version:\n{}", version);
if let Some(testrun) = request_testrun(&server_address).await? {
let log = probe.run_and_get_log(&Some(testrun.gateway_identity_key));
submit_results(&server_address, testrun.testrun_id, log).await?;
} else {
tracing::info!("No testruns available, exiting")
}
Ok(())
}
}
const URL_BASE: &str = "internal/testruns";
#[instrument(level = "debug", skip_all)]
async fn request_testrun(server_addr: &str) -> anyhow::Result<Option<TestrunAssignment>> {
let target_url = format!("{}/{}", server_addr, URL_BASE);
let client = reqwest::Client::new();
let res = client.get(target_url).send().await?;
let status = res.status();
let response_text = res.text().await?;
if status.is_client_error() {
bail!("{}: {}", status, response_text);
} else if status.is_server_error() {
if matches!(status, reqwest::StatusCode::SERVICE_UNAVAILABLE)
&& response_text.contains("No testruns available")
{
return Ok(None);
} else {
bail!("{}: {}", status, response_text);
}
}
serde_json::from_str(&response_text)
.map(|testrun| {
tracing::info!("Received testrun assignment: {:?}", testrun);
testrun
})
.map_err(|err| {
tracing::error!("err");
err.into()
})
}
#[instrument(level = "debug", skip(probe_outcome))]
async fn submit_results(
server_addr: &str,
testrun_id: i64,
probe_outcome: String,
) -> anyhow::Result<()> {
let target_url = format!("{}/{}/{}", server_addr, URL_BASE, testrun_id);
let client = reqwest::Client::new();
let res = client
.post(target_url)
.body(probe_outcome)
.send()
.await
.and_then(|response| response.error_for_status())?;
tracing::debug!("Submitted results: {})", res.status());
Ok(())
}
@@ -0,0 +1,59 @@
use std::{fs::File, io::Write, path::Path};
use tracing::info;
pub(crate) fn generate_key_pair(path: impl AsRef<Path>) -> anyhow::Result<()> {
let priv_key_path = path.as_ref();
let mut rng = rand::thread_rng();
let keypair = nym_crypto::asymmetric::identity::KeyPair::new(&mut rng);
info!("Generated keypair as Base58-encoded string");
let mut private_key_file = File::create(priv_key_path)?;
private_key_file.write_all(keypair.private_key().to_base58_string().as_bytes())?;
let pub_key_path = priv_key_path.with_extension("public");
let mut public_key_file = File::create(&pub_key_path)?;
public_key_file.write_all(keypair.public_key().to_base58_string().as_bytes())?;
info!(
"Saved Base58-encoded keypair, private key to {}, public key to {}",
priv_key_path.display(),
pub_key_path.display()
);
info!("Public key should be whitelisted with NS API");
Ok(())
}
#[cfg(test)]
mod test {
use nym_crypto::asymmetric::ed25519::PrivateKey;
use tempfile::TempDir;
use super::*;
use std::{
fs::{self},
path::PathBuf,
};
#[test]
fn can_generate_valid_keypair() {
let tmp_dir = TempDir::new().unwrap();
let pkey_file = PathBuf::from_iter(&[
tmp_dir.path().to_path_buf(),
PathBuf::from("agent-key-private"),
]);
generate_key_pair(&pkey_file).expect("Failed to generate keypair");
let pkey_raw = fs::read_to_string(&pkey_file).expect("Failed to read file");
let key = PrivateKey::from_base58_string(pkey_raw).expect("Failed to load key");
let msg = "hello, world";
let signature = key.sign(msg);
key.public_key()
.verify(msg, &signature)
.expect("Failed to verify signature");
}
}
+74
View File
@@ -0,0 +1,74 @@
use crate::probe::GwProbe;
use clap::{Parser, Subcommand};
use nym_bin_common::bin_info;
use std::sync::OnceLock;
pub(crate) mod generate_keypair;
pub(crate) mod run_probe;
// Helper for passing LONG_VERSION to clap
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, Debug)]
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
pub(crate) struct Args {
#[command(subcommand)]
pub(crate) command: Command,
}
#[derive(Subcommand, Debug)]
pub(crate) enum Command {
RunProbe {
#[arg(short, long, env = "NODE_STATUS_AGENT_SERVER_ADDRESS")]
server_address: String,
#[arg(short = 'p', long, env = "NODE_STATUS_AGENT_SERVER_PORT")]
server_port: u16,
/// base58-encoded private key
#[arg(long, env = "NODE_STATUS_AGENT_AUTH_KEY")]
ns_api_auth_key: String,
/// path of binary to run
#[arg(long, env = "NODE_STATUS_AGENT_PROBE_PATH")]
probe_path: String,
},
GenerateKeypair {
#[arg(long)]
path: Option<String>,
},
}
impl Args {
pub(crate) async fn execute(&self) -> anyhow::Result<()> {
match &self.command {
Command::RunProbe {
server_address,
server_port,
ns_api_auth_key,
probe_path,
} => run_probe::run_probe(
server_address,
server_port.to_owned(),
ns_api_auth_key,
probe_path,
)
.await
.inspect_err(|err| {
tracing::error!("{err}");
})?,
Command::GenerateKeypair { path } => {
let path = path
.to_owned()
.unwrap_or_else(|| String::from("private-key"));
generate_keypair::generate_key_pair(path)?
}
}
Ok(())
}
}
+119
View File
@@ -0,0 +1,119 @@
use anyhow::{bail, Context};
use nym_common_models::ns_api::{SubmitResults, TestrunAssignment};
use nym_crypto::asymmetric::ed25519::{PrivateKey, PublicKey};
use tracing::instrument;
use crate::cli::GwProbe;
const URL_BASE: &str = "internal/testruns";
pub(crate) async fn run_probe(
server_address: &str,
server_port: u16,
ns_api_auth_key: &str,
probe_path: &str,
) -> anyhow::Result<()> {
let server_address = format!("{}:{}", server_address, server_port);
test_ns_api_conn(&server_address).await?;
let auth_key = PrivateKey::from_base58_string(ns_api_auth_key)
.context("Couldn't parse auth key, exiting")?;
let probe = GwProbe::new(probe_path.to_string());
let version = probe.version().await;
tracing::info!("Probe version:\n{}", version);
if let Some(testrun) = request_testrun(auth_key.public_key(), &server_address).await? {
let log = probe.run_and_get_log(&Some(testrun.gateway_identity_key));
submit_results(auth_key, &server_address, testrun.testrun_id, log).await?;
} else {
tracing::info!("No testruns available, exiting")
}
Ok(())
}
async fn test_ns_api_conn(server_addr: &str) -> anyhow::Result<()> {
reqwest::get(server_addr)
.await
.map(|res| {
tracing::info!(
"Testing connection to NS API at {server_addr}: {}",
res.status()
);
})
.map_err(|err| anyhow::anyhow!("Couldn't connect to server on {}: {}", server_addr, err))
}
#[instrument(level = "debug", skip_all)]
pub(crate) async fn request_testrun(
auth_key: PublicKey,
server_addr: &str,
) -> anyhow::Result<Option<TestrunAssignment>> {
let target_url = format!("{}/{}", server_addr, URL_BASE);
let client = reqwest::Client::new();
let res = client
.get(target_url)
.body(auth_key.to_base58_string())
.send()
.await?;
let status = res.status();
let response_text = res.text().await?;
if status.is_client_error() {
bail!("{}: {}", status, response_text);
} else if status.is_server_error() {
if matches!(status, reqwest::StatusCode::SERVICE_UNAVAILABLE)
&& response_text.contains("No testruns available")
{
return Ok(None);
} else {
bail!("{}: {}", status, response_text);
}
}
serde_json::from_str(&response_text)
.map(|testrun| {
tracing::info!("Received testrun assignment: {:?}", testrun);
testrun
})
.map_err(|err| {
tracing::error!("err");
err.into()
})
}
#[instrument(level = "debug", skip(auth_key, probe_outcome))]
pub(crate) async fn submit_results(
auth_key: PrivateKey,
server_addr: &str,
testrun_id: i64,
probe_outcome: String,
) -> anyhow::Result<()> {
let target_url = format!("{}/{}/{}", server_addr, URL_BASE, testrun_id);
let results = sign_message(auth_key, probe_outcome);
let client = reqwest::Client::new();
let res = client
.post(target_url)
.json(&results)
.send()
.await
.and_then(|response| response.error_for_status())?;
tracing::debug!("Submitted results: {})", res.status());
Ok(())
}
fn sign_message(key: PrivateKey, probe_outcome: String) -> SubmitResults {
let signature = key.sign(&probe_outcome);
SubmitResults {
message: probe_outcome,
signature,
public_key: key.public_key(),
}
}
-15
View File
@@ -11,26 +11,11 @@ async fn main() -> anyhow::Result<()> {
setup_tracing();
let args = Args::parse();
let server_addr = format!("{}:{}", args.server_address, args.server_port);
test_ns_api_conn(&server_addr).await?;
args.execute().await?;
Ok(())
}
async fn test_ns_api_conn(server_addr: &str) -> anyhow::Result<()> {
reqwest::get(server_addr)
.await
.map(|res| {
tracing::info!(
"Testing connection to NS API at {server_addr}: {}",
res.status()
);
})
.map_err(|err| anyhow::anyhow!("Couldn't connect to server on {}: {}", server_addr, err))
}
pub(crate) fn setup_tracing() {
fn directive_checked(directive: impl Into<String>) -> Directive {
directive
+1
View File
@@ -23,6 +23,7 @@ futures-util = { workspace = true }
moka = { workspace = true, features = ["future"] }
nym-bin-common = { path = "../common/bin-common", features = ["models"]}
nym-common-models = { path = "../common/models" }
nym-crypto = { path = "../common/crypto", features = ["asymmetric", "serde"] }
nym-explorer-client = { path = "../explorer-api/explorer-client" }
nym-network-defaults = { path = "../common/network-defaults" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
@@ -7,6 +7,7 @@ export RUST_LOG=${RUST_LOG:-debug}
export NYM_API_CLIENT_TIMEOUT=60
export EXPLORER_CLIENT_TIMEOUT=60
export NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL=60
export NODE_STATUS_API_AGENT_KEY_LIST="H4z8kx5Kkf5JMQHhxaW1MwYndjKCDHC7HsVhHTFfBZ4J,AKYZeKaJTzJrWtidk3BZz7wsm4GapaTY4GwQYVL43cmc"
export ENVIRONMENT="qa.env"
+27 -2
View File
@@ -1,5 +1,6 @@
use clap::Parser;
use nym_bin_common::bin_info;
use nym_crypto::asymmetric::ed25519::PublicKey;
use reqwest::Url;
use std::{sync::OnceLock, time::Duration};
@@ -56,7 +57,7 @@ pub(crate) struct Cli {
#[clap(
long,
default_value = "600",
default_value = "300",
env = "NODE_STATUS_API_MONITOR_REFRESH_INTERVAL"
)]
#[arg(value_parser = parse_duration)]
@@ -64,11 +65,35 @@ pub(crate) struct Cli {
#[clap(
long,
default_value = "600",
default_value = "300",
env = "NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL"
)]
#[arg(value_parser = parse_duration)]
pub(crate) testruns_refresh_interval: Duration,
#[clap(long, env = "NODE_STATUS_API_AGENT_KEY_LIST")]
#[arg(value_parser = parse_key_list)]
agent_key_list: KeyList,
}
impl Cli {
pub(crate) fn agent_key_list(&self) -> &Vec<PublicKey> {
&self.agent_key_list.0
}
}
// We need a list of keys from clap. But if we define CLI argument as Vec<T>,
// clap interprets that as type T which can be given as a CLI argument multiple
// times (so all of them are stored in a Vec<T>). Thus we wrap Vec in a newtype
// pattern to have a list of keys and make clap happy.
#[derive(Debug, Clone)]
struct KeyList(Vec<PublicKey>);
fn parse_key_list(arg: &str) -> anyhow::Result<KeyList> {
arg.split(',')
.map(|value| PublicKey::from_base58_string(value.trim()).map_err(anyhow::Error::from))
.collect::<anyhow::Result<Vec<_>>>()
.map(KeyList)
}
fn parse_duration(arg: &str) -> Result<std::time::Duration, std::num::ParseIntError> {
+50 -15
View File
@@ -4,6 +4,8 @@ use axum::{
extract::{Path, State},
Router,
};
use nym_common_models::ns_api::SubmitResults;
use nym_crypto::asymmetric::ed25519::PublicKey;
use reqwest::StatusCode;
use crate::db::models::TestRunStatus;
@@ -28,10 +30,14 @@ pub(crate) fn routes() -> Router<AppState> {
}
#[tracing::instrument(level = "debug", skip_all)]
async fn request_testrun(State(state): State<AppState>) -> HttpResult<Json<TestrunAssignment>> {
// TODO dz log agent's key
async fn request_testrun(
State(state): State<AppState>,
body: String,
) -> HttpResult<Json<TestrunAssignment>> {
// TODO dz log agent's network probe version
tracing::debug!("Agent requested testrun");
let agent_pubkey = authenticate_agent(&body, &state)?;
tracing::debug!("Agent {} requested testrun", agent_pubkey);
let db = state.db_pool();
let mut conn = db
@@ -42,10 +48,11 @@ async fn request_testrun(State(state): State<AppState>) -> HttpResult<Json<Testr
return match db::queries::testruns::get_oldest_testrun_and_make_it_pending(&mut conn).await {
Ok(res) => {
if let Some(testrun) = res {
tracing::debug!(
"🏃‍ Assigned testrun row_id {} gateway {} to agent",
tracing::info!(
"🏃‍ Assigned testrun row_id {} gateway {} to agent {}",
&testrun.testrun_id,
testrun.gateway_identity_key
testrun.gateway_identity_key,
agent_pubkey
);
Ok(Json(testrun))
} else {
@@ -57,13 +64,20 @@ async fn request_testrun(State(state): State<AppState>) -> HttpResult<Json<Testr
};
}
// TODO dz accept testrun_id as query parameter
#[tracing::instrument(level = "debug", skip_all)]
async fn submit_testrun(
Path(testrun_id): Path<i64>,
State(state): State<AppState>,
body: String,
Json(probe_results): Json<SubmitResults>,
) -> HttpResult<StatusCode> {
let agent_pubkey = authenticate_agent(&probe_results.public_key.to_base58_string(), &state)?;
agent_pubkey
.verify(&probe_results.message, &probe_results.signature)
.map_err(|_| {
tracing::warn!("Message verification failed, rejecting");
HttpError::unauthorized()
})?;
let db = state.db_pool();
let mut conn = db
.acquire()
@@ -84,27 +98,31 @@ async fn submit_testrun(
HttpError::internal_with_logging("No gateway found for testrun")
})?;
tracing::debug!(
"Agent submitted testrun {} for gateway {} ({} bytes)",
"Agent {} submitted testrun {} for gateway {} ({} bytes)",
agent_pubkey,
testrun_id,
gw_identity,
body.len(),
&probe_results.message.len(),
);
// TODO dz this should be part of a single transaction: commit after everything is done
queries::testruns::update_testrun_status(&mut conn, testrun_id, TestRunStatus::Complete)
.await
.map_err(HttpError::internal_with_logging)?;
queries::testruns::update_gateway_last_probe_log(&mut conn, testrun.gateway_id, &body)
.await
.map_err(HttpError::internal_with_logging)?;
let result = get_result_from_log(&body);
queries::testruns::update_gateway_last_probe_log(
&mut conn,
testrun.gateway_id,
&probe_results.message,
)
.await
.map_err(HttpError::internal_with_logging)?;
let result = get_result_from_log(&probe_results.message);
queries::testruns::update_gateway_last_probe_result(&mut conn, testrun.gateway_id, &result)
.await
.map_err(HttpError::internal_with_logging)?;
queries::testruns::update_gateway_score(&mut conn, testrun.gateway_id)
.await
.map_err(HttpError::internal_with_logging)?;
// TODO dz log gw identity key
tracing::info!(
"✅ Testrun row_id {} for gateway {} complete",
@@ -115,6 +133,23 @@ async fn submit_testrun(
Ok(StatusCode::CREATED)
}
fn authenticate_agent(base58_pubkey: &str, state: &AppState) -> HttpResult<PublicKey> {
let agent_pubkey = PublicKey::from_base58_string(base58_pubkey).map_err(|_| {
if base58_pubkey.is_empty() {
tracing::warn!("Auth key missing from request body, rejecting");
} else {
tracing::warn!("Failed to deserialize key from request body, rejecting");
}
HttpError::unauthorized()
})?;
if !state.is_registered(&agent_pubkey) {
tracing::warn!("Public key {} not registered, rejecting", agent_pubkey);
return Err(HttpError::unauthorized());
}
Ok(agent_pubkey)
}
fn get_result_from_log(log: &str) -> String {
let re = regex::Regex::new(r"\n\{\s").unwrap();
let result: Vec<_> = re.splitn(log, 2).collect();
+7
View File
@@ -15,6 +15,13 @@ impl HttpError {
}
}
pub(crate) fn unauthorized() -> Self {
Self {
message: serde_json::json!({"message": "Make sure your public key si registered with NS API"}).to_string(),
status: axum::http::StatusCode::UNAUTHORIZED,
}
}
pub(crate) fn internal_with_logging(msg: impl Display) -> Self {
tracing::error!("{}", msg.to_string());
Self::internal()
+3 -1
View File
@@ -1,5 +1,6 @@
use axum::Router;
use core::net::SocketAddr;
use nym_crypto::asymmetric::ed25519::PublicKey;
use tokio::{net::TcpListener, task::JoinHandle};
use tokio_util::sync::{CancellationToken, WaitForCancellationFutureOwned};
@@ -14,10 +15,11 @@ pub(crate) async fn start_http_api(
db_pool: DbPool,
http_port: u16,
nym_http_cache_ttl: u64,
agent_key_list: Vec<PublicKey>,
) -> anyhow::Result<ShutdownHandles> {
let router_builder = RouterBuilder::with_default_routes();
let state = AppState::new(db_pool, nym_http_cache_ttl);
let state = AppState::new(db_pool, nym_http_cache_ttl, agent_key_list);
let router = router_builder.with_state(state);
// TODO dz do we need this to be configurable?
+8 -1
View File
@@ -1,6 +1,7 @@
use std::{sync::Arc, time::Duration};
use moka::{future::Cache, Entry};
use nym_crypto::asymmetric::ed25519::PublicKey;
use tokio::sync::RwLock;
use crate::{
@@ -12,13 +13,15 @@ use crate::{
pub(crate) struct AppState {
db_pool: DbPool,
cache: HttpCache,
agent_key_list: Vec<PublicKey>,
}
impl AppState {
pub(crate) fn new(db_pool: DbPool, cache_ttl: u64) -> Self {
pub(crate) fn new(db_pool: DbPool, cache_ttl: u64, agent_key_list: Vec<PublicKey>) -> Self {
Self {
db_pool,
cache: HttpCache::new(cache_ttl),
agent_key_list,
}
}
@@ -29,6 +32,10 @@ impl AppState {
pub(crate) fn cache(&self) -> &HttpCache {
&self.cache
}
pub(crate) fn is_registered(&self, agent_pubkey: &PublicKey) -> bool {
self.agent_key_list.contains(agent_pubkey)
}
}
static GATEWAYS_LIST_KEY: &str = "gateways";
+5
View File
@@ -14,6 +14,9 @@ async fn main() -> anyhow::Result<()> {
let args = cli::Cli::parse();
let agent_key_list = args.agent_key_list();
tracing::info!("Registered {} agent keys", agent_key_list.len());
let connection_url = args.database_url.clone();
tracing::debug!("Using config:\n{:#?}", args);
@@ -31,12 +34,14 @@ async fn main() -> anyhow::Result<()> {
.await;
tracing::info!("Started monitor task");
});
testruns::spawn(storage.pool_owned(), args.testruns_refresh_interval).await;
let shutdown_handles = http::server::start_http_api(
storage.pool_owned(),
args.http_port,
args.nym_http_cache_ttl,
agent_key_list.to_owned(),
)
.await
.expect("Failed to start server");
+1 -2
View File
@@ -1,4 +1,5 @@
use crate::db::models::GatewayIdentityDto;
use crate::db::DbPool;
use futures_util::TryStreamExt;
use std::time::Duration;
@@ -24,8 +25,6 @@ pub(crate) async fn spawn(pool: DbPool, refresh_interval: Duration) {
});
}
// TODO dz make number of max agents configurable
#[instrument(level = "debug", name = "testrun_queue", skip_all)]
async fn run(pool: &DbPool) -> anyhow::Result<()> {
tracing::info!("Spawning testruns...");