pub mod basic;
pub mod oidc;
#[cfg(test)]
#[path = "../../tests/unit/security/tests.rs"]
mod tests;
use crate::core::{ProxyError, ProxyRequest, ProxyResponse};
use crate::security::basic::{BasicAuthConfig, BasicAuthProvider};
use crate::security::oidc::{OidcConfig, OidcProvider};
use crate::{debug_fmt, error_fmt, trace_fmt};
use async_trait::async_trait;
use once_cell::sync::Lazy;
use serde::Deserialize;
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::RwLock as StdRwLock;
use std::{fmt, sync::Arc};
#[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> {
trace_fmt!(
"SecurityChain",
"Security provider '{}' skipping pre-auth (default implementation)",
self.name()
);
Ok(request)
}
async fn post(
&self,
_request: ProxyRequest,
response: ProxyResponse,
) -> Result<ProxyResponse, ProxyError> {
trace_fmt!(
"SecurityChain",
"Security provider '{}' skipping post-auth (default implementation)",
self.name()
);
Ok(response)
}
}
#[derive(Debug)]
pub struct SecurityChain {
providers: Vec<Arc<dyn SecurityProvider>>,
}
impl Default for SecurityChain {
fn default() -> Self {
Self::new()
}
}
impl SecurityChain {
pub fn new() -> Self {
Self {
providers: Vec::new(),
}
}
pub async fn from_configs(cfgs: Vec<ProviderConfig>) -> Result<Self, ProxyError> {
let mut chain = SecurityChain::new();
debug_fmt!(
"SecurityChain",
"Building security chain from {} provider configs",
cfgs.len()
);
for c in cfgs {
let provider = SecurityProviderFactory::create_provider(&c.type_, c.config).await?;
chain.add(provider);
}
Ok(chain)
}
pub fn add(&mut self, p: Arc<dyn SecurityProvider>) {
self.providers.push(p);
}
pub async fn apply_pre(&self, mut req: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
trace_fmt!(
"SecurityChain",
"Applying security pre-auth chain with {} providers",
self.providers.len()
);
for p in &self.providers {
if p.stage().is_pre() {
trace_fmt!("SecurityChain", "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));
error_fmt!("SecurityChain", "Security pre-auth failed: {}", err);
return Err(err);
}
}
}
}
Ok(req)
}
pub async fn apply_post(
&self,
req: ProxyRequest,
mut resp: ProxyResponse,
) -> Result<ProxyResponse, ProxyError> {
trace_fmt!(
"SecurityChain",
"Applying security post-auth chain with {} providers",
self.providers.len()
);
for p in &self.providers {
if p.stage().is_post() {
trace_fmt!("SecurityChain", "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));
error_fmt!("SecurityChain", "Security post-auth failed: {}", err);
return Err(err);
}
}
}
}
Ok(resp)
}
}
#[derive(Debug, Deserialize)]
pub struct ProviderConfig {
#[serde(rename = "type")]
pub type_: String,
pub config: serde_json::Value,
}
pub type SecurityProviderConstructor =
fn(
serde_json::Value,
) -> Pin<Box<dyn Future<Output = Result<Arc<dyn SecurityProvider>, ProxyError>> + Send>>;
static SECURITY_PROVIDER_REGISTRY: Lazy<StdRwLock<HashMap<String, SecurityProviderConstructor>>> =
Lazy::new(|| StdRwLock::new(HashMap::new()));
pub fn register_security_provider(name: &str, ctor: SecurityProviderConstructor) {
SECURITY_PROVIDER_REGISTRY
.write()
.expect("SECURITY_PROVIDER_REGISTRY poisoned")
.insert(name.to_string(), ctor);
}
fn get_registered_security_provider(name: &str) -> Option<SecurityProviderConstructor> {
SECURITY_PROVIDER_REGISTRY
.read()
.expect("SECURITY_PROVIDER_REGISTRY poisoned")
.get(name)
.copied()
}
#[derive(Debug)]
pub struct SecurityProviderFactory;
impl SecurityProviderFactory {
pub async fn create_provider(
provider_type: &str,
config: serde_json::Value,
) -> Result<Arc<dyn SecurityProvider>, ProxyError> {
debug_fmt!(
"SecurityProviderFactory",
"Creating security provider of type '{}'",
provider_type
);
if let Some(ctor) = get_registered_security_provider(provider_type) {
return ctor(config).await;
}
match provider_type {
"oidc" => {
let oidc_config: OidcConfig = serde_json::from_value(config).map_err(|e| {
ProxyError::SecurityError(format!("Invalid OIDC provider config: {e}"))
})?;
let provider = OidcProvider::discover(oidc_config).await?;
Ok(Arc::new(provider))
}
"basic" => {
let basic_auth_config: BasicAuthConfig =
serde_json::from_value(config).map_err(|e| {
ProxyError::SecurityError(format!(
"Invalid Basic Auth provider config: {e}"
))
})?;
let provider = BasicAuthProvider::new(basic_auth_config)?;
Ok(Arc::new(provider))
}
_ => Err(ProxyError::SecurityError(format!(
"Unknown security provider type: {provider_type}"
))),
}
}
}