1pub mod basic;
11pub mod oidc;
12
13#[cfg(test)]
14#[path = "../../tests/unit/security/tests.rs"]
15mod tests;
16
17use crate::core::{ProxyError, ProxyRequest, ProxyResponse};
18use crate::security::basic::{BasicAuthConfig, BasicAuthProvider};
19use crate::security::oidc::{OidcConfig, OidcProvider};
20use crate::{debug_fmt, error_fmt, trace_fmt};
21use async_trait::async_trait;
22use once_cell::sync::Lazy;
23use serde::Deserialize;
24use std::collections::HashMap;
25use std::future::Future;
26use std::pin::Pin;
27use std::sync::RwLock as StdRwLock;
28use std::{fmt, sync::Arc};
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum SecurityStage {
33 Pre,
34 Post,
35 Both,
36}
37
38impl SecurityStage {
39 pub const fn is_pre(self) -> bool {
40 matches!(self, Self::Pre | Self::Both)
41 }
42 pub const fn is_post(self) -> bool {
43 matches!(self, Self::Post | Self::Both)
44 }
45}
46
47#[async_trait]
49pub trait SecurityProvider: fmt::Debug + Send + Sync {
50 fn stage(&self) -> SecurityStage;
52 fn name(&self) -> &str;
54
55 async fn pre(&self, request: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
57 trace_fmt!(
58 "SecurityChain",
59 "Security provider '{}' skipping pre-auth (default implementation)",
60 self.name()
61 );
62 Ok(request)
63 }
64
65 async fn post(
67 &self,
68 _request: ProxyRequest,
69 response: ProxyResponse,
70 ) -> Result<ProxyResponse, ProxyError> {
71 trace_fmt!(
72 "SecurityChain",
73 "Security provider '{}' skipping post-auth (default implementation)",
74 self.name()
75 );
76 Ok(response)
77 }
78}
79
80#[derive(Debug)]
82pub struct SecurityChain {
83 providers: Vec<Arc<dyn SecurityProvider>>,
84}
85
86impl Default for SecurityChain {
87 fn default() -> Self {
88 Self::new()
89 }
90}
91
92impl SecurityChain {
93 pub fn new() -> Self {
94 Self {
95 providers: Vec::new(),
96 }
97 }
98
99 pub async fn from_configs(cfgs: Vec<ProviderConfig>) -> Result<Self, ProxyError> {
101 let mut chain = SecurityChain::new();
102
103 debug_fmt!(
104 "SecurityChain",
105 "Building security chain from {} provider configs",
106 cfgs.len()
107 );
108
109 for c in cfgs {
110 let provider = SecurityProviderFactory::create_provider(&c.type_, c.config).await?;
111 chain.add(provider);
112 }
113
114 Ok(chain)
115 }
116
117 pub fn add(&mut self, p: Arc<dyn SecurityProvider>) {
118 self.providers.push(p);
119 }
120
121 pub async fn apply_pre(&self, mut req: ProxyRequest) -> Result<ProxyRequest, ProxyError> {
122 trace_fmt!(
123 "SecurityChain",
124 "Applying security pre-auth chain with {} providers",
125 self.providers.len()
126 );
127
128 for p in &self.providers {
129 if p.stage().is_pre() {
130 trace_fmt!("SecurityChain", "Running pre-auth provider: {}", p.name());
131 match p.pre(req).await {
132 Ok(new_req) => {
133 req = new_req;
134 }
135 Err(e) => {
136 let err = ProxyError::SecurityError(format!("{}: {}", p.name(), e));
137 error_fmt!("SecurityChain", "Security pre-auth failed: {}", err);
138 return Err(err);
139 }
140 }
141 }
142 }
143 Ok(req)
144 }
145
146 pub async fn apply_post(
147 &self,
148 req: ProxyRequest,
149 mut resp: ProxyResponse,
150 ) -> Result<ProxyResponse, ProxyError> {
151 trace_fmt!(
152 "SecurityChain",
153 "Applying security post-auth chain with {} providers",
154 self.providers.len()
155 );
156
157 for p in &self.providers {
158 if p.stage().is_post() {
159 trace_fmt!("SecurityChain", "Running post-auth provider: {}", p.name());
160 match p.post(req.clone(), resp).await {
161 Ok(new_resp) => {
162 resp = new_resp;
163 }
164 Err(e) => {
165 let err = ProxyError::SecurityError(format!("{}: {}", p.name(), e));
166 error_fmt!("SecurityChain", "Security post-auth failed: {}", err);
167 return Err(err);
168 }
169 }
170 }
171 }
172 Ok(resp)
173 }
174}
175
176#[derive(Debug, Deserialize)]
177pub struct ProviderConfig {
178 #[serde(rename = "type")]
179 pub type_: String,
180 pub config: serde_json::Value,
181}
182
183pub type SecurityProviderConstructor =
187 fn(
188 serde_json::Value,
189 ) -> Pin<Box<dyn Future<Output = Result<Arc<dyn SecurityProvider>, ProxyError>> + Send>>;
190
191static SECURITY_PROVIDER_REGISTRY: Lazy<StdRwLock<HashMap<String, SecurityProviderConstructor>>> =
193 Lazy::new(|| StdRwLock::new(HashMap::new()));
194
195pub fn register_security_provider(name: &str, ctor: SecurityProviderConstructor) {
197 SECURITY_PROVIDER_REGISTRY
198 .write()
199 .expect("SECURITY_PROVIDER_REGISTRY poisoned")
200 .insert(name.to_string(), ctor);
201}
202
203fn get_registered_security_provider(name: &str) -> Option<SecurityProviderConstructor> {
205 SECURITY_PROVIDER_REGISTRY
206 .read()
207 .expect("SECURITY_PROVIDER_REGISTRY poisoned")
208 .get(name)
209 .copied()
210}
211
212#[derive(Debug)]
214pub struct SecurityProviderFactory;
215
216impl SecurityProviderFactory {
217 pub async fn create_provider(
219 provider_type: &str,
220 config: serde_json::Value,
221 ) -> Result<Arc<dyn SecurityProvider>, ProxyError> {
222 debug_fmt!(
223 "SecurityProviderFactory",
224 "Creating security provider of type '{}'",
225 provider_type
226 );
227 if let Some(ctor) = get_registered_security_provider(provider_type) {
228 return ctor(config).await;
229 }
230
231 match provider_type {
232 "oidc" => {
233 let oidc_config: OidcConfig = serde_json::from_value(config).map_err(|e| {
234 ProxyError::SecurityError(format!("Invalid OIDC provider config: {e}"))
235 })?;
236 let provider = OidcProvider::discover(oidc_config).await?;
237 Ok(Arc::new(provider))
238 }
239 "basic" => {
240 let basic_auth_config: BasicAuthConfig =
241 serde_json::from_value(config).map_err(|e| {
242 ProxyError::SecurityError(format!(
243 "Invalid Basic Auth provider config: {e}"
244 ))
245 })?;
246 let provider = BasicAuthProvider::new(basic_auth_config)?;
247 Ok(Arc::new(provider))
248 }
249 _ => Err(ProxyError::SecurityError(format!(
250 "Unknown security provider type: {provider_type}"
251 ))),
252 }
253 }
254}