foxy/security/
mod.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Security subsystem – runs before/after the main filter pipeline.
6//!
7//! Initially ships with *zero* providers; downstream crates add their own by
8//! implementing [`SecurityProvider`] and registering them on [`ProxyCore`].
9
10pub mod oidc;
11pub mod basic;
12
13#[cfg(test)]
14mod tests;
15
16use std::collections::HashMap;
17use std::future::Future;
18use std::pin::Pin;
19use async_trait::async_trait;
20use std::{fmt, sync::Arc};
21use once_cell::sync::Lazy;
22use serde::Deserialize;
23use std::sync::RwLock as StdRwLock;
24use crate::core::{ProxyError, ProxyRequest, ProxyResponse};
25use crate::{debug_fmt, error_fmt, trace_fmt};
26use crate::security::oidc::{OidcConfig, OidcProvider};
27use crate::security::basic::{BasicAuthConfig, BasicAuthProvider};
28
29/// When in the request/response lifecycle should a provider run?
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum SecurityStage {
32    Pre,
33    Post,
34    Both,
35}
36
37impl SecurityStage {
38    pub const fn is_pre(self) -> bool { matches!(self, Self::Pre | Self::Both) }
39    pub const fn is_post(self) -> bool { matches!(self, Self::Post | Self::Both) }
40}
41
42/// A unit of security logic – e.g. BasicAuth, JWT, OIDC, mTLS …
43#[async_trait]
44pub trait SecurityProvider: fmt::Debug + Send + Sync {
45    /// Which phase(s) does this provider participate in?
46    fn stage(&self) -> SecurityStage;
47    /// Name shown in logs / error messages.
48    fn name(&self) -> &str;
49
50    /// Optionally mutate/validate the inbound request *before* routing.
51    async fn pre(
52        &self,
53        request: ProxyRequest,
54    ) -> Result<ProxyRequest, ProxyError> {
55        trace_fmt!("SecurityChain", "Security provider '{}' skipping pre-auth (default implementation)", self.name());
56        Ok(request)
57    }
58
59    /// Optionally inspect/validate the response *after* the upstream call.
60    async fn post(
61        &self,
62        _request: ProxyRequest,
63        response: ProxyResponse,
64    ) -> Result<ProxyResponse, ProxyError> {
65        trace_fmt!("SecurityChain", "Security provider '{}' skipping post-auth (default implementation)", self.name());
66        Ok(response)
67    }
68}
69
70/// Executes all registered providers.
71#[derive(Debug)]
72pub struct SecurityChain {
73    providers: Vec<Arc<dyn SecurityProvider>>,
74}
75
76impl Default for SecurityChain {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82impl SecurityChain {
83    pub fn new() -> Self {
84        Self { providers: Vec::new() }
85    }
86
87    /// Build from raw config list.
88    pub async fn from_configs(cfgs: Vec<ProviderConfig>) -> Result<Self, ProxyError> {
89        let mut chain = SecurityChain::new();
90
91        debug_fmt!("SecurityChain", "Building security chain from {} provider configs", cfgs.len());
92
93        for c in cfgs {
94            let provider = SecurityProviderFactory::create_provider(&c.type_, c.config).await?;
95            chain.add(provider);
96        }
97
98        Ok(chain)
99    }
100
101    pub fn add(&mut self, p: Arc<dyn SecurityProvider>) { self.providers.push(p); }
102
103    pub async fn apply_pre(
104        &self,
105        mut req: ProxyRequest,
106    ) -> Result<ProxyRequest, ProxyError> {
107        trace_fmt!("SecurityChain", "Applying security pre-auth chain with {} providers", self.providers.len());
108
109        for p in &self.providers {
110            if p.stage().is_pre() {
111                trace_fmt!("SecurityChain", "Running pre-auth provider: {}", p.name());
112                match p.pre(req).await {
113                    Ok(new_req) => {
114                        req = new_req;
115                    },
116                    Err(e) => {
117                        let err = ProxyError::SecurityError(format!("{}: {}", p.name(), e));
118                        error_fmt!("SecurityChain", "Security pre-auth failed: {}", err);
119                        return Err(err);
120                    }
121                }
122            }
123        }
124        Ok(req)
125    }
126
127    pub async fn apply_post(
128        &self,
129        req: ProxyRequest,
130        mut resp: ProxyResponse,
131    ) -> Result<ProxyResponse, ProxyError> {
132        trace_fmt!("SecurityChain", "Applying security post-auth chain with {} providers", self.providers.len());
133
134        for p in &self.providers {
135            if p.stage().is_post() {
136                trace_fmt!("SecurityChain", "Running post-auth provider: {}", p.name());
137                match p.post(req.clone(), resp).await {
138                    Ok(new_resp) => {
139                        resp = new_resp;
140                    },
141                    Err(e) => {
142                        let err = ProxyError::SecurityError(format!("{}: {}", p.name(), e));
143                        error_fmt!("SecurityChain", "Security post-auth failed: {}", err);
144                        return Err(err);
145                    }
146                }
147            }
148        }
149        Ok(resp)
150    }
151}
152
153#[derive(Debug, Deserialize)]
154pub struct ProviderConfig {
155    #[serde(rename = "type")]
156    pub type_: String,
157    pub config: serde_json::Value,
158}
159
160
161/// Constructor signature every dynamic security provider must implement.
162/// Because providers may need to perform async operations (like OIDC discovery),
163/// the constructor returns a pinned, boxed future.
164pub type SecurityProviderConstructor =
165fn(serde_json::Value) -> Pin<Box<dyn Future<Output = Result<Arc<dyn SecurityProvider>, ProxyError>> + Send>>;
166
167
168/// Global registry for security providers.
169static SECURITY_PROVIDER_REGISTRY: Lazy<StdRwLock<HashMap<String, SecurityProviderConstructor>>> =
170    Lazy::new(|| StdRwLock::new(HashMap::new()));
171
172/// Register a security provider under a unique name.
173pub fn register_security_provider(name: &str, ctor: SecurityProviderConstructor) {
174    SECURITY_PROVIDER_REGISTRY
175        .write()
176        .expect("SECURITY_PROVIDER_REGISTRY poisoned")
177        .insert(name.to_string(), ctor);
178}
179
180/// Internal helper to get a registered security provider constructor.
181fn get_registered_security_provider(name: &str) -> Option<SecurityProviderConstructor> {
182    SECURITY_PROVIDER_REGISTRY
183        .read()
184        .expect("SECURITY_PROVIDER_REGISTRY poisoned")
185        .get(name)
186        .copied()
187}
188
189/// Factory for creating security providers based on configuration.
190#[derive(Debug)]
191pub struct SecurityProviderFactory;
192
193impl SecurityProviderFactory {
194    /// Create a security provider based on its type and configuration.
195    pub async fn create_provider(
196        provider_type: &str,
197        config: serde_json::Value,
198    ) -> Result<Arc<dyn SecurityProvider>, ProxyError> {
199        debug_fmt!("SecurityProviderFactory", "Creating security provider of type '{}'", provider_type);
200        if let Some(ctor) = get_registered_security_provider(provider_type) {
201            return ctor(config).await;
202        }
203
204        match provider_type {
205            "oidc" => {
206                let oidc_config: OidcConfig = serde_json::from_value(config)
207                    .map_err(|e| ProxyError::SecurityError(format!("Invalid OIDC provider config: {e}")))?;
208                let provider = OidcProvider::discover(oidc_config).await?;
209                Ok(Arc::new(provider))
210            },
211            "basic" => {
212                let basic_auth_config: BasicAuthConfig = serde_json::from_value(config)
213                    .map_err(|e| ProxyError::SecurityError(format!("Invalid Basic Auth provider config: {e}")))?;
214                let provider = BasicAuthProvider::new(basic_auth_config)?;
215                Ok(Arc::new(provider))
216            },
217            _ => Err(ProxyError::SecurityError(format!("Unknown security provider type: {provider_type}"))),
218        }
219    }
220}