JWT auth read and caches KEY from system env.

This commit is contained in:
Ara Sadoyan
2026-05-19 15:26:05 +02:00
parent 37ef118861
commit 4bbedee27b
5 changed files with 36 additions and 26 deletions

View File

@@ -1,4 +1,5 @@
use crate::utils::jwt::check_jwt; use crate::utils::jwt::{check_jwt, JWT_TOKEN};
use crate::utils::structs::InnerAuth;
use axum::http::StatusCode; use axum::http::StatusCode;
use base64::engine::general_purpose::STANDARD; use base64::engine::general_purpose::STANDARD;
use base64::Engine; use base64::Engine;
@@ -8,7 +9,7 @@ use pingora_core::upstreams::peer::HttpPeer;
use pingora_http::ResponseHeader; use pingora_http::ResponseHeader;
use pingora_proxy::Session; use pingora_proxy::Session;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{Arc, LazyLock}; use std::sync::LazyLock;
use subtle::ConstantTimeEq; use subtle::ConstantTimeEq;
use urlencoding::decode; use urlencoding::decode;
@@ -18,7 +19,7 @@ trait AuthValidator {
} }
struct BasicAuth<'a>(&'a str); struct BasicAuth<'a>(&'a str);
struct ApiKeyAuth<'a>(&'a str); struct ApiKeyAuth<'a>(&'a str);
struct JwtAuth<'a>(&'a str); struct JwtAuth();
struct ForwardAuth<'a>(&'a str); struct ForwardAuth<'a>(&'a str);
pub static AUTH_CONNECTOR: LazyLock<Connector> = LazyLock::new(|| Connector::new(None)); pub static AUTH_CONNECTOR: LazyLock<Connector> = LazyLock::new(|| Connector::new(None));
@@ -175,18 +176,19 @@ impl AuthValidator for ApiKeyAuth<'_> {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl AuthValidator for JwtAuth<'_> { impl AuthValidator for JwtAuth {
async fn validate(&self, session: &mut Session) -> bool { async fn validate(&self, session: &mut Session) -> bool {
println!("{:?}", self.0); if let Some(jwtsecret) = JWT_TOKEN.clone() {
let jwtsecret = self.0; // println!(" ===> {:?}", jwtsecret);
if let Some(tok) = get_query_param(session, "araleztoken") { if let Some(tok) = get_query_param(session, "araleztoken") {
return check_jwt(tok.as_str(), jwtsecret); return check_jwt(tok.as_str(), jwtsecret.as_ref());
} }
if let Some(auth_header) = session.get_header("authorization") { if let Some(auth_header) = session.get_header("authorization") {
if let Ok(header_str) = auth_header.to_str() { if let Ok(header_str) = auth_header.to_str() {
if let Some((scheme, token)) = header_str.split_once(' ') { if let Some((scheme, token)) = header_str.split_once(' ') {
if scheme.eq_ignore_ascii_case("bearer") { if scheme.eq_ignore_ascii_case("bearer") {
return check_jwt(token, jwtsecret); return check_jwt(token, jwtsecret.as_ref());
}
} }
} }
} }
@@ -195,14 +197,14 @@ impl AuthValidator for JwtAuth<'_> {
} }
} }
pub async fn authenticate(auth_type: &Arc<str>, credentials: &Arc<str>, session: &mut Session) -> bool { pub async fn authenticate(auth: &InnerAuth, session: &mut Session) -> bool {
match &**auth_type { match &*auth.auth_type {
"basic" => BasicAuth(credentials).validate(session).await, "basic" => BasicAuth(&*auth.auth_cred).validate(session).await,
"apikey" => ApiKeyAuth(credentials).validate(session).await, "apikey" => ApiKeyAuth(&*auth.auth_cred).validate(session).await,
"jwt" => JwtAuth(credentials).validate(session).await, "jwt" => JwtAuth().validate(session).await,
"forward" => ForwardAuth(credentials).validate(session).await, "forward" => ForwardAuth(&*auth.auth_cred).validate(session).await,
_ => { _ => {
log::warn!("Unsupported authentication mechanism : {}", auth_type); log::warn!("Unsupported authentication mechanism : {}", &*auth.auth_type);
false false
} }
} }

View File

@@ -4,8 +4,9 @@ use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use moka::sync::Cache; use moka::sync::Cache;
use moka::Expiry; use moka::Expiry;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::env;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::sync::LazyLock; use std::sync::{Arc, LazyLock};
use std::time::{Duration, Instant, SystemTime}; use std::time::{Duration, Instant, SystemTime};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@@ -23,6 +24,11 @@ struct Expired {
static JWT_VALIDATION: LazyLock<Validation> = LazyLock::new(|| Validation::new(Algorithm::HS256)); static JWT_VALIDATION: LazyLock<Validation> = LazyLock::new(|| Validation::new(Algorithm::HS256));
pub static JWT_TOKEN: LazyLock<Option<Arc<str>>> = LazyLock::new(|| match env::var("JWT_KEY") {
Ok(key) if !key.is_empty() => Some(Arc::from(key.as_str())),
_ => None,
});
static JWT_CACHE: LazyLock<Cache<u64, u64>> = LazyLock::new(|| Cache::builder().max_capacity(100_000).expire_after(JwtExpiry).build()); static JWT_CACHE: LazyLock<Cache<u64, u64>> = LazyLock::new(|| Cache::builder().max_capacity(100_000).expire_after(JwtExpiry).build());
struct JwtExpiry; struct JwtExpiry;
impl Expiry<u64, u64> for JwtExpiry { impl Expiry<u64, u64> for JwtExpiry {

View File

@@ -85,6 +85,7 @@ pub async fn load_configuration(d: &str, kind: &str) -> (Option<Configuration>,
let mut parsed: Config = match serde_yml::from_str(&yaml_data) { let mut parsed: Config = match serde_yml::from_str(&yaml_data) {
Ok(cfg) => cfg, Ok(cfg) => cfg,
Err(e) => { Err(e) => {
println!("================================================");
error!("Failed to parse upstreams file: {}", e); error!("Failed to parse upstreams file: {}", e);
return (None, e.to_string()); return (None, e.to_string());
} }
@@ -136,6 +137,7 @@ async fn populate_headers_and_auth(config: &mut Configuration, parsed: &Config)
} }
} }
} }
let global_headers: DashMap<Arc<str>, Vec<(String, Arc<str>)>> = DashMap::new(); let global_headers: DashMap<Arc<str>, Vec<(String, Arc<str>)>> = DashMap::new();
global_headers.insert(Arc::from("/"), ch); global_headers.insert(Arc::from("/"), ch);
config.client_headers.insert(Arc::from("GLOBAL_CLIENT_HEADERS"), global_headers); config.client_headers.insert(Arc::from("GLOBAL_CLIENT_HEADERS"), global_headers);
@@ -162,7 +164,7 @@ async fn populate_headers_and_auth(config: &mut Configuration, parsed: &Config)
if let Some(pa) = &parsed.authorization { if let Some(pa) = &parsed.authorization {
let y: InnerAuth = InnerAuth { let y: InnerAuth = InnerAuth {
auth_type: Arc::from(pa.auth_type.clone()), auth_type: Arc::from(pa.auth_type.clone()),
auth_cred: Arc::from(pa.auth_cred.clone()), auth_cred: Arc::from(pa.auth_cred.clone().unwrap_or_default()),
}; };
config.extraparams.authentication = Some(Arc::from(y)); config.extraparams.authentication = Some(Arc::from(y));
} }
@@ -191,7 +193,7 @@ async fn populate_file_upstreams(config: &mut Configuration, parsed: &Config) {
if let Some(pa) = &path_config.authorization { if let Some(pa) = &path_config.authorization {
let y: InnerAuth = InnerAuth { let y: InnerAuth = InnerAuth {
auth_type: Arc::from(pa.auth_type.clone()), auth_type: Arc::from(pa.auth_type.clone()),
auth_cred: Arc::from(pa.auth_cred.clone()), auth_cred: Arc::from(pa.auth_cred.clone().unwrap_or_default()),
}; };
path_auth = Some(Arc::from(y)); path_auth = Some(Arc::from(y));
} }

View File

@@ -77,7 +77,7 @@ pub struct Auth {
#[serde(rename = "type")] #[serde(rename = "type")]
pub auth_type: String, pub auth_type: String,
#[serde(rename = "data")] #[serde(rename = "data")]
pub auth_cred: String, pub auth_cred: Option<String>,
} }
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Debug, Default, Serialize, Deserialize)]
pub struct PathConfig { pub struct PathConfig {

View File

@@ -85,7 +85,7 @@ impl ProxyHttp for LB {
None => return Ok(false), None => return Ok(false),
Some(ref innermap) => { Some(ref innermap) => {
if let Some(auth) = _ctx.extraparams.authentication.as_ref().or(innermap.authorization.as_ref()) { if let Some(auth) = _ctx.extraparams.authentication.as_ref().or(innermap.authorization.as_ref()) {
if !authenticate(&auth.auth_type, &auth.auth_cred, session).await { if !authenticate(&auth, session).await {
let _ = session.respond_error(401).await; let _ = session.respond_error(401).await;
warn!("Forbidden: {:?}, {}", session.client_addr(), session.req_header().uri.path()); warn!("Forbidden: {:?}, {}", session.client_addr(), session.req_header().uri.path());
return Ok(true); return Ok(true);