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 provider_valid.is_err() {
72 if let Err(e) = component_valid {
73 return Err(ContextValidationError::InvalidComponentJWT(e.to_string()));
74 }
75 }
76
77 if Self::valid_host(&self.host_jwt).is_err() {
78 return Err(ContextValidationError::InvalidHostJWT(
79 Self::valid_host(&self.host_jwt).unwrap_err().to_string(),
80 ));
81 }
82 Ok(())
83 }
84
85 fn valid_component(token: &str) -> anyhow::Result<()> {
86 let v = validate_token::<Component>(token)?;
87 ensure!(!v.expired, "token expired at `{}`", v.expires_human);
88 ensure!(
89 !v.cannot_use_yet,
90 "token cannot be used before `{}`",
91 v.not_before_human
92 );
93 ensure!(v.signature_valid, "signature is not valid");
94 Ok(())
95 }
96
97 fn valid_provider(token: &str) -> anyhow::Result<()> {
98 let v = validate_token::<CapabilityProvider>(token)?;
99 ensure!(!v.expired, "token expired at `{}`", v.expires_human);
100 ensure!(
101 !v.cannot_use_yet,
102 "token cannot be used before `{}`",
103 v.not_before_human
104 );
105 ensure!(v.signature_valid, "signature is not valid");
106
107 Ok(())
108 }
109
110 fn valid_host(token: &str) -> anyhow::Result<()> {
111 let v = validate_token::<Host>(token)?;
112 ensure!(!v.expired, "token expired at `{}`", v.expires_human);
113 ensure!(
114 !v.cannot_use_yet,
115 "token cannot be used before `{}`",
116 v.not_before_human
117 );
118 ensure!(v.signature_valid, "signature is not valid");
119 Ok(())
120 }
121}
122
123#[derive(Serialize, Deserialize)]
129pub struct SecretRequest {
130 pub key: String,
135 pub field: Option<String>,
136 pub version: Option<String>,
138 pub context: Context,
139}
140
141#[derive(Serialize, Deserialize, Default)]
144pub struct SecretResponse {
145 #[serde(skip_serializing_if = "Option::is_none")]
146 pub secret: Option<Secret>,
147 #[serde(skip_serializing_if = "Option::is_none")]
148 pub error: Option<GetSecretError>,
149}
150
151#[derive(Serialize, Deserialize, Default)]
153pub struct Secret {
154 pub version: String,
155 pub string_secret: Option<String>,
156 pub binary_secret: Option<Vec<u8>>,
157}
158
159#[derive(Debug, Clone, PartialEq, Eq)]
161pub struct SecretConfig {
162 pub name: String,
164 pub backend: String,
166 pub key: String,
168 pub field: Option<String>,
170 pub version: Option<String>,
172 pub policy: Policy,
175
176 pub secret_type: String,
181}
182
183impl SecretConfig {
184 pub fn new(
185 name: String,
186 backend: String,
187 key: String,
188 field: Option<String>,
189 version: Option<String>,
190 policy_properties: HashMap<String, serde_json::Value>,
191 ) -> Self {
192 Self {
193 name,
194 backend,
195 key,
196 field,
197 version,
198 policy: Policy::new(policy_properties),
199 secret_type: SECRET_TYPE.to_string(),
200 }
201 }
202
203 pub fn try_into_request(
210 self,
211 entity_jwt: &str,
212 host_jwt: &str,
213 application_name: Option<&String>,
214 ) -> Result<SecretRequest, anyhow::Error> {
215 Ok(SecretRequest {
216 key: self.key,
217 field: self.field,
218 version: self.version,
219 context: Context {
220 entity_jwt: entity_jwt.to_string(),
221 host_jwt: host_jwt.to_string(),
222 application: Application {
223 name: application_name.cloned(),
224 policy: serde_json::to_string(&self.policy)
225 .context("failed to serialize secret policy as string")?,
226 },
227 },
228 })
229 }
230}
231
232impl TryInto<HashMap<String, String>> for SecretConfig {
236 type Error = anyhow::Error;
237
238 fn try_into(self) -> Result<HashMap<String, String>, Self::Error> {
250 let mut map = HashMap::from([
251 ("name".into(), self.name),
252 ("type".into(), self.secret_type),
253 ("backend".into(), self.backend),
254 ("key".into(), self.key),
255 ]);
256 if let Some(field) = self.field {
257 map.insert("field".to_string(), field);
258 }
259 if let Some(version) = self.version {
260 map.insert("version".to_string(), version);
261 }
262
263 map.insert(
264 "policy".to_string(),
265 serde_json::to_string(&self.policy).context("failed to serialize policy string")?,
266 );
267 Ok(map)
268 }
269}
270
271impl Serialize for SecretConfig {
274 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
275 where
276 S: Serializer,
277 {
278 let field_count = if self.version.is_some() { 6 } else { 5 };
279 let mut state = serializer.serialize_struct("SecretReference", field_count)?;
280 state.serialize_field("name", &self.name)?;
281 state.serialize_field("backend", &self.backend)?;
282 state.serialize_field("key", &self.key)?;
283 if let Some(v) = self.version.as_ref() {
284 state.serialize_field("version", v)?;
285 }
286
287 let policy_json = serde_json::to_string(&self.policy).map_err(serde::ser::Error::custom)?;
289 state.serialize_field("policy", &policy_json)?;
290 state.serialize_field("type", &self.secret_type)?;
291
292 state.end()
293 }
294}
295
296impl<'de> Deserialize<'de> for SecretConfig {
297 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
298 where
299 D: Deserializer<'de>,
300 {
301 #[derive(Deserialize)]
302 struct Helper {
303 name: String,
304 backend: String,
305 key: String,
306 field: Option<String>,
307 version: Option<String>,
308 policy: String,
309 #[serde(rename = "type")]
310 ty: String,
311 }
312
313 let helper = Helper::deserialize(deserializer)?;
314
315 let policy: Policy =
317 serde_json::from_str(&helper.policy).map_err(serde::de::Error::custom)?;
318
319 Ok(SecretConfig {
320 name: helper.name,
321 backend: helper.backend,
322 key: helper.key,
323 field: helper.field,
324 version: helper.version,
325 policy,
326 secret_type: helper.ty,
327 })
328 }
329}
330
331#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
332pub struct Policy {
333 #[serde(rename = "type")]
334 policy_type: String,
335 properties: HashMap<String, serde_json::Value>,
336}
337
338impl Default for Policy {
339 fn default() -> Self {
340 Self {
341 policy_type: SECRET_POLICY_PROPERTIES_TYPE.to_string(),
342 properties: Default::default(),
343 }
344 }
345}
346
347impl Policy {
348 pub fn new(properties: HashMap<String, serde_json::Value>) -> Self {
350 Self {
351 properties,
352 ..Default::default()
353 }
354 }
355}
356
357#[async_trait]
358pub trait SecretsServer {
359 async fn get(&self, request: SecretRequest) -> Result<SecretResponse, GetSecretError>;
361
362 fn server_xkey(&self) -> XKey;
364}
365
366#[cfg(test)]
367mod test {
368 use std::collections::HashMap;
369 #[test]
370 fn test_secret_config_hashmap_try_into() {
371 let properties = HashMap::from([(
372 String::from("key"),
373 serde_json::Value::String("value".to_string()),
374 )]);
375 let secret_config = crate::SecretConfig::new(
376 "name".to_string(),
377 "backend".to_string(),
378 "key".to_string(),
379 Some("field".to_string()),
380 Some("version".to_string()),
381 properties,
382 );
383
384 let map: HashMap<String, String> = secret_config
385 .clone()
386 .try_into()
387 .expect("should be able to convert to hashmap");
388
389 assert_eq!(map.get("name"), Some(&secret_config.name));
390 assert_eq!(map.get("type"), Some(&secret_config.secret_type));
391 assert_eq!(map.get("backend"), Some(&secret_config.backend));
392 assert_eq!(map.get("key"), Some(&secret_config.key));
393 assert_eq!(map.get("field"), secret_config.field.as_ref());
394 assert_eq!(map.get("version"), secret_config.version.as_ref());
395 assert_eq!(
396 map.get("policy"),
397 Some(
398 &serde_json::to_string(&secret_config.policy)
399 .expect("should be able to serialize policy")
400 )
401 );
402 }
403}