1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
//!
//! Strongbox clients
//!
use crate::{Client, Error};
use std::collections::HashMap;

use crate::Result;

/// Certificate API
pub mod tls;

const SBOX_VAULTS: &str = "v1/state/strongbox/vaults";

/// A Strobox vault can contain one or more `Secret` key value stores.
pub struct Vault {
    client: Client,
    vault_url: String,
}

impl Vault {
    /// List all secret stores
    pub async fn list(client: &Client) -> Result<Vec<String>> {
        let resp: serde_json::Value = client.get_json(SBOX_VAULTS, Some(&[("keys", "")])).await?;

        resp.as_array()
            .ok_or_else(|| crate::Error::API("Expected an array".into()))?
            .iter()
            .inspect(|s| tracing::debug!("{:#?}", s))
            .map(|s| {
                s.as_str()
                    .map(str::to_string)
                    .ok_or_else(|| crate::Error::API("Expected a name".into()))
            })
            .collect()
    }

    pub(crate) async fn open(client: &Client, vault: &str) -> Result<Self> {
        let vault_url = format!("{}/{}", SBOX_VAULTS, vault);
        tracing::debug!("Opening vault at path: {}", vault_url);
        // Try to get the sbox vault
        let _vault: serde_json::Value = client.get_json(&vault_url, None).await?;
        Ok(Self {
            client: client.clone(),
            vault_url,
        })
    }

    /// Open a secret
    pub async fn open_secrets(&self, name: &str) -> Result<Secrets> {
        let map_url = format!("{}/secrets/{}", self.vault_url, name);

        let json: serde_json::Value = self.client.get_json(&map_url, None).await?;

        let kv = json
            .as_object()
            .ok_or_else(|| Error::general("expected a JSON object in secrets"))?;

        let mut cache = HashMap::new();
        if let Some(data) = kv.get("dict").and_then(serde_json::Value::as_object) {
            for (k, v) in data {
                cache.insert(
                    k.clone(),
                    v.as_str()
                        .ok_or_else(|| Error::general("Expected secret value to be a string"))?
                        .to_string(),
                );
            }
        }

        tracing::debug!("Successfully loaded {}", name);

        Ok(Secrets { cache })
    }
}

/// Strongbox key value map
#[derive(Clone)]
pub struct Secrets {
    cache: HashMap<String, String>,
}

impl Secrets {
    /// Try to get a value
    #[must_use]
    pub fn get(&self, key: &str) -> Option<&String> {
        self.cache.get(key)
    }
}

impl std::fmt::Debug for Secrets {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_list().entries(self.cache.keys()).finish()
    }
}