1use anyhow::Result;
11use async_trait::async_trait;
12use clap::{Parser, Subcommand};
13use std::fs;
14use tracing::info;
15
16use crate::interface::VaultInterface;
17use crate::vault::common::VaultStatus;
18use crate::vault::{UnsealResult, VaultError};
19
20use crate::vault::setup_root::{setup_root_vault, RootSetupConfig, RootSetupResult};
22use crate::vault::setup_sub::{setup_sub_vault, SubSetupConfig, SubSetupResult};
23use crate::vault::wizard::run_setup_wizard;
24
25#[derive(Parser)]
26#[command(
27 name = "merka-vault",
28 about = "Vault provisioning CLI (supports unseal, status, and multi-step setup)",
29 version = "0.2.0"
30)]
31pub struct Cli {
32 #[arg(long, default_value = "http://127.0.0.1:8200", env = "ROOT_VAULT_ADDR")]
34 pub vault_addr: String,
35
36 #[command(subcommand)]
37 pub command: Commands,
38}
39
40#[derive(Subcommand)]
41pub enum Commands {
42 List,
44
45 Unseal {
47 #[arg(long, value_name = "UNSEAL_KEY")]
49 keys: Vec<String>,
50
51 #[arg(long)]
53 keys_file: Option<String>,
54 },
55
56 Status {
58 #[arg(long)]
60 vault_addr: Option<String>,
61 },
62
63 Setup,
65
66 SetupRoot {
68 #[arg(long, default_value = "http://127.0.0.1:8200")]
70 root_addr: String,
71
72 #[arg(long, default_value_t = 1)]
74 secret_shares: u8,
75
76 #[arg(long, default_value_t = 1)]
77 secret_threshold: u8,
78
79 #[arg(long, default_value = "autounseal-key")]
81 key_name: String,
82
83 #[arg(long, default_value = "local")]
85 mode: String,
86
87 #[arg(long)]
89 output_file: Option<String>,
90 },
91
92 SetupSub {
94 #[arg(long, default_value = "http://127.0.0.1:8202")]
96 sub_addr: String,
97
98 #[arg(long, default_value = "example.com")]
100 domain: String,
101
102 #[arg(long, default_value = "8760h")]
104 ttl: String,
105
106 #[arg(long)]
108 root_addr: Option<String>,
109
110 #[arg(long)]
112 root_token: String,
113 },
114
115 GetTransitToken {
117 #[arg(long, default_value = "http://127.0.0.1:8200")]
119 root_addr: String,
120
121 #[arg(long)]
123 root_token: String,
124
125 #[arg(long, default_value = "autounseal-key")]
127 key_name: String,
128 },
129
130 Server {
132 #[arg(long, default_value = "127.0.0.1:8080")]
134 listen_addr: String,
135
136 #[arg(long, default_value = "http://127.0.0.1:8200")]
138 vault_addr: String,
139
140 #[arg(long, default_value = "merka_vault.db")]
142 db_path: String,
143 },
144}
145
146pub struct VaultCli;
150
151#[async_trait]
152impl VaultInterface for VaultCli {
153 async fn check_status(&self, addr: &str) -> Result<VaultStatus, VaultError> {
154 match crate::vault::common::check_vault_status(addr).await {
155 Ok(status) => Ok(status),
156 Err(err) => Err(VaultError::Api(format!("Status check error: {}", err))),
157 }
158 }
159
160 async fn unseal(&self, addr: &str, keys: Vec<String>) -> Result<UnsealResult, VaultError> {
161 match crate::vault::init::unseal_root_vault(addr, keys).await {
162 Ok(unseal_resp) => Ok(unseal_resp),
163 Err(err) => Err(VaultError::Api(format!("Unseal error: {}", err))),
164 }
165 }
166
167 async fn setup_root(
168 &self,
169 addr: &str,
170 secret_shares: u8,
171 secret_threshold: u8,
172 key_name: &str,
173 ) -> Result<String, VaultError> {
174 let config = RootSetupConfig {
175 root_addr: addr.to_string(),
176 secret_shares,
177 secret_threshold,
178 key_name: key_name.to_string(),
179 mode: "local".to_string(),
180 output_file: None,
181 };
182
183 match setup_root_vault(config).await {
184 Ok(RootSetupResult {
185 root_init,
186 unwrapped_token,
187 }) => {
188 info!(
189 "Root Vault setup is complete. Root token = {}",
190 root_init.root_token
191 );
192 Ok(unwrapped_token)
193 }
194 Err(e) => Err(VaultError::Api(format!("Root setup error: {}", e))),
195 }
196 }
197
198 async fn setup_sub(
199 &self,
200 root_addr: &str,
201 root_token: &str,
202 sub_addr: &str,
203 domain: &str,
204 ttl: &str,
205 ) -> Result<String, VaultError> {
206 let config = SubSetupConfig {
207 sub_addr: sub_addr.to_string(),
208 domain: domain.to_string(),
209 ttl: ttl.to_string(),
210 root_addr: root_addr.to_string(),
211 root_token: root_token.to_string(),
212 };
213
214 match setup_sub_vault(config).await {
215 Ok(SubSetupResult {
216 sub_init,
217 pki_roles,
218 }) => {
219 info!(
220 "Sub Vault is auto-unsealed: root token = {}",
221 sub_init.root_token
222 );
223 Ok(pki_roles.1)
224 }
225 Err(e) => Err(VaultError::Api(format!("Sub setup error: {}", e))),
226 }
227 }
228
229 async fn get_unwrapped_transit_token(
230 &self,
231 root_addr: &str,
232 root_token: &str,
233 key_name: &str,
234 ) -> Result<String, VaultError> {
235 if let Err(e) =
237 crate::vault::autounseal::setup_transit_autounseal(root_addr, root_token, key_name)
238 .await
239 {
240 return Err(VaultError::Api(format!(
241 "Failed to setup transit auto-unseal: {}",
242 e
243 )));
244 }
245
246 let wrap_ttl = "300s";
248 let wrapped_token = match crate::vault::transit::generate_wrapped_transit_token(
249 root_addr,
250 root_token,
251 "autounseal", wrap_ttl,
253 )
254 .await
255 {
256 Ok(token) => token,
257 Err(e) => {
258 return Err(VaultError::Api(format!(
259 "Failed to generate wrapped token: {}",
260 e
261 )))
262 }
263 };
264
265 let unwrapped_token =
267 match crate::vault::autounseal::unwrap_token(root_addr, &wrapped_token).await {
268 Ok(token) => token,
269 Err(e) => return Err(VaultError::Api(format!("Failed to unwrap token: {}", e))),
270 };
271
272 info!("Got unwrapped token for auto-unseal: {}", unwrapped_token);
273
274 Ok(unwrapped_token)
275 }
276}
277
278pub async fn run_cli() -> Result<()> {
286 let cli = Cli::parse();
288 let vault_cli = VaultCli;
289
290 match cli.command {
291 Commands::Status { vault_addr } => {
292 let addr = vault_addr.as_deref().unwrap_or(&cli.vault_addr);
294
295 info!("Checking Vault status at {}", addr);
296 let status = vault_cli.check_status(addr).await?;
297
298 println!("Vault Status for {}:", addr);
299 println!(" Initialized: {}", status.initialized);
300 println!(" Sealed: {}", status.sealed);
301 if status.sealed {
302 println!(" Seal Progress: {}/{}", status.progress, status.t);
303 }
304 println!(" Version: {}", status.version);
305 }
306
307 Commands::Unseal { keys, keys_file } => {
308 let mut all_keys = keys;
310 if let Some(file) = keys_file {
311 let file_content = fs::read_to_string(file)?;
312 let file_keys: Vec<String> = file_content
313 .lines()
314 .filter(|l| !l.trim().is_empty())
315 .map(|l| l.trim().to_string())
316 .collect();
317 all_keys.extend(file_keys);
318 }
319
320 info!("Unsealing Vault with {} keys", all_keys.len());
321 let unseal_result = vault_cli.unseal(&cli.vault_addr, all_keys).await?;
322
323 println!("Unseal operation result:");
324 println!(" Sealed: {}", unseal_result.sealed);
325 println!(
326 " Progress: {}/{}",
327 unseal_result.progress, unseal_result.threshold
328 );
329 if !unseal_result.sealed {
330 println!(" Success: Vault is now unsealed!");
331 } else {
332 println!(
333 " Additional keys needed: {} more",
334 unseal_result.threshold - unseal_result.progress
335 );
336 }
337 }
338
339 Commands::SetupRoot {
340 root_addr,
341 secret_shares,
342 secret_threshold,
343 key_name,
344 mode,
345 output_file,
346 } => {
347 let config = RootSetupConfig {
348 root_addr,
349 secret_shares,
350 secret_threshold,
351 key_name,
352 mode,
353 output_file,
354 };
355
356 let result = setup_root_vault(config).await?;
357 println!(
358 "Root setup complete! Root token: {}",
359 result.root_init.root_token
360 );
361 println!("Unwrapped token for sub-vault: {}", result.unwrapped_token);
362 }
363
364 Commands::SetupSub {
365 root_addr,
366 root_token,
367 sub_addr,
368 domain,
369 ttl,
370 } => {
371 let root_addr = root_addr.unwrap_or_else(|| cli.vault_addr.clone());
372 let config = SubSetupConfig {
373 sub_addr,
374 domain,
375 ttl,
376 root_addr,
377 root_token,
378 };
379
380 let result = setup_sub_vault(config).await?;
381 println!(
382 "Sub vault setup complete! Root token: {}",
383 result.sub_init.root_token
384 );
385 println!("PKI role: {}", result.pki_roles.1);
386 }
387
388 Commands::Setup => {
389 println!("Starting the interactive setup wizard...");
390 match run_setup_wizard().await {
391 Ok(result) => {
392 println!("Setup wizard completed successfully!");
393 if let Some(root_result) = result.root_result {
394 println!("Root Vault setup: SUCCESS");
395 println!("Root token: {}", root_result.root_init.root_token);
396 }
397 if let Some(sub_result) = result.sub_result {
398 println!("Sub Vault setup: SUCCESS");
399 println!("Sub token: {}", sub_result.sub_init.root_token);
400 }
401 }
402 Err(e) => {
403 println!("Setup wizard encountered an error: {}", e);
404 return Err(e);
405 }
406 }
407 }
408
409 Commands::GetTransitToken {
410 root_addr,
411 root_token,
412 key_name,
413 } => {
414 let token = vault_cli
415 .get_unwrapped_transit_token(&root_addr, &root_token, &key_name)
416 .await?;
417 println!("Unwrapped transit token: {}", token);
418 }
419
420 Commands::List => {
421 println!("Known vaults:");
424 println!(" Root vault: {}", cli.vault_addr);
425 match vault_cli.check_status(&cli.vault_addr).await {
426 Ok(status) => {
427 println!(" Initialized: {}", status.initialized);
428 println!(" Sealed: {}", status.sealed);
429 }
430 Err(e) => {
431 println!(" Status: Error - {}", e);
432 }
433 }
434
435 let sub_addr = cli.vault_addr.replace(":8200", ":8202");
437 match vault_cli.check_status(&sub_addr).await {
438 Ok(status) => {
439 println!(" Sub vault: {}", sub_addr);
440 println!(" Initialized: {}", status.initialized);
441 println!(" Sealed: {}", status.sealed);
442 }
443 Err(_) => {
444 println!(" Sub vault: Not detected at {}", sub_addr);
445 }
446 }
447 }
448
449 Commands::Server {
450 listen_addr,
451 vault_addr,
452 db_path,
453 } => {
454 println!("Starting the web server on {}...", listen_addr);
455 println!("Using vault at: {}", vault_addr);
456 println!("Database path: {}", db_path);
457
458 crate::server::start_server(&listen_addr, &vault_addr, &db_path).await?;
460 }
461 }
462
463 Ok(())
464}