pub mod oidc;
#[cfg(test)]
mod tests;
use async_trait::async_trait;
use std::{fmt, sync::Arc};
use serde::Deserialize;
use crate::core::{ProxyError, ProxyRequest, ProxyResponse};
use crate::security::oidc::{OidcConfig, OidcProvider};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SecurityStage {
Pre,
Post,
Both,
}
impl SecurityStage {
pub const fn is_pre(self) -> bool { matches!(self, Self::Pre | Self::Both) }
pub const fn is_post(self) -> bool { matches!(self, Self::Post | Self::Both) }
}
#[async_trait]
pub trait SecurityProvider: fmt::Debug + Send + Sync {
fn stage(&self) -> SecurityStage;
fn name(&self) -> &str;
async fn pre(
&self,
request: ProxyRequest,
) -> Result<ProxyRequest, ProxyError> {
log::trace!("Security provider '{}' skipping pre-auth (default implementation)", self.name());
Ok(request)
}
async fn post(
&self,
_request: ProxyRequest,
response: ProxyResponse,
) -> Result<ProxyResponse, ProxyError> {
log::trace!("Security provider '{}' skipping post-auth (default implementation)", self.name());
Ok(response)
}
}
#[derive(Debug)]
pub struct SecurityChain {
providers: Vec<Arc<dyn SecurityProvider>>,
bypass_routes: Vec<String>,
}
impl SecurityChain {
pub fn new(bypass_routes: Vec<String>) -> Self {
Self { providers: Vec::new(), bypass_routes }
}
pub async fn from_configs(cfgs: Vec<ProviderConfig>) -> Result<Self, ProxyError> {
let mut chain = SecurityChain { providers: Vec::new(), bypass_routes: Vec::new() };
log::debug!("Building security chain from {} provider configs", cfgs.len());
for c in cfgs {
match c {
ProviderConfig::Oidc { config } => {
log::debug!("Initializing OIDC provider with issuer: {}", config.issuer_uri);
match OidcProvider::discover(config).await {
Ok(p) => {
log::info!("Successfully initialized OIDC provider");
chain.add(Arc::new(p));
},
Err(e) => {
log::error!("Failed to initialize OIDC provider: {}", e);
return Err(e);
}
}
}
}
}
Ok(chain)
}
pub fn add(&mut self, p: Arc<dyn SecurityProvider>) { self.providers.push(p); }
fn is_bypassed(&self, path: &str) -> bool {
let bypassed = self.bypass_routes.iter().any(|p| path.starts_with(p));
if bypassed {
log::debug!("Security bypass for path: {}", path);
}
bypassed
}
pub async fn apply_pre(
&self,
mut req: ProxyRequest,
) -> Result<ProxyRequest, ProxyError> {
if self.is_bypassed(&req.path) {
return Ok(req);
}
log::trace!("Applying security pre-auth chain with {} providers", self.providers.len());
for p in &self.providers {
if p.stage().is_pre() {
log::trace!("Running pre-auth provider: {}", p.name());
match p.pre(req).await {
Ok(new_req) => {
req = new_req;
},
Err(e) => {
let err = ProxyError::SecurityError(format!("{}: {}", p.name(), e));
log::error!("Security pre-auth failed: {}", err);
return Err(err);
}
}
}
}
Ok(req)
}
pub async fn apply_post(
&self,
req: ProxyRequest,
mut resp: ProxyResponse,
) -> Result<ProxyResponse, ProxyError> {
if self.is_bypassed(&req.path) {
return Ok(resp);
}
log::trace!("Applying security post-auth chain with {} providers", self.providers.len());
for p in &self.providers {
if p.stage().is_post() {
log::trace!("Running post-auth provider: {}", p.name());
match p.post(req.clone(), resp).await {
Ok(new_resp) => {
resp = new_resp;
},
Err(e) => {
let err = ProxyError::SecurityError(format!("{}: {}", p.name(), e));
log::error!("Security post-auth failed: {}", err);
return Err(err);
}
}
}
}
Ok(resp)
}
}
#[derive(Debug, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum ProviderConfig {
Oidc { config: OidcConfig },
}