Skip to main content

bitwarden_access_connector/resolver/
mod.rs

1//! Per-target credential resolution. [`config::ConfigCredentialResolver`] resolves credentials:
2//! the config file wins per key, and the environment is the fallback.
3
4pub(crate) mod config;
5pub(crate) mod env;
6
7use std::collections::HashMap;
8
9use async_trait::async_trait;
10use bitwarden_pam_sealed::SealedTargetSystemKind;
11use bitwarden_sensitive_value::Sensitive;
12use uuid::Uuid;
13
14/// Resolved credentials keyed by suffix, the env var name after the `<TARGET_ID>_` prefix (e.g.
15/// `CLIENT_SECRET`).
16#[derive(Debug)]
17pub(crate) struct ResolvedCredentials {
18    inner: HashMap<String, Sensitive<String>>,
19}
20
21impl ResolvedCredentials {
22    pub(crate) fn new() -> Self {
23        Self {
24            inner: HashMap::new(),
25        }
26    }
27
28    pub(crate) fn insert(&mut self, key: String, value: String) {
29        self.inner.insert(key, Sensitive::from(value));
30    }
31
32    pub(crate) fn get(&self, key: &str) -> Option<&Sensitive<String>> {
33        self.inner.get(key)
34    }
35
36    pub(crate) fn iter(&self) -> impl Iterator<Item = (&String, &Sensitive<String>)> {
37        self.inner.iter()
38    }
39}
40
41impl Default for ResolvedCredentials {
42    fn default() -> Self {
43        Self::new()
44    }
45}
46
47/// Errors from resolving credentials for a target system.
48#[derive(Debug, thiserror::Error)]
49pub(crate) enum ResolveError {
50    /// Required credentials are missing. Carries their env var names only, never values, so it
51    /// is safe to report.
52    #[error("missing required credential variables: {}", .0.join(", "))]
53    Missing(Vec<String>),
54}
55
56/// Resolves credentials for a target system. `async` so an implementation can call an external
57/// secrets manager.
58#[async_trait]
59pub(crate) trait CredentialResolver: Send + Sync {
60    /// The resolved map contains at least the suffixes `kind` requires.
61    async fn resolve(
62        &self,
63        target_system_id: Uuid,
64        kind: SealedTargetSystemKind,
65    ) -> Result<ResolvedCredentials, ResolveError>;
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn resolved_credentials_get_and_insert() {
74        let mut creds = ResolvedCredentials::new();
75        assert!(creds.get("FOO").is_none());
76        creds.insert("FOO".to_string(), "bar".to_string());
77        assert!(creds.get("FOO").is_some());
78    }
79
80    #[test]
81    fn resolve_error_missing_contains_names() {
82        let names = vec!["TENANT_ID".to_string(), "CLIENT_SECRET".to_string()];
83        let err = ResolveError::Missing(names.clone());
84        let msg = err.to_string();
85        assert!(msg.contains("TENANT_ID"));
86        assert!(msg.contains("CLIENT_SECRET"));
87    }
88}