Changes in authentication

This commit is contained in:
Ara Sadoyan
2026-04-08 19:05:19 +02:00
parent 389c12119a
commit f135106a44
4 changed files with 20 additions and 36 deletions

1
Cargo.lock generated
View File

@@ -147,6 +147,7 @@ dependencies = [
"serde_json", "serde_json",
"serde_yml", "serde_yml",
"sha2 0.11.0", "sha2 0.11.0",
"subtle",
"tokio", "tokio",
"tonic", "tonic",
"tower-http", "tower-http",

View File

@@ -43,3 +43,4 @@ tower-http = { version = "0.6.8", features = ["fs"] }
privdrop = "0.5.6" privdrop = "0.5.6"
ctrlc = "3.5.2" ctrlc = "3.5.2"
serde_json = "1.0.149" serde_json = "1.0.149"
subtle = "2.6.1"

View File

@@ -4,6 +4,7 @@ use base64::Engine;
use pingora_proxy::Session; use pingora_proxy::Session;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use subtle::ConstantTimeEq;
use urlencoding::decode; use urlencoding::decode;
trait AuthValidator { trait AuthValidator {
@@ -19,8 +20,8 @@ impl AuthValidator for BasicAuth<'_> {
if let Some(h) = header.to_str().ok() { if let Some(h) = header.to_str().ok() {
if let Some((_, val)) = h.split_once(' ') { if let Some((_, val)) = h.split_once(' ') {
if let Some(decoded) = STANDARD.decode(val).ok() { if let Some(decoded) = STANDARD.decode(val).ok() {
if let Some(decoded_str) = String::from_utf8(decoded).ok() { if decoded.as_slice().ct_eq(self.0.as_bytes()).into() {
return decoded_str == self.0; return true;
} }
} }
} }
@@ -33,10 +34,9 @@ impl AuthValidator for BasicAuth<'_> {
impl AuthValidator for ApiKeyAuth<'_> { impl AuthValidator for ApiKeyAuth<'_> {
fn validate(&self, session: &Session) -> bool { fn validate(&self, session: &Session) -> bool {
if let Some(header) = session.get_header("x-api-key") { if let Some(header) = session.get_header("x-api-key") {
if let Some(header) = header.to_str().ok() { if let Some(h) = header.to_str().ok() {
return header == self.0; return h.as_bytes().ct_eq(self.0.as_bytes()).into();
} }
// return header.to_str().ok().unwrap() == self.0;
} }
false false
} }
@@ -60,27 +60,14 @@ impl AuthValidator for JwtAuth<'_> {
false false
} }
} }
fn validate(auth: &dyn AuthValidator, session: &Session) -> bool {
auth.validate(session)
}
// pub fn authenticate(c: &[Arc<str>], session: &Session) -> bool {
pub fn authenticate(auth_type: &Arc<str>, credentials: &Arc<str>, session: &Session) -> bool { pub fn authenticate(auth_type: &Arc<str>, credentials: &Arc<str>, session: &Session) -> bool {
match &*auth_type.clone() { match &**auth_type {
"basic" => { "basic" => BasicAuth(credentials).validate(session),
let auth = BasicAuth(&*credentials.clone()); "apikey" => ApiKeyAuth(credentials).validate(session),
validate(&auth, session) "jwt" => JwtAuth(credentials).validate(session),
}
"apikey" => {
let auth = ApiKeyAuth(&*credentials.clone());
validate(&auth, session)
}
"jwt" => {
let auth = JwtAuth(&*credentials.clone());
validate(&auth, session)
}
_ => { _ => {
println!("Unsupported authentication mechanism : {}", auth_type); log::warn!("Unsupported authentication mechanism : {}", auth_type);
false false
} }
} }
@@ -98,6 +85,5 @@ pub fn get_query_param(session: &Session, key: &str) -> Option<String> {
Some((k, v)) Some((k, v))
}) })
.collect(); .collect();
params.get(key).and_then(|v| decode(v).ok()).map(|s| s.to_string())
params.get(key).map(|v| decode(v).ok()).flatten().map(|s| s.to_string())
} }

View File

@@ -3,7 +3,7 @@ use crate::utils::structs::{Config, Configuration, UpstreamsDashMap};
use crate::utils::tools::{upstreams_liveness_json, upstreams_to_json}; use crate::utils::tools::{upstreams_liveness_json, upstreams_to_json};
use axum::body::Body; use axum::body::Body;
use axum::extract::{Query, State}; use axum::extract::{Query, State};
use axum::http::{Response, StatusCode}; use axum::http::{header::HeaderMap, Response, StatusCode};
use axum::response::IntoResponse; use axum::response::IntoResponse;
use axum::routing::{get, post}; use axum::routing::{get, post};
use axum::{Json, Router}; use axum::{Json, Router};
@@ -18,6 +18,7 @@ use std::collections::HashMap;
// use std::net::SocketAddr; // use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::time::{Duration, SystemTime, UNIX_EPOCH};
use subtle::ConstantTimeEq;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
@@ -88,23 +89,18 @@ pub async fn run_server(config: &APIUpstreamProvider, mut to_return: Sender<Conf
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();
} }
async fn conf(State(st): State<AppState>, Query(params): Query<HashMap<String, String>>, content: String) -> impl IntoResponse { async fn conf(State(st): State<AppState>, Query(params): Query<HashMap<String, String>>, headers: HeaderMap, content: String) -> impl IntoResponse {
if !st.config_api_enabled { if !st.config_api_enabled {
return Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Config API is disabled !\n")).unwrap(); return Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Config API is disabled !\n")).unwrap();
} }
if let Some(s) = params.get("key") { 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.to_owned() == st.master_key { if s.as_bytes().ct_eq(st.master_key.as_bytes()).into() {
let strcontent = content.as_str(); let strcontent = content.as_str();
let parsed = serde_yml::from_str::<Config>(strcontent); let parsed = serde_yml::from_str::<Config>(strcontent);
match parsed { match parsed {
Ok(_) => { Ok(_) => {
if let Some(s) = params.get("key") { let _ = tokio::spawn(async move { apply_config(content.as_str(), st).await });
if s.to_owned() == st.master_key { return Response::builder().status(StatusCode::OK).body(Body::from("Accepted! Applying in background\n")).unwrap();
let _ = tokio::spawn(async move { apply_config(content.as_str(), st).await });
return Response::builder().status(StatusCode::OK).body(Body::from("Accepted! Applying in background\n")).unwrap();
}
}
return Response::builder().status(StatusCode::FORBIDDEN).body(Body::from("Access Denied !\n")).unwrap();
} }
Err(err) => { Err(err) => {
error!("Failed to parse upstreams file: {}", err); error!("Failed to parse upstreams file: {}", err);