1pub mod binary;
78pub mod crypto;
79pub mod error;
80pub mod models;
81pub mod schema;
82pub mod vault;
83
84pub use crypto::SecretKey;
86pub use error::{Result, VaultError};
87pub use models::{Credential, CredentialType, Host, SecureString};
88pub use vault::Vault;
89
90pub use crypto::{
92 NONCE_SIZE, TAG_SIZE, KEY_SIZE,
93 VAULT_FORMAT_VERSION, VAULT_MAGIC,
94};
95
96pub use schema::CURRENT_VERSION as SCHEMA_VERSION;
98
99pub const VERSION: &str = env!("CARGO_PKG_VERSION");
101
102#[cfg(test)]
103mod integration_tests {
104 use super::*;
105 use tempfile::NamedTempFile;
106
107 #[test]
108 fn test_full_workflow() -> Result<()> {
109 let key = SecretKey::random();
111 let vault = Vault::new(key.clone())?;
112
113 let mut web_server = Host::new(
115 "Web Server".to_string(),
116 "web.example.com".to_string(),
117 22,
118 );
119 web_server = web_server.with_username("deploy".to_string());
120 web_server = web_server.with_startup_command("cd /var/www && source env.sh".to_string());
121 web_server.add_env_var("NODE_ENV".to_string(), "production".to_string());
122 web_server.add_env_var("PORT".to_string(), "3000".to_string());
123
124 let db_server = Host::new(
125 "Database Server".to_string(),
126 "db.example.com".to_string(),
127 22,
128 );
129
130 vault.add_host(&web_server)?;
131 vault.add_host(&db_server)?;
132
133 let ssh_key_cred = Credential::new_ssh_key(
135 "Deploy Key".to_string(),
136 CredentialType::OpenSsh,
137 "-----BEGIN OPENSSH PRIVATE KEY-----\ntest_key_data\n-----END OPENSSH PRIVATE KEY-----".to_string(),
138 Some("ssh-ed25519 AAAAC3... user@example.com".to_string()),
139 None,
140 );
141
142 let password_cred = Credential::new_username_password(
143 "Admin Password".to_string(),
144 "admin".to_string(),
145 "super_secure_password_123!".to_string(),
146 );
147
148 vault.add_credential(&ssh_key_cred)?;
149 vault.add_credential(&password_cred)?;
150
151 vault.link_credential_to_host(web_server.id, ssh_key_cred.id, true)?;
153 vault.link_credential_to_host(db_server.id, password_cred.id, true)?;
154
155 let hosts = vault.list_hosts()?;
157 assert_eq!(hosts.len(), 2);
158
159 let credentials = vault.list_credentials()?;
160 assert_eq!(credentials.len(), 2);
161
162 let web_server_creds = vault.get_host_credentials(web_server.id)?;
163 assert_eq!(web_server_creds.len(), 1);
164 assert_eq!(web_server_creds[0].0.name, "Deploy Key");
165 assert!(web_server_creds[0].1); let encrypted_bytes = vault.export_to_bytes()?;
169 assert!(!encrypted_bytes.is_empty());
170
171 let loaded_vault = Vault::load_from_bytes(&encrypted_bytes, key)?;
173
174 let loaded_hosts = loaded_vault.list_hosts()?;
175 assert_eq!(loaded_hosts.len(), 2);
176
177 let loaded_web = loaded_hosts.iter().find(|h| h.name == "Web Server").unwrap();
179 assert_eq!(loaded_web.environment_variables.get("NODE_ENV"), Some(&"production".to_string()));
180 assert_eq!(loaded_web.environment_variables.get("PORT"), Some(&"3000".to_string()));
181 assert_eq!(loaded_web.startup_command.as_deref(), Some("cd /var/www && source env.sh"));
182
183 Ok(())
184 }
185
186 #[test]
187 fn test_file_roundtrip() -> Result<()> {
188 let temp_file = NamedTempFile::new().unwrap();
189 let path = temp_file.path();
190
191 let key = SecretKey::random();
193 let vault = Vault::new(key.clone())?;
194
195 let host = Host::new("Test Server".to_string(), "test.example.com".to_string(), 2222);
196 vault.add_host(&host)?;
197
198 let encrypted_data = vault.export_to_bytes()?;
199 std::fs::write(path, &encrypted_data)?;
200
201 let file_data = std::fs::read(path)?;
203 let loaded = Vault::load_from_bytes(&file_data, key)?;
204
205 let hosts = loaded.list_hosts()?;
206 assert_eq!(hosts.len(), 1);
207 assert_eq!(hosts[0].name, "Test Server");
208 assert_eq!(hosts[0].port, 2222);
209
210 Ok(())
211 }
212
213 #[test]
214 fn test_wrong_key() {
215 let correct_key = SecretKey::random();
216 let wrong_key = SecretKey::random();
217
218 let vault = Vault::new(correct_key).unwrap();
219 let bytes = vault.export_to_bytes().unwrap();
220
221 let result = Vault::load_from_bytes(&bytes, wrong_key);
222 assert!(matches!(result, Err(VaultError::InvalidKey)));
223 }
224
225 #[test]
226 fn test_update_operations() -> Result<()> {
227 let key = SecretKey::random();
228 let vault = Vault::new(key)?;
229
230 let mut host = Host::new("Server".to_string(), "example.com".to_string(), 22);
232 vault.add_host(&host)?;
233
234 host.name = "Updated Server".to_string();
235 host.port = 2222;
236 host.add_env_var("NEW_VAR".to_string(), "value".to_string());
237 vault.update_host(&host)?;
238
239 let updated = vault.get_host(host.id)?.unwrap();
240 assert_eq!(updated.name, "Updated Server");
241 assert_eq!(updated.port, 2222);
242 assert_eq!(updated.environment_variables.get("NEW_VAR"), Some(&"value".to_string()));
243
244 let mut cred = Credential::new_username_password(
246 "Cred".to_string(),
247 "user".to_string(),
248 "pass".to_string(),
249 );
250 vault.add_credential(&cred)?;
251
252 cred.name = "Updated Cred".to_string();
253 vault.update_credential(&cred)?;
254
255 let updated_cred = vault.get_credential(cred.id)?.unwrap();
256 assert_eq!(updated_cred.name, "Updated Cred");
257
258 Ok(())
259 }
260
261 #[test]
262 fn test_delete_operations() -> Result<()> {
263 let key = SecretKey::random();
264 let vault = Vault::new(key)?;
265
266 let host = Host::new("Server".to_string(), "example.com".to_string(), 22);
267 vault.add_host(&host)?;
268
269 let cred = Credential::new_username_password(
270 "Cred".to_string(),
271 "user".to_string(),
272 "pass".to_string(),
273 );
274 vault.add_credential(&cred)?;
275
276 vault.delete_host(host.id)?;
278 assert!(vault.get_host(host.id)?.is_none());
279
280 vault.delete_credential(cred.id)?;
282 assert!(vault.get_credential(cred.id)?.is_none());
283
284 Ok(())
285 }
286
287 #[test]
288 fn test_multiple_credentials_per_host() -> Result<()> {
289 let key = SecretKey::random();
290 let vault = Vault::new(key)?;
291
292 let host = Host::new("Server".to_string(), "example.com".to_string(), 22);
293 vault.add_host(&host)?;
294
295 let cred1 = Credential::new_username_password(
296 "Cred 1".to_string(),
297 "user1".to_string(),
298 "pass1".to_string(),
299 );
300 let cred2 = Credential::new_username_password(
301 "Cred 2".to_string(),
302 "user2".to_string(),
303 "pass2".to_string(),
304 );
305
306 vault.add_credential(&cred1)?;
307 vault.add_credential(&cred2)?;
308
309 vault.link_credential_to_host(host.id, cred1.id, true)?;
310 vault.link_credential_to_host(host.id, cred2.id, false)?;
311
312 let host_creds = vault.get_host_credentials(host.id)?;
313 assert_eq!(host_creds.len(), 2);
314
315 assert_eq!(host_creds[0].0.id, cred1.id);
317 assert!(host_creds[0].1);
318
319 Ok(())
320 }
321
322
323 #[test]
324 fn test_all_credential_types() -> Result<()> {
325 let key = SecretKey::random();
326 let vault = Vault::new(key)?;
327
328 let types = vec![
329 CredentialType::UsernamePassword,
330 CredentialType::Rsa,
331 CredentialType::OpenSsh,
332 CredentialType::Ed25519,
333 CredentialType::Ecdsa,
334 CredentialType::Certificate,
335 CredentialType::Custom,
336 ];
337
338 for cred_type in types {
339 let cred = Credential::new_ssh_key(
340 format!("{:?} Key", cred_type),
341 cred_type,
342 "test_private_key".to_string(),
343 Some("test_public_key".to_string()),
344 None,
345 );
346 vault.add_credential(&cred)?;
347
348 let retrieved = vault.get_credential(cred.id)?.unwrap();
349 assert_eq!(retrieved.credential_type, cred_type);
350 }
351
352 let all_creds = vault.list_credentials()?;
353 assert_eq!(all_creds.len(), 7);
354
355 Ok(())
356 }
357}