2023-08-04 04:57:55 +00:00
|
|
|
use std::ffi::OsString;
|
2023-05-06 19:01:56 +00:00
|
|
|
use std::process::Command as ChildCommand;
|
2023-09-19 03:13:29 +00:00
|
|
|
use std::time::Duration;
|
2023-05-06 19:01:56 +00:00
|
|
|
|
|
|
|
use clap::{
|
|
|
|
Command,
|
|
|
|
Arg,
|
|
|
|
ArgMatches,
|
|
|
|
ArgAction
|
|
|
|
};
|
2023-09-19 03:13:29 +00:00
|
|
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
2023-05-06 19:01:56 +00:00
|
|
|
|
2023-09-19 03:13:29 +00:00
|
|
|
use crate::credentials::Credentials;
|
2023-05-06 19:01:56 +00:00
|
|
|
use crate::errors::*;
|
2023-09-19 03:13:29 +00:00
|
|
|
use crate::server::{Request, Response};
|
|
|
|
|
|
|
|
#[cfg(unix)]
|
|
|
|
use {
|
|
|
|
std::os::unix::process::CommandExt,
|
|
|
|
std::path::Path,
|
|
|
|
tokio::net::UnixStream,
|
|
|
|
};
|
|
|
|
|
|
|
|
#[cfg(windows)]
|
|
|
|
use {
|
|
|
|
tokio::net::windows::named_pipe::{NamedPipeClient, ClientOptions},
|
|
|
|
windows::Win32::Foundation::ERROR_PIPE_BUSY,
|
|
|
|
};
|
2023-05-06 19:01:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
pub fn parser() -> Command<'static> {
|
|
|
|
Command::new("creddy")
|
2023-09-14 22:22:38 +00:00
|
|
|
.version(env!("CARGO_PKG_VERSION"))
|
2023-05-06 19:01:56 +00:00
|
|
|
.about("A friendly AWS credentials manager")
|
|
|
|
.subcommand(
|
|
|
|
Command::new("run")
|
|
|
|
.about("Launch Creddy")
|
|
|
|
)
|
|
|
|
.subcommand(
|
2023-09-19 03:13:29 +00:00
|
|
|
Command::new("get")
|
|
|
|
.about("Request AWS credentials from Creddy and output to stdout")
|
2023-05-06 19:01:56 +00:00
|
|
|
.arg(
|
|
|
|
Arg::new("base")
|
|
|
|
.short('b')
|
|
|
|
.long("base")
|
|
|
|
.action(ArgAction::SetTrue)
|
|
|
|
.help("Use base credentials instead of session credentials")
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.subcommand(
|
|
|
|
Command::new("exec")
|
|
|
|
.about("Inject AWS credentials into the environment of another command")
|
|
|
|
.trailing_var_arg(true)
|
|
|
|
.arg(
|
|
|
|
Arg::new("base")
|
|
|
|
.short('b')
|
|
|
|
.long("base")
|
|
|
|
.action(ArgAction::SetTrue)
|
|
|
|
.help("Use base credentials instead of session credentials")
|
|
|
|
)
|
|
|
|
.arg(
|
|
|
|
Arg::new("command")
|
|
|
|
.multiple_values(true)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-09-19 03:13:29 +00:00
|
|
|
pub fn get(args: &ArgMatches) -> Result<(), CliError> {
|
2023-05-06 19:01:56 +00:00
|
|
|
let base = args.get_one("base").unwrap_or(&false);
|
2023-09-19 03:13:29 +00:00
|
|
|
let output = match get_credentials(*base)? {
|
|
|
|
Credentials::Base(creds) => serde_json::to_string(&creds).unwrap(),
|
|
|
|
Credentials::Session(creds) => serde_json::to_string(&creds).unwrap(),
|
|
|
|
};
|
|
|
|
println!("{output}");
|
2023-05-06 19:01:56 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn exec(args: &ArgMatches) -> Result<(), CliError> {
|
|
|
|
let base = *args.get_one("base").unwrap_or(&false);
|
|
|
|
let mut cmd_line = args.get_many("command")
|
|
|
|
.ok_or(ExecError::NoCommand)?;
|
|
|
|
|
|
|
|
let cmd_name: &String = cmd_line.next().unwrap(); // Clap guarantees that there will be at least one
|
|
|
|
let mut cmd = ChildCommand::new(cmd_name);
|
|
|
|
cmd.args(cmd_line);
|
|
|
|
|
2023-09-19 03:13:29 +00:00
|
|
|
match get_credentials(base)? {
|
|
|
|
Credentials::Base(creds) => {
|
|
|
|
cmd.env("AWS_ACCESS_KEY_ID", creds.access_key_id);
|
|
|
|
cmd.env("AWS_SECRET_ACCESS_KEY", creds.secret_access_key);
|
|
|
|
},
|
|
|
|
Credentials::Session(creds) => {
|
|
|
|
cmd.env("AWS_ACCESS_KEY_ID", creds.access_key_id);
|
|
|
|
cmd.env("AWS_SECRET_ACCESS_KEY", creds.secret_access_key);
|
|
|
|
cmd.env("AWS_SESSION_TOKEN", creds.session_token);
|
|
|
|
}
|
2023-05-06 19:01:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(unix)]
|
2023-05-09 16:40:49 +00:00
|
|
|
{
|
2023-08-04 04:57:55 +00:00
|
|
|
// cmd.exec() never returns if successful
|
|
|
|
let e = cmd.exec();
|
|
|
|
match e.kind() {
|
|
|
|
std::io::ErrorKind::NotFound => {
|
|
|
|
let name: OsString = cmd_name.into();
|
|
|
|
Err(ExecError::NotFound(name).into())
|
|
|
|
}
|
2023-09-12 21:10:57 +00:00
|
|
|
_ => Err(ExecError::ExecutionFailed(e).into()),
|
2023-08-04 04:57:55 +00:00
|
|
|
}
|
2023-05-09 16:40:49 +00:00
|
|
|
}
|
2023-05-06 19:01:56 +00:00
|
|
|
|
|
|
|
#[cfg(windows)]
|
|
|
|
{
|
2023-08-04 04:57:55 +00:00
|
|
|
let mut child = match cmd.spawn() {
|
|
|
|
Ok(c) => c,
|
|
|
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
|
|
|
let name: OsString = cmd_name.into();
|
|
|
|
return Err(ExecError::NotFound(name).into());
|
|
|
|
}
|
|
|
|
Err(e) => return Err(ExecError::ExecutionFailed(e).into()),
|
|
|
|
};
|
|
|
|
|
2023-05-06 19:01:56 +00:00
|
|
|
let status = child.wait()
|
|
|
|
.map_err(|e| ExecError::ExecutionFailed(e))?;
|
|
|
|
std::process::exit(status.code().unwrap_or(1));
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-05-06 23:56:45 +00:00
|
|
|
#[tokio::main]
|
2023-09-19 03:13:29 +00:00
|
|
|
async fn get_credentials(base: bool) -> Result<Credentials, RequestError> {
|
|
|
|
let req = Request::GetAwsCredentials { base };
|
|
|
|
let mut data = serde_json::to_string(&req).unwrap();
|
|
|
|
// server expects newline marking end of request
|
|
|
|
data.push('\n');
|
2023-05-06 19:01:56 +00:00
|
|
|
|
2023-09-19 03:13:29 +00:00
|
|
|
let mut stream = connect().await?;
|
|
|
|
stream.write_all(&data.as_bytes()).await?;
|
2023-05-06 19:01:56 +00:00
|
|
|
|
2023-09-19 03:13:29 +00:00
|
|
|
let mut buf = Vec::with_capacity(1024);
|
2023-05-06 23:56:45 +00:00
|
|
|
stream.read_to_end(&mut buf).await?;
|
2023-09-19 03:13:29 +00:00
|
|
|
let res: Result<Response, ServerError> = serde_json::from_slice(&buf)?;
|
|
|
|
match res {
|
|
|
|
Ok(Response::Aws(creds)) => Ok(creds),
|
|
|
|
// Eventually we will want this
|
|
|
|
// Ok(r) => Err(RequestError::Unexpected(r)),
|
|
|
|
Err(e) => Err(RequestError::Server(e)),
|
|
|
|
}
|
|
|
|
}
|
2023-05-06 19:01:56 +00:00
|
|
|
|
|
|
|
|
2023-09-19 03:13:29 +00:00
|
|
|
#[cfg(windows)]
|
|
|
|
async fn connect() -> Result<NamedPipeClient, std::io::Error> {
|
|
|
|
// apparently attempting to connect can fail if there's already a client connected
|
|
|
|
loop {
|
|
|
|
match ClientOptions::new().open(r"\\.\pipe\creddy-requests") {
|
|
|
|
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;
|
2023-05-06 19:01:56 +00:00
|
|
|
}
|
2023-09-19 03:13:29 +00:00
|
|
|
}
|
2023-05-06 19:01:56 +00:00
|
|
|
|
|
|
|
|
2023-09-19 03:13:29 +00:00
|
|
|
#[cfg(unix)]
|
|
|
|
async fn connect() -> Result<UnixStream, std::io::Error> {
|
|
|
|
let path = Path::from("/tmp/creddy-requests");
|
|
|
|
std::fs::remove_file(path)?;
|
|
|
|
UnixStream::connect(path)
|
2023-05-06 19:01:56 +00:00
|
|
|
}
|