1use crate::error::Result;
2use async_trait::async_trait;
3use std::collections::HashMap;
4
5pub mod age;
7pub mod aws_kms;
8pub mod aws_ps;
9pub mod aws_sm;
10pub mod azure_kms;
11pub mod azure_sm;
12pub mod bitwarden;
13pub mod bitwarden_sm;
14pub mod doppler;
15#[cfg(not(target_env = "musl"))]
16pub mod fido2;
17pub mod foks;
18pub mod gcp_kms;
19pub mod gcp_sm;
20pub mod hw_encrypt;
21pub mod infisical;
22pub mod keepass;
23pub mod keychain;
24pub mod onepassword;
25pub mod password_store;
26pub mod passwordstate;
27pub mod plain;
28pub mod proton_pass;
29pub mod resolved;
30pub mod resolver;
31pub mod secret_ref;
32pub mod vault;
33pub mod yubikey;
34pub mod yubikey_usb;
35
36pub use bitwarden::BitwardenBackend;
37pub use resolver::resolve_provider_config;
38pub use secret_ref::{OptionStringOrSecretRef, StringOrSecretRef};
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum ProviderCapability {
43 Encryption,
45 RemoteStorage,
47 RemoteRead,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum WizardCategory {
54 Local,
55 PasswordManager,
56 CloudKms,
57 CloudSecretsManager,
58 OsKeychain,
59}
60
61impl WizardCategory {
62 pub fn display_name(&self) -> &'static str {
64 match self {
65 Self::Local => "Local (easy to start)",
66 Self::PasswordManager => "Password Manager",
67 Self::CloudKms => "Cloud KMS",
68 Self::CloudSecretsManager => "Cloud Secrets Manager",
69 Self::OsKeychain => "OS Keychain",
70 }
71 }
72
73 pub fn description(&self) -> &'static str {
75 match self {
76 Self::Local => "Plain text or local encryption - no external dependencies",
77 Self::PasswordManager => {
78 "1Password, Bitwarden, Infisical - use your existing password manager"
79 }
80 Self::CloudKms => "AWS KMS, Azure Key Vault, GCP KMS - encrypt with cloud keys",
81 Self::CloudSecretsManager => {
82 "AWS, Azure, GCP, HashiCorp Vault - store secrets remotely"
83 }
84 Self::OsKeychain => "Use your operating system's secure keychain",
85 }
86 }
87
88 pub fn all() -> &'static [WizardCategory] {
90 &[
91 Self::Local,
92 Self::PasswordManager,
93 Self::CloudKms,
94 Self::CloudSecretsManager,
95 Self::OsKeychain,
96 ]
97 }
98}
99
100#[derive(Debug, Clone)]
102pub struct WizardField {
103 pub name: &'static str,
105 pub label: &'static str,
107 pub placeholder: &'static str,
109 pub required: bool,
111}
112
113#[derive(Debug, Clone)]
115pub struct WizardInfo {
116 pub provider_type: &'static str,
118 pub display_name: &'static str,
120 pub description: &'static str,
122 pub category: WizardCategory,
124 pub setup_instructions: &'static str,
126 pub default_name: &'static str,
128 pub fields: &'static [WizardField],
130}
131
132mod generated {
134 pub(super) mod providers_config {
135 include!(concat!(env!("OUT_DIR"), "/generated/providers_config.rs"));
136 }
137 pub(super) mod providers_methods {
138 include!(concat!(env!("OUT_DIR"), "/generated/providers_methods.rs"));
139 }
140 pub(super) mod providers_instantiate {
141 #[cfg(not(target_env = "musl"))]
143 use super::super::fido2;
144 use super::super::{
145 age, aws_kms, aws_ps, aws_sm, azure_kms, azure_sm, bitwarden, bitwarden_sm, doppler,
146 foks, gcp_kms, gcp_sm, infisical, keepass, keychain, onepassword, password_store,
147 passwordstate, plain, proton_pass, vault, yubikey,
148 };
149 include!(concat!(
150 env!("OUT_DIR"),
151 "/generated/providers_instantiate.rs"
152 ));
153 }
154 pub(super) mod providers_wizard {
155 include!(concat!(env!("OUT_DIR"), "/generated/providers_wizard.rs"));
156 }
157 pub(super) mod providers_resolver {
158 include!(concat!(env!("OUT_DIR"), "/generated/providers_resolver.rs"));
159 }
160}
161
162pub use generated::providers_config::{ProviderConfig, ResolvedProviderConfig};
164pub use generated::providers_instantiate::get_provider_from_resolved;
165pub use generated::providers_wizard::ALL_WIZARD_INFO;
166
167#[async_trait]
168pub trait Provider: Send + Sync {
169 async fn get_secret(&self, value: &str) -> Result<String>;
171
172 async fn get_secrets_batch(
185 &self,
186 secrets: &[(String, String)],
187 ) -> HashMap<String, Result<String>> {
188 get_secrets_concurrent(self, secrets, 10).await
189 }
190
191 async fn encrypt(&self, _value: &str) -> Result<String> {
193 Err(crate::error::FnoxError::Provider(
195 "This provider does not support encryption".to_string(),
196 ))
197 }
198
199 async fn put_secret(&self, _key: &str, value: &str) -> Result<String> {
208 let capabilities = self.capabilities();
209
210 if capabilities.contains(&ProviderCapability::Encryption) {
211 self.encrypt(value).await
213 } else if capabilities.contains(&ProviderCapability::RemoteStorage) {
214 Err(crate::error::FnoxError::Provider(
216 "Remote storage provider must implement put_secret".to_string(),
217 ))
218 } else {
219 Err(crate::error::FnoxError::Provider(
221 "This provider does not support storing secrets".to_string(),
222 ))
223 }
224 }
225
226 fn capabilities(&self) -> Vec<ProviderCapability> {
228 vec![ProviderCapability::RemoteRead]
230 }
231
232 async fn test_connection(&self) -> Result<()> {
234 Ok(())
236 }
237}
238
239pub async fn get_secrets_concurrent(
244 provider: &(impl Provider + ?Sized),
245 secrets: &[(String, String)],
246 concurrency: usize,
247) -> HashMap<String, Result<String>> {
248 use futures::stream::{self, StreamExt};
249
250 let secrets_vec: Vec<_> = secrets.to_vec();
252
253 let results: Vec<_> = stream::iter(secrets_vec)
255 .map(|(key, value)| async move {
256 let result = provider.get_secret(&value).await;
257 (key, result)
258 })
259 .buffer_unordered(concurrency)
260 .collect()
261 .await;
262
263 results.into_iter().collect()
264}
265
266impl ProviderConfig {
267 pub fn wizard_info_by_category(category: WizardCategory) -> Vec<&'static WizardInfo> {
269 ALL_WIZARD_INFO
270 .iter()
271 .filter(|info| info.category == category)
272 .collect()
273 }
274}
275
276pub async fn get_provider_resolved(
284 config: &crate::config::Config,
285 profile: &str,
286 provider_name: &str,
287 provider_config: &ProviderConfig,
288) -> Result<Box<dyn Provider>> {
289 let resolved = resolve_provider_config(config, profile, provider_name, provider_config).await?;
290 get_provider_from_resolved(provider_name, &resolved)
291}