Compare commits

..

2 Commits

Author SHA1 Message Date
a49bd47e8c make server_addr configurable for client 2024-07-12 14:33:09 -04:00
5cf848f7fe clean up warnings 2024-07-11 06:04:56 -04:00
11 changed files with 65 additions and 68 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "creddy", "name": "creddy",
"version": "0.5.2", "version": "0.5.3",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",

2
src-tauri/Cargo.lock generated
View File

@ -1196,7 +1196,7 @@ dependencies = [
[[package]] [[package]]
name = "creddy" name = "creddy"
version = "0.5.2" version = "0.5.3"
dependencies = [ dependencies = [
"argon2", "argon2",
"auto-launch", "auto-launch",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "creddy" name = "creddy"
version = "0.5.2" version = "0.5.3"
description = "A friendly AWS credentials manager" description = "A friendly AWS credentials manager"
authors = ["Joseph Montanaro"] authors = ["Joseph Montanaro"]
license = "" license = ""

View File

@ -11,13 +11,7 @@ use std::{
fn main() { fn main() {
let args = cli::parser().get_matches(); let res = match cli::parser().get_matches().subcommand() {
if let Some(true) = args.get_one::<bool>("help") {
cli::parser().print_help().unwrap(); // if we can't print help we can't print an error
process::exit(0);
}
let res = match args.subcommand() {
None | Some(("run", _)) => launch_gui(), None | Some(("run", _)) => launch_gui(),
Some(("get", m)) => cli::get(m), Some(("get", m)) => cli::get(m),
Some(("exec", m)) => cli::exec(m), Some(("exec", m)) => cli::exec(m),

View File

@ -1,4 +1,5 @@
use std::ffi::OsString; use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Command as ChildCommand; use std::process::Command as ChildCommand;
#[cfg(windows)] #[cfg(windows)]
use std::time::Duration; use std::time::Duration;
@ -9,6 +10,7 @@ use clap::{
ArgMatches, ArgMatches,
ArgAction, ArgAction,
builder::PossibleValuesParser, builder::PossibleValuesParser,
value_parser,
}; };
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
@ -37,6 +39,14 @@ pub fn parser() -> Command<'static> {
Command::new("creddy") Command::new("creddy")
.version(env!("CARGO_PKG_VERSION")) .version(env!("CARGO_PKG_VERSION"))
.about("A friendly AWS credentials manager") .about("A friendly AWS credentials manager")
.arg(
Arg::new("server_addr")
.short('a')
.long("server-addr")
.takes_value(true)
.value_parser(value_parser!(PathBuf))
.help("Connect to the main Creddy process at this address")
)
.subcommand( .subcommand(
Command::new("run") Command::new("run")
.about("Launch Creddy") .about("Launch Creddy")
@ -71,6 +81,7 @@ pub fn parser() -> Command<'static> {
Arg::new("name") Arg::new("name")
.short('n') .short('n')
.long("name") .long("name")
.takes_value(true)
.help("If unspecified, use default credentials") .help("If unspecified, use default credentials")
) )
.arg( .arg(
@ -94,8 +105,9 @@ pub fn parser() -> Command<'static> {
pub fn get(args: &ArgMatches) -> Result<(), CliError> { pub fn get(args: &ArgMatches) -> Result<(), CliError> {
let name = args.get_one("name").cloned(); let name = args.get_one("name").cloned();
let base = *args.get_one("base").unwrap_or(&false); let base = *args.get_one("base").unwrap_or(&false);
let addr = args.get_one("server_addr").cloned();
let output = match make_request(&Request::GetAwsCredentials { name, base })? { let output = match make_request(addr, &Request::GetAwsCredentials { name, base })? {
Response::AwsBase(creds) => serde_json::to_string(&creds).unwrap(), Response::AwsBase(creds) => serde_json::to_string(&creds).unwrap(),
Response::AwsSession(creds) => serde_json::to_string(&creds).unwrap(), Response::AwsSession(creds) => serde_json::to_string(&creds).unwrap(),
r => return Err(RequestError::Unexpected(r).into()), r => return Err(RequestError::Unexpected(r).into()),
@ -108,6 +120,7 @@ pub fn get(args: &ArgMatches) -> Result<(), CliError> {
pub fn exec(args: &ArgMatches) -> Result<(), CliError> { pub fn exec(args: &ArgMatches) -> Result<(), CliError> {
let name = args.get_one("name").cloned(); let name = args.get_one("name").cloned();
let base = *args.get_one("base").unwrap_or(&false); let base = *args.get_one("base").unwrap_or(&false);
let addr = args.get_one("server_addr").cloned();
let mut cmd_line = args.get_many("command") let mut cmd_line = args.get_many("command")
.ok_or(ExecError::NoCommand)?; .ok_or(ExecError::NoCommand)?;
@ -115,7 +128,7 @@ pub fn exec(args: &ArgMatches) -> Result<(), CliError> {
let mut cmd = ChildCommand::new(cmd_name); let mut cmd = ChildCommand::new(cmd_name);
cmd.args(cmd_line); cmd.args(cmd_line);
match make_request(&Request::GetAwsCredentials { name, base })? { match make_request(addr, &Request::GetAwsCredentials { name, base })? {
Response::AwsBase(creds) => { Response::AwsBase(creds) => {
cmd.env("AWS_ACCESS_KEY_ID", creds.access_key_id); cmd.env("AWS_ACCESS_KEY_ID", creds.access_key_id);
cmd.env("AWS_SECRET_ACCESS_KEY", creds.secret_access_key); cmd.env("AWS_SECRET_ACCESS_KEY", creds.secret_access_key);
@ -160,6 +173,7 @@ pub fn exec(args: &ArgMatches) -> Result<(), CliError> {
pub fn invoke_shortcut(args: &ArgMatches) -> Result<(), CliError> { pub fn invoke_shortcut(args: &ArgMatches) -> Result<(), CliError> {
let addr = args.get_one("server_addr").cloned();
let action = match args.get_one::<String>("action").map(|s| s.as_str()) { let action = match args.get_one::<String>("action").map(|s| s.as_str()) {
Some("show_window") => ShortcutAction::ShowWindow, Some("show_window") => ShortcutAction::ShowWindow,
Some("launch_terminal") => ShortcutAction::LaunchTerminal, Some("launch_terminal") => ShortcutAction::LaunchTerminal,
@ -167,7 +181,7 @@ pub fn invoke_shortcut(args: &ArgMatches) -> Result<(), CliError> {
}; };
let req = Request::InvokeShortcut(action); let req = Request::InvokeShortcut(action);
match make_request(&req) { match make_request(addr, &req) {
Ok(Response::Empty) => Ok(()), Ok(Response::Empty) => Ok(()),
Ok(r) => Err(RequestError::Unexpected(r).into()), Ok(r) => Err(RequestError::Unexpected(r).into()),
Err(e) => Err(e.into()), Err(e) => Err(e.into()),
@ -176,12 +190,12 @@ pub fn invoke_shortcut(args: &ArgMatches) -> Result<(), CliError> {
#[tokio::main] #[tokio::main]
async fn make_request(req: &Request) -> Result<Response, RequestError> { async fn make_request(addr: Option<PathBuf>, req: &Request) -> Result<Response, RequestError> {
let mut data = serde_json::to_string(req).unwrap(); let mut data = serde_json::to_string(req).unwrap();
// server expects newline marking end of request // server expects newline marking end of request
data.push('\n'); data.push('\n');
let mut stream = connect().await?; let mut stream = connect(addr).await?;
stream.write_all(&data.as_bytes()).await?; stream.write_all(&data.as_bytes()).await?;
let mut buf = Vec::with_capacity(1024); let mut buf = Vec::with_capacity(1024);
@ -192,10 +206,10 @@ async fn make_request(req: &Request) -> Result<Response, RequestError> {
#[cfg(windows)] #[cfg(windows)]
async fn connect() -> Result<NamedPipeClient, std::io::Error> { async fn connect(addr: Option<PathBuf>) -> Result<NamedPipeClient, std::io::Error> {
// apparently attempting to connect can fail if there's already a client connected // apparently attempting to connect can fail if there's already a client connected
loop { loop {
let addr = srv::addr("creddy-server"); let addr = addr.unwrap_or_else(|| srv::addr("creddy-server"));
match ClientOptions::new().open(&addr) { match ClientOptions::new().open(&addr) {
Ok(stream) => return Ok(stream), Ok(stream) => return Ok(stream),
Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY.0 as i32) => (), Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY.0 as i32) => (),
@ -207,7 +221,7 @@ async fn connect() -> Result<NamedPipeClient, std::io::Error> {
#[cfg(unix)] #[cfg(unix)]
async fn connect() -> Result<UnixStream, std::io::Error> { async fn connect(addr: Option<PathBuf>) -> Result<UnixStream, std::io::Error> {
let path = srv::addr("creddy-server"); let path = addr.unwrap_or_else(|| srv::addr("creddy-server"));
UnixStream::connect(&path).await UnixStream::connect(&path).await
} }

View File

@ -203,19 +203,6 @@ mod tests {
) )
} }
fn test_uuid() -> Uuid {
Uuid::try_parse("00000000-0000-0000-0000-000000000000").unwrap()
}
fn test_uuid_2() -> Uuid {
Uuid::try_parse("ffffffff-ffff-ffff-ffff-ffffffffffff").unwrap()
}
fn test_uuid_random() -> Uuid {
let bytes = Crypto::salt();
Uuid::from_slice(&bytes[..16]).unwrap()
}
#[sqlx::test(fixtures("aws_credentials"))] #[sqlx::test(fixtures("aws_credentials"))]
async fn test_load(pool: SqlitePool) { async fn test_load(pool: SqlitePool) {

View File

@ -112,15 +112,16 @@ impl CredentialRecord {
Ok(Self::from_parts(row, credential)) Ok(Self::from_parts(row, credential))
} }
// pub async fn load(id: &Uuid, crypto: &Crypto, pool: &SqlitePool) -> Result<Self, LoadCredentialsError> { #[cfg(test)]
// let row: CredentialRow = sqlx::query_as("SELECT * FROM credentials WHERE id = ?") pub async fn load(id: &Uuid, crypto: &Crypto, pool: &SqlitePool) -> Result<Self, LoadCredentialsError> {
// .bind(id) let row: CredentialRow = sqlx::query_as("SELECT * FROM credentials WHERE id = ?")
// .fetch_optional(pool) .bind(id)
// .await? .fetch_optional(pool)
// .ok_or(LoadCredentialsError::NoCredentials)?; .await?
.ok_or(LoadCredentialsError::NoCredentials)?;
// Self::load_credential(row, crypto, pool).await Self::load_credential(row, crypto, pool).await
// } }
pub async fn load_by_name(name: &str, crypto: &Crypto, pool: &SqlitePool) -> Result<Self, LoadCredentialsError> { pub async fn load_by_name(name: &str, crypto: &Crypto, pool: &SqlitePool) -> Result<Self, LoadCredentialsError> {
let row: CredentialRow = sqlx::query_as("SELECT * FROM credentials WHERE name = ?") let row: CredentialRow = sqlx::query_as("SELECT * FROM credentials WHERE name = ?")

View File

@ -298,7 +298,6 @@ fn deserialize_algorithm<'de, D>(deserializer: D) -> Result<Algorithm, D::Error>
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::fs::{self, File}; use std::fs::{self, File};
use ssh_key::Fingerprint;
use sqlx::types::uuid::uuid; use sqlx::types::uuid::uuid;
use super::*; use super::*;
@ -447,7 +446,7 @@ mod tests {
async fn test_load_db(pool: SqlitePool) { async fn test_load_db(pool: SqlitePool) {
let crypto = Crypto::fixed(); let crypto = Crypto::fixed();
let id = uuid!("11111111-1111-1111-1111-111111111111"); let id = uuid!("11111111-1111-1111-1111-111111111111");
let k = SshKey::load(&id, &crypto, &pool).await SshKey::load(&id, &crypto, &pool).await
.expect("Failed to load SSH key from database"); .expect("Failed to load SSH key from database");
} }

View File

@ -44,12 +44,14 @@ pub async fn load_bytes(pool: &SqlitePool, name: &str) -> Result<Option<Vec<u8>>
} }
// pub async fn delete(pool: &SqlitePool, name: &str) -> Result<(), sqlx::Error> { // we don't have a need for this right now, but we will some day
// sqlx::query!("DELETE FROM kv WHERE name = ?", name) #[cfg(test)]
// .execute(pool) pub async fn delete(pool: &SqlitePool, name: &str) -> Result<(), sqlx::Error> {
// .await?; sqlx::query!("DELETE FROM kv WHERE name = ?", name)
// Ok(()) .execute(pool)
// } .await?;
Ok(())
}
pub async fn delete_multi(pool: &SqlitePool, names: &[&str]) -> Result<(), sqlx::Error> { pub async fn delete_multi(pool: &SqlitePool, names: &[&str]) -> Result<(), sqlx::Error> {

View File

@ -50,7 +50,7 @@
} }
}, },
"productName": "creddy", "productName": "creddy",
"version": "0.5.2", "version": "0.5.3",
"identifier": "creddy", "identifier": "creddy",
"plugins": {}, "plugins": {},
"app": { "app": {