index : bitwarden-ssh-agent.git

ascending towards madness

author holly sparkles <sparkles@holly.sh> 2023-08-04 10:32:02.0 +00:00:00
committer holly sparkles <sparkles@holly.sh> 2023-08-04 10:32:02.0 +00:00:00
commit
ce8e03cfb8b9a1dd32d11c7246ad9512936c0d45 [patch]
tree
9c9c398c5dc916b22fcf3623dc0a20df5af80de2
parent
8376ba45126292ad414a35be7299cab96b620d29
parent
4be75024c640def5d656976356a8c26c2147c836
download
ce8e03cfb8b9a1dd32d11c7246ad9512936c0d45.tar.gz

Merge branch 'feature/color-output' into develop



Diff

 Cargo.toml    |  3 ++-
 src/bwutil.rs |  5 +++--
 src/main.rs   | 44 ++++++++++++++++++++++++++++++++------------
 3 files changed, 37 insertions(+), 15 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 8e90ff6..9ca8805 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,4 +9,5 @@ edition = "2021"
gumdrop = "0.8.1"
anyhow = "1.0.72"
serde = { version = "1.0.177", features = ["derive"] }
serde_json = "1.0.104"
\ No newline at end of file
serde_json = "1.0.104"
colored = "2.0.4"
\ No newline at end of file
diff --git a/src/bwutil.rs b/src/bwutil.rs
index 0e007e5..0254ae9 100644
--- a/src/bwutil.rs
+++ b/src/bwutil.rs
@@ -1,4 +1,5 @@
use std::process::{Command, Output, Stdio};
use colored::*;
use serde::Deserialize;
use anyhow::{anyhow, Result, Context};

@@ -77,7 +78,7 @@ pub fn exec_folder_search(session: &str, folder_name: &str) -> Result<Vec<Bitwar
			["list", "folders", "--search", &folder_name, "--session", &session].to_vec());
	let result: String = String::from_utf8_lossy(&folders.stdout).to_string();
	if result.is_empty() {
		Err(anyhow!("Could not authenticate."))
		Err(anyhow!("Could not authenticate. This could mean your session has expired.".red()))
	}
	else {
		Ok(serde_json::from_str(&result).with_context(|| "Could not deserialize folder search results.")?)
@@ -90,7 +91,7 @@ pub fn exec_list_folder_items(session: &str, folder_id: &str) -> Result<Vec<Bitw
			["list", "items", "--folderid", &folder_id, "--session", &session].to_vec());
	let result = String::from_utf8_lossy(&items.stdout).to_string();
	if result.is_empty() {
		Err(anyhow!("Could not authenticate."))
		Err(anyhow!("Could not authenticate. This could mean your session has expired.".red()))
	}
	else {
		Ok(serde_json::from_str(&result).with_context(|| "Could not deserialize item search results.")?)
diff --git a/src/main.rs b/src/main.rs
index 24362d5..27a8356 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,24 +1,41 @@
use std::{env, process::{Command, Stdio}};
use std::{env, process::{Command, Stdio}, ffi::OsString};
use std::str::FromStr;
use gumdrop::Options;
use anyhow::{Result, Context};
use colored::*;

mod util;
mod bwutil;

/// Environment variable housing an existing Bitwarden session.
const SESSION_ENV_KEY: &str = "BW_SESSION";
const BW_SSH_DEBUG_ENV_KEY: &str = "BW_SSH_DEBUG";
/// Environment variable referencing the name of the field containing the SSH key passphrase (if any).
const BW_FIELD_KEY_PASSPHRASE: &str = "BW_KEY_PASSPHRASE";

/// A macro to print when the debug CLI arg is enabled.
macro_rules! debug_println {
    ($debug:expr, $($arg:tt)*) => {
		if $debug {
			println!($($arg)*);
    ($($arg:tt)*) => {
		if let Ok(enabled) = env::var(BW_SSH_DEBUG_ENV_KEY) {
			if bool::from_str(&enabled).unwrap_or(false) {
				println!("{}",format!($($arg)*).yellow());
			}
		}
    }
}

macro_rules! warn_println {
    ($($arg:tt)*) => {
		println!("{}",format!($($arg)*).yellow());
    }
}

macro_rules! info_println {
    ($($arg:tt)*) => {
		println!("{}",format!($($arg)*).green());
    }
}

fn main() -> Result<()> {
	// This environment variable only set when calling ssh-agent, so return the passphrase to authenticate and then quit.
	let unlock_passphrase = env::var(BW_FIELD_KEY_PASSPHRASE);
@@ -27,7 +44,10 @@ fn main() -> Result<()> {
		return Ok(())
	}

	// Parse args and set the debug environment variable
    let args: util::Cli = util::Cli::parse_args_default_or_exit();

	env::set_var(BW_SSH_DEBUG_ENV_KEY, OsString::from(args.debug.to_string()));
	if args.version {
		println!("{}", &util::get_version_string()?);
		return Ok(());
@@ -36,12 +56,13 @@ fn main() -> Result<()> {
	let session_token: String = check_session_token(&args)?;
	if !session_token.is_empty() {
		let folders = bwutil::exec_folder_search(&session_token, &args.folder)?;
		debug_println!(args.debug, "Found {} folder(s) named `{}`", folders.len(), args.folder);
		debug_println!("Found {} folder(s) named `{}`", folders.len().to_string().cyan(), args.folder.cyan());
		
		// Retrieve items from each folder since there may be multiple folders with the same name.
		for folder in folders {
			let folder_items = bwutil::exec_list_folder_items(&session_token, &folder.id)?;
			debug_println!(args.debug, "Found {} item(s) in folder `{}` id({})", folder_items.len(), folder.name, folder.id);
			
			debug_println!("Found {} item(s) in folder `{}` id({}) with at least one custom field and attachment", folder_items.len().to_string().cyan(), folder.name.cyan(), folder.id.cyan());
			
			for item in folder_items {
				// In order for this to be considered a valid SSH key item, this item needs to have the following fields:
@@ -65,7 +86,6 @@ fn main() -> Result<()> {
					if let Some(attachments) = &item.attachments {
						for attachment in attachments {
							if attachment.file_name.eq(&key_filename) {
								debug_println!(args.debug, "Item `{}` id({}) meets all requirements. Adding to `ssh-agent`", item.name, item.id);
								let _key = register_key(&item.id, &attachment.id, &key_passphrase, &session_token)?;
							}
						}
@@ -85,7 +105,7 @@ fn main() -> Result<()> {
/// 4. Login or unlock based on login status and use that.
/// An invalid `BW_SESSION` or `--session` will prompt a login.
fn check_session_token(args: &util::Cli) -> Result<String> {
	println!("Getting Bitwarden session...");
	info_println!("Getting Bitwarden session...");
	let mut session_token: String = String::new();
	// Get session flag from the user
	if args.session.trim().is_empty() {
@@ -93,18 +113,18 @@ fn check_session_token(args: &util::Cli) -> Result<String> {
		let env_key = env::var(SESSION_ENV_KEY);
		match env_key {
			Ok(key) => {
				println!("{} is set. Reusing existing session.", SESSION_ENV_KEY);
				info_println!("{} is set. Reusing existing session.", SESSION_ENV_KEY);
				// We found it, set our session key
				session_token.push_str(&key);
			},
			Err(_) => {
				// We don't have a token to reuse, so get it from Bitwarden
				println!("{} is not set. Attempting to login.", SESSION_ENV_KEY);
				warn_println!("{} is not set. Attempting to login.", SESSION_ENV_KEY);
				let token: &String = &bwutil::get_session_token()?;

				if !token.is_empty() {
					println!("Successfully unlocked. To re-use this session, run:");
					println!("export {}=\"{}\"", SESSION_ENV_KEY, token);
					info_println!("Successfully unlocked. To re-use this session, run:");
					info_println!("export {}=\"{}\"", SESSION_ENV_KEY, token);
				}
				session_token.push_str(token)
			}