split into workspace so CLI can be a standalone crate
This commit is contained in:
parent
27c2f467c4
commit
55801384eb
19
src-tauri/Cargo.lock
generated
19
src-tauri/Cargo.lock
generated
@ -1204,9 +1204,8 @@ dependencies = [
|
|||||||
"aws-sdk-sts",
|
"aws-sdk-sts",
|
||||||
"aws-smithy-types",
|
"aws-smithy-types",
|
||||||
"aws-types",
|
"aws-types",
|
||||||
"base64 0.22.1",
|
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"clap",
|
"creddy_cli",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
"futures",
|
"futures",
|
||||||
"is-terminal",
|
"is-terminal",
|
||||||
@ -1241,6 +1240,18 @@ dependencies = [
|
|||||||
"windows 0.51.1",
|
"windows 0.51.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "creddy_cli"
|
||||||
|
version = "0.5.3"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"clap",
|
||||||
|
"dirs 5.0.1",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.13"
|
version = "0.5.13"
|
||||||
@ -4619,9 +4630,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.118"
|
version = "1.0.120"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4"
|
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa 1.0.11",
|
"itoa 1.0.11",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
@ -9,37 +9,40 @@ default-run = "creddy"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.57"
|
rust-version = "1.57"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "creddy_cli"
|
|
||||||
path = "src/bin/creddy_cli.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "creddy"
|
name = "creddy"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
# we use a workspace so that we can split out the CLI and make it possible to build independently
|
||||||
|
[workspace]
|
||||||
|
members = ["creddy_cli"]
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
dirs = "5.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
tokio = { version = ">=1.19", features = ["full"] }
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.0.0-beta", features = [] }
|
tauri-build = { version = "2.0.0-beta", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde_json = "1.0"
|
creddy_cli = { path = "./creddy_cli" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
tauri = { version = "2.0.0-beta", features = ["tray-icon"] }
|
tauri = { version = "2.0.0-beta", features = ["tray-icon"] }
|
||||||
sodiumoxide = "0.2.7"
|
sodiumoxide = "0.2.7"
|
||||||
tokio = { version = ">=1.19", features = ["full"] }
|
|
||||||
sysinfo = "0.26.8"
|
sysinfo = "0.26.8"
|
||||||
aws-config = "1.5.3"
|
aws-config = "1.5.3"
|
||||||
aws-types = "1.3.2"
|
aws-types = "1.3.2"
|
||||||
aws-sdk-sts = "1.33.0"
|
aws-sdk-sts = "1.33.0"
|
||||||
aws-smithy-types = "1.2.0"
|
aws-smithy-types = "1.2.0"
|
||||||
|
dirs = { workspace = true }
|
||||||
thiserror = "1.0.38"
|
thiserror = "1.0.38"
|
||||||
once_cell = "1.16.0"
|
once_cell = "1.16.0"
|
||||||
strum = "0.24"
|
strum = "0.24"
|
||||||
strum_macros = "0.24"
|
strum_macros = "0.24"
|
||||||
auto-launch = "0.4.0"
|
auto-launch = "0.4.0"
|
||||||
dirs = "5.0"
|
|
||||||
clap = { version = "3.2.23", features = ["derive"] }
|
|
||||||
is-terminal = "0.4.7"
|
is-terminal = "0.4.7"
|
||||||
argon2 = { version = "0.5.0", features = ["std"] }
|
argon2 = { version = "0.5.0", features = ["std"] }
|
||||||
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
|
||||||
@ -55,7 +58,10 @@ ssh-agent-lib = "0.4.0"
|
|||||||
ssh-key = { version = "0.6.6", features = ["rsa", "ed25519", "encryption"] }
|
ssh-key = { version = "0.6.6", features = ["rsa", "ed25519", "encryption"] }
|
||||||
signature = "2.2.0"
|
signature = "2.2.0"
|
||||||
tokio-stream = "0.1.15"
|
tokio-stream = "0.1.15"
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio", "uuid"] }
|
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio", "uuid"] }
|
||||||
|
tokio = { workspace = true }
|
||||||
tokio-util = { version = "0.7.11", features = ["codec"] }
|
tokio-util = { version = "0.7.11", features = ["codec"] }
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
openssl = "0.10.64"
|
openssl = "0.10.64"
|
||||||
@ -71,8 +77,5 @@ default = ["custom-protocol"]
|
|||||||
# DO NOT remove this
|
# DO NOT remove this
|
||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
base64 = "0.22.1"
|
|
||||||
|
|
||||||
# [profile.dev.build-override]
|
# [profile.dev.build-override]
|
||||||
# opt-level = 3
|
# opt-level = 3
|
||||||
|
12
src-tauri/creddy_cli/Cargo.toml
Normal file
12
src-tauri/creddy_cli/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "creddy_cli"
|
||||||
|
version = "0.5.3"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.86"
|
||||||
|
clap = { version = "3.2.23", features = ["derive"] }
|
||||||
|
dirs = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
@ -1,9 +1,12 @@
|
|||||||
use std::ffi::OsString;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command as ChildCommand;
|
use std::process::Command as ChildCommand;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::{bail, Context};
|
||||||
use clap::{
|
use clap::{
|
||||||
Command,
|
Command,
|
||||||
Arg,
|
Arg,
|
||||||
@ -11,34 +14,22 @@ use clap::{
|
|||||||
ArgAction,
|
ArgAction,
|
||||||
builder::PossibleValuesParser,
|
builder::PossibleValuesParser,
|
||||||
value_parser,
|
value_parser,
|
||||||
};
|
};
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
use crate::errors::*;
|
use crate::proto::{
|
||||||
use crate::srv::{
|
CliCredential,
|
||||||
self,
|
CliRequest,
|
||||||
Request,
|
CliResponse,
|
||||||
Response
|
ServerError,
|
||||||
};
|
ShortcutAction,
|
||||||
use crate::shortcuts::ShortcutAction;
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
use {
|
|
||||||
std::os::unix::process::CommandExt,
|
|
||||||
tokio::net::UnixStream,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
use {
|
|
||||||
tokio::net::windows::named_pipe::{NamedPipeClient, ClientOptions},
|
|
||||||
windows::Win32::Foundation::ERROR_PIPE_BUSY,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
pub fn parser() -> Command<'static> {
|
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 credential manager")
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("server_addr")
|
Arg::new("server_addr")
|
||||||
.short('a')
|
.short('a')
|
||||||
@ -102,56 +93,53 @@ pub fn parser() -> Command<'static> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn get(args: &ArgMatches, global_args: &ArgMatches) -> Result<(), CliError> {
|
pub fn get(args: &ArgMatches, global_args: &ArgMatches) -> anyhow::Result<()> {
|
||||||
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 = global_args.get_one("server_addr").cloned();
|
let addr = global_args.get_one("server_addr").cloned();
|
||||||
|
|
||||||
let output = match make_request(addr, &Request::GetAwsCredentials { name, base })? {
|
let output = match make_request(addr, &CliRequest::GetCredential { name, base })?? {
|
||||||
Response::AwsBase(creds) => serde_json::to_string(&creds).unwrap(),
|
CliResponse::Credential(c) => serde_json::to_string_pretty(&c).unwrap(),
|
||||||
Response::AwsSession(creds) => serde_json::to_string(&creds).unwrap(),
|
r => bail!("Unexpected response from server: {r}"),
|
||||||
r => return Err(RequestError::Unexpected(r).into()),
|
|
||||||
};
|
};
|
||||||
println!("{output}");
|
println!("{output}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn exec(args: &ArgMatches, global_args: &ArgMatches) -> Result<(), CliError> {
|
pub fn exec(args: &ArgMatches, global_args: &ArgMatches) -> anyhow::Result<()> {
|
||||||
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 = global_args.get_one("server_addr").cloned();
|
let addr = global_args.get_one("server_addr").cloned();
|
||||||
let mut cmd_line = args.get_many("command")
|
// Clap guarantees that cmd_line will be a sequence of at least 1 item
|
||||||
.ok_or(ExecError::NoCommand)?;
|
// test this!
|
||||||
|
let mut cmd_line = args.get_many("command").unwrap();
|
||||||
let cmd_name: &String = cmd_line.next().unwrap(); // Clap guarantees that there will be at least one
|
let cmd_name: &String = cmd_line.next().unwrap();
|
||||||
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(addr, &Request::GetAwsCredentials { name, base })? {
|
match make_request(addr, &CliRequest::GetCredential { name, base })?? {
|
||||||
Response::AwsBase(creds) => {
|
CliResponse::Credential(CliCredential::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);
|
||||||
},
|
},
|
||||||
Response::AwsSession(creds) => {
|
CliResponse::Credential(CliCredential::AwsSession(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);
|
||||||
cmd.env("AWS_SESSION_TOKEN", creds.session_token);
|
cmd.env("AWS_SESSION_TOKEN", creds.session_token);
|
||||||
},
|
},
|
||||||
r => return Err(RequestError::Unexpected(r).into()),
|
r => bail!("Unexpected response from server: {r}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
// cmd.exec() never returns if successful
|
// cmd.exec() never returns if successful
|
||||||
let e = cmd.exec();
|
let e = cmd.exec();
|
||||||
match e.kind() {
|
Err(e).with_context(|| {
|
||||||
std::io::ErrorKind::NotFound => {
|
// eventually figure out how to display the actual command
|
||||||
let name: OsString = cmd_name.into();
|
format!("Failed to execute command")
|
||||||
Err(ExecError::NotFound(name).into())
|
})?;
|
||||||
}
|
Ok(())
|
||||||
_ => Err(ExecError::ExecutionFailed(e).into()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@ -172,7 +160,7 @@ pub fn exec(args: &ArgMatches, global_args: &ArgMatches) -> Result<(), CliError>
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn invoke_shortcut(args: &ArgMatches, global_args: &ArgMatches) -> Result<(), CliError> {
|
pub fn invoke_shortcut(args: &ArgMatches, global_args: &ArgMatches) -> anyhow::Result<()> {
|
||||||
let addr = global_args.get_one("server_addr").cloned();
|
let addr = global_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,
|
||||||
@ -180,48 +168,32 @@ pub fn invoke_shortcut(args: &ArgMatches, global_args: &ArgMatches) -> Result<()
|
|||||||
Some(&_) | None => unreachable!("Unknown shortcut action"), // guaranteed by clap
|
Some(&_) | None => unreachable!("Unknown shortcut action"), // guaranteed by clap
|
||||||
};
|
};
|
||||||
|
|
||||||
let req = Request::InvokeShortcut(action);
|
let req = CliRequest::InvokeShortcut(action);
|
||||||
match make_request(addr, &req) {
|
match make_request(addr, &req)?? {
|
||||||
Ok(Response::Empty) => Ok(()),
|
CliResponse::Empty => Ok(()),
|
||||||
Ok(r) => Err(RequestError::Unexpected(r).into()),
|
r => bail!("Unexpected response from server: {r}"),
|
||||||
Err(e) => Err(e.into()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Explanation for double-result: the server will return a (serialized) Result
|
||||||
|
// to indicate when the operation succeeded or failed, which we deserialize.
|
||||||
|
// However, the operation may fail to even communicate with the server, in
|
||||||
|
// which case we return the outer Result
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn make_request(addr: Option<PathBuf>, req: &Request) -> Result<Response, RequestError> {
|
async fn make_request(
|
||||||
|
addr: Option<PathBuf>,
|
||||||
|
req: &CliRequest
|
||||||
|
) -> anyhow::Result<Result<CliResponse, ServerError>> {
|
||||||
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(addr).await?;
|
let mut stream = crate::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);
|
||||||
stream.read_to_end(&mut buf).await?;
|
stream.read_to_end(&mut buf).await?;
|
||||||
let res: Result<Response, ServerError> = serde_json::from_slice(&buf)?;
|
let res: Result<CliResponse, ServerError> = serde_json::from_slice(&buf)?;
|
||||||
Ok(res?)
|
Ok(res)
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
async fn connect(addr: Option<PathBuf>) -> Result<NamedPipeClient, std::io::Error> {
|
|
||||||
// apparently attempting to connect can fail if there's already a client connected
|
|
||||||
loop {
|
|
||||||
let addr = addr.unwrap_or_else(|| srv::addr("creddy-server"));
|
|
||||||
match ClientOptions::new().open(&addr) {
|
|
||||||
Ok(stream) => return Ok(stream),
|
|
||||||
Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY.0 as i32) => (),
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
|
||||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
async fn connect(addr: Option<PathBuf>) -> Result<UnixStream, std::io::Error> {
|
|
||||||
let path = addr.unwrap_or_else(|| srv::addr("creddy-server"));
|
|
||||||
UnixStream::connect(&path).await
|
|
||||||
}
|
}
|
39
src-tauri/creddy_cli/src/lib.rs
Normal file
39
src-tauri/creddy_cli/src/lib.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
mod cli;
|
||||||
|
pub use cli::{
|
||||||
|
exec,
|
||||||
|
get,
|
||||||
|
parser,
|
||||||
|
invoke_shortcut,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) use platform::connect;
|
||||||
|
pub use platform::server_addr;
|
||||||
|
|
||||||
|
mod proto;
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
mod platform {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use tokio::net::UnixStream;
|
||||||
|
|
||||||
|
pub async fn connect(addr: Option<PathBuf>) -> Result<UnixStream, std::io::Error> {
|
||||||
|
let path = addr.unwrap_or_else(|| server_addr("creddy-server"));
|
||||||
|
UnixStream::connect(&path).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn server_addr(sock_name: &str) -> PathBuf {
|
||||||
|
let mut path = dirs::runtime_dir()
|
||||||
|
.unwrap_or_else(|| PathBuf::from("/tmp"));
|
||||||
|
path.push(format!("{sock_name}.sock"));
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod platform {
|
||||||
|
pub fn server_addr(sock_name: &str) -> String {
|
||||||
|
format!(r"\\.\pipe\{sock_name}")
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,14 @@
|
|||||||
// Windows isn't really amenable to having a single executable work as both a CLI and GUI app,
|
use std::env;
|
||||||
// so we just have a second binary for CLI usage
|
use std::process::{self, Command};
|
||||||
use creddy::{
|
|
||||||
cli,
|
|
||||||
errors::CliError,
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
env,
|
|
||||||
process::{self, Command},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let global_matches = cli::parser().get_matches();
|
let global_matches = creddy_cli::parser().get_matches();
|
||||||
let res = match global_matches.subcommand() {
|
let res = match global_matches.subcommand() {
|
||||||
None | Some(("run", _)) => launch_gui(),
|
None | Some(("run", _)) => launch_gui(),
|
||||||
Some(("get", m)) => cli::get(m, &global_matches),
|
Some(("get", m)) => creddy_cli::get(m, &global_matches),
|
||||||
Some(("exec", m)) => cli::exec(m, &global_matches),
|
Some(("exec", m)) => creddy_cli::exec(m, &global_matches),
|
||||||
Some(("shortcut", m)) => cli::invoke_shortcut(m, &global_matches),
|
Some(("shortcut", m)) => creddy_cli::invoke_shortcut(m, &global_matches),
|
||||||
_ => unreachable!("Unknown subcommand"),
|
_ => unreachable!("Unknown subcommand"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -27,7 +19,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn launch_gui() -> Result<(), CliError> {
|
fn launch_gui() -> anyhow::Result<()> {
|
||||||
let mut path = env::current_exe()?;
|
let mut path = env::current_exe()?;
|
||||||
path.pop(); // bin dir
|
path.pop(); // bin dir
|
||||||
|
|
90
src-tauri/creddy_cli/src/proto.rs
Normal file
90
src-tauri/creddy_cli/src/proto.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
use std::fmt::{
|
||||||
|
Display,
|
||||||
|
Formatter,
|
||||||
|
Error as FmtError
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum CliRequest {
|
||||||
|
GetCredential {
|
||||||
|
name: Option<String>,
|
||||||
|
base: bool,
|
||||||
|
},
|
||||||
|
InvokeShortcut(ShortcutAction),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum ShortcutAction {
|
||||||
|
ShowWindow,
|
||||||
|
LaunchTerminal,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum CliResponse {
|
||||||
|
Credential(CliCredential),
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CliResponse {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
|
||||||
|
match self {
|
||||||
|
CliResponse::Credential(CliCredential::AwsBase(_)) => write!(f, "Credential (AwsBase)"),
|
||||||
|
CliResponse::Credential(CliCredential::AwsSession(_)) => write!(f, "Credential (AwsSession)"),
|
||||||
|
CliResponse::Empty => write!(f, "Empty"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum CliCredential {
|
||||||
|
AwsBase(AwsBaseCredential),
|
||||||
|
AwsSession(AwsSessionCredential),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct AwsBaseCredential {
|
||||||
|
#[serde(default = "default_aws_version")]
|
||||||
|
pub version: usize,
|
||||||
|
pub access_key_id: String,
|
||||||
|
pub secret_access_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct AwsSessionCredential {
|
||||||
|
#[serde(default = "default_aws_version")]
|
||||||
|
pub version: usize,
|
||||||
|
pub access_key_id: String,
|
||||||
|
pub secret_access_key: String,
|
||||||
|
pub session_token: String,
|
||||||
|
// we don't need to know the expiration for the CLI, so just use a string here
|
||||||
|
pub expiration: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn default_aws_version() -> usize { 1 }
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct ServerError {
|
||||||
|
code: String,
|
||||||
|
msg: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ServerError {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
|
||||||
|
write!(f, "Error response ({}) from server: {}", self.code, self.msg)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ServerError {}
|
@ -36,7 +36,7 @@ pub trait ShowError<T, E>
|
|||||||
fn error_print_prefix(self, prefix: &str);
|
fn error_print_prefix(self, prefix: &str);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, E> ShowError<T, E> for Result<T, E>
|
impl<T, E> ShowError<T, E> for Result<T, E>
|
||||||
where E: std::fmt::Display
|
where E: std::fmt::Display
|
||||||
{
|
{
|
||||||
fn error_popup(self, title: &str) {
|
fn error_popup(self, title: &str) {
|
||||||
@ -91,7 +91,7 @@ impl<E: Error> Serialize for SerializeUpstream<E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_upstream_err<E, M>(err: &E, map: &mut M) -> Result<(), M::Error>
|
fn serialize_upstream_err<E, M>(err: &E, map: &mut M) -> Result<(), M::Error>
|
||||||
where
|
where
|
||||||
E: Error,
|
E: Error,
|
||||||
M: serde::ser::SerializeMap,
|
M: serde::ser::SerializeMap,
|
||||||
@ -370,7 +370,7 @@ pub enum RequestError {
|
|||||||
#[error("Error response from server: {0}")]
|
#[error("Error response from server: {0}")]
|
||||||
Server(ServerError),
|
Server(ServerError),
|
||||||
#[error("Unexpected response from server")]
|
#[error("Unexpected response from server")]
|
||||||
Unexpected(crate::srv::Response),
|
Unexpected(crate::srv::CliResponse),
|
||||||
#[error("The server did not respond with valid JSON")]
|
#[error("The server did not respond with valid JSON")]
|
||||||
InvalidJson(#[from] serde_json::Error),
|
InvalidJson(#[from] serde_json::Error),
|
||||||
#[error("Error reading/writing stream: {0}")]
|
#[error("Error reading/writing stream: {0}")]
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod cli;
|
|
||||||
mod config;
|
mod config;
|
||||||
mod credentials;
|
mod credentials;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
|
@ -5,21 +5,20 @@
|
|||||||
|
|
||||||
use creddy::{
|
use creddy::{
|
||||||
app,
|
app,
|
||||||
cli,
|
|
||||||
errors::ShowError,
|
errors::ShowError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let global_matches = cli::parser().get_matches();
|
let global_matches = creddy_cli::parser().get_matches();
|
||||||
let res = match global_matches.subcommand() {
|
let res = match global_matches.subcommand() {
|
||||||
None | Some(("run", _)) => {
|
None | Some(("run", _)) => {
|
||||||
app::run().error_popup("Creddy encountered an error");
|
app::run().error_popup("Creddy encountered an error");
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
Some(("get", m)) => cli::get(m, &global_matches),
|
Some(("get", m)) => creddy_cli::get(m, &global_matches),
|
||||||
Some(("exec", m)) => cli::exec(m, &global_matches),
|
Some(("exec", m)) => creddy_cli::exec(m, &global_matches),
|
||||||
Some(("shortcut", m)) => cli::invoke_shortcut(m, &global_matches),
|
Some(("shortcut", m)) => creddy_cli::invoke_shortcut(m, &global_matches),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,8 +9,9 @@ use crate::shortcuts::{self, ShortcutAction};
|
|||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
use super::{
|
use super::{
|
||||||
CloseWaiter,
|
CloseWaiter,
|
||||||
Request,
|
CliCredential,
|
||||||
Response,
|
CliRequest,
|
||||||
|
CliResponse,
|
||||||
Stream,
|
Stream,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -43,13 +44,12 @@ async fn handle(
|
|||||||
let waiter = CloseWaiter { stream: &mut stream };
|
let waiter = CloseWaiter { stream: &mut stream };
|
||||||
|
|
||||||
|
|
||||||
let req: Request = serde_json::from_slice(&buf)?;
|
let req: CliRequest = serde_json::from_slice(&buf)?;
|
||||||
let res = match req {
|
let res = match req {
|
||||||
Request::GetAwsCredentials { name, base } => get_aws_credentials(
|
CliRequest::GetCredential{ name, base } => get_aws_credentials(
|
||||||
name, base, client, app_handle, waiter
|
name, base, client, app_handle, waiter
|
||||||
).await,
|
).await,
|
||||||
Request::InvokeShortcut(action) => invoke_shortcut(action).await,
|
CliRequest::InvokeShortcut(action) => invoke_shortcut(action).await,
|
||||||
Request::GetSshSignature(_) => return Err(HandlerError::Denied),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// doesn't make sense to send the error to the client if the client has already left
|
// doesn't make sense to send the error to the client if the client has already left
|
||||||
@ -63,9 +63,9 @@ async fn handle(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async fn invoke_shortcut(action: ShortcutAction) -> Result<Response, HandlerError> {
|
async fn invoke_shortcut(action: ShortcutAction) -> Result<CliResponse, HandlerError> {
|
||||||
shortcuts::exec_shortcut(action);
|
shortcuts::exec_shortcut(action);
|
||||||
Ok(Response::Empty)
|
Ok(CliResponse::Empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ async fn get_aws_credentials(
|
|||||||
client: Client,
|
client: Client,
|
||||||
app_handle: AppHandle,
|
app_handle: AppHandle,
|
||||||
mut waiter: CloseWaiter<'_>,
|
mut waiter: CloseWaiter<'_>,
|
||||||
) -> Result<Response, HandlerError> {
|
) -> Result<CliResponse, HandlerError> {
|
||||||
let state = app_handle.state::<AppState>();
|
let state = app_handle.state::<AppState>();
|
||||||
let rehide_ms = {
|
let rehide_ms = {
|
||||||
let config = state.config.read().await;
|
let config = state.config.read().await;
|
||||||
@ -108,11 +108,11 @@ async fn get_aws_credentials(
|
|||||||
Approval::Approved => {
|
Approval::Approved => {
|
||||||
if response.base {
|
if response.base {
|
||||||
let creds = state.get_aws_base(name).await?;
|
let creds = state.get_aws_base(name).await?;
|
||||||
Ok(Response::AwsBase(creds))
|
Ok(CliResponse::Credential(CliCredential::AwsBase(creds)))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let creds = state.get_aws_session(name).await?;
|
let creds = state.get_aws_session(name).await?.clone();
|
||||||
Ok(Response::AwsSession(creds.clone()))
|
Ok(CliResponse::Credential(CliCredential::AwsSession(creds)))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Approval::Denied => Err(HandlerError::Denied),
|
Approval::Denied => Err(HandlerError::Denied),
|
||||||
@ -129,4 +129,4 @@ async fn get_aws_credentials(
|
|||||||
|
|
||||||
lease.release();
|
lease.release();
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ use tauri::{
|
|||||||
};
|
};
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use ssh_agent_lib::proto::message::SignRequest;
|
|
||||||
|
|
||||||
use crate::credentials::{AwsBaseCredential, AwsSessionCredential};
|
use crate::credentials::{AwsBaseCredential, AwsSessionCredential};
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
@ -15,25 +14,32 @@ use crate::shortcuts::ShortcutAction;
|
|||||||
pub mod creddy_server;
|
pub mod creddy_server;
|
||||||
pub mod agent;
|
pub mod agent;
|
||||||
use platform::Stream;
|
use platform::Stream;
|
||||||
pub use platform::addr;
|
|
||||||
|
|
||||||
|
|
||||||
|
// These types match what's defined in creddy_cli, but they are separate types
|
||||||
|
// so that we avoid polluting the standalone CLI with a bunch of dependencies
|
||||||
|
// that would make it impossible to build a completely static-linked version
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub enum Request {
|
pub enum CliRequest {
|
||||||
GetAwsCredentials {
|
GetCredential {
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
base: bool,
|
base: bool,
|
||||||
},
|
},
|
||||||
GetSshSignature(SignRequest),
|
|
||||||
InvokeShortcut(ShortcutAction),
|
InvokeShortcut(ShortcutAction),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub enum Response {
|
pub enum CliResponse {
|
||||||
|
Credential(CliCredential),
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum CliCredential {
|
||||||
AwsBase(AwsBaseCredential),
|
AwsBase(AwsBaseCredential),
|
||||||
AwsSession(AwsSessionCredential),
|
AwsSession(AwsSessionCredential),
|
||||||
Empty,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -92,7 +98,7 @@ mod platform {
|
|||||||
pub type Stream = UnixStream;
|
pub type Stream = UnixStream;
|
||||||
|
|
||||||
pub fn bind(sock_name: &str) -> std::io::Result<(UnixListener, PathBuf)> {
|
pub fn bind(sock_name: &str) -> std::io::Result<(UnixListener, PathBuf)> {
|
||||||
let path = addr(sock_name);
|
let path = creddy_cli::server_addr(sock_name);
|
||||||
match std::fs::remove_file(&path) {
|
match std::fs::remove_file(&path) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) if e.kind() == ErrorKind::NotFound => (),
|
Err(e) if e.kind() == ErrorKind::NotFound => (),
|
||||||
@ -112,14 +118,6 @@ mod platform {
|
|||||||
|
|
||||||
Ok((stream, pid))
|
Ok((stream, pid))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn addr(sock_name: &str) -> PathBuf {
|
|
||||||
let mut path = dirs::runtime_dir()
|
|
||||||
.unwrap_or_else(|| PathBuf::from("/tmp"));
|
|
||||||
path.push(format!("{sock_name}.sock"));
|
|
||||||
path
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -140,7 +138,7 @@ mod platform {
|
|||||||
pub type Stream = NamedPipeServer;
|
pub type Stream = NamedPipeServer;
|
||||||
|
|
||||||
pub fn bind(sock_name: &str) -> std::io::Result<(String, NamedPipeServer)> {
|
pub fn bind(sock_name: &str) -> std::io::Result<(String, NamedPipeServer)> {
|
||||||
let addr = addr(sock_name);
|
let addr = creddy_cli::server_addr(sock_name);
|
||||||
let listener = ServerOptions::new()
|
let listener = ServerOptions::new()
|
||||||
.first_pipe_instance(true)
|
.first_pipe_instance(true)
|
||||||
.create(&addr)?;
|
.create(&addr)?;
|
||||||
@ -163,8 +161,4 @@ mod platform {
|
|||||||
unsafe { GetNamedPipeClientProcessId(handle, &mut pid as *mut u32)? };
|
unsafe { GetNamedPipeClientProcessId(handle, &mut pid as *mut u32)? };
|
||||||
Ok((stream, pid))
|
Ok((stream, pid))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn addr(sock_name: &str) -> String {
|
|
||||||
format!(r"\\.\pipe\{sock_name}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user