1use anyhow::{ensure, Context as _};
2use async_trait::async_trait;
3use nkeys::XKey;
4use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer};
5use std::collections::HashMap;
6use wascap::jwt::{validate_token, CapabilityProvider, Component, Host};
7
8mod errors;
9pub use crate::errors::*;
10
11pub const SECRET_API_VERSION: &str = "v1alpha1";
13
14pub const WASMCLOUD_HOST_XKEY: &str = "WasmCloud-Host-Xkey";
17pub const RESPONSE_XKEY: &str = "Server-Response-Xkey";
18
19pub const SECRET_TYPE: &str = "secret.wasmcloud.dev/v1alpha1";
23
24pub const SECRET_POLICY_PROPERTIES_TYPE: &str = "properties.secret.wasmcloud.dev/v1alpha1";
27
28pub const SECRET_PREFIX: &str = "SECRET";
30
31#[derive(Serialize, Deserialize, Default)]
33pub struct Context {
34 pub entity_jwt: String,
36 pub host_jwt: String,
38 pub application: Application,
40}
41
42#[derive(Serialize, Deserialize, Default)]
44pub struct Application {
45 #[serde(default)]
47 pub name: Option<String>,
48
49 #[serde(default)]
53 pub policy: String,
54}
55
56impl Context {
57 pub fn valid_claims(&self) -> Result<(), ContextValidationError> {
59 let component_valid = Self::valid_component(&self.entity_jwt);
60 let provider_valid = Self::valid_provider(&self.entity_jwt);
61 if component_valid.is_err() && provider_valid.is_err() {
62 if let Err(e) = component_valid {
63 return Err(ContextValidationError::InvalidComponentJWT(e.to_string()));
64 } else {
65 return Err(ContextValidationError::InvalidProviderJWT(
66 provider_valid.unwrap_err().to_string(),
67 ));
68 }
69 }
70
71 if Self::valid_host(&self.host_jwt).is_err() {
72 return Err(ContextValidationError::InvalidHostJWT(
73 Self::valid_host(&self.host_jwt).unwrap_err().to_string(),
74 ));
75 }
76 Ok(())
77 }
78
79 fn valid_component(token: &str) -> anyhow::Result<()> {
80 let v = validate_token::<Component>(token)?;
81 ensure!(!v.expired, "token expired at `{}`", v.expires_human);
82 ensure!(
83 !v.cannot_use_yet,
84 "token cannot be used before `{}`",
85 v.not_before_human
86 );
87 ensure!(v.signature_valid, "signature is not valid");
88 Ok(())
89 }
90
91 fn valid_provider(token: &str) -> anyhow::Result<()> {
92 let v = validate_token::<CapabilityProvider>(token)?;
93 ensure!(!v.expired, "token expired at `{}`", v.expires_human);
94 ensure!(
95 !v.cannot_use_yet,
96 "token cannot be used before `{}`",
97 v.not_before_human
98 );
99 ensure!(v.signature_valid, "signature is not valid");
100
101 Ok(())
102 }
103
104 fn valid_host(token: &str) -> anyhow::Result<()> {
105 let v = validate_token::<Host>(token)?;
106 ensure!(!v.expired, "token expired at `{}`", v.expires_human);
107 ensure!(
108 !v.cannot_use_yet,
109 "token cannot be used before `{}`",
110 v.not_before_human
111 );
112 ensure!(v.signature_valid, "signature is not valid");
113 Ok(())
114 }
115}
116
117#[derive(Serialize, Deserialize)]
123pub struct SecretRequest {
124 pub key: String,
129 pub field: Option<String>,
130 pub version: Option<String>,
132 pub context: Context,
133}
134
135#[derive(Serialize, Deserialize, Default)]
138pub struct SecretResponse {
139 #[serde(skip_serializing_if = "Option::is_none")]
140 pub secret: Option<Secret>,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub error: Option<GetSecretError>,
143}
144
145#[derive(Serialize, Deserialize, Default)]
147pub struct Secret {
148 pub version: String,
149 pub string_secret: Option<String>,
150 pub binary_secret: Option<Vec<u8>>,
151}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct SecretConfig {
156 pub name: String,
158 pub backend: String,
160 pub key: String,
162 pub field: Option<String>,
164 pub version: Option<String>,
166 pub policy: Policy,
169
170 pub secret_type: String,
175}
176
177impl SecretConfig {
178 pub fn new(
179 name: String,
180 backend: String,
181 key: String,
182 field: Option<String>,
183 version: Option<String>,
184 policy_properties: HashMap<String, serde_json::Value>,
185 ) -> Self {
186 Self {
187 name,
188 backend,
189 key,
190 field,
191 version,
192 policy: Policy::new(policy_properties),
193 secret_type: SECRET_TYPE.to_string(),
194 }
195 }
196
197 pub fn try_into_request(
204 self,
205 entity_jwt: &str,
206 host_jwt: &str,
207 application_name: Option<&String>,
208 ) -> Result<SecretRequest, anyhow::Error> {
209 Ok(SecretRequest {
210 key: self.key,
211 field: self.field,
212 version: self.version,
213 context: Context {
214 entity_jwt: entity_jwt.to_string(),
215 host_jwt: host_jwt.to_string(),
216 application: Application {
217 name: application_name.cloned(),
218 policy: serde_json::to_string(&self.policy)
219 .context("failed to serialize secret policy as string")?,
220 },
221 },
222 })
223 }
224}
225
226impl TryInto<HashMap<String, String>> for SecretConfig {
230 type Error = anyhow::Error;
231
232 fn try_into(self) -> Result<HashMap<String, String>, Self::Error> {
244 let mut map = HashMap::from([
245 ("name".into(), self.name),
246 ("type".into(), self.secret_type),
247 ("backend".into(), self.backend),
248 ("key".into(), self.key),
249 ]);
250 if let Some(field) = self.field {
251 map.insert("field".to_string(), field);
252 }
253 if let Some(version) = self.version {
254 map.insert("version".to_string(), version);
255 }
256
257 map.insert(
258 "policy".to_string(),
259 serde_json::to_string(&self.policy).context("failed to serialize policy string")?,
260 );
261 Ok(map)
262 }
263}
264
265impl Serialize for SecretConfig {
268 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
269 where
270 S: Serializer,
271 {
272 let field_count = if self.version.is_some() { 6 } else { 5 };
273 let mut state = serializer.serialize_struct("SecretReference", field_count)?;
274 state.serialize_field("name", &self.name)?;
275 state.serialize_field("backend", &self.backend)?;
276 state.serialize_field("key", &self.key)?;
277 if let Some(v) = self.version.as_ref() {
278 state.serialize_field("version", v)?;
279 }
280
281 let policy_json = serde_json::to_string(&self.policy).map_err(serde::ser::Error::custom)?;
283 state.serialize_field("policy", &policy_json)?;
284 state.serialize_field("type", &self.secret_type)?;
285
286 state.end()
287 }
288}
289
290impl<'de> Deserialize<'de> for SecretConfig {
291 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
292 where
293 D: Deserializer<'de>,
294 {
295 #[derive(Deserialize)]
296 struct Helper {
297 name: String,
298 backend: String,
299 key: String,
300 field: Option<String>,
301 version: Option<String>,
302 policy: String,
303 #[serde(rename = "type")]
304 ty: String,
305 }
306
307 let helper = Helper::deserialize(deserializer)?;
308
309 let policy: Policy =
311 serde_json::from_str(&helper.policy).map_err(serde::de::Error::custom)?;
312
313 Ok(SecretConfig {
314 name: helper.name,
315 backend: helper.backend,
316 key: helper.key,
317 field: helper.field,
318 version: helper.version,
319 policy,
320 secret_type: helper.ty,
321 })
322 }
323}
324
325#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
326pub struct Policy {
327 #[serde(rename = "type")]
328 policy_type: String,
329 properties: HashMap<String, serde_json::Value>,
330}
331
332impl Default for Policy {
333 fn default() -> Self {
334 Self {
335 policy_type: SECRET_POLICY_PROPERTIES_TYPE.to_string(),
336 properties: Default::default(),
337 }
338 }
339}
340
341impl Policy {
342 pub fn new(properties: HashMap<String, serde_json::Value>) -> Self {
344 Self {
345 properties,
346 ..Default::default()
347 }
348 }
349}
350
351#[async_trait]
352pub trait SecretsServer {
353 async fn get(&self, request: SecretRequest) -> Result<SecretResponse, GetSecretError>;
355
356 fn server_xkey(&self) -> XKey;
358}
359
360#[cfg(test)]
361mod test {
362 use std::collections::HashMap;
363 #[test]
364 fn test_secret_config_hashmap_try_into() {
365 let properties = HashMap::from([(
366 String::from("key"),
367 serde_json::Value::String("value".to_string()),
368 )]);
369 let secret_config = crate::SecretConfig::new(
370 "name".to_string(),
371 "backend".to_string(),
372 "key".to_string(),
373 Some("field".to_string()),
374 Some("version".to_string()),
375 properties,
376 );
377
378 let map: HashMap<String, String> = secret_config
379 .clone()
380 .try_into()
381 .expect("should be able to convert to hashmap");
382
383 assert_eq!(map.get("name"), Some(&secret_config.name));
384 assert_eq!(map.get("type"), Some(&secret_config.secret_type));
385 assert_eq!(map.get("backend"), Some(&secret_config.backend));
386 assert_eq!(map.get("key"), Some(&secret_config.key));
387 assert_eq!(map.get("field"), secret_config.field.as_ref());
388 assert_eq!(map.get("version"), secret_config.version.as_ref());
389 assert_eq!(
390 map.get("policy"),
391 Some(
392 &serde_json::to_string(&secret_config.policy)
393 .expect("should be able to serialize policy")
394 )
395 );
396 }
397}