Skip to main content

wrkflw_secrets/
lib.rs

1// Copyright 2024 wrkflw contributors
2// SPDX-License-Identifier: MIT
3
4//! # wrkflw-secrets
5//!
6//! Comprehensive secrets management for wrkflw workflow execution.
7//! Supports multiple secret providers and secure handling throughout the execution pipeline.
8//!
9//! ## Features
10//!
11//! - **Multiple Secret Providers**: Environment variables, file-based storage, with extensibility for cloud providers
12//! - **Secret Substitution**: GitHub Actions-style secret references (`${{ secrets.SECRET_NAME }}`)
13//! - **Automatic Masking**: Intelligent secret detection and masking in logs and output
14//! - **Rate Limiting**: Built-in protection against secret access abuse
15//! - **Caching**: Configurable caching for improved performance
16//! - **Input Validation**: Comprehensive validation of secret names and values
17//! - **Thread Safety**: Full async/await support with thread-safe operations
18//!
19//! ## Quick Start
20//!
21//! ```rust
22//! use wrkflw_secrets::{SecretManager, SecretMasker, SecretSubstitution};
23//!
24//! #[tokio::main]
25//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
26//!     // Initialize the secret manager with default configuration
27//!     let manager = SecretManager::default().await?;
28//!     
29//!     // Set an environment variable for testing
30//!     std::env::set_var("API_TOKEN", "secret_api_token_123");
31//!     
32//!     // Retrieve a secret
33//!     let secret = manager.get_secret("API_TOKEN").await?;
34//!     println!("Secret value: {}", secret.value());
35//!     
36//!     // Use secret substitution
37//!     let mut substitution = SecretSubstitution::new(&manager);
38//!     let template = "Using token: ${{ secrets.API_TOKEN }}";
39//!     let resolved = substitution.substitute(template).await?;
40//!     println!("Resolved: {}", resolved);
41//!     
42//!     // Set up secret masking
43//!     let mut masker = SecretMasker::new();
44//!     masker.add_secret("secret_api_token_123");
45//!     
46//!     let log_message = "Failed to authenticate with token: secret_api_token_123";
47//!     let masked = masker.mask(log_message);
48//!     println!("Masked: {}", masked); // Will show: "Failed to authenticate with token: se***123"
49//!     
50//!     // Clean up
51//!     std::env::remove_var("API_TOKEN");
52//!     Ok(())
53//! }
54//! ```
55//!
56//! ## Configuration
57//!
58//! ```rust
59//! use wrkflw_secrets::{SecretConfig, SecretProviderConfig, SecretManager};
60//! use std::collections::HashMap;
61//!
62//! #[tokio::main]
63//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
64//!     let mut providers = HashMap::new();
65//!     
66//!     // Environment variable provider with prefix
67//!     providers.insert(
68//!         "env".to_string(),
69//!         SecretProviderConfig::Environment {
70//!             prefix: Some("MYAPP_SECRET_".to_string())
71//!         }
72//!     );
73//!     
74//!     // File-based provider
75//!     providers.insert(
76//!         "file".to_string(),
77//!         SecretProviderConfig::File {
78//!             path: "/path/to/secrets.json".to_string()
79//!         }
80//!     );
81//!     
82//!     let config = SecretConfig {
83//!         default_provider: "env".to_string(),
84//!         providers,
85//!         enable_masking: true,
86//!         timeout_seconds: 30,
87//!         enable_caching: true,
88//!         cache_ttl_seconds: 300,
89//!         rate_limit: Default::default(),
90//!     };
91//!     
92//!     let manager = SecretManager::new(config).await?;
93//!     Ok(())
94//! }
95//! ```
96//!
97//! ## Security Features
98//!
99//! ### Input Validation
100//!
101//! All secret names and values are validated to prevent injection attacks and ensure compliance
102//! with naming conventions.
103//!
104//! ### Rate Limiting
105//!
106//! Built-in rate limiting prevents abuse and denial-of-service attacks on secret providers.
107//!
108//! ### Automatic Pattern Detection
109//!
110//! The masking system automatically detects and masks common secret patterns:
111//! - GitHub Personal Access Tokens (`ghp_*`)
112//! - AWS Access Keys (`AKIA*`)
113//! - JWT tokens
114//! - API keys and tokens
115//!
116//! ### Memory Safety
117//!
118//! Secrets are handled with care to minimize exposure in memory and logs.
119//!
120//! ## Provider Support
121//!
122//! ### Environment Variables
123//!
124//! ```rust
125//! use wrkflw_secrets::{SecretProviderConfig, SecretManager, SecretConfig};
126//!
127//! // With prefix for better security
128//! let provider = SecretProviderConfig::Environment {
129//!     prefix: Some("MYAPP_".to_string())
130//! };
131//! ```
132//!
133//! ### File-based Storage
134//!
135//! Supports JSON, YAML, and environment file formats:
136//!
137//! ```json
138//! {
139//!   "database_password": "super_secret_password",
140//!   "api_key": "your_api_key_here"
141//! }
142//! ```
143//!
144//! ```yaml
145//! database_password: super_secret_password
146//! api_key: your_api_key_here
147//! ```
148//!
149//! ```bash
150//! # Environment format
151//! DATABASE_PASSWORD=super_secret_password
152//! API_KEY="your_api_key_here"
153//! ```
154
155pub mod config;
156pub mod error;
157pub mod manager;
158pub mod masking;
159pub mod providers;
160pub mod rate_limit;
161pub mod storage;
162pub mod substitution;
163pub mod validation;
164
165pub use config::{SecretConfig, SecretProviderConfig};
166pub use error::{SecretError, SecretResult};
167pub use manager::SecretManager;
168pub use masking::SecretMasker;
169pub use providers::{SecretProvider, SecretValue};
170pub use substitution::SecretSubstitution;
171
172/// Re-export commonly used types
173pub mod prelude {
174    pub use crate::{
175        SecretConfig, SecretError, SecretManager, SecretMasker, SecretProvider, SecretResult,
176        SecretSubstitution, SecretValue,
177    };
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183    use uuid;
184
185    #[tokio::test]
186    async fn test_basic_secret_management() {
187        let config = SecretConfig::default();
188        let manager = SecretManager::new(config)
189            .await
190            .expect("Failed to create manager");
191
192        // Use a unique test secret name to avoid conflicts
193        let test_secret_name = format!(
194            "TEST_SECRET_{}",
195            uuid::Uuid::new_v4().to_string().replace('-', "_")
196        );
197        std::env::set_var(&test_secret_name, "secret_value");
198
199        let result = manager.get_secret(&test_secret_name).await;
200        assert!(result.is_ok());
201
202        let secret = result.unwrap();
203        assert_eq!(secret.value(), "secret_value");
204
205        std::env::remove_var(&test_secret_name);
206    }
207
208    #[tokio::test]
209    async fn test_secret_substitution() {
210        let config = SecretConfig::default();
211        let manager = SecretManager::new(config)
212            .await
213            .expect("Failed to create manager");
214
215        // Use a unique test secret name to avoid conflicts
216        let test_secret_name = format!(
217            "GITHUB_TOKEN_{}",
218            uuid::Uuid::new_v4().to_string().replace('-', "_")
219        );
220        std::env::set_var(&test_secret_name, "ghp_test_token");
221
222        let mut substitution = SecretSubstitution::new(&manager);
223        let input = format!("echo 'Token: ${{{{ secrets.{} }}}}'", test_secret_name);
224
225        let result = substitution.substitute(&input).await;
226        assert!(result.is_ok());
227
228        let output = result.unwrap();
229        assert!(output.contains("ghp_test_token"));
230
231        std::env::remove_var(&test_secret_name);
232    }
233
234    #[tokio::test]
235    async fn test_secret_masking() {
236        let mut masker = SecretMasker::new();
237        masker.add_secret("secret123");
238        masker.add_secret("password456");
239
240        let input = "The secret is secret123 and password is password456";
241        let masked = masker.mask(input);
242
243        assert!(masked.contains("***"));
244        assert!(!masked.contains("secret123"));
245        assert!(!masked.contains("password456"));
246    }
247}