merka_vault/
database.rs

1//! Database module for the Merka Vault library
2//!
3//! This module implements the SQLite-based persistence layer for the Merka Vault library.
4//! It is primarily accessed through the actor module, which ensures proper coordination
5//! of database operations with vault operations.
6//!
7//! Architectural role:
8//! - Provides persistence for vault credentials and relationships
9//! - Should be accessed through the actor module rather than directly
10//! - Manages connection pooling and transaction handling
11
12use log::{debug, error, info, warn};
13use r2d2::{Pool, PooledConnection};
14use r2d2_sqlite::SqliteConnectionManager;
15use rusqlite::{params, Error as SQLiteError, Result as SQLiteResult};
16use serde::{Deserialize, Serialize};
17use std::collections::HashMap;
18use std::sync::Arc;
19
20/// The VaultCredentials struct represents stored vault credentials
21#[derive(Serialize, Deserialize, Debug, Clone, Default)]
22pub struct VaultCredentials {
23    // Root vault credentials
24    pub root_unseal_keys: Vec<String>,
25    pub root_token: String,
26    // Sub vault credentials
27    pub sub_token: String,
28    // Transit token for auto-unseal
29    pub transit_token: String,
30}
31
32/// Database connection pool type
33pub type DbPool = Pool<SqliteConnectionManager>;
34pub type DbConnection = PooledConnection<SqliteConnectionManager>;
35
36/// Database manager for merka-vault
37#[derive(Clone)]
38pub struct DatabaseManager {
39    pool: Arc<DbPool>,
40}
41
42impl DatabaseManager {
43    /// Create a new database manager with the specified database file
44    pub fn new(db_path: &str) -> Result<Self, r2d2::Error> {
45        let manager = SqliteConnectionManager::file(db_path);
46        let pool = Pool::new(manager)?;
47
48        // Initialize the database with required tables
49        let connection = pool.get()?;
50        Self::init_database(&connection).unwrap_or_else(|e| {
51            error!("Failed to initialize database: {}", e);
52        });
53
54        Ok(Self {
55            pool: Arc::new(pool),
56        })
57    }
58
59    /// Initialize the database with required tables
60    fn init_database(conn: &DbConnection) -> SQLiteResult<()> {
61        // Create tables for vault credentials
62        conn.execute(
63            "CREATE TABLE IF NOT EXISTS vault_credentials (
64                id INTEGER PRIMARY KEY,
65                root_unseal_keys TEXT NOT NULL,
66                root_token TEXT NOT NULL,
67                sub_token TEXT NOT NULL,
68                transit_token TEXT NOT NULL
69            )",
70            [],
71        )?;
72
73        // Create table for vault relationships (unsealer relationships)
74        conn.execute(
75            "CREATE TABLE IF NOT EXISTS vault_relationships (
76                id INTEGER PRIMARY KEY,
77                sub_addr TEXT NOT NULL UNIQUE,
78                root_addr TEXT NOT NULL
79            )",
80            [],
81        )?;
82
83        info!("Database initialized successfully");
84        Ok(())
85    }
86
87    /// Save vault credentials to the database
88    pub fn save_vault_credentials(&self, credentials: &VaultCredentials) -> SQLiteResult<()> {
89        let conn = self.pool.get().map_err(|e| {
90            error!("Failed to get database connection: {}", e);
91            SQLiteError::ExecuteReturnedResults
92        })?;
93
94        // Serialize the root_unseal_keys vector to JSON
95        let root_unseal_keys =
96            serde_json::to_string(&credentials.root_unseal_keys).map_err(|e| {
97                error!("Failed to serialize root_unseal_keys: {}", e);
98                SQLiteError::ExecuteReturnedResults
99            })?;
100
101        // Check if credentials already exist
102        let count: i64 = conn.query_row("SELECT COUNT(*) FROM vault_credentials", [], |row| {
103            row.get(0)
104        })?;
105
106        if count > 0 {
107            // Update existing credentials
108            conn.execute(
109                "UPDATE vault_credentials SET
110                    root_unseal_keys = ?,
111                    root_token = ?,
112                    sub_token = ?,
113                    transit_token = ?
114                WHERE id = 1",
115                params![
116                    root_unseal_keys,
117                    credentials.root_token,
118                    credentials.sub_token,
119                    credentials.transit_token
120                ],
121            )?;
122            debug!("Updated existing vault credentials in database");
123        } else {
124            // Insert new credentials
125            conn.execute(
126                "INSERT INTO vault_credentials (
127                    root_unseal_keys, root_token, sub_token, transit_token
128                ) VALUES (?, ?, ?, ?)",
129                params![
130                    root_unseal_keys,
131                    credentials.root_token,
132                    credentials.sub_token,
133                    credentials.transit_token
134                ],
135            )?;
136            debug!("Inserted new vault credentials into database");
137        }
138
139        info!("✅ Vault credentials successfully saved to database");
140        info!("  Root token length: {}", credentials.root_token.len());
141        info!("  Root unseal keys: {}", credentials.root_unseal_keys.len());
142        info!("  Sub token length: {}", credentials.sub_token.len());
143        info!(
144            "  Transit token length: {}",
145            credentials.transit_token.len()
146        );
147
148        Ok(())
149    }
150
151    /// Load vault credentials from the database
152    pub fn load_vault_credentials(&self) -> SQLiteResult<VaultCredentials> {
153        let conn = self.pool.get().map_err(|e| {
154            error!("Failed to get database connection: {}", e);
155            SQLiteError::ExecuteReturnedResults
156        })?;
157
158        let result = conn.query_row(
159            "SELECT root_unseal_keys, root_token, sub_token, transit_token FROM vault_credentials LIMIT 1",
160            [],
161            |row| {
162                let root_unseal_keys_json: String = row.get(0)?;
163
164                // Deserialize the root_unseal_keys JSON back to vector
165                let root_unseal_keys: Vec<String> = serde_json::from_str(&root_unseal_keys_json)
166                    .map_err(|e| {
167                        error!("Failed to deserialize root_unseal_keys: {}", e);
168                        SQLiteError::ExecuteReturnedResults
169                    })?;
170
171                Ok(VaultCredentials {
172                    root_unseal_keys,
173                    root_token: row.get(1)?,
174                    sub_token: row.get(2)?,
175                    transit_token: row.get(3)?,
176                })
177            },
178        );
179
180        match result {
181            Ok(credentials) => {
182                info!("Loaded vault credentials from database");
183                info!("  Root token length: {}", credentials.root_token.len());
184                info!("  Root unseal keys: {}", credentials.root_unseal_keys.len());
185                info!("  Sub token length: {}", credentials.sub_token.len());
186                info!(
187                    "  Transit token length: {}",
188                    credentials.transit_token.len()
189                );
190
191                Ok(credentials)
192            }
193            Err(e) => {
194                warn!("Failed to load vault credentials from database: {}", e);
195
196                // Return default credentials when none exist
197                if e == SQLiteError::QueryReturnedNoRows {
198                    warn!("No credentials found in database, returning default");
199                    return Ok(VaultCredentials::default());
200                }
201
202                Err(e)
203            }
204        }
205    }
206
207    /// Save an unsealer relationship to the database
208    pub fn save_unsealer_relationship(&self, sub_addr: &str, root_addr: &str) -> SQLiteResult<()> {
209        let conn = self.pool.get().map_err(|e| {
210            error!("Failed to get database connection: {}", e);
211            SQLiteError::ExecuteReturnedResults
212        })?;
213
214        // Use insert or replace to handle updates
215        conn.execute(
216            "INSERT OR REPLACE INTO vault_relationships (sub_addr, root_addr) VALUES (?, ?)",
217            params![sub_addr, root_addr],
218        )?;
219
220        info!(
221            "Saved unsealer relationship: sub={}, root={}",
222            sub_addr, root_addr
223        );
224
225        Ok(())
226    }
227
228    /// Load all unsealer relationships from the database
229    pub fn load_unsealer_relationships(&self) -> SQLiteResult<HashMap<String, String>> {
230        let conn = self.pool.get().map_err(|e| {
231            error!("Failed to get database connection: {}", e);
232            SQLiteError::ExecuteReturnedResults
233        })?;
234
235        let mut stmt = conn.prepare("SELECT sub_addr, root_addr FROM vault_relationships")?;
236
237        let rows = stmt.query_map([], |row| {
238            Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
239        })?;
240
241        let mut relationships = HashMap::new();
242        for row_result in rows {
243            match row_result {
244                Ok((sub_addr, root_addr)) => {
245                    relationships.insert(sub_addr, root_addr);
246                }
247                Err(e) => {
248                    warn!("Error reading relationship row: {}", e);
249                }
250            }
251        }
252
253        info!(
254            "Loaded {} unsealer relationships from database",
255            relationships.len()
256        );
257        Ok(relationships)
258    }
259
260    /// Delete an unsealer relationship from the database
261    pub fn delete_unsealer_relationship(&self, sub_addr: &str) -> SQLiteResult<()> {
262        let conn = self.pool.get().map_err(|e| {
263            error!("Failed to get database connection: {}", e);
264            SQLiteError::ExecuteReturnedResults
265        })?;
266
267        conn.execute(
268            "DELETE FROM vault_relationships WHERE sub_addr = ?",
269            params![sub_addr],
270        )?;
271
272        info!("Deleted unsealer relationship for sub={}", sub_addr);
273        Ok(())
274    }
275
276    /// Get pool for use in the application
277    pub fn get_pool(&self) -> Arc<DbPool> {
278        self.pool.clone()
279    }
280}