Skip to main content

camel_component_wasm/
security_policy.rs

1use std::collections::HashMap;
2use std::path::Path;
3use std::sync::Arc;
4
5use async_trait::async_trait;
6
7use camel_api::security_policy::{AuthorizationDecision, SecurityPolicy, principal_from_exchange};
8use camel_api::{CamelError, Exchange};
9use camel_core::Registry;
10
11use camel_config::config::WasmSecurityPolicyConfig;
12
13use crate::config::WasmConfig;
14use crate::error::WasmError;
15use crate::security_policy_bindings::AuthorizationPolicy as AuthorizationPolicyGuest;
16use crate::serde_bridge;
17use crate::wasm_plugin_context::WasmPluginContext;
18
19pub struct WasmSecurityPolicy {
20    ctx: WasmPluginContext,
21}
22
23impl WasmSecurityPolicy {
24    pub async fn new(
25        module_path: impl AsRef<Path>,
26        wasm_config: crate::config::WasmConfig,
27        registry: Arc<std::sync::Mutex<Registry>>,
28        init_config: HashMap<String, String>,
29    ) -> Result<Self, WasmError> {
30        let ctx = WasmPluginContext::new(module_path, wasm_config, registry, init_config).await?;
31        Ok(Self { ctx })
32    }
33}
34
35#[async_trait]
36impl SecurityPolicy for WasmSecurityPolicy {
37    async fn evaluate(&self, exchange: &mut Exchange) -> Result<AuthorizationDecision, CamelError> {
38        let mut store = self.ctx.create_store(exchange.properties.clone());
39
40        let plugin = AuthorizationPolicyGuest::instantiate_async(
41            &mut store,
42            &self.ctx.component,
43            &self.ctx.linker,
44        )
45        .await
46        .map_err(|e| WasmError::InstantiationFailed(e.to_string()))?;
47
48        let wasm_exchange = serde_bridge::exchange_to_wasm(exchange)?;
49        let sp_exchange: crate::security_policy_bindings::camel::plugin::types::WasmExchange =
50            wasm_exchange.into();
51
52        let result = plugin
53            .call_evaluate(&mut store, &sp_exchange)
54            .await
55            .map_err(|e| self.ctx.classify_error(e))?;
56
57        match result {
58            Ok(None) => {
59                let principal = principal_from_exchange(exchange).ok_or_else(|| {
60                    CamelError::ProcessorError(
61                        "authorization policy granted but no principal found in exchange".into(),
62                    )
63                })?;
64                Ok(AuthorizationDecision::Granted { principal })
65            }
66            Ok(Some(reason)) => Ok(AuthorizationDecision::Denied {
67                reason,
68                required: vec![],
69                actual: vec![],
70            }),
71            Err(wasm_err) => Err(CamelError::ProcessorError(format!(
72                "wasm authorization policy evaluate failed: {wasm_err:?}"
73            ))),
74        }
75    }
76}
77
78pub async fn build_security_policy_registry(
79    policies: &HashMap<String, WasmSecurityPolicyConfig>,
80    registry: Arc<std::sync::Mutex<Registry>>,
81) -> Result<camel_auth::SecurityPolicyRegistry, WasmError> {
82    let policy_registry = camel_auth::SecurityPolicyRegistry::new();
83
84    for (name, policy_config) in policies {
85        let wasm_policy = WasmSecurityPolicy::new(
86            &policy_config.path,
87            WasmConfig::from_limits(&policy_config.limits),
88            registry.clone(),
89            policy_config.config.clone(),
90        )
91        .await
92        .map_err(|e| {
93            WasmError::InstantiationFailed(format!(
94                "failed to create WASM security policy '{}': {}",
95                name, e
96            ))
97        })?;
98
99        policy_registry.register(name.clone(), Arc::new(wasm_policy));
100    }
101
102    Ok(policy_registry)
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use std::sync::OnceLock;
109
110    fn test_tokio_handle() -> tokio::runtime::Handle {
111        static RT: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
112        RT.get_or_init(|| tokio::runtime::Runtime::new().expect("test runtime"))
113            .handle()
114            .clone()
115    }
116
117    #[tokio::test]
118    async fn test_wasm_security_policy_new_missing_file() {
119        let registry = Arc::new(std::sync::Mutex::new(Registry::new()));
120        let config = crate::config::WasmConfig::default();
121        let result =
122            WasmSecurityPolicy::new("/nonexistent/module.wasm", config, registry, HashMap::new())
123                .await;
124        let err = match result {
125            Ok(_) => panic!("expected error for non-existent module"),
126            Err(e) => e,
127        };
128        assert!(
129            matches!(err, WasmError::ModuleNotFound(_)),
130            "expected ModuleNotFound, got: {err:?}"
131        );
132    }
133
134    #[test]
135    fn test_wasm_security_policy_host_state_creation() {
136        let registry = Arc::new(std::sync::Mutex::new(Registry::new()));
137        let host_state = crate::runtime::WasmRuntime::create_host_state(
138            registry,
139            HashMap::new(),
140            crate::state_store::StateStore::new(),
141            test_tokio_handle(),
142            0,
143        );
144        assert!(host_state.properties.is_empty());
145    }
146
147    #[tokio::test]
148    async fn build_security_policy_registry_error_path_for_missing_module() {
149        let policies: HashMap<String, WasmSecurityPolicyConfig> = std::iter::once((
150            "missing".to_string(),
151            WasmSecurityPolicyConfig {
152                path: "/nonexistent/policy.wasm".into(),
153                limits: Default::default(),
154                config: Default::default(),
155            },
156        ))
157        .collect();
158        let registry = Arc::new(std::sync::Mutex::new(Registry::new()));
159        let result = build_security_policy_registry(&policies, registry).await;
160        let err = match result {
161            Ok(_) => panic!("expected error for missing module"),
162            Err(e) => e,
163        };
164        assert!(
165            matches!(err, WasmError::InstantiationFailed(_))
166                || matches!(err, WasmError::ModuleNotFound(_)),
167            "expected InstantiationFailed or ModuleNotFound, got: {err:?}"
168        );
169    }
170}