Skip to main content

basil_core/core/backend/
vault.rs

1//! Vault-compatible (`HashiCorp` Vault or `OpenBao`) `transit` backend,
2//! authenticated with a **static token** (`X-Vault-Token`).
3//!
4//! All the transit wire logic lives in [`super::transit::TransitClient`]; this
5//! backend just supplies a fixed token. For SPIFFE/SVID-based auth against the
6//! same engine, see [`super::spiffe::SpiffeVaultBackend`].
7
8use async_trait::async_trait;
9use zeroize::Zeroizing;
10
11use basil_proto::{AeadAlgorithm, CiphertextEnvelope, KeyMaterial, KeyType};
12
13use super::pki::PkiClient;
14use super::transit::{TransitClient, transit_aead_type};
15use super::{
16    Backend, BackendError, KeyMetadata, KvValue, NewKey, PublicKey, SignOptions, X509Bundle,
17    X509Svid,
18};
19
20pub struct VaultBackend {
21    transit: TransitClient,
22    pki: PkiClient,
23    token: String,
24}
25
26impl VaultBackend {
27    /// Build a backend talking to the vault at `addr` with `token`, using the
28    /// transit engine mounted at `mount`.
29    pub fn new(
30        addr: impl Into<String>,
31        token: impl Into<String>,
32        mount: impl Into<String>,
33    ) -> Result<Self, BackendError> {
34        crate::ensure_crypto_provider();
35        let http = reqwest::Client::builder()
36            .build()
37            .map_err(|e| BackendError::Transport(e.to_string()))?;
38        let addr = addr.into();
39        Ok(Self {
40            transit: TransitClient::new(http.clone(), &addr, &mount.into()),
41            pki: PkiClient::new(http, &addr),
42            token: token.into(),
43        })
44    }
45}
46
47#[async_trait]
48impl Backend for VaultBackend {
49    fn kind(&self) -> &'static str {
50        "vault"
51    }
52
53    async fn new_key(&self, key_type: KeyType) -> Result<NewKey, BackendError> {
54        self.transit.new_key(&self.token, key_type).await
55    }
56
57    async fn create_named_key(
58        &self,
59        key_id: &str,
60        key_type: KeyType,
61    ) -> Result<NewKey, BackendError> {
62        self.transit
63            .create_named_key(&self.token, key_id, key_type)
64            .await
65    }
66
67    async fn create_named_aead(
68        &self,
69        key_id: &str,
70        aead: AeadAlgorithm,
71    ) -> Result<(), BackendError> {
72        self.transit
73            .create_named_aead(&self.token, key_id, transit_aead_type(aead))
74            .await
75    }
76
77    async fn public_key(&self, key_id: &str) -> Result<Vec<u8>, BackendError> {
78        self.transit.read_public_key(&self.token, key_id).await
79    }
80
81    async fn public_key_with_meta(&self, key_id: &str) -> Result<PublicKey, BackendError> {
82        self.transit
83            .read_public_key_with_meta(&self.token, key_id)
84            .await
85    }
86
87    async fn key_metadata(&self, key_id: &str) -> Result<KeyMetadata, BackendError> {
88        self.transit.read_key_metadata(&self.token, key_id).await
89    }
90
91    async fn public_keys(
92        &self,
93        key_id: &str,
94    ) -> Result<std::collections::BTreeMap<u32, Vec<u8>>, BackendError> {
95        self.transit.read_public_keys(&self.token, key_id).await
96    }
97
98    async fn import(
99        &self,
100        key_id: &str,
101        key_type: KeyType,
102        material: &KeyMaterial,
103    ) -> Result<NewKey, BackendError> {
104        self.transit
105            .import(&self.token, key_id, key_type, material)
106            .await
107    }
108
109    async fn sign(&self, key_id: &str, message: &[u8]) -> Result<Vec<u8>, BackendError> {
110        self.transit.sign(&self.token, key_id, message).await
111    }
112
113    async fn sign_with_options(
114        &self,
115        key_id: &str,
116        message: &[u8],
117        options: SignOptions,
118    ) -> Result<Vec<u8>, BackendError> {
119        self.transit
120            .sign_with_options(&self.token, key_id, message, options)
121            .await
122    }
123
124    async fn verify(
125        &self,
126        key_id: &str,
127        message: &[u8],
128        signature: &[u8],
129    ) -> Result<bool, BackendError> {
130        self.transit
131            .verify(&self.token, key_id, message, signature)
132            .await
133    }
134
135    async fn verify_with_options(
136        &self,
137        key_id: &str,
138        message: &[u8],
139        signature: &[u8],
140        options: SignOptions,
141    ) -> Result<bool, BackendError> {
142        self.transit
143            .verify_with_options(&self.token, key_id, message, signature, options)
144            .await
145    }
146
147    async fn encrypt(
148        &self,
149        key_id: &str,
150        algorithm: AeadAlgorithm,
151        plaintext: &[u8],
152        aad: Option<&[u8]>,
153    ) -> Result<CiphertextEnvelope, BackendError> {
154        self.transit
155            .encrypt(&self.token, key_id, algorithm, plaintext, aad)
156            .await
157    }
158
159    async fn decrypt(
160        &self,
161        key_id: &str,
162        envelope: &CiphertextEnvelope,
163        aad: Option<&[u8]>,
164    ) -> Result<Vec<u8>, BackendError> {
165        self.transit
166            .decrypt(&self.token, key_id, envelope, aad)
167            .await
168    }
169
170    async fn rotate(&self, key_id: &str) -> Result<u32, BackendError> {
171        self.transit.rotate(&self.token, key_id).await
172    }
173
174    async fn kv_get(&self, key_id: &str, version: Option<u32>) -> Result<KvValue, BackendError> {
175        self.transit.kv_get(&self.token, key_id, version).await
176    }
177
178    async fn kv_get_secret(
179        &self,
180        key_id: &str,
181        version: Option<u32>,
182    ) -> Result<Zeroizing<Vec<u8>>, BackendError> {
183        self.transit
184            .kv_get_secret(&self.token, key_id, version)
185            .await
186    }
187
188    async fn kv_put(&self, key_id: &str, value: &[u8]) -> Result<u32, BackendError> {
189        self.transit.kv_put(&self.token, key_id, value).await
190    }
191
192    async fn configure_versions(
193        &self,
194        key_id: &str,
195        min_decryption_version: Option<u32>,
196        min_available_version: Option<u32>,
197    ) -> Result<(), BackendError> {
198        self.transit
199            .configure_versions(
200                &self.token,
201                key_id,
202                min_decryption_version,
203                min_available_version,
204            )
205            .await
206    }
207
208    async fn issue_x509_svid(
209        &self,
210        key_id: &str,
211        spiffe_id: &str,
212        ttl_seconds: u64,
213    ) -> Result<X509Svid, BackendError> {
214        self.pki
215            .issue_x509_svid(&self.token, key_id, spiffe_id, ttl_seconds)
216            .await
217    }
218
219    async fn issue_x509_cert(
220        &self,
221        key_id: &str,
222        request: &super::X509CertRequest,
223    ) -> Result<X509Svid, BackendError> {
224        self.pki.issue_x509_cert(&self.token, key_id, request).await
225    }
226
227    async fn x509_bundle(&self, key_id: &str) -> Result<X509Bundle, BackendError> {
228        self.pki.x509_bundle(&self.token, key_id).await
229    }
230}