wasmcloud_secrets_client/
lib.rs1use async_nats::HeaderMap;
2use nkeys::XKey;
3use wasmcloud_secrets_types::{
4 Secret, SecretRequest, SecretResponse, RESPONSE_XKEY, WASMCLOUD_HOST_XKEY,
5};
6
7const DEFAULT_API_VERSION: &str = "v1alpha1";
9
10#[derive(Debug, thiserror::Error)]
12pub enum SecretClientError {
13 #[error("failed to convert server xkey: {0}")]
14 ConvertServerXkey(String),
15 #[error("failed to parse server xkey: {0}")]
16 ParseServerXkey(nkeys::error::Error),
17 #[error("failed to fetch server xkey: {0}")]
18 RequestServerXkey(async_nats::RequestError),
19 #[error("invalid xkey: {0}")]
20 InvalidXkey(nkeys::error::Error),
21 #[error("failed to seal secret request: {0}")]
22 SealSecretRequest(nkeys::error::Error),
23 #[error("failed to send secret request: {0}")]
24 SendSecretRequest(async_nats::RequestError),
25 #[error("failed to serialize secret request: {0}")]
26 SerializeSecretRequest(serde_json::error::Error),
27 #[error("failed to parse xkey from server response: {0}")]
28 ParseServerResponseXkey(nkeys::error::Error),
29 #[error("failed to open secret response: {0}")]
30 OpenSecretResponse(nkeys::error::Error),
31 #[error("failed to deserialize secret response: {0}")]
32 DeserializeSecretResponse(serde_json::error::Error),
33 #[error("server error: {0}")]
34 Server(String),
35 #[error("missing secret: {0}")]
36 MissingSecret(String),
37}
38
39#[derive(Debug)]
44struct SecretsTopic(String);
45
46impl SecretsTopic {
47 pub(crate) fn new(prefix: &str, backend: &str, api_version: Option<&str>) -> Self {
48 let version = api_version.unwrap_or(DEFAULT_API_VERSION);
49 Self(format!("{prefix}.{version}.{backend}"))
50 }
51
52 pub fn get(&self) -> String {
53 format!("{}.{}", self.0, "get")
54 }
55
56 pub fn server_xkey(&self) -> String {
57 format!("{}.{}", self.0, "server_xkey")
58 }
59}
60
61#[derive(Debug)]
63pub struct Client {
64 client: async_nats::Client,
66 topic: SecretsTopic,
68 server_xkey: XKey,
70}
71
72impl Client {
73 pub async fn new(
75 backend: &str,
76 prefix: &str,
77 nats_client: async_nats::Client,
78 ) -> Result<Self, SecretClientError> {
79 Self::new_with_version(backend, prefix, nats_client, None).await
80 }
81
82 pub async fn new_with_version(
84 backend: &str,
85 prefix: &str,
86 nats_client: async_nats::Client,
87 api_version: Option<&str>,
88 ) -> Result<Self, SecretClientError> {
89 let secrets_topic = SecretsTopic::new(prefix, backend, api_version);
90
91 let resp = nats_client
93 .request(secrets_topic.server_xkey(), "".into())
94 .await
95 .map_err(SecretClientError::RequestServerXkey)?;
96 let s = std::str::from_utf8(&resp.payload)
97 .map_err(|e| SecretClientError::ConvertServerXkey(e.to_string()))?;
98 let server_xkey = XKey::from_public_key(s).map_err(SecretClientError::ParseServerXkey)?;
99
100 Ok(Self {
101 client: nats_client,
102 topic: secrets_topic,
103 server_xkey,
104 })
105 }
106
107 pub async fn get(
109 &self,
110 secret_request: SecretRequest,
111 request_xkey: XKey,
112 ) -> Result<Secret, SecretClientError> {
113 if let Err(e) = request_xkey.seed() {
115 return Err(SecretClientError::InvalidXkey(e));
116 }
117
118 let request = serde_json::to_string(&secret_request)
119 .map_err(SecretClientError::SerializeSecretRequest)?;
120 let encrypted_request = request_xkey
121 .seal(request.as_bytes(), &self.server_xkey)
122 .map_err(SecretClientError::SealSecretRequest)?;
123
124 let response = self
125 .client
126 .request_with_headers(
127 self.topic.get(),
128 self.request_headers(request_xkey.public_key()),
129 encrypted_request.into(),
130 )
131 .await
132 .map_err(SecretClientError::SendSecretRequest)?;
133
134 let headers = response.headers.unwrap_or_default();
135 let Some(response_xkey_header) = headers.get(RESPONSE_XKEY) else {
139 let sr: SecretResponse = serde_json::from_slice(&response.payload)
140 .map_err(SecretClientError::DeserializeSecretResponse)?;
141
142 if let Some(error) = sr.error {
143 return Err(SecretClientError::Server(error.to_string()));
144 }
145 return Err(SecretClientError::Server(
146 "unhandled server error (the server errored without explanation)".into(),
147 ));
148 };
149
150 let response_xkey = XKey::from_public_key(response_xkey_header.as_str())
151 .map_err(SecretClientError::ParseServerResponseXkey)?;
152
153 let decrypted = request_xkey
154 .open(&response.payload, &response_xkey)
155 .map_err(SecretClientError::OpenSecretResponse)?;
156
157 let sr: SecretResponse = serde_json::from_slice(&decrypted)
158 .map_err(SecretClientError::DeserializeSecretResponse)?;
159
160 sr.secret.ok_or_else(|| {
161 SecretClientError::MissingSecret(format!(
162 "no secret found with name [{}]",
163 secret_request.key
164 ))
165 })
166 }
167
168 fn request_headers(&self, pubkey: String) -> HeaderMap {
170 let mut headers = HeaderMap::new();
171 headers.insert(WASMCLOUD_HOST_XKEY, pubkey.as_str());
172 headers
173 }
174}