From 00062b00dab1dc8365fb6e6f80fd9037b4cb4dfd Mon Sep 17 00:00:00 2001 From: Ara Sadoyan Date: Mon, 18 May 2026 20:38:30 +0200 Subject: [PATCH] Removed authentication from API server, JWT master key as environment variable --- src/utils/auth.rs | 14 ++--- src/utils/discovery.rs | 5 +- src/utils/parceyaml.rs | 7 ++- src/utils/structs.rs | 2 +- src/web/webserver.rs | 123 +++++++++++++++++++---------------------- 5 files changed, 69 insertions(+), 82 deletions(-) diff --git a/src/utils/auth.rs b/src/utils/auth.rs index 143f601..5e89c97 100644 --- a/src/utils/auth.rs +++ b/src/utils/auth.rs @@ -1,22 +1,17 @@ use crate::utils::jwt::check_jwt; -// use reqwest::Client; use axum::http::StatusCode; use base64::engine::general_purpose::STANDARD; use base64::Engine; +use pingora::http::RequestHeader; +use pingora_core::connectors::http::Connector; +use pingora_core::upstreams::peer::HttpPeer; +use pingora_http::ResponseHeader; use pingora_proxy::Session; use std::collections::HashMap; use std::sync::{Arc, LazyLock}; use subtle::ConstantTimeEq; use urlencoding::decode; -// use pingora::http::{RequestHeader, ResponseHeader, StatusCode}; -use pingora::http::RequestHeader; -// --------------------------------- // -use pingora_core::connectors::http::Connector; -use pingora_core::upstreams::peer::HttpPeer; -use pingora_http::ResponseHeader; -// --------------------------------- // - #[async_trait::async_trait] trait AuthValidator { async fn validate(&self, session: &mut Session) -> bool; @@ -182,6 +177,7 @@ impl AuthValidator for ApiKeyAuth<'_> { #[async_trait::async_trait] impl AuthValidator for JwtAuth<'_> { async fn validate(&self, session: &mut Session) -> bool { + println!("{:?}", self.0); let jwtsecret = self.0; if let Some(tok) = get_query_param(session, "araleztoken") { return check_jwt(tok.as_str(), jwtsecret); diff --git a/src/utils/discovery.rs b/src/utils/discovery.rs index c69bb6e..43e37fe 100644 --- a/src/utils/discovery.rs +++ b/src/utils/discovery.rs @@ -9,13 +9,10 @@ use std::sync::Arc; pub struct APIUpstreamProvider { pub config_api_enabled: bool, pub address: String, - pub masterkey: String, + pub masterkey: Option, pub certs_dir: String, pub config_dir: String, pub upstreams_file: String, - // pub tls_address: Option, - // pub tls_certificate: Option, - // pub tls_key_file: Option, pub file_server_address: Option, pub file_server_folder: Option, pub current_upstreams: Arc, diff --git a/src/utils/parceyaml.rs b/src/utils/parceyaml.rs index f082d92..294732b 100644 --- a/src/utils/parceyaml.rs +++ b/src/utils/parceyaml.rs @@ -11,10 +11,10 @@ use log4rs::{ encode::pattern::PatternEncoder, }; use std::collections::HashMap; -use std::fs; use std::path::Path; use std::sync::atomic::AtomicUsize; use std::sync::{Arc, LazyLock}; +use std::{env, fs}; pub static DOMAINS: LazyLock> = LazyLock::new(DashMap::new); @@ -236,6 +236,11 @@ pub fn parce_main_config(path: &str) -> AppConfig { let reply = DashMap::new(); let cfg: HashMap = serde_yml::from_str(&data).expect("Failed to parse main config file"); let mut cfo: AppConfig = serde_yml::from_str(&data).expect("Failed to parse main config file"); + + if let Ok(jwt_key) = env::var("JWT_KEY") { + cfo.master_key = Some(jwt_key); + }; + log_builder(&cfo, &cfo.log_file); cfo.hc_method = cfo.hc_method.to_uppercase(); for (k, v) in cfg { diff --git a/src/utils/structs.rs b/src/utils/structs.rs index 7f31b0b..6e64a83 100644 --- a/src/utils/structs.rs +++ b/src/utils/structs.rs @@ -108,7 +108,7 @@ pub struct AppConfig { pub hc_method: String, pub upstreams_conf: String, pub log_level: String, - pub master_key: String, + pub master_key: Option, pub config_address: String, pub proxy_address_http: String, pub config_api_enabled: bool, diff --git a/src/web/webserver.rs b/src/web/webserver.rs index 7d9971e..75e747d 100644 --- a/src/web/webserver.rs +++ b/src/web/webserver.rs @@ -8,7 +8,7 @@ use crate::utils::structs::{Config, Configuration, UpstreamsDashMap}; use crate::utils::tools::{upstreams_liveness_json, upstreams_to_json}; use axum::body::Body; use axum::extract::{Query, State}; -use axum::http::{header::HeaderMap, Response, StatusCode}; +use axum::http::{Response, StatusCode}; use axum::response::IntoResponse; use axum::routing::{any, get, post}; use axum::{Json, Router}; @@ -21,7 +21,6 @@ use serde::Serialize; use std::collections::HashMap; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use subtle::ConstantTimeEq; use tokio::net::TcpListener; use tower_http::services::ServeDir; @@ -32,7 +31,7 @@ struct OutToken { #[derive(Clone)] struct AppState { - master_key: String, + master_key: Option, cert_creds: String, certs_dir: String, upstreams_file: String, @@ -95,32 +94,27 @@ pub async fn run_server(config: &APIUpstreamProvider, mut to_return: Sender, Query(params): Query>, headers: HeaderMap, content: String) -> impl IntoResponse { +async fn conf(State(st): State, Query(params): Query>, content: String) -> impl IntoResponse { if !st.config_api_enabled { return Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Config API is disabled !\n")).unwrap(); } - // if let Some(s) = headers.get("x-api-key").and_then(|v| v.to_str().ok()).or(params.get("key").map(|s| s.as_str())) { - if key_authorization(&headers, ¶ms, &st.master_key) { - let strcontent = content.as_str(); - let parsed = serde_yml::from_str::(strcontent); - match parsed { - Ok(_) => { - if let Some(_) = params.get("save") { - drop(tokio::spawn(async move { apply_config(content.as_str(), st, true).await })); - } else { - drop(tokio::spawn(async move { apply_config(content.as_str(), st, false).await })); - } - // apply_config(content.as_str(), st).await; - return Response::builder().status(StatusCode::OK).body(Body::from("Accepted! Applying in background\n")).unwrap(); - } - Err(err) => { - error!("Failed to parse upstreams file: {}", err); - return Response::builder().status(StatusCode::BAD_GATEWAY).body(Body::from(format!("Failed: {}\n", err))).unwrap(); + let strcontent = content.as_str(); + let parsed = serde_yml::from_str::(strcontent); + match parsed { + Ok(_) => { + if let Some(_) = params.get("save") { + drop(tokio::spawn(async move { apply_config(content.as_str(), st, true).await })); + } else { + drop(tokio::spawn(async move { apply_config(content.as_str(), st, false).await })); } + Response::builder().status(StatusCode::OK).body(Body::from("Accepted! Applying in background\n")).unwrap() + } + Err(err) => { + error!("Failed to parse upstreams file: {}", err); + Response::builder().status(StatusCode::BAD_GATEWAY).body(Body::from(format!("Failed: {}\n", err))).unwrap() } } - Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Access Denied !\n")).unwrap() } async fn apply_config(content: &str, mut st: AppState, save: bool) { @@ -137,34 +131,42 @@ async fn apply_config(content: &str, mut st: AppState, save: bool) { } async fn jwt_gen(State(state): State, Json(payload): Json) -> (StatusCode, Json) { - if payload.master_key == state.master_key { - let now = SystemTime::now() + Duration::from_secs(payload.exp * 60); - let expire = now.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(); + if let Some(master_key) = &state.master_key { + if &payload.master_key == master_key { + let now = SystemTime::now() + Duration::from_secs(payload.exp * 60); + let expire = now.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(); - let claim = Claims { - master_key: String::new(), - owner: payload.owner, - exp: expire, - random: payload.random, - }; - match encode(&Header::default(), &claim, &EncodingKey::from_secret(payload.master_key.as_ref())) { - Ok(t) => { - let tok = OutToken { token: t }; - debug!("Generating token: {:?}", tok.token); - (StatusCode::CREATED, Json(tok)) - } - Err(e) => { - let tok = OutToken { token: "ERROR".to_string() }; - error!("Failed to generate token: {:?}", e); - (StatusCode::INTERNAL_SERVER_ERROR, Json(tok)) + let claim = Claims { + master_key: String::new(), + owner: payload.owner, + exp: expire, + random: payload.random, + }; + match encode(&Header::default(), &claim, &EncodingKey::from_secret(payload.master_key.as_ref())) { + Ok(t) => { + let tok = OutToken { token: t }; + debug!("Generating token: {:?}", tok.token); + (StatusCode::CREATED, Json(tok)) + } + Err(e) => { + let tok = OutToken { token: "ERROR".to_string() }; + error!("Failed to generate token: {:?}", e); + (StatusCode::INTERNAL_SERVER_ERROR, Json(tok)) + } } + } else { + let tok = OutToken { + token: "Unauthorised".to_string(), + }; + warn!("Unauthorised JWT generate request: {:?}", tok); + (StatusCode::FORBIDDEN, Json(tok)) } } else { let tok = OutToken { - token: "Unauthorised".to_string(), + token: "ERROR Getting JWT_KEY environment variable".to_string(), }; - warn!("Unauthorised JWT generate request: {:?}", tok); - (StatusCode::FORBIDDEN, Json(tok)) + error!("ERROR Getting JWT_KEY environment variable"); + (StatusCode::INTERNAL_SERVER_ERROR, Json(tok)) } } @@ -221,11 +223,7 @@ async fn status(State(st): State, Query(params): Query, Query(params): Query>, headers: HeaderMap) -> impl IntoResponse { - if !key_authorization(&headers, ¶ms, &state.master_key) { - return Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Access Denied !\n")).unwrap(); - } - +async fn acme_create(State(state): State) -> impl IntoResponse { match account::load_or_create(state.cert_creds.as_str()).await { Ok(txt) => { return Response::builder() @@ -243,16 +241,7 @@ async fn acme_create(State(state): State, Query(params): Query, - axum::extract::Path(domain): axum::extract::Path, - Query(params): Query>, - headers: HeaderMap, -) -> impl IntoResponse { - if !key_authorization(&headers, ¶ms, &state.master_key) { - return Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Access Denied !\n")).unwrap(); - } - +async fn acme_order(State(state): State, axum::extract::Path(domain): axum::extract::Path) -> impl IntoResponse { let domain_clean = domain.trim_matches('/'); match order::order(domain_clean, state.cert_creds.as_str(), state.certs_dir).await { Ok(txt) => { @@ -292,13 +281,13 @@ pub async fn http01_challenge(axum::extract::Path(token): axum::extract::Path, masterkey: &str) -> bool { - if let Some(s) = headers.get("x-api-key").and_then(|v| v.to_str().ok()).or(params.get("key").map(|s| s.as_str())) { - if s.as_bytes().ct_eq(masterkey.as_bytes()).into() { - return true; - } - } - false -} +// fn key_authorization(headers: &HeaderMap, params: &HashMap, masterkey: &str) -> bool { +// if let Some(s) = headers.get("x-api-key").and_then(|v| v.to_str().ok()).or(params.get("key").map(|s| s.as_str())) { +// if s.as_bytes().ct_eq(masterkey.as_bytes()).into() { +// return true; +// } +// } +// false +// } // -- ⚝ by Dave -- in NeoVim ⚝ --