Removed authentication from API server, JWT master key as environment variable

This commit is contained in:
Ara Sadoyan
2026-05-18 20:38:30 +02:00
parent 2ce290abcf
commit 00062b00da
5 changed files with 69 additions and 82 deletions

View File

@@ -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);

View File

@@ -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<String>,
pub certs_dir: String,
pub config_dir: String,
pub upstreams_file: String,
// pub tls_address: Option<String>,
// pub tls_certificate: Option<String>,
// pub tls_key_file: Option<String>,
pub file_server_address: Option<String>,
pub file_server_folder: Option<String>,
pub current_upstreams: Arc<UpstreamsDashMap>,

View File

@@ -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<DashMap<String, bool>> = LazyLock::new(DashMap::new);
@@ -236,6 +236,11 @@ pub fn parce_main_config(path: &str) -> AppConfig {
let reply = DashMap::new();
let cfg: HashMap<String, String> = 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 {

View File

@@ -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<String>,
pub config_address: String,
pub proxy_address_http: String,
pub config_api_enabled: bool,

View File

@@ -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<String>,
cert_creds: String,
certs_dir: String,
upstreams_file: String,
@@ -95,13 +94,11 @@ pub async fn run_server(config: &APIUpstreamProvider, mut to_return: Sender<Conf
axum::serve(listener, app).await.unwrap();
}
async fn conf(State(st): State<AppState>, Query(params): Query<HashMap<String, String>>, headers: HeaderMap, content: String) -> impl IntoResponse {
async fn conf(State(st): State<AppState>, Query(params): Query<HashMap<String, String>>, 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, &params, &st.master_key) {
let strcontent = content.as_str();
let parsed = serde_yml::from_str::<Config>(strcontent);
match parsed {
@@ -111,17 +108,14 @@ async fn conf(State(st): State<AppState>, Query(params): Query<HashMap<String, S
} 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();
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();
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) {
let sl = crate::utils::parceyaml::load_configuration(content, "content").await;
@@ -137,7 +131,8 @@ async fn apply_config(content: &str, mut st: AppState, save: bool) {
}
async fn jwt_gen(State(state): State<AppState>, Json(payload): Json<Claims>) -> (StatusCode, Json<OutToken>) {
if payload.master_key == state.master_key {
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();
@@ -166,6 +161,13 @@ async fn jwt_gen(State(state): State<AppState>, Json(payload): Json<Claims>) ->
warn!("Unauthorised JWT generate request: {:?}", tok);
(StatusCode::FORBIDDEN, Json(tok))
}
} else {
let tok = OutToken {
token: "ERROR Getting JWT_KEY environment variable".to_string(),
};
error!("ERROR Getting JWT_KEY environment variable");
(StatusCode::INTERNAL_SERVER_ERROR, Json(tok))
}
}
async fn metrics() -> impl IntoResponse {
@@ -221,11 +223,7 @@ async fn status(State(st): State<AppState>, Query(params): Query<HashMap<String,
}
#[allow(clippy::needless_return)]
async fn acme_create(State(state): State<AppState>, Query(params): Query<HashMap<String, String>>, headers: HeaderMap) -> impl IntoResponse {
if !key_authorization(&headers, &params, &state.master_key) {
return Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Access Denied !\n")).unwrap();
}
async fn acme_create(State(state): State<AppState>) -> 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<AppState>, Query(params): Query<HashMap
};
}
#[allow(clippy::needless_return)]
async fn acme_order(
State(state): State<AppState>,
axum::extract::Path(domain): axum::extract::Path<String>,
Query(params): Query<HashMap<String, String>>,
headers: HeaderMap,
) -> impl IntoResponse {
if !key_authorization(&headers, &params, &state.master_key) {
return Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Access Denied !\n")).unwrap();
}
async fn acme_order(State(state): State<AppState>, axum::extract::Path(domain): axum::extract::Path<String>) -> 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<St
.unwrap()
}
fn key_authorization(headers: &HeaderMap, params: &HashMap<String, String>, 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<String, String>, 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 ⚝ --