227 lines
6.4 KiB
Rust
227 lines
6.4 KiB
Rust
mod auth;
|
|
mod crypto;
|
|
mod engines;
|
|
mod error;
|
|
mod logging;
|
|
mod seal;
|
|
mod server;
|
|
mod storage;
|
|
mod telemetry;
|
|
mod vault;
|
|
|
|
// Re-export all public types
|
|
use std::path::Path;
|
|
|
|
pub use auth::{AuthConfig, CedarAuthConfig, TokenAuthConfig};
|
|
pub use crypto::{AwsLcCryptoConfig, CryptoConfig, OpenSSLCryptoConfig, RustCryptoCryptoConfig};
|
|
pub use engines::{EngineConfig, EnginesConfig};
|
|
pub use error::{ConfigError, ConfigResult};
|
|
pub use logging::LoggingConfig;
|
|
pub use seal::{AutoUnsealConfig, SealConfig, ShamirSealConfig};
|
|
pub use server::ServerSection;
|
|
pub use storage::{
|
|
EtcdStorageConfig, FilesystemStorageConfig, PostgreSQLStorageConfig, StorageConfig,
|
|
SurrealDBStorageConfig,
|
|
};
|
|
pub use telemetry::TelemetryConfig;
|
|
pub use vault::VaultSection;
|
|
|
|
/// Main vault configuration
|
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
|
pub struct VaultConfig {
|
|
#[serde(default)]
|
|
pub vault: VaultSection,
|
|
|
|
#[serde(default)]
|
|
pub server: ServerSection,
|
|
|
|
pub storage: StorageConfig,
|
|
|
|
#[serde(default)]
|
|
pub crypto: CryptoConfig,
|
|
|
|
#[serde(default)]
|
|
pub seal: SealConfig,
|
|
|
|
#[serde(default)]
|
|
pub auth: AuthConfig,
|
|
|
|
#[serde(default)]
|
|
pub engines: EnginesConfig,
|
|
|
|
#[serde(default)]
|
|
pub logging: LoggingConfig,
|
|
|
|
#[serde(default)]
|
|
pub telemetry: TelemetryConfig,
|
|
}
|
|
|
|
impl VaultConfig {
|
|
/// Load configuration from TOML file
|
|
pub fn from_file<P: AsRef<Path>>(path: P) -> ConfigResult<Self> {
|
|
let content = std::fs::read_to_string(path)?;
|
|
Self::from_str(&content)
|
|
}
|
|
|
|
/// Load configuration from TOML string
|
|
#[allow(clippy::should_implement_trait)]
|
|
pub fn from_str(content: &str) -> ConfigResult<Self> {
|
|
let content = Self::substitute_env_vars(content)?;
|
|
let config: Self = toml::from_str(&content)?;
|
|
config.validate()?;
|
|
Ok(config)
|
|
}
|
|
|
|
/// Validate configuration
|
|
fn validate(&self) -> ConfigResult<()> {
|
|
// Validate crypto backend
|
|
let valid_crypto_backends = ["openssl", "aws-lc", "rustcrypto", "tongsuo"];
|
|
if !valid_crypto_backends.contains(&self.vault.crypto_backend.as_str()) {
|
|
return Err(ConfigError::UnknownCryptoBackend(
|
|
self.vault.crypto_backend.clone(),
|
|
));
|
|
}
|
|
|
|
// Validate storage backend
|
|
let valid_storage_backends = ["filesystem", "surrealdb", "etcd", "postgresql"];
|
|
if !valid_storage_backends.contains(&self.storage.backend.as_str()) {
|
|
return Err(ConfigError::UnknownStorageBackend(
|
|
self.storage.backend.clone(),
|
|
));
|
|
}
|
|
|
|
// Validate seal type
|
|
let valid_seal_types = ["shamir", "auto", "transit"];
|
|
if !valid_seal_types.contains(&self.seal.seal_type.as_str()) {
|
|
return Err(ConfigError::InvalidSealConfig(
|
|
"Invalid seal type".to_string(),
|
|
));
|
|
}
|
|
|
|
// Validate Shamir configuration
|
|
if self.seal.seal_type == "shamir" {
|
|
if self.seal.shamir.shares < 2 {
|
|
return Err(ConfigError::InvalidSealConfig(
|
|
"Shamir shares must be >= 2".to_string(),
|
|
));
|
|
}
|
|
if self.seal.shamir.threshold > self.seal.shamir.shares {
|
|
return Err(ConfigError::InvalidSealConfig(
|
|
"Shamir threshold must be <= shares".to_string(),
|
|
));
|
|
}
|
|
if self.seal.shamir.threshold < 1 {
|
|
return Err(ConfigError::InvalidSealConfig(
|
|
"Shamir threshold must be >= 1".to_string(),
|
|
));
|
|
}
|
|
}
|
|
|
|
// Validate engine mount paths (no duplicates, valid format)
|
|
let paths = self.engines.all_paths();
|
|
let mut seen = std::collections::HashSet::new();
|
|
for path in paths {
|
|
if !path.starts_with('/') || path == "/" {
|
|
return Err(ConfigError::InvalidMountPath(path));
|
|
}
|
|
if !seen.insert(path.clone()) {
|
|
return Err(ConfigError::DuplicateMountPath(path));
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Substitute environment variables in format ${VAR_NAME}
|
|
fn substitute_env_vars(content: &str) -> ConfigResult<String> {
|
|
let re = regex::Regex::new(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
|
|
.map_err(|e| ConfigError::Invalid(e.to_string()))?;
|
|
|
|
let result = re.replace_all(content, |caps: ®ex::Captures| {
|
|
let var_name = &caps[1];
|
|
std::env::var(var_name).unwrap_or_else(|_| format!("${{{}}}", var_name))
|
|
});
|
|
|
|
// Check if any variables remain unsubstituted
|
|
if re.is_match(&result) {
|
|
if let Some(m) = re.find(&result) {
|
|
let var_name = &result[m.start() + 2..m.end() - 1];
|
|
return Err(ConfigError::EnvVarNotFound(var_name.to_string()));
|
|
}
|
|
}
|
|
|
|
Ok(result.to_string())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_config_env_var_substitution() {
|
|
std::env::set_var("TEST_PASSWORD", "secret123");
|
|
|
|
let config_str = r#"
|
|
[storage]
|
|
backend = "surrealdb"
|
|
|
|
[storage.surrealdb]
|
|
password = "${TEST_PASSWORD}"
|
|
"#;
|
|
|
|
let config = VaultConfig::from_str(config_str).expect("Failed to parse config");
|
|
assert_eq!(
|
|
config.storage.surrealdb.password,
|
|
Some("secret123".to_string())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_validation_invalid_crypto_backend() {
|
|
let config_str = r#"
|
|
[vault]
|
|
crypto_backend = "invalid"
|
|
|
|
[storage]
|
|
backend = "filesystem"
|
|
"#;
|
|
|
|
let result = VaultConfig::from_str(config_str);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_validation_shamir_threshold() {
|
|
let config_str = r#"
|
|
[storage]
|
|
backend = "filesystem"
|
|
|
|
[seal]
|
|
seal_type = "shamir"
|
|
|
|
[seal.shamir]
|
|
shares = 3
|
|
threshold = 5
|
|
"#;
|
|
|
|
let result = VaultConfig::from_str(config_str);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_default_values() {
|
|
let config_str = r#"
|
|
[storage]
|
|
backend = "filesystem"
|
|
"#;
|
|
|
|
let config = VaultConfig::from_str(config_str).expect("Failed to parse config");
|
|
assert_eq!(config.vault.crypto_backend, "openssl");
|
|
assert_eq!(config.server.address, "127.0.0.1:8200");
|
|
assert_eq!(config.seal.seal_type, "shamir");
|
|
assert_eq!(config.seal.shamir.shares, 5);
|
|
assert_eq!(config.seal.shamir.threshold, 3);
|
|
}
|
|
}
|