Initial upload: api-token-vault Rust CLI tool with encrypted vault storage
This commit is contained in:
375
app/api-token-vault/src/main.rs
Normal file
375
app/api-token-vault/src/main.rs
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
mod cli;
|
||||||
|
mod vault;
|
||||||
|
mod token;
|
||||||
|
mod rotation;
|
||||||
|
mod env_injector;
|
||||||
|
mod crypto;
|
||||||
|
|
||||||
|
use cli::Cli;
|
||||||
|
use vault::Vault;
|
||||||
|
use crypto::CryptoManager;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
match cli.command {
|
||||||
|
cli::Commands::Init { master_password, project } => {
|
||||||
|
let password = master_password.unwrap_or_else(|| {
|
||||||
|
rpassword::prompt_password("Enter master password: ").unwrap_or_else(|_| {
|
||||||
|
eprintln!("Failed to read password");
|
||||||
|
process::exit(1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let project_name = project.unwrap_or_else(|| {
|
||||||
|
"default".to_string()
|
||||||
|
});
|
||||||
|
|
||||||
|
match Vault::initialize(&password, &project_name) {
|
||||||
|
Ok(_) => println!("Vault initialized for project '{}'", project_name),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to initialize vault: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cli::Commands::Generate { name, length, project, master_password } => {
|
||||||
|
let password = master_password.unwrap_or_else(|| {
|
||||||
|
rpassword::prompt_password("Enter master password: ").unwrap_or_else(|_| {
|
||||||
|
eprintln!("Failed to read password");
|
||||||
|
process::exit(1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let project_name = project.unwrap_or_else(|| {
|
||||||
|
std::env::var("API_VAULT_PROJECT").unwrap_or_else(|_| "default".to_string())
|
||||||
|
});
|
||||||
|
|
||||||
|
let token_length = length.unwrap_or(32);
|
||||||
|
|
||||||
|
match Vault::load(&password, &project_name) {
|
||||||
|
Ok(mut vault) => {
|
||||||
|
match vault.generate_token(&name, token_length) {
|
||||||
|
Ok(generated_token) => {
|
||||||
|
match vault.save(&password) {
|
||||||
|
Ok(_) => println!("Generated token '{}': {}", name, generated_token),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to save vault: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to generate token: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to load vault: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cli::Commands::List { project, master_password } => {
|
||||||
|
let password = master_password.unwrap_or_else(|| {
|
||||||
|
rpassword::prompt_password("Enter master password: ").unwrap_or_else(|_| {
|
||||||
|
eprintln!("Failed to read password");
|
||||||
|
process::exit(1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let project_name = project.unwrap_or_else(|| {
|
||||||
|
std::env::var("API_VAULT_PROJECT").unwrap_or_else(|_| "default".to_string())
|
||||||
|
});
|
||||||
|
|
||||||
|
match Vault::load(&password, &project_name) {
|
||||||
|
Ok(vault) => {
|
||||||
|
if vault.tokens.is_empty() {
|
||||||
|
println!("No tokens found in vault for project '{}'", project_name);
|
||||||
|
} else {
|
||||||
|
println!("Tokens in vault for project '{}':", project_name);
|
||||||
|
println!("{}", "-".repeat(50));
|
||||||
|
for (name, token_data) in &vault.tokens {
|
||||||
|
println!(" Name: {}", name);
|
||||||
|
println!(" Created: {}", token_data.created_at);
|
||||||
|
if let Some(expires) = token_data.expires_at {
|
||||||
|
println!(" Expires: {}", expires);
|
||||||
|
} else {
|
||||||
|
println!(" Expires: Never");
|
||||||
|
}
|
||||||
|
println!(" Rotated: {}", if token_data.auto_rotate { "Yes" } else { "No" });
|
||||||
|
println!("{}", "-".repeat(50));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to load vault: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cli::Commands::Get { name, project, master_password, raw } => {
|
||||||
|
let password = master_password.unwrap_or_else(|| {
|
||||||
|
rpassword::prompt_password("Enter master password: ").unwrap_or_else(|_| {
|
||||||
|
eprintln!("Failed to read password");
|
||||||
|
process::exit(1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let project_name = project.unwrap_or_else(|| {
|
||||||
|
std::env::var("API_VAULT_PROJECT").unwrap_or_else(|_| "default".to_string())
|
||||||
|
});
|
||||||
|
|
||||||
|
match Vault::load(&password, &project_name) {
|
||||||
|
Ok(vault) => {
|
||||||
|
match vault.get_token(&name) {
|
||||||
|
Some(token_data) => {
|
||||||
|
if raw {
|
||||||
|
print!("{}", token_data.value);
|
||||||
|
} else {
|
||||||
|
println!("Token '{}': {}", name, token_data.value);
|
||||||
|
println!("Created: {}", token_data.created_at);
|
||||||
|
if let Some(expires) = &token_data.expires_at {
|
||||||
|
println!("Expires: {}", expires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
eprintln!("Token '{}' not found", name);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to load vault: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cli::Commands::Delete { name, project, master_password } => {
|
||||||
|
let password = master_password.unwrap_or_else(|| {
|
||||||
|
rpassword::prompt_password("Enter master password: ").unwrap_or_else(|_| {
|
||||||
|
eprintln!("Failed to read password");
|
||||||
|
process::exit(1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let project_name = project.unwrap_or_else(|| {
|
||||||
|
std::env::var("API_VAULT_PROJECT").unwrap_or_else(|_| "default".to_string())
|
||||||
|
});
|
||||||
|
|
||||||
|
match Vault::load(&password, &project_name) {
|
||||||
|
Ok(mut vault) => {
|
||||||
|
match vault.remove_token(&name) {
|
||||||
|
Ok(_) => {
|
||||||
|
match vault.save(&password) {
|
||||||
|
Ok(_) => println!("Token '{}' deleted", name),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to save vault: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to delete token: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to load vault: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cli::Commands::Rotate { name, project, master_password, force } => {
|
||||||
|
let password = master_password.unwrap_or_else(|| {
|
||||||
|
rpassword::prompt_password("Enter master password: ").unwrap_or_else(|_| {
|
||||||
|
eprintln!("Failed to read password");
|
||||||
|
process::exit(1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let project_name = project.unwrap_or_else(|| {
|
||||||
|
std::env::var("API_VAULT_PROJECT").unwrap_or_else(|_| "default".to_string())
|
||||||
|
});
|
||||||
|
|
||||||
|
match Vault::load(&password, &project_name) {
|
||||||
|
Ok(mut vault) => {
|
||||||
|
match vault.rotate_token(&name, force) {
|
||||||
|
Ok(new_value) => {
|
||||||
|
match vault.save(&password) {
|
||||||
|
Ok(_) => println!("Rotated token '{}': {}", name, new_value),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to save vault: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to rotate token: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to load vault: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cli::Commands::SetRotation { name, days, project, master_password } => {
|
||||||
|
let password = master_password.unwrap_or_else(|| {
|
||||||
|
rpassword::prompt_password("Enter master password: ").unwrap_or_else(|_| {
|
||||||
|
eprintln!("Failed to read password");
|
||||||
|
process::exit(1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let project_name = project.unwrap_or_else(|| {
|
||||||
|
std::env::var("API_VAULT_PROJECT").unwrap_or_else(|_| "default".to_string())
|
||||||
|
});
|
||||||
|
|
||||||
|
match Vault::load(&password, &project_name) {
|
||||||
|
Ok(mut vault) => {
|
||||||
|
match vault.set_rotation(&name, days) {
|
||||||
|
Ok(_) => {
|
||||||
|
match vault.save(&password) {
|
||||||
|
Ok(_) => println!("Auto-rotation set for '{}': every {} days", name, days),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to save vault: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to set rotation: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to load vault: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cli::Commands::Inject { env_file, token_prefix, project, master_password, dry_run } => {
|
||||||
|
let password = master_password.unwrap_or_else(|| {
|
||||||
|
rpassword::prompt_password("Enter master password: ").unwrap_or_else(|_| {
|
||||||
|
eprintln!("Failed to read password");
|
||||||
|
process::exit(1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let project_name = project.unwrap_or_else(|| {
|
||||||
|
std::env::var("API_VAULT_PROJECT").unwrap_or_else(|_| "default".to_string())
|
||||||
|
});
|
||||||
|
|
||||||
|
let env_path = env_file.unwrap_or_else(|| {
|
||||||
|
".env".to_string()
|
||||||
|
});
|
||||||
|
|
||||||
|
match Vault::load(&password, &project_name) {
|
||||||
|
Ok(vault) => {
|
||||||
|
match env_injector::inject_tokens(&vault, &env_path, token_prefix, dry_run) {
|
||||||
|
Ok(()) => {
|
||||||
|
if dry_run {
|
||||||
|
println!("Dry run complete. No changes made.");
|
||||||
|
} else {
|
||||||
|
println!("Tokens injected into {}", env_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to inject tokens: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to load vault: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cli::Commands::CheckExpired { project, master_password } => {
|
||||||
|
let password = master_password.unwrap_or_else(|| {
|
||||||
|
rpassword::prompt_password("Enter master password: ").unwrap_or_else(|_| {
|
||||||
|
eprintln!("Failed to read password");
|
||||||
|
process::exit(1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let project_name = project.unwrap_or_else(|| {
|
||||||
|
std::env::var("API_VAULT_PROJECT").unwrap_or_else(|_| "default".to_string())
|
||||||
|
});
|
||||||
|
|
||||||
|
match Vault::load(&password, &project_name) {
|
||||||
|
Ok(vault) => {
|
||||||
|
let expired = vault.check_expired_tokens();
|
||||||
|
if expired.is_empty() {
|
||||||
|
println!("No expired tokens found");
|
||||||
|
} else {
|
||||||
|
println!("Expired tokens:");
|
||||||
|
for (name, token_data) in expired {
|
||||||
|
println!(" {} - expired at {}", name, token_data.expires_at.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to load vault: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cli::Commands::RotateExpired { project, master_password } => {
|
||||||
|
let password = master_password.unwrap_or_else(|| {
|
||||||
|
rpassword::prompt_password("Enter master password: ").unwrap_or_else(|_| {
|
||||||
|
eprintln!("Failed to read password");
|
||||||
|
process::exit(1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let project_name = project.unwrap_or_else(|| {
|
||||||
|
std::env::var("API_VAULT_PROJECT").unwrap_or_else(|_| "default".to_string())
|
||||||
|
});
|
||||||
|
|
||||||
|
match Vault::load(&password, &project_name) {
|
||||||
|
Ok(mut vault) => {
|
||||||
|
let rotated = vault.rotate_expired_tokens();
|
||||||
|
if rotated.is_empty() {
|
||||||
|
println!("No expired tokens to rotate");
|
||||||
|
} else {
|
||||||
|
match vault.save(&password) {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("Rotated {} expired token(s):", rotated.len());
|
||||||
|
for name in rotated {
|
||||||
|
println!(" - {}", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to save vault: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to load vault: {}", e);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user