use crate::error::Result;
use async_trait::async_trait;
use std::collections::HashMap;
pub mod age;
pub mod aws_kms;
pub mod aws_ps;
pub mod aws_sm;
pub mod azure_kms;
pub mod azure_sm;
pub mod bitwarden;
pub mod bitwarden_sm;
pub mod doppler;
pub mod fido2;
pub mod gcp_kms;
pub mod gcp_sm;
pub mod hw_encrypt;
pub mod infisical;
pub mod keepass;
pub mod keychain;
pub mod onepassword;
pub mod password_store;
pub mod passwordstate;
pub mod plain;
pub mod proton_pass;
pub mod resolved;
pub mod resolver;
pub mod secret_ref;
pub mod vault;
pub mod yubikey;
pub mod yubikey_usb;
pub use bitwarden::BitwardenBackend;
pub use resolver::resolve_provider_config;
pub use secret_ref::{OptionStringOrSecretRef, StringOrSecretRef};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProviderCapability {
Encryption,
RemoteStorage,
RemoteRead,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WizardCategory {
Local,
PasswordManager,
CloudKms,
CloudSecretsManager,
OsKeychain,
}
impl WizardCategory {
pub fn display_name(&self) -> &'static str {
match self {
Self::Local => "Local (easy to start)",
Self::PasswordManager => "Password Manager",
Self::CloudKms => "Cloud KMS",
Self::CloudSecretsManager => "Cloud Secrets Manager",
Self::OsKeychain => "OS Keychain",
}
}
pub fn description(&self) -> &'static str {
match self {
Self::Local => "Plain text or local encryption - no external dependencies",
Self::PasswordManager => {
"1Password, Bitwarden, Infisical - use your existing password manager"
}
Self::CloudKms => "AWS KMS, Azure Key Vault, GCP KMS - encrypt with cloud keys",
Self::CloudSecretsManager => {
"AWS, Azure, GCP, HashiCorp Vault - store secrets remotely"
}
Self::OsKeychain => "Use your operating system's secure keychain",
}
}
pub fn all() -> &'static [WizardCategory] {
&[
Self::Local,
Self::PasswordManager,
Self::CloudKms,
Self::CloudSecretsManager,
Self::OsKeychain,
]
}
}
#[derive(Debug, Clone)]
pub struct WizardField {
pub name: &'static str,
pub label: &'static str,
pub placeholder: &'static str,
pub required: bool,
}
#[derive(Debug, Clone)]
pub struct WizardInfo {
pub provider_type: &'static str,
pub display_name: &'static str,
pub description: &'static str,
pub category: WizardCategory,
pub setup_instructions: &'static str,
pub default_name: &'static str,
pub fields: &'static [WizardField],
}
mod generated {
pub(super) mod providers_config {
include!(concat!(env!("OUT_DIR"), "/generated/providers_config.rs"));
}
pub(super) mod providers_methods {
include!(concat!(env!("OUT_DIR"), "/generated/providers_methods.rs"));
}
pub(super) mod providers_instantiate {
use super::super::{
age, aws_kms, aws_ps, aws_sm, azure_kms, azure_sm, bitwarden, bitwarden_sm, doppler,
fido2, gcp_kms, gcp_sm, infisical, keepass, keychain, onepassword, password_store,
passwordstate, plain, proton_pass, vault, yubikey,
};
include!(concat!(
env!("OUT_DIR"),
"/generated/providers_instantiate.rs"
));
}
pub(super) mod providers_wizard {
include!(concat!(env!("OUT_DIR"), "/generated/providers_wizard.rs"));
}
pub(super) mod providers_resolver {
include!(concat!(env!("OUT_DIR"), "/generated/providers_resolver.rs"));
}
}
pub use generated::providers_config::{ProviderConfig, ResolvedProviderConfig};
pub use generated::providers_instantiate::get_provider_from_resolved;
pub use generated::providers_wizard::ALL_WIZARD_INFO;
#[async_trait]
pub trait Provider: Send + Sync {
async fn get_secret(&self, value: &str) -> Result<String>;
async fn get_secrets_batch(
&self,
secrets: &[(String, String)],
) -> HashMap<String, Result<String>> {
get_secrets_concurrent(self, secrets, 10).await
}
async fn encrypt(&self, _value: &str) -> Result<String> {
Err(crate::error::FnoxError::Provider(
"This provider does not support encryption".to_string(),
))
}
async fn put_secret(&self, _key: &str, value: &str) -> Result<String> {
let capabilities = self.capabilities();
if capabilities.contains(&ProviderCapability::Encryption) {
self.encrypt(value).await
} else if capabilities.contains(&ProviderCapability::RemoteStorage) {
Err(crate::error::FnoxError::Provider(
"Remote storage provider must implement put_secret".to_string(),
))
} else {
Err(crate::error::FnoxError::Provider(
"This provider does not support storing secrets".to_string(),
))
}
}
fn capabilities(&self) -> Vec<ProviderCapability> {
vec![ProviderCapability::RemoteRead]
}
async fn test_connection(&self) -> Result<()> {
Ok(())
}
}
pub async fn get_secrets_concurrent(
provider: &(impl Provider + ?Sized),
secrets: &[(String, String)],
concurrency: usize,
) -> HashMap<String, Result<String>> {
use futures::stream::{self, StreamExt};
let secrets_vec: Vec<_> = secrets.to_vec();
let results: Vec<_> = stream::iter(secrets_vec)
.map(|(key, value)| async move {
let result = provider.get_secret(&value).await;
(key, result)
})
.buffer_unordered(concurrency)
.collect()
.await;
results.into_iter().collect()
}
impl ProviderConfig {
pub fn wizard_info_by_category(category: WizardCategory) -> Vec<&'static WizardInfo> {
ALL_WIZARD_INFO
.iter()
.filter(|info| info.category == category)
.collect()
}
}
pub async fn get_provider_resolved(
config: &crate::config::Config,
profile: &str,
provider_name: &str,
provider_config: &ProviderConfig,
) -> Result<Box<dyn Provider>> {
let resolved = resolve_provider_config(config, profile, provider_name, provider_config).await?;
get_provider_from_resolved(provider_name, &resolved)
}