1
use std::fs::{
2
  self,
3
  File,
4
};
5
use std::io::Write as _;
6
use std::path::Path;
7

            
8
use anyhow::Context as _;
9
use clap::Args;
10
use pgp::{
11
  Deserializable as _,
12
  Message,
13
  SignedSecretKey,
14
};
15

            
16
use crate::secrets::context::Context;
17
use crate::secrets::vault::{
18
  verify_key,
19
  verify_vault,
20
};
21

            
22
#[derive(Debug, Args)]
23
pub struct ExportSecrets {
24
  #[arg(help = "The secret identifier or prefix to export")]
25
  path: String,
26

            
27
  #[arg(short, long, help = "The output file")]
28
  output: Option<String>,
29

            
30
  #[arg(short, long, help = "The path to the secret vault")]
31
  vault_location: Option<String>,
32

            
33
  #[arg(long, help = "The keys location")]
34
  keys_location: Option<String>,
35

            
36
  #[arg(short, long, help = "The key name")]
37
  key_name: Option<String>,
38
}
39

            
40
impl ExportSecrets {
41
  pub fn execute(&self, context: &Context) -> anyhow::Result<()> {
42
    let path: &str = &self.path.clone();
43
    let vault_location: &str = &self
44
      .vault_location
45
      .clone()
46
      .unwrap_or_else(|| context.vault_location());
47
    let keys_location: &str = &self
48
      .keys_location
49
      .clone()
50
      .unwrap_or_else(|| context.keys_location());
51
    let key_name: &str = &self.key_name.clone().unwrap_or_else(|| context.key_name());
52

            
53
    assert!(!path.is_empty(), "Path or prefix must be provided");
54
    assert!(!vault_location.is_empty(), "Vault location must be provided");
55
    assert!(!keys_location.is_empty(), "Keys location must be provided");
56
    assert!(!key_name.is_empty(), "Key name must be provided");
57

            
58
    verify_vault(vault_location)?;
59
    verify_key(keys_location, key_name)?;
60

            
61
    // Open the secret key file
62
    let key_name_with_ext = format!("{key_name}.key");
63
    let key_path = Path::new(keys_location).join(key_name_with_ext);
64
    let mut secret_key_string = File::open(key_path)?;
65
    let (signed_secret_key, _) = SignedSecretKey::from_armor_single(&mut secret_key_string)?;
66
    signed_secret_key.verify()?;
67

            
68
    let secret_path = Path::new(vault_location).join(path);
69
    let mut values = Vec::new();
70

            
71
    if secret_path.exists() && secret_path.is_dir() {
72
      // Check for file and subdirectories
73
      let entries = fs::read_dir(secret_path.clone())?
74
        .filter_map(Result::ok)
75
        .collect::<Vec<_>>();
76

            
77
      // Check for data files in the subdirectories
78
      for entry in entries {
79
        let data_path = if entry.path().is_dir() {
80
          entry.path().join("data.asc")
81
        } else {
82
          entry.path()
83
        };
84

            
85
        // Read the data file
86
        if data_path.exists() && data_path.is_file() {
87
          let mut data_file = File::open(data_path)?;
88
          let (message, _) = Message::from_armor_single(&mut data_file)?;
89
          let (decrypted_message, _) = message.decrypt(String::new, &[&signed_secret_key])?;
90
          let value = decrypted_message
91
            .get_literal()
92
            .ok_or_else(|| anyhow::anyhow!("Secret value is not a literal"))?
93
            .to_string()
94
            .context("Failed to read secret value")?;
95

            
96
          values.push(value);
97
        }
98
      }
99

            
100
      if values.is_empty() {
101
        println!("No secrets found for path: {}", path);
102
      } else {
103
        // Write the values to the output file if provided
104
        // Otherwise, print the values to stdout which can be redirected
105
        // to a file
106
        if let Some(output) = &self.output {
107
          let mut output_file = File::create(output)?;
108
          for value in values {
109
            writeln!(output_file, "{}", value)?;
110
          }
111
          output_file.flush()?;
112
        } else {
113
          for value in values {
114
            println!("{}", value);
115
          }
116
        }
117
      }
118
    } else {
119
      println!("Path does not exist or is not a directory");
120
    }
121

            
122
    Ok(())
123
  }
124
}