Crates.io | wrkflw-secrets |
lib.rs | wrkflw-secrets |
version | 0.7.3 |
created_at | 2025-08-22 07:45:00.5655+00 |
updated_at | 2025-08-28 07:32:32.658393+00 |
description | Secrets management for wrkflw workflow execution engine |
homepage | https://github.com/bahdotsh/wrkflw |
repository | https://github.com/bahdotsh/wrkflw |
max_upload_size | |
id | 1806028 |
size | 167,471 |
Comprehensive secrets management for wrkflw workflow execution. This crate provides secure handling of secrets with support for multiple providers, encryption, masking, and GitHub Actions-compatible variable substitution.
${{ secrets.* }}
syntaxuse wrkflw_secrets::prelude::*;
#[tokio::main]
async fn main() -> SecretResult<()> {
// Create a secret manager with default configuration
let manager = SecretManager::default().await?;
// Set an environment variable
std::env::set_var("GITHUB_TOKEN", "ghp_your_token_here");
// Get a secret
let secret = manager.get_secret("GITHUB_TOKEN").await?;
println!("Token: {}", secret.value());
// Use secret substitution
let mut substitution = SecretSubstitution::new(&manager);
let template = "curl -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' https://api.github.com";
let resolved = substitution.substitute(template).await?;
// Mask secrets in logs
let mut masker = SecretMasker::new();
masker.add_secret(secret.value());
let safe_log = masker.mask(&resolved);
println!("Safe log: {}", safe_log);
Ok(())
}
# Set default provider
export WRKFLW_DEFAULT_SECRET_PROVIDER=env
# Enable/disable secret masking
export WRKFLW_SECRET_MASKING=true
# Set operation timeout
export WRKFLW_SECRET_TIMEOUT=30
Create ~/.wrkflw/secrets.yml
:
default_provider: env
enable_masking: true
timeout_seconds: 30
enable_caching: true
cache_ttl_seconds: 300
providers:
env:
type: environment
prefix: "WRKFLW_SECRET_"
file:
type: file
path: "~/.wrkflw/secrets.json"
vault:
type: vault
url: "https://vault.example.com"
auth:
method: token
token: "${VAULT_TOKEN}"
mount_path: "secret"
The simplest provider reads secrets from environment variables:
// With prefix
std::env::set_var("WRKFLW_SECRET_API_KEY", "secret_value");
let secret = manager.get_secret_from_provider("env", "API_KEY").await?;
// Without prefix
std::env::set_var("GITHUB_TOKEN", "ghp_token");
let secret = manager.get_secret_from_provider("env", "GITHUB_TOKEN").await?;
Store secrets in JSON, YAML, or environment files:
JSON format (secrets.json
):
{
"API_KEY": "secret_api_key",
"DB_PASSWORD": "secret_password"
}
Environment format (secrets.env
):
API_KEY=secret_api_key
DB_PASSWORD="quoted password"
GITHUB_TOKEN='single quoted token'
YAML format (secrets.yml
):
API_KEY: secret_api_key
DB_PASSWORD: secret_password
providers:
vault:
type: vault
url: "https://vault.example.com"
auth:
method: token
token: "${VAULT_TOKEN}"
mount_path: "secret"
providers:
aws:
type: aws_secrets_manager
region: "us-east-1"
role_arn: "arn:aws:iam::123456789012:role/SecretRole" # optional
providers:
azure:
type: azure_key_vault
vault_url: "https://myvault.vault.azure.net/"
auth:
method: service_principal
client_id: "${AZURE_CLIENT_ID}"
client_secret: "${AZURE_CLIENT_SECRET}"
tenant_id: "${AZURE_TENANT_ID}"
providers:
gcp:
type: gcp_secret_manager
project_id: "my-project"
key_file: "/path/to/service-account.json" # optional
Support for GitHub Actions-compatible secret references:
let mut substitution = SecretSubstitution::new(&manager);
// Default provider
let template = "TOKEN=${{ secrets.GITHUB_TOKEN }}";
let resolved = substitution.substitute(template).await?;
// Specific provider
let template = "API_KEY=${{ secrets.vault:API_KEY }}";
let resolved = substitution.substitute(template).await?;
Automatically mask secrets in logs and output:
let mut masker = SecretMasker::new();
// Add specific secrets
masker.add_secret("secret_value");
// Automatic pattern detection for common secret types
let log = "Token: ghp_1234567890123456789012345678901234567890";
let masked = masker.mask(log);
// Output: "Token: ghp_***"
Supported patterns:
ghp_*
)ghs_*
)gho_*
)AKIA*
)For sensitive environments, use encrypted storage:
use wrkflw_secrets::storage::{EncryptedSecretStore, KeyDerivation};
// Create encrypted store
let (mut store, key) = EncryptedSecretStore::new()?;
// Add secrets
store.add_secret(&key, "API_KEY", "secret_value")?;
// Save to file
store.save_to_file("secrets.encrypted").await?;
// Load from file
let loaded_store = EncryptedSecretStore::load_from_file("secrets.encrypted").await?;
let secret = loaded_store.get_secret(&key, "API_KEY")?;
All operations return SecretResult<T>
with comprehensive error types:
match manager.get_secret("MISSING_SECRET").await {
Ok(secret) => println!("Secret: {}", secret.value()),
Err(SecretError::NotFound { name }) => {
eprintln!("Secret '{}' not found", name);
}
Err(SecretError::ProviderNotFound { provider }) => {
eprintln!("Provider '{}' not configured", provider);
}
Err(SecretError::AuthenticationFailed { provider, reason }) => {
eprintln!("Auth failed for {}: {}", provider, reason);
}
Err(e) => eprintln!("Error: {}", e),
}
Monitor provider health:
let health_results = manager.health_check().await;
for (provider, result) in health_results {
match result {
Ok(()) => println!("✓ {} is healthy", provider),
Err(e) => println!("✗ {} failed: {}", provider, e),
}
}
Protect against abuse with built-in rate limiting:
use wrkflw_secrets::rate_limit::RateLimitConfig;
use std::time::Duration;
let mut config = SecretConfig::default();
config.rate_limit = RateLimitConfig {
max_requests: 100, // Max requests per window
window_duration: Duration::from_secs(60), // 1 minute window
enabled: true,
};
let manager = SecretManager::new(config).await?;
// Rate limiting is automatically applied to all secret access operations
match manager.get_secret("API_KEY").await {
Ok(secret) => println!("Success: {}", secret.value()),
Err(SecretError::RateLimitExceeded(msg)) => {
println!("Rate limited: {}", msg);
}
Err(e) => println!("Other error: {}", e),
}
All inputs are automatically validated:
// Secret names must:
// - Be 1-255 characters long
// - Contain only letters, numbers, underscores, hyphens, and dots
// - Not start or end with dots
// - Not contain consecutive dots
// - Not be reserved system names
// Secret values must:
// - Be under 1MB in size
// - Not contain null bytes
// - Be valid UTF-8
// Invalid examples that will be rejected:
manager.get_secret("").await; // Empty name
manager.get_secret("invalid/name").await; // Invalid characters
manager.get_secret(".hidden").await; // Starts with dot
manager.get_secret("CON").await; // Reserved name
let config = SecretConfig {
enable_caching: true,
cache_ttl_seconds: 300, // 5 minutes
..Default::default()
};
OnceLock
Run performance benchmarks:
cargo bench -p wrkflw-secrets
Enable optional providers:
[dependencies]
wrkflw-secrets = { version = "0.1", features = ["vault-provider", "aws-provider"] }
Available features:
env-provider
(default)file-provider
(default)vault-provider
aws-provider
azure-provider
gcp-provider
all-providers
MIT License - see LICENSE file for details.