Remove openbao helper and replace it with more general program
Some checks failed
/ check (push) Failing after 2m26s
/ terraform-providers (push) Successful in 58s
/ systems (push) Successful in 30m33s
/ dev-shell (push) Successful in 2m10s
/ rust-packages (push) Failing after 3m16s

This gets rid of the messy nix code for handling bitwarden
secrets, and unifies it all into a nice single program
in rust. Ensuring that only the needed secrets are loaded.
This commit is contained in:
Kaare Hoff Skovgaard 2025-08-05 21:59:07 +02:00
parent e6a152e95c
commit 8640dce7bc
Signed by: khs
GPG key ID: C7D890804F01E9F0
31 changed files with 1159 additions and 958 deletions

View file

@ -108,7 +108,7 @@ pub enum BitwardenEntryFieldType {
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
pub struct BitwardenEntryField {
pub name: String,
pub name: Option<String>,
pub value: Option<String>,
#[serde(rename = "type")]
pub field_type: BitwardenEntryFieldType,
@ -154,6 +154,7 @@ pub enum BitwardenOrganizationUserStatus {
pub struct BitwardenSession {
session_id: Option<String>,
items: Option<Vec<BitwardenEntry>>,
}
impl BitwardenSession {
@ -171,11 +172,8 @@ impl BitwardenSession {
Ok(())
}
pub fn new_or_authenticate(
username: Option<&str>,
bw_unlock_purpose: &str,
) -> anyhow::Result<Self> {
let bw = BitwardenSession::new_if_authenticated(username, bw_unlock_purpose, true)?;
pub fn unlock() -> anyhow::Result<Self> {
let bw = BitwardenSession::new_if_authenticated(true)?;
if let Some(bw) = bw {
bw.sync()?;
Ok(bw)
@ -187,7 +185,7 @@ impl BitwardenSession {
.stderr_inherit()
.try_spawn_to_string()?;
// Just logged in, no point in syncing
let bw = BitwardenSession::new_if_authenticated(username, bw_unlock_purpose, false)?;
let bw = BitwardenSession::new_if_authenticated(false)?;
if let Some(bw) = bw {
Ok(bw)
} else {
@ -198,67 +196,61 @@ impl BitwardenSession {
}
}
fn new_if_authenticated(
username: Option<&str>,
bw_unlock_purpose: &str,
sync: bool,
) -> anyhow::Result<Option<Self>> {
fn new_if_authenticated(sync: bool) -> anyhow::Result<Option<Self>> {
let status: BitwardenAuthenticationStatus = proc::Command::new("bw")
.args(["--nointeraction", "status"])
.try_spawn_to_json()?;
let Some(user) = status.user() else {
let Some(_) = status.user() else {
return Ok(None);
};
if let Some(username) = username {
if user.user_email != username {
return Err(anyhow::format_err!(
"Authenticated user in bitwarden does not match the expected user of {}, was {}",
username,
user.user_email
));
}
}
let is_unlocked: bool = matches!(status, BitwardenAuthenticationStatus::Unlocked(_));
if sync && !is_unlocked {
log::info!("Syncing Bitwarden...");
let _ = proc::Command::new("bw").arg("sync").try_spawn_to_string()?;
}
log::info!("Unlocking bitwarden...");
let session_id = if is_unlocked {
None
} else {
log::info!("Unlocking bitwarden...");
Some(
proc::Command::new("bitwarden-unlock")
.args(["--purpose", bw_unlock_purpose])
proc::Command::new("bw")
.args(["unlock", "--raw"])
.stderr_inherit()
.stdin_inherit()
.try_spawn_to_string()?,
)
};
Ok(Some(Self { session_id }))
Ok(Some(Self {
session_id,
items: None,
}))
}
pub fn list_items(&self) -> anyhow::Result<Vec<BitwardenEntry>> {
log::info!("Listing bitwarden items...");
self.bw_command()
// Pretty format for better error messages during json decoding issues
.args(["--pretty", "list", "items"])
.try_spawn_to_json()
pub fn list_items(&mut self) -> anyhow::Result<&[BitwardenEntry]> {
if self.items.is_none() {
log::info!("Listing bitwarden items...");
let result = self
.bw_command()
// Pretty format for better error messages during json decoding issues
.args(["--pretty", "list", "items"])
.try_spawn_to_json()?;
let _ = self.items.insert(result);
}
Ok(self.items.as_deref().expect("Items have just been set"))
}
pub fn get_item(&self, name: &str) -> anyhow::Result<Option<BitwardenEntry>> {
let mut items = self.list_items()?;
let Some(idx) = items
.iter()
.enumerate()
.find_map(|(idx, e)| if e.name() == name { Some(idx) } else { None })
else {
return Ok(None);
};
let item = items.swap_remove(idx);
Ok(Some(item))
pub fn get_item(&mut self, name: &str) -> anyhow::Result<Option<&BitwardenEntry>> {
let items = self.list_items()?;
Ok(items.iter().find(|e| e.name() == name))
}
pub fn get_attachment(&self, entry: &BitwardenEntry, name: &str) -> anyhow::Result<Vec<u8>> {
pub fn get_attachment(
&mut self,
entry: &BitwardenEntry,
name: &str,
) -> anyhow::Result<Vec<u8>> {
self.bw_command()
.args(["get", "attachment", name, "--itemid"])
.arg(entry.id.as_str())
@ -266,22 +258,19 @@ impl BitwardenSession {
.try_spawn_to_bytes()
}
pub fn list_own_items(&self) -> anyhow::Result<Vec<BitwardenEntry>> {
let mut items = self.list_items()?;
items.retain(|i| i.organization_id.as_ref().is_none_or(|o| o.is_empty()));
Ok(items)
}
pub fn create_item(&self, item: &CommandBitwardenEntry) -> anyhow::Result<BitwardenEntry> {
pub fn create_item(&mut self, item: &CommandBitwardenEntry) -> anyhow::Result<BitwardenEntry> {
log::info!("Creating bitwarden entry {name}", name = item.name);
self.bw_command()
let res = self
.bw_command()
.args(["create", "item"])
.stdin_json_base64(item)?
.try_spawn_to_json()
.try_spawn_to_json()?;
let _ = self.items.take();
Ok(res)
}
pub fn update_item(
&self,
&mut self,
to_update: &BitwardenEntry,
update_with: &CommandBitwardenEntry,
) -> anyhow::Result<()> {
@ -295,10 +284,11 @@ impl BitwardenSession {
.args(["edit", "item", &to_update.id])
.stdin_json_base64(update_with)?
.try_spawn_to_string()?;
let _ = self.items.take();
Ok(())
}
pub fn delete_item(&self, to_delete: &BitwardenEntry) -> anyhow::Result<()> {
pub fn delete_item(&mut self, to_delete: &BitwardenEntry) -> anyhow::Result<()> {
log::info!(
"Deleting bitwarden entry {name}, with id: {id}",
id = to_delete.id,
@ -308,13 +298,13 @@ impl BitwardenSession {
.bw_command()
.args(["delete", "item", &to_delete.id])
.try_spawn_to_string()?;
let _ = self.items.take();
Ok(())
}
}
impl Drop for BitwardenSession {
fn drop(&mut self) {
log::info!("Locking bitwarden session...");
if self.session_id.is_some() {
if let Err(e) = self
.bw_command()
@ -349,9 +339,6 @@ impl BitwardenAuthenticationStatus {
#[derive(Debug, Deserialize)]
struct BitwardenAuthenticationUser {
#[serde(rename = "userEmail")]
user_email: String,
#[serde(rename = "userId")]
#[allow(dead_code)]
user_id: String,