diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 0000000..5da36da --- /dev/null +++ b/.clippy.toml @@ -0,0 +1,17 @@ +# Generated by dev-system/ci +# Clippy configuration for Rust linting + +# Lint level thresholds +cognitive-complexity-threshold = 25 +type-complexity-threshold = 500 +excessive-nesting-threshold = 5 + +# Allowed patterns (prevent lints on specific code) +# allow-expect-in-tests = true +# allow-unwrap-in-tests = true + +# Single-character variable name threshold +single-char-binding-names-threshold = 4 + +# Note: Lint configurations belong in Cargo.toml under [lints.clippy] or [workspace.lints.clippy] +# This file only contains clippy configuration parameters, not lint levels diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9822345..c15a01c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,8 +9,8 @@ repos: - repo: local hooks: - id: rust-fmt - name: Rust formatting (cargo fmt) - entry: bash -c 'cargo fmt --all -- --check' + name: Rust formatting (cargo +nightly fmt) + entry: bash -c 'cargo +nightly fmt --all -- --check' language: system types: [rust] pass_filenames: false diff --git a/rustfmt.toml b/.rustfmt.toml similarity index 75% rename from rustfmt.toml rename to .rustfmt.toml index 13e16e1..8bd3887 100644 --- a/rustfmt.toml +++ b/.rustfmt.toml @@ -1,5 +1,6 @@ # Generated by dev-system/ci # Rustfmt configuration for consistent Rust code formatting +# Configured for cargo +nightly fmt with advanced features enabled # Basic formatting options edition = "2021" @@ -8,50 +9,45 @@ hard_tabs = false tab_spaces = 4 newline_style = "Unix" -# Comment formatting -comment_width = 80 -wrap_comments = true - # Code structure use_small_heuristics = "Default" -# Spaces and indentation -fn_single_line = false -fn_args_layout = "Tall" -where_single_line = false - -# Match expressions -match_block_trailing_comma = false - # Imports reorder_imports = true reorder_modules = true remove_nested_parens = true group_imports = "StdExternalCrate" +# Match expressions +match_block_trailing_comma = false + # Chains chain_width = 60 -chain_indent = "Block" -# Formatting -format_strings = true -format_code_in_doc_comments = false +# Comment formatting (nightly) +comment_width = 80 +wrap_comments = true normalize_comments = true normalize_doc_attributes = true -# Line breaks -match_arm_blocks = true -overflow_delimited_expressions = false -blank_lines_lower_bound = 0 -blank_lines_upper_bound = 1 +# Spaces and indentation (nightly) +fn_single_line = false +fn_params_layout = "Tall" +where_single_line = false -# Performance -condense_wildcard_imports = false +# Formatting (nightly) +format_strings = true +format_code_in_doc_comments = false -# Spaces +# Spaces (nightly) space_before_colon = false space_after_colon = true spaces_around_ranges = false -# Stability -unstable_features = false +# Line breaks (nightly) +match_arm_blocks = true +blank_lines_lower_bound = 0 +blank_lines_upper_bound = 1 + +# Enable nightly features +unstable_features = true diff --git a/clippy.toml b/clippy.toml deleted file mode 100644 index e946976..0000000 --- a/clippy.toml +++ /dev/null @@ -1,46 +0,0 @@ -# Generated by dev-system/ci -# Clippy configuration for Rust linting - -# Lint level thresholds -cognitive-complexity-threshold = 25 -type-complexity-threshold = 500 -excessive-nesting-threshold = 5 - -# Allowed patterns (prevent lints on specific code) -# allow-expect-in-tests = true -# allow-unwrap-in-tests = true - -# Single-letter lifetime parameters -single-char-lifetime-names-threshold = 4 - -# Lint configuration -[clippy] -# Additional lints to enable by default -enable = [] - -# Specific lint configurations -[lints] -# Warn on panics in tests (but allow expect) -"clippy::panic" = "warn" -# Warn on todo! and unimplemented! macros -"clippy::todo" = "warn" -# Warn on large copies -"clippy::large-include-file" = "warn" - -# These are good practices but not strict requirements -"clippy::missing-docs-in-crate-items" = "allow" -"clippy::missing-errors-doc" = "allow" - -# Performance lints -"clippy::perf" = "warn" -"clippy::single-match" = "warn" -"clippy::match-bool" = "warn" - -# Style lints -"clippy::style" = "warn" -"clippy::all" = "warn" - -# Pedantic is too strict for production code, so warn only on important ones -"clippy::pedantic" = "allow" -"clippy::match-wild-err-arm" = "warn" -"clippy::or-patterns" = "warn" diff --git a/justfile b/justfile index 0784358..effc53a 100644 --- a/justfile +++ b/justfile @@ -158,12 +158,12 @@ ci-full: # Format all code [doc("Format Rust code")] fmt: - cargo fmt --all + @just dev::fmt # Check formatting [doc("Check formatting without modifying")] fmt-check: - cargo fmt --all -- --check + @just dev::fmt-check # Run clippy linter [doc("Run clippy with all warnings denied")] @@ -279,4 +279,4 @@ help MODULE="": else \ echo "Unknown module: {{ MODULE }}"; \ echo "Available: build, test, dev, deploy, vault"; \ - fi \ No newline at end of file + fi diff --git a/justfiles/dev.just b/justfiles/dev.just index 632979d..913e8b5 100644 --- a/justfiles/dev.just +++ b/justfiles/dev.just @@ -20,12 +20,12 @@ help: # Format all code [doc("Format all Rust code")] fmt: - cargo fmt --all + cargo +nightly fmt --all # Check formatting without modifying [doc("Check formatting")] fmt-check: - cargo fmt --all -- --check + cargo +nightly fmt --all -- --check # Lint with clippy [doc("Run clippy linter (all targets, all features)")] diff --git a/src/api/handlers.rs b/src/api/handlers.rs index 5c85232..4038d25 100644 --- a/src/api/handlers.rs +++ b/src/api/handlers.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + #[cfg(feature = "server")] use axum::{ extract::{Path, State}, @@ -6,7 +8,6 @@ use axum::{ Json, }; use serde_json::{json, Value}; -use std::sync::Arc; use super::ApiResponse; use crate::core::VaultCore; diff --git a/src/api/middleware.rs b/src/api/middleware.rs index 83ee786..1f38b52 100644 --- a/src/api/middleware.rs +++ b/src/api/middleware.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + /// API middleware for authentication and authorization use axum::{ extract::{Request, State}, @@ -5,7 +7,6 @@ use axum::{ middleware::Next, response::Response, }; -use std::sync::Arc; use tracing::{error, warn}; use crate::auth::extract_bearer_token; diff --git a/src/api/mod.rs b/src/api/mod.rs index 3649974..a15c46a 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -9,10 +9,9 @@ pub mod middleware; #[cfg(feature = "server")] pub mod tls; -pub use server::build_router; - use serde::{Deserialize, Serialize}; use serde_json::Value; +pub use server::build_router; /// Standard API response envelope #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/api/server.rs b/src/api/server.rs index 0634428..7054bf6 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + #[cfg(feature = "server")] use axum::{ extract::State, @@ -6,7 +8,6 @@ use axum::{ routing::{get, post}, Json, Router, }; -use std::sync::Arc; use super::handlers; use super::{ApiResponse, HealthResponse, SealRequest, SealStatus}; @@ -181,9 +182,10 @@ pub fn build_router(_vault: Arc) -> Router<()> { #[cfg(test)] mod tests { - use super::*; use serde_json::json; + use super::*; + #[test] fn test_api_response_success() { let response = ApiResponse::success(json!({"key": "value"})); diff --git a/src/api/tls.rs b/src/api/tls.rs index fab4eb3..c34cc75 100644 --- a/src/api/tls.rs +++ b/src/api/tls.rs @@ -1,4 +1,3 @@ -use crate::error::{Result, VaultError}; #[cfg(feature = "server")] use std::path::PathBuf; @@ -7,6 +6,8 @@ use rustls::ServerConfig; #[cfg(feature = "server")] use tokio_rustls::TlsAcceptor; +use crate::error::{Result, VaultError}; + /// TLS/mTLS configuration from vault config #[derive(Debug, Clone)] pub struct TlsConfig { @@ -57,10 +58,11 @@ impl TlsConfig { /// Create a rustls ServerConfig from certificate and key files #[cfg(feature = "server")] pub fn load_server_config(tls: &TlsConfig) -> Result { - use rustls::pki_types::CertificateDer; use std::fs::File; use std::io::BufReader; + use rustls::pki_types::CertificateDer; + // Validate paths first tls.validate()?; @@ -100,11 +102,12 @@ pub fn load_server_config(tls: &TlsConfig) -> Result { /// Create a rustls ServerConfig with mTLS (client certificate verification) #[cfg(feature = "server")] pub fn load_server_config_with_mtls(tls: &TlsConfig) -> Result { - use rustls::pki_types::CertificateDer; - use rustls::server::WebPkiClientVerifier; use std::fs::File; use std::io::BufReader; + use rustls::pki_types::CertificateDer; + use rustls::server::WebPkiClientVerifier; + // Validate paths first tls.validate()?; @@ -187,10 +190,12 @@ pub fn create_tls_acceptor(tls: &TlsConfig) -> Result { #[cfg(test)] mod tests { - use super::*; use std::fs; + use tempfile::TempDir; + use super::*; + fn create_test_cert_and_key(temp_dir: &TempDir) -> (PathBuf, PathBuf) { // Create a self-signed certificate for testing // Using openssl would require it as a dependency for tests, @@ -198,7 +203,8 @@ mod tests { let cert_path = temp_dir.path().join("cert.pem"); let key_path = temp_dir.path().join("key.pem"); - // Minimal self-signed cert (created with: openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes) + // Minimal self-signed cert (created with: openssl req -x509 -newkey rsa:2048 + // -keyout key.pem -out cert.pem -days 365 -nodes) let cert_content = r#"-----BEGIN CERTIFICATE----- MIIDazCCAlOgAwIBAgIUfEYF3nU/nfKYZcKgkX9vZj0VqAAwDQYJKoZIhvcNAQEL BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM diff --git a/src/auth/cedar.rs b/src/auth/cedar.rs index d6375ea..7e5bb8f 100644 --- a/src/auth/cedar.rs +++ b/src/auth/cedar.rs @@ -1,14 +1,14 @@ use std::collections::HashMap; use std::path::PathBuf; -use crate::error::{AuthError, AuthResult}; - #[cfg(feature = "cedar")] use { cedar_policy::{Authorizer, Entities, PolicySet}, std::sync::{Arc, RwLock}, }; +use crate::error::{AuthError, AuthResult}; + /// Authorization decision result #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AuthDecision { @@ -45,98 +45,96 @@ impl CedarEvaluator { } } + /// Helper function to read and validate a single Cedar policy file + fn read_cedar_policy_file(path: &std::path::Path) -> AuthResult> { + let is_cedar = path.extension().and_then(|ext| ext.to_str()) == Some("cedar"); + if !is_cedar { + return Ok(None); + } + + let policy_content = std::fs::read_to_string(path).map_err(|e| { + AuthError::CedarPolicy(format!( + "Failed to read policy file {}: {}", + path.display(), + e + )) + })?; + + Ok(Some((path.display().to_string(), policy_content))) + } + /// Load policies from the configured directory pub fn load_policies(&self) -> AuthResult<()> { - if let Some(dir) = &self.policies_dir { - if !dir.exists() { - return Err(AuthError::CedarPolicy(format!( - "Policies directory not found: {}", - dir.display() - ))); + let dir = match &self.policies_dir { + Some(d) => d, + None => return Ok(()), + }; + + if !dir.exists() { + return Err(AuthError::CedarPolicy(format!( + "Policies directory not found: {}", + dir.display() + ))); + } + + let entries = std::fs::read_dir(dir) + .map_err(|e| AuthError::CedarPolicy(format!("Failed to read policies dir: {}", e)))?; + + #[cfg(feature = "cedar")] + { + use std::str::FromStr; + + let all_policies: Result, AuthError> = entries + .map(|entry| { + let entry = entry.map_err(|e| { + AuthError::CedarPolicy(format!("Failed to read policy entry: {}", e)) + })?; + Self::read_cedar_policy_file(&entry.path()) + }) + .collect(); + + let all_policies: Vec<_> = all_policies?.into_iter().flatten().collect(); + + if all_policies.is_empty() { + return Err(AuthError::CedarPolicy( + "No Cedar policies found in configured directory".to_string(), + )); } - let entries = std::fs::read_dir(dir).map_err(|e| { - AuthError::CedarPolicy(format!("Failed to read policies dir: {}", e)) + let combined = all_policies + .iter() + .map(|(_, content)| content.as_str()) + .collect::>() + .join("\n"); + + let policy_set = PolicySet::from_str(&combined).map_err(|e| { + AuthError::CedarPolicy(format!("Failed to parse Cedar policies: {}", e)) })?; - #[cfg(feature = "cedar")] - { - use std::str::FromStr; + *self.policies.write().unwrap() = Some(policy_set); + } - let mut all_policies = Vec::new(); - let mut policy_count = 0; - - for entry in entries { + #[cfg(not(feature = "cedar"))] + { + let policy_count: Result = entries + .map(|entry| { let entry = entry.map_err(|e| { AuthError::CedarPolicy(format!("Failed to read policy entry: {}", e)) })?; + Ok(Self::read_cedar_policy_file(&entry.path())?.is_some() as usize) + }) + .sum(); - let path = entry.path(); - if path.extension().and_then(|ext| ext.to_str()) == Some("cedar") { - let policy_content = std::fs::read_to_string(&path).map_err(|e| { - AuthError::CedarPolicy(format!( - "Failed to read policy file {}: {}", - path.display(), - e - )) - })?; - - all_policies.push((path.display().to_string(), policy_content)); - policy_count += 1; - } - } - - if policy_count == 0 { - return Err(AuthError::CedarPolicy( - "No Cedar policies found in configured directory".to_string(), - )); - } - - // Combine all policy files - let combined = all_policies - .iter() - .map(|(_, content)| content.as_str()) - .collect::>() - .join("\n"); - - // Parse policies from Cedar syntax - let policy_set = PolicySet::from_str(&combined).map_err(|e| { - AuthError::CedarPolicy(format!("Failed to parse Cedar policies: {}", e)) - })?; - - *self.policies.write().unwrap() = Some(policy_set); + if policy_count? == 0 { + return Err(AuthError::CedarPolicy( + "No Cedar policies found in configured directory".to_string(), + )); } - #[cfg(not(feature = "cedar"))] - { - let mut policy_count = 0; - for entry in entries { - let entry = entry.map_err(|e| { - AuthError::CedarPolicy(format!("Failed to read policy entry: {}", e)) - })?; - - let path = entry.path(); - if path.extension().and_then(|ext| ext.to_str()) == Some("cedar") { - let _policy_content = std::fs::read_to_string(&path).map_err(|e| { - AuthError::CedarPolicy(format!( - "Failed to read policy file {}: {}", - path.display(), - e - )) - })?; - policy_count += 1; - } - } - - if policy_count == 0 { - return Err(AuthError::CedarPolicy( - "No Cedar policies found in configured directory".to_string(), - )); - } - - // Without cedar feature, we can only validate files exist - tracing::warn!("Cedar feature not enabled - policy evaluation will not work. Compile with --features cedar"); - } + tracing::warn!( + "Cedar feature not enabled - policy evaluation will not work. Compile with \ + --features cedar" + ); } Ok(()) @@ -195,7 +193,8 @@ impl CedarEvaluator { /// - principal: entity making the request (e.g., "user::alice") /// - action: action being requested (e.g., "Action::read") /// - resource: resource being accessed (e.g., "Secret::database_password") - /// - context: additional context for decision (e.g., IP address, MFA status) + /// - context: additional context for decision (e.g., IP address, MFA + /// status) pub fn evaluate( &self, principal: &str, @@ -203,7 +202,8 @@ impl CedarEvaluator { resource: &str, context: Option<&HashMap>, ) -> AuthResult { - // Note: principal, action, resource, context are used in cedar feature, unused without + // Note: principal, action, resource, context are used in cedar feature, unused + // without #[allow(unused_variables)] let _ = (principal, action, resource, context); #[cfg(feature = "cedar")] @@ -291,10 +291,12 @@ impl CedarEvaluator { #[cfg(test)] mod tests { - use super::*; use std::fs; + use tempfile::TempDir; + use super::*; + #[test] fn test_cedar_evaluator_creation() { let evaluator = CedarEvaluator::new(None, None); diff --git a/src/auth/middleware.rs b/src/auth/middleware.rs index 9ef61f4..db4522a 100644 --- a/src/auth/middleware.rs +++ b/src/auth/middleware.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + #[cfg(feature = "server")] use axum::{ extract::Request, @@ -5,7 +7,6 @@ use axum::{ middleware::Next, response::{IntoResponse, Response}, }; -use std::sync::Arc; #[cfg(feature = "server")] use crate::core::VaultCore; @@ -59,7 +60,8 @@ impl IntoResponse for TokenValidationError { } #[cfg(feature = "server")] -/// Middleware for token validation (optional - checks if token is valid when present) +/// Middleware for token validation (optional - checks if token is valid when +/// present) pub async fn optional_token_validation( headers: HeaderMap, vault: Arc, @@ -81,7 +83,8 @@ pub async fn optional_token_validation( } #[cfg(feature = "server")] -/// Middleware for mandatory token validation (rejects requests without valid token) +/// Middleware for mandatory token validation (rejects requests without valid +/// token) pub async fn required_token_validation( headers: HeaderMap, vault: Arc, diff --git a/src/auth/mod.rs b/src/auth/mod.rs index 21da9d0..2624094 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -5,7 +5,6 @@ pub mod token; pub mod middleware; pub use cedar::{AuthDecision, CedarEvaluator}; -pub use token::{Token, TokenManager, TokenMetadata}; - #[cfg(feature = "server")] pub use middleware::{extract_bearer_token, TokenValidationError}; +pub use token::{Token, TokenManager, TokenMetadata}; diff --git a/src/auth/token.rs b/src/auth/token.rs index c1de32c..9a7ae13 100644 --- a/src/auth/token.rs +++ b/src/auth/token.rs @@ -1,6 +1,7 @@ +use std::sync::Arc; + use chrono::{DateTime, Duration, Utc}; use serde::{Deserialize, Serialize}; -use std::sync::Arc; use uuid::Uuid; use crate::crypto::CryptoBackend; @@ -206,14 +207,17 @@ impl TokenManager { let mut tokens = Vec::new(); for token_id in token_ids { - // Extract token ID from storage key let parts: Vec<&str> = token_id.split('/').collect(); - if let Some(id) = parts.last() { - if let Ok(Some(token)) = self.lookup(id).await { - if token.metadata.client_id == client_id { - tokens.push(token); - } - } + let Some(id) = parts.last() else { + continue; + }; + + let Ok(Some(token)) = self.lookup(id).await else { + continue; + }; + + if token.metadata.client_id == client_id { + tokens.push(token); } } @@ -223,11 +227,12 @@ impl TokenManager { #[cfg(test)] mod tests { + use tempfile::TempDir; + use super::*; use crate::config::{FilesystemStorageConfig, StorageConfig}; use crate::crypto::CryptoRegistry; use crate::storage::StorageRegistry; - use tempfile::TempDir; async fn setup_token_manager() -> Result<(TokenManager, TempDir)> { let temp_dir = TempDir::new().map_err(|e| VaultError::storage(e.to_string()))?; diff --git a/src/background/lease_revocation.rs b/src/background/lease_revocation.rs index 775ee46..71e4763 100644 --- a/src/background/lease_revocation.rs +++ b/src/background/lease_revocation.rs @@ -1,15 +1,15 @@ -use chrono::Utc; use std::collections::VecDeque; use std::sync::Arc; use std::time::Duration; + +use chrono::Utc; use tokio::sync::RwLock; use tokio::task::JoinHandle; use crate::error::Result; -use crate::storage::{Lease, StorageBackend}; - #[cfg(test)] use crate::error::VaultError; +use crate::storage::{Lease, StorageBackend}; /// Configuration for lease revocation worker #[derive(Debug, Clone)] @@ -250,10 +250,11 @@ impl LeaseRevocationWorker { #[cfg(test)] mod tests { + use tempfile::TempDir; + use super::*; use crate::config::{FilesystemStorageConfig, StorageConfig}; use crate::storage::StorageRegistry; - use tempfile::TempDir; async fn setup_worker() -> Result<(LeaseRevocationWorker, TempDir)> { let temp_dir = TempDir::new().map_err(|e| VaultError::storage(e.to_string()))?; diff --git a/src/cli/client.rs b/src/cli/client.rs index 726075f..93da662 100644 --- a/src/cli/client.rs +++ b/src/cli/client.rs @@ -1,10 +1,11 @@ #[cfg(feature = "cli")] -use crate::error::{Result, VaultError}; -#[cfg(feature = "cli")] use reqwest::{Client, Response, StatusCode}; #[cfg(feature = "cli")] use serde_json::{json, Value}; +#[cfg(feature = "cli")] +use crate::error::{Result, VaultError}; + #[cfg(feature = "cli")] pub struct VaultClient { client: Client, diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 0f40c4f..3da3790 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -4,11 +4,12 @@ pub mod commands; #[cfg(feature = "cli")] pub mod client; -#[cfg(feature = "cli")] -use clap::{Parser, Subcommand}; #[cfg(feature = "cli")] use std::path::PathBuf; +#[cfg(feature = "cli")] +use clap::{Parser, Subcommand}; + #[cfg(feature = "cli")] /// SecretumVault CLI - Post-quantum secrets management #[derive(Parser)] diff --git a/src/config/auth.rs b/src/config/auth.rs index d90d3ce..eb88cc5 100644 --- a/src/config/auth.rs +++ b/src/config/auth.rs @@ -1,6 +1,7 @@ -use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use serde::{Deserialize, Serialize}; + /// Authentication configuration #[derive(Debug, Clone, Deserialize, Serialize, Default)] pub struct AuthConfig { diff --git a/src/config/engines.rs b/src/config/engines.rs index c134741..5baf120 100644 --- a/src/config/engines.rs +++ b/src/config/engines.rs @@ -1,6 +1,7 @@ -use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use serde::{Deserialize, Serialize}; + /// Secrets engines configuration #[derive(Debug, Clone, Deserialize, Serialize, Default)] pub struct EnginesConfig { diff --git a/src/config/logging.rs b/src/config/logging.rs index 951ee11..f812488 100644 --- a/src/config/logging.rs +++ b/src/config/logging.rs @@ -1,6 +1,7 @@ -use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use serde::{Deserialize, Serialize}; + /// Logging configuration #[derive(Debug, Clone, Deserialize, Serialize)] pub struct LoggingConfig { diff --git a/src/config/mod.rs b/src/config/mod.rs index 5435279..151d0ae 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -10,6 +10,8 @@ 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}; @@ -18,14 +20,12 @@ pub use logging::LoggingConfig; pub use seal::{AutoUnsealConfig, SealConfig, ShamirSealConfig}; pub use server::ServerSection; pub use storage::{ - EtcdStorageConfig, FilesystemStorageConfig, PostgreSQLStorageConfig, - StorageConfig, SurrealDBStorageConfig, + EtcdStorageConfig, FilesystemStorageConfig, PostgreSQLStorageConfig, StorageConfig, + SurrealDBStorageConfig, }; pub use telemetry::TelemetryConfig; pub use vault::VaultSection; -use std::path::Path; - /// Main vault configuration #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] pub struct VaultConfig { diff --git a/src/config/server.rs b/src/config/server.rs index 8faaa2a..f4ac96e 100644 --- a/src/config/server.rs +++ b/src/config/server.rs @@ -1,6 +1,7 @@ -use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use serde::{Deserialize, Serialize}; + /// Server configuration #[derive(Debug, Clone, Deserialize, Serialize)] pub struct ServerSection { diff --git a/src/config/storage.rs b/src/config/storage.rs index d170aa5..9776900 100644 --- a/src/config/storage.rs +++ b/src/config/storage.rs @@ -1,6 +1,7 @@ -use serde::{Deserialize, Serialize}; use std::path::PathBuf; +use serde::{Deserialize, Serialize}; + /// Storage configuration #[derive(Debug, Clone, Deserialize, Serialize)] pub struct StorageConfig { diff --git a/src/core/vault.rs b/src/core/vault.rs index 38fd4b3..098bfff 100644 --- a/src/core/vault.rs +++ b/src/core/vault.rs @@ -2,6 +2,8 @@ use std::collections::HashMap; use std::sync::Arc; use crate::auth::TokenManager; +#[cfg(feature = "server")] +use crate::background::LeaseRevocationWorker; use crate::config::VaultConfig; use crate::crypto::CryptoBackend; use crate::engines::{DatabaseEngine, Engine, KVEngine, PkiEngine, TransitEngine}; @@ -9,9 +11,6 @@ use crate::error::Result; use crate::storage::StorageBackend; use crate::telemetry::Metrics; -#[cfg(feature = "server")] -use crate::background::LeaseRevocationWorker; - /// Vault core - manages engines, crypto backend, and storage pub struct VaultCore { /// Mounted secrets engines (mount_path -> engine) @@ -78,19 +77,19 @@ impl VaultCore { /// Find engine by path prefix pub fn route_to_engine(&self, path: &str) -> Option<&dyn Engine> { - // Find the longest matching mount path let mut best_match: Option<(&str, &dyn Engine)> = None; for (mount_path, engine) in &self.engines { - if path.starts_with(mount_path) { - match best_match { - None => best_match = Some((mount_path, engine.as_ref())), - Some((best_path, _)) => { - if mount_path.len() > best_path.len() { - best_match = Some((mount_path, engine.as_ref())); - } - } - } + if !path.starts_with(mount_path) { + continue; + } + + let should_update = best_match + .map(|(best_path, _)| mount_path.len() > best_path.len()) + .unwrap_or(true); + + if should_update { + best_match = Some((mount_path, engine.as_ref())); } } @@ -102,15 +101,16 @@ impl VaultCore { let mut best_match: Option<(&str, &str)> = None; for mount_path in self.engines.keys() { - if path.starts_with(mount_path) { - match best_match { - None => best_match = Some((mount_path, path)), - Some((best_path, _)) => { - if mount_path.len() > best_path.len() { - best_match = Some((mount_path, path)); - } - } - } + if !path.starts_with(mount_path) { + continue; + } + + let should_update = best_match + .map(|(best_path, _)| mount_path.len() > best_path.len()) + .unwrap_or(true); + + if should_update { + best_match = Some((mount_path, path)); } } @@ -193,11 +193,12 @@ impl EngineRegistry { #[cfg(test)] mod tests { + use tempfile::TempDir; + use super::*; use crate::config::{ EngineConfig, FilesystemStorageConfig, SealConfig, ShamirSealConfig, StorageConfig, }; - use tempfile::TempDir; fn create_test_vault_config(temp_dir: &TempDir) -> VaultConfig { VaultConfig { diff --git a/src/crypto/backend.rs b/src/crypto/backend.rs index 952f54d..ac606c4 100644 --- a/src/crypto/backend.rs +++ b/src/crypto/backend.rs @@ -1,6 +1,7 @@ +use std::sync::Arc; + use async_trait::async_trait; use serde::{Deserialize, Serialize}; -use std::sync::Arc; use super::openssl_backend::OpenSSLBackend; use crate::config::CryptoConfig; @@ -95,7 +96,8 @@ pub struct KeyPair { pub public_key: PublicKey, } -/// Crypto backend trait - abstraction over different cryptographic implementations +/// Crypto backend trait - abstraction over different cryptographic +/// implementations #[async_trait] pub trait CryptoBackend: Send + Sync + std::fmt::Debug { /// Generate a keypair for the given algorithm diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 211ebc9..51fe759 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -5,11 +5,10 @@ pub mod rustcrypto_backend; #[cfg(feature = "aws-lc")] pub mod aws_lc; +#[cfg(feature = "aws-lc")] +pub use aws_lc::AwsLcBackend; pub use backend::{ CryptoBackend, CryptoRegistry, KeyAlgorithm, KeyPair, PrivateKey, PublicKey, SymmetricAlgorithm, }; pub use openssl_backend::OpenSSLBackend; pub use rustcrypto_backend::RustCryptoBackend; - -#[cfg(feature = "aws-lc")] -pub use aws_lc::AwsLcBackend; diff --git a/src/crypto/rustcrypto_backend.rs b/src/crypto/rustcrypto_backend.rs index b4a7d4b..fddb5ac 100644 --- a/src/crypto/rustcrypto_backend.rs +++ b/src/crypto/rustcrypto_backend.rs @@ -6,9 +6,10 @@ //! - Symmetric: AES-256-GCM, ChaCha20-Poly1305 //! - Hashing: SHA-256, SHA-512 +use std::fmt; + use async_trait::async_trait; use rand::RngCore; -use std::fmt; use crate::crypto::backend::{ CryptoBackend, KeyAlgorithm, KeyPair, PrivateKey, PublicKey, SymmetricAlgorithm, diff --git a/src/engines/database.rs b/src/engines/database.rs index 0968573..c5ec9db 100644 --- a/src/engines/database.rs +++ b/src/engines/database.rs @@ -1,10 +1,11 @@ +use std::collections::HashMap; +use std::sync::Arc; + use async_trait::async_trait; use chrono::{Duration, Utc}; use rand::Rng; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; -use std::collections::HashMap; -use std::sync::Arc; use tokio::sync::RwLock; use super::Engine as SecretEngine; @@ -426,11 +427,12 @@ impl SecretEngine for DatabaseEngine { #[cfg(test)] mod tests { + use tempfile::TempDir; + use super::*; use crate::config::{FilesystemStorageConfig, SealConfig, ShamirSealConfig, StorageConfig}; use crate::crypto::CryptoRegistry; use crate::storage::StorageRegistry; - use tempfile::TempDir; async fn setup_engine() -> Result<(DatabaseEngine, TempDir)> { let temp_dir = TempDir::new().map_err(|e| VaultError::storage(e.to_string()))?; diff --git a/src/engines/kv.rs b/src/engines/kv.rs index f2e83fd..1d8ab76 100644 --- a/src/engines/kv.rs +++ b/src/engines/kv.rs @@ -1,8 +1,9 @@ +use std::sync::Arc; + use async_trait::async_trait; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use std::sync::Arc; use super::Engine; use crate::core::SealMechanism; @@ -268,12 +269,13 @@ impl Engine for KVEngine { #[cfg(test)] mod tests { + use serde_json::json; + use tempfile::TempDir; + use super::*; use crate::config::{FilesystemStorageConfig, SealConfig, ShamirSealConfig, StorageConfig}; use crate::crypto::CryptoRegistry; use crate::storage::StorageRegistry; - use serde_json::json; - use tempfile::TempDir; async fn setup_engine() -> Result<(KVEngine, TempDir, Arc)> { let temp_dir = TempDir::new().map_err(|e| VaultError::storage(e.to_string()))?; diff --git a/src/engines/mod.rs b/src/engines/mod.rs index fa04465..250b6bb 100644 --- a/src/engines/mod.rs +++ b/src/engines/mod.rs @@ -3,13 +3,12 @@ pub mod kv; pub mod pki; pub mod transit; +use async_trait::async_trait; pub use database::DatabaseEngine; pub use kv::KVEngine; pub use pki::PkiEngine; -pub use transit::TransitEngine; - -use async_trait::async_trait; use serde_json::Value; +pub use transit::TransitEngine; use crate::error::Result; diff --git a/src/engines/pki.rs b/src/engines/pki.rs index 915141f..0f14e4c 100644 --- a/src/engines/pki.rs +++ b/src/engines/pki.rs @@ -1,8 +1,9 @@ +use std::sync::Arc; + use async_trait::async_trait; use chrono::{Duration, Utc}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; -use std::sync::Arc; use super::Engine as SecretEngine; use crate::core::SealMechanism; @@ -556,11 +557,12 @@ impl SecretEngine for PkiEngine { #[cfg(test)] mod tests { + use tempfile::TempDir; + use super::*; use crate::config::{FilesystemStorageConfig, SealConfig, ShamirSealConfig, StorageConfig}; use crate::crypto::CryptoRegistry; use crate::storage::StorageRegistry; - use tempfile::TempDir; async fn setup_engine() -> Result<(PkiEngine, TempDir)> { let temp_dir = TempDir::new().map_err(|e| VaultError::storage(e.to_string()))?; diff --git a/src/engines/transit.rs b/src/engines/transit.rs index bfca6d4..9d7c371 100644 --- a/src/engines/transit.rs +++ b/src/engines/transit.rs @@ -1,9 +1,10 @@ +use std::collections::HashMap; +use std::sync::Arc; + use async_trait::async_trait; use base64::engine::general_purpose::STANDARD as BASE64; use base64::Engine as _; use serde_json::{json, Value}; -use std::collections::HashMap; -use std::sync::Arc; use super::Engine; use crate::core::SealMechanism; @@ -114,7 +115,8 @@ impl TransitEngine { let current_version = key.current_version; drop(keys); - // Encrypt plaintext using the current key version (lock is dropped before await) + // Encrypt plaintext using the current key version (lock is dropped before + // await) let ciphertext = self .crypto .encrypt_symmetric(&key_material, plaintext, SymmetricAlgorithm::Aes256Gcm) @@ -214,7 +216,8 @@ impl Engine for TransitEngine { .ok_or_else(|| VaultError::storage("Missing 'plaintext' in request".to_string()))?; let _ciphertext = self.encrypt(key_name, plaintext.as_bytes()).await?; - // Note: In a full implementation, this would return the ciphertext in the response + // Note: In a full implementation, this would return the ciphertext + // in the response } else if let Some(key_name) = path.strip_prefix("decrypt/") { let ciphertext = data .get("ciphertext") @@ -224,7 +227,8 @@ impl Engine for TransitEngine { })?; let _plaintext = self.decrypt(key_name, ciphertext).await?; - // Note: In a full implementation, this would return the plaintext in the response + // Note: In a full implementation, this would return the plaintext + // in the response } else if let Some(key_name) = path.strip_prefix("rewrap/") { let ciphertext = data .get("ciphertext") @@ -234,7 +238,8 @@ impl Engine for TransitEngine { })?; let _new_ciphertext = self.rewrap(key_name, ciphertext).await?; - // Note: In a full implementation, this would return the new ciphertext in the response + // Note: In a full implementation, this would return the new + // ciphertext in the response } Ok(()) @@ -279,11 +284,12 @@ impl Engine for TransitEngine { #[cfg(test)] mod tests { + use tempfile::TempDir; + use super::*; use crate::config::{FilesystemStorageConfig, SealConfig, ShamirSealConfig, StorageConfig}; use crate::crypto::CryptoRegistry; use crate::storage::StorageRegistry; - use tempfile::TempDir; async fn setup_engine() -> Result<(TransitEngine, TempDir)> { let temp_dir = TempDir::new().map_err(|e| VaultError::storage(e.to_string()))?; diff --git a/src/error.rs b/src/error.rs index 745fe24..da59843 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,6 @@ use std::backtrace::Backtrace; use std::fmt; + use thiserror::Error; /// Main vault error type diff --git a/src/main.rs b/src/main.rs index 0621386..9a7b2c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,16 @@ +#[cfg(feature = "cli")] +use std::path::PathBuf; +#[cfg(feature = "cli")] +use std::sync::Arc; + #[cfg(feature = "cli")] use clap::Parser; - #[cfg(feature = "cli")] use secretumvault::cli::{Cli, Command, OperatorCommand, SecretCommand}; #[cfg(feature = "cli")] use secretumvault::config::VaultConfig; #[cfg(feature = "cli")] use secretumvault::core::VaultCore; -#[cfg(feature = "cli")] -use std::path::PathBuf; -#[cfg(feature = "cli")] -use std::sync::Arc; #[tokio::main] #[cfg(feature = "cli")] @@ -53,7 +53,10 @@ async fn server_command( #[cfg(feature = "server")] { - eprintln!("Note: Server mode via CLI is limited. Use library API with --features server for full functionality including TLS."); + eprintln!( + "Note: Server mode via CLI is limited. Use library API with --features server for \ + full functionality including TLS." + ); eprintln!("Server feature not fully implemented in CLI mode."); std::process::exit(1); } diff --git a/src/storage/etcd.rs b/src/storage/etcd.rs index 93b11f8..4b1349e 100644 --- a/src/storage/etcd.rs +++ b/src/storage/etcd.rs @@ -13,13 +13,15 @@ //! ``` //! //! For development, run etcd with: -//! - Docker: `docker run -d --name etcd -p 2379:2379 quay.io/coreos/etcd:v3.5.0` +//! - Docker: `docker run -d --name etcd -p 2379:2379 +//! quay.io/coreos/etcd:v3.5.0` //! - Local: `etcd` (requires etcd binary installed) +use std::sync::Arc; + use async_trait::async_trait; use chrono::{DateTime, Utc}; use serde_json::{json, Value}; -use std::sync::Arc; use crate::config::EtcdStorageConfig; use crate::error::{StorageError, StorageResult}; diff --git a/src/storage/filesystem.rs b/src/storage/filesystem.rs index 2bcb4ef..12914fb 100644 --- a/src/storage/filesystem.rs +++ b/src/storage/filesystem.rs @@ -1,8 +1,9 @@ -use async_trait::async_trait; -use chrono::{DateTime, Utc}; use std::fs; use std::path::{Path, PathBuf}; +use async_trait::async_trait; +use chrono::{DateTime, Utc}; + use crate::config::FilesystemStorageConfig; use crate::error::StorageError; use crate::storage::{ @@ -314,9 +315,10 @@ impl StorageBackend for FilesystemBackend { #[cfg(test)] mod tests { - use super::*; use tempfile::TempDir; + use super::*; + fn create_test_backend() -> (FilesystemBackend, TempDir) { let temp_dir = TempDir::new().expect("Failed to create temp dir"); let config = FilesystemStorageConfig { diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 27d202f..b74079b 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -1,8 +1,9 @@ +use std::collections::HashMap; +use std::sync::Arc; + use async_trait::async_trait; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::sync::Arc; pub mod filesystem; pub mod postgresql; @@ -13,17 +14,15 @@ pub mod surrealdb; #[cfg(feature = "etcd-storage")] pub mod etcd; -use crate::config::StorageConfig; -use crate::error::{Result, StorageResult}; - +#[cfg(feature = "etcd-storage")] +pub use etcd::EtcdBackend; pub use filesystem::FilesystemBackend; pub use postgresql::PostgreSQLBackend; - #[cfg(feature = "surrealdb-storage")] pub use surrealdb::SurrealDBBackend; -#[cfg(feature = "etcd-storage")] -pub use etcd::EtcdBackend; +use crate::config::StorageConfig; +use crate::error::{Result, StorageResult}; /// Encrypted data stored in backend #[derive(Debug, Clone, Serialize, Deserialize)] @@ -160,7 +159,8 @@ impl StorageRegistry { } if config.backend == "postgresql" && cfg!(not(feature = "postgresql-storage")) { return Err(crate::VaultError::config( - "PostgreSQL backend not enabled. Compile with --features postgresql-storage" + "PostgreSQL backend not enabled. Compile with --features \ + postgresql-storage", )); } Err(crate::VaultError::config(format!( diff --git a/src/storage/postgresql.rs b/src/storage/postgresql.rs index c584897..e3747ca 100644 --- a/src/storage/postgresql.rs +++ b/src/storage/postgresql.rs @@ -1,12 +1,14 @@ //! PostgreSQL storage backend for SecretumVault //! //! Provides persistent secret storage using PostgreSQL as the backend. -//! This implementation uses an in-memory store (production would use sqlx + real DB). +//! This implementation uses an in-memory store (production would use sqlx + +//! real DB). + +use std::collections::HashMap; +use std::sync::Arc; use async_trait::async_trait; use chrono::{DateTime, Utc}; -use std::collections::HashMap; -use std::sync::Arc; use tokio::sync::RwLock; use crate::config::PostgreSQLStorageConfig; diff --git a/src/storage/surrealdb.rs b/src/storage/surrealdb.rs index c319bf4..e054891 100644 --- a/src/storage/surrealdb.rs +++ b/src/storage/surrealdb.rs @@ -12,19 +12,20 @@ //! url = "ws://localhost:8000" # For future real SurrealDB connections //! ``` +use std::collections::HashMap; +use std::sync::Arc; + use async_trait::async_trait; use chrono::{DateTime, Utc}; use serde_json::{json, Value}; -use std::collections::HashMap; -use std::sync::Arc; use tokio::sync::RwLock; use crate::config::SurrealDBStorageConfig; use crate::error::{StorageError, StorageResult}; use crate::storage::{EncryptedData, Lease, StorageBackend, StoredKey, StoredPolicy}; -/// SurrealDB storage backend - in-memory implementation with SurrealDB semantics -/// Tables are organized as HashMap> +/// SurrealDB storage backend - in-memory implementation with SurrealDB +/// semantics Tables are organized as HashMap> pub struct SurrealDBBackend { store: Arc>>>, } diff --git a/src/telemetry.rs b/src/telemetry.rs index 1e5217f..ed2272d 100644 --- a/src/telemetry.rs +++ b/src/telemetry.rs @@ -6,10 +6,11 @@ //! - Metrics collection and reporting //! - Performance monitoring -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; use tokio::sync::RwLock; use tracing::{debug, error, info, span, warn, Level};