From 314ffb0c21c81ea6db55f0b4ca576cf53d69e955 Mon Sep 17 00:00:00 2001 From: Casey Marshall Date: Fri, 5 May 2023 13:00:56 -0500 Subject: [PATCH] feat!: add service management cli commands Add CLI subcommands for adding (idempotent), listing and deleting service v3 keys. --- src/bin/main.rs | 88 +++++++++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 2 ++ src/secrets.rs | 17 ++++++++++ 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 0e3d263..bf48266 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,9 +1,9 @@ use std::fs::File; use std::io::Read; -use clap::Parser; +use clap::{Parser, Subcommand}; -use onionpipe::{config, OnionPipe, Result}; +use onionpipe::{config, OnionPipe, PipeError, Result}; #[derive(Parser)] #[command(name = "onionpipe")] @@ -12,13 +12,36 @@ struct Cli { #[arg(long)] config: Option, + #[clap(subcommand)] + commands: Option, + forwards: Vec, } +#[derive(Subcommand)] +enum Commands { + #[clap(subcommand)] + Service(ServiceCommands), +} + +#[derive(Subcommand)] +enum ServiceCommands { + Add { name: String }, + Delete { name: String }, + List, +} + #[tokio::main] async fn main() { let cli = Cli::parse(); - let rc = match run(cli).await { + + let result = match &cli.commands { + Some(Commands::Service(ServiceCommands::Add { ref name })) => add_service(name).await, + Some(Commands::Service(ServiceCommands::Delete { ref name })) => delete_service(name).await, + Some(Commands::Service(ServiceCommands::List)) => list_services().await, + None => run(cli).await, + }; + let rc = match result { Ok(_) => 0, Err(e) => { eprintln!("{}", e); @@ -28,6 +51,65 @@ async fn main() { std::process::exit(rc) } +async fn add_service(name: &str) -> Result<()> { + let config_dir = match dirs::config_dir() { + Some(dir) => dir, + None => { + return Err(PipeError::CLI("failed to locate config dir".to_string())); + } + }; + let secrets_dir = config_dir.join("onionpipe"); + let mut secret_store = onionpipe::secrets::SecretStore::new(secrets_dir.to_str().unwrap()); + let key_bytes = secret_store.ensure_service(name)?; + let onion_addr = torut::onion::TorSecretKeyV3::from(key_bytes) + .public() + .get_onion_address() + .to_string(); + println!("{}\t{}", name, onion_addr); + Ok(()) +} + +async fn delete_service(name: &str) -> Result<()> { + let config_dir = match dirs::config_dir() { + Some(dir) => dir, + None => { + return Err(PipeError::CLI("failed to locate config dir".to_string())); + } + }; + let secrets_dir = config_dir.join("onionpipe"); + let mut secret_store = onionpipe::secrets::SecretStore::new(secrets_dir.to_str().unwrap()); + match secret_store.delete_service(name)? { + Some(()) => { + println!("service {} deleted", name); + Ok(()) + } + None => Err(PipeError::CLI( + format!("{}: service not found", name).to_string(), + )), + } +} + +async fn list_services() -> Result<()> { + let config_dir = match dirs::config_dir() { + Some(dir) => dir, + None => { + return Err(PipeError::CLI("failed to locate config dir".to_string())); + } + }; + let secrets_dir = config_dir.join("onionpipe"); + let secret_store = onionpipe::secrets::SecretStore::new(secrets_dir.to_str().unwrap()); + let services = secret_store.list_services()?; + for service_name in services { + let key_bytes = secret_store.get_service(&service_name)?.unwrap(); + let onion_addr = torut::onion::TorSecretKeyV3::from(key_bytes) + .public() + .get_onion_address() + .to_string(); + println!("{}\t{}", service_name, onion_addr); + } + Ok(()) +} + async fn run(cli: Cli) -> Result<()> { unsafe { libc::umask(0o077); diff --git a/src/lib.rs b/src/lib.rs index a3af581..7384f5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,8 @@ pub enum PipeError { Join(#[from] tokio::task::JoinError), #[error("invalid socket address: {0}")] ParseAddr(#[from] net::AddrParseError), + #[error("command failed: {0}")] + CLI(String), #[error("invalid config: {0}")] Config(String), #[error("config parse error: {0}")] diff --git a/src/secrets.rs b/src/secrets.rs index 9fad094..a30b7d3 100644 --- a/src/secrets.rs +++ b/src/secrets.rs @@ -26,6 +26,23 @@ impl SecretStore { } } + pub fn get_service(&self, name: &str) -> Result> { + let service_dir = path::PathBuf::from(&self.secrets_dir).join(SERVICES_DIR); + if !service_dir.exists() { + Ok(None) + } else { + let service_file = service_dir.join(name); + if !service_file.exists() { + Ok(None) + } else { + let contents = fs::read(&service_file)?; + let mut key = [0; 64]; + key.copy_from_slice(&contents); + Ok(Some(key)) + } + } + } + pub fn ensure_service(&mut self, name: &str) -> Result<[u8; 64]> { let service_dir = path::PathBuf::from(&self.secrets_dir).join(SERVICES_DIR); if !service_dir.exists() {