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}