mirror of
https://github.com/Kruhlmann/punlock.git
synced 2025-10-28 14:43:35 +00:00
Add domain support
This commit is contained in:
45
Cargo.lock
generated
45
Cargo.lock
generated
@@ -121,6 +121,16 @@ version = "1.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "caps"
|
||||||
|
version = "0.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "190baaad529bcfbde9e1a19022c42781bdb6ff9de25721abdb8fd98c0807730b"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -188,6 +198,15 @@ dependencies = [
|
|||||||
"dirs-sys",
|
"dirs-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs-sys"
|
name = "dirs-sys"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -517,9 +536,11 @@ name = "punlock"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"caps",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"clap",
|
"clap",
|
||||||
"directories",
|
"directories",
|
||||||
|
"dirs",
|
||||||
"futures",
|
"futures",
|
||||||
"jmespath",
|
"jmespath",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -560,7 +581,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"libredox",
|
"libredox",
|
||||||
"thiserror",
|
"thiserror 2.0.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -763,13 +784,33 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.12"
|
version = "2.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl 2.0.12",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.98"
|
||||||
|
caps = "0.5.5"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
clap = { version = "4.5.37", features = ["derive"] }
|
clap = { version = "4.5.37", features = ["derive"] }
|
||||||
directories = "6.0.0"
|
directories = "6.0.0"
|
||||||
|
dirs = "6.0.0"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
jmespath = "0.3.0"
|
jmespath = "0.3.0"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
|
|||||||
11
shell.nix
Normal file
11
shell.nix
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{ pkgs ? import <nixpkgs> { } }:
|
||||||
|
|
||||||
|
pkgs.mkShell {
|
||||||
|
buildInputs = [
|
||||||
|
pkgs.cargo-llvm-cov
|
||||||
|
pkgs.openssl
|
||||||
|
pkgs.pkg-config
|
||||||
|
pkgs.rust-analyzer
|
||||||
|
pkgs.rustup
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -14,32 +14,47 @@ impl Bitwarden<()> {
|
|||||||
Self { email, session: () }
|
Self { email, session: () }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn authenticate(self) -> anyhow::Result<Bitwarden<String>> {
|
pub async fn authenticate(self, domain: Option<String>) -> anyhow::Result<Bitwarden<String>> {
|
||||||
Command::new("bw")
|
Command::new("bw")
|
||||||
.args(&["logout"])
|
.args(["logout"])
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
.status()
|
.status()
|
||||||
.await
|
.await
|
||||||
|
.inspect(|_| tracing::debug!("spawn logout"))
|
||||||
|
.inspect_err(|error| tracing::error!(?error, "spawn logout"))
|
||||||
.ok();
|
.ok();
|
||||||
|
if let Some(ref d) = domain {
|
||||||
|
Command::new("bw")
|
||||||
|
.args(["config", "server", &d])
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.status()
|
||||||
|
.await
|
||||||
|
.inspect(|_| tracing::debug!(?domain, "spawn config server"))
|
||||||
|
.inspect_err(|error| tracing::error!(?error, ?domain, "spawn config server"))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
loop {
|
loop {
|
||||||
let password = rpassword::prompt_password(&format!(
|
let prompt = match domain {
|
||||||
"Enter bitwarden password for {}: ",
|
Some(ref d) => format!("Enter password for bitwarden[{d}] user {}: ", self.email),
|
||||||
self.email
|
None => format!("Enter password for bitwarden user {}: ", self.email),
|
||||||
))
|
};
|
||||||
.inspect_err(|error| tracing::error!(?error, "read password"))
|
let password = rpassword::prompt_password(prompt)
|
||||||
.unwrap_or("".to_string());
|
.inspect_err(|error| tracing::error!(?error, "read password"))
|
||||||
|
.unwrap_or("".to_string());
|
||||||
if password.trim().is_empty() {
|
if password.trim().is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let out = Command::new("bw")
|
let out = Command::new("bw")
|
||||||
.args(&["login", self.email.as_ref(), &password, "--raw"])
|
.args(["login", self.email.as_ref(), &password, "--raw"])
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.inspect_err(|error| tracing::error!(?error, "bw login"))?;
|
.inspect(|_| tracing::debug!("spawn login"))
|
||||||
|
.inspect_err(|error| tracing::error!(?error, "spawn login"))?;
|
||||||
|
|
||||||
if !out.status.success() {
|
if !out.status.success() {
|
||||||
let error = String::from_utf8_lossy(&out.stderr);
|
let error = String::from_utf8_lossy(&out.stderr);
|
||||||
@@ -62,16 +77,16 @@ impl Bitwarden<()> {
|
|||||||
impl Bitwarden<String> {
|
impl Bitwarden<String> {
|
||||||
pub async fn fetch(&self, entry: &PunlockConfigurationEntry) -> anyhow::Result<String> {
|
pub async fn fetch(&self, entry: &PunlockConfigurationEntry) -> anyhow::Result<String> {
|
||||||
let bw = Command::new("bw")
|
let bw = Command::new("bw")
|
||||||
.args(&["get", "item", &entry.id, "--session", &self.session])
|
.args(["get", "item", &entry.id, "--session", &self.session])
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.spawn()
|
.spawn()
|
||||||
.inspect_err(|error| tracing::error!(?error, "bw get"))?;
|
.inspect_err(|error| tracing::error!(?error, "spawn get"))?;
|
||||||
|
|
||||||
let output = bw
|
let output = bw
|
||||||
.wait_with_output()
|
.wait_with_output()
|
||||||
.await
|
.await
|
||||||
.inspect_err(|error| tracing::error!(?error, "bw output"))?;
|
.inspect_err(|error| tracing::error!(?error, "spawn get"))?;
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
let err = String::from_utf8_lossy(&output.stderr);
|
let err = String::from_utf8_lossy(&output.stderr);
|
||||||
@@ -97,12 +112,13 @@ impl Bitwarden<String> {
|
|||||||
|
|
||||||
pub async fn logout(&self) -> anyhow::Result<()> {
|
pub async fn logout(&self) -> anyhow::Result<()> {
|
||||||
Command::new("bw")
|
Command::new("bw")
|
||||||
.args(&["logout", "--session", &self.session])
|
.args(["logout", "--session", &self.session])
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
.status()
|
.status()
|
||||||
.await
|
.await
|
||||||
.inspect_err(|error| tracing::error!(?error, "bw logout"))?;
|
.inspect(|_| tracing::debug!("spawn logout"))
|
||||||
|
.inspect_err(|error| tracing::error!(?error, "spawn logout"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,14 @@ pub struct PunlockConfigurationEntry {
|
|||||||
pub id: String,
|
pub id: String,
|
||||||
pub query: String,
|
pub query: String,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
|
pub links: Option<Vec<String>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub public: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct PartialPunlockConfiguration {
|
pub struct PartialPunlockConfiguration {
|
||||||
|
pub domain: Option<String>,
|
||||||
pub version: Option<String>,
|
pub version: Option<String>,
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
pub entries: Option<Vec<PunlockConfigurationEntry>>,
|
pub entries: Option<Vec<PunlockConfigurationEntry>>,
|
||||||
@@ -59,6 +63,7 @@ impl PartialPunlockConfiguration {
|
|||||||
pub struct PunlockConfiguration {
|
pub struct PunlockConfiguration {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub email: Email,
|
pub email: Email,
|
||||||
|
pub domain: Option<String>,
|
||||||
pub entries: Vec<PunlockConfigurationEntry>,
|
pub entries: Vec<PunlockConfigurationEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +72,7 @@ impl TryFrom<PartialPunlockConfiguration> for PunlockConfiguration {
|
|||||||
|
|
||||||
fn try_from(value: PartialPunlockConfiguration) -> anyhow::Result<Self> {
|
fn try_from(value: PartialPunlockConfiguration) -> anyhow::Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
domain: value.domain,
|
||||||
version: value
|
version: value
|
||||||
.version
|
.version
|
||||||
.unwrap_or(LATEST_CONFIGURATION_VERSION.to_string()),
|
.unwrap_or(LATEST_CONFIGURATION_VERSION.to_string()),
|
||||||
@@ -77,7 +83,7 @@ impl TryFrom<PartialPunlockConfiguration> for PunlockConfiguration {
|
|||||||
} else {
|
} else {
|
||||||
Email::from_stdin()
|
Email::from_stdin()
|
||||||
},
|
},
|
||||||
entries: value.entries.unwrap_or(Vec::new()),
|
entries: value.entries.unwrap_or_default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ impl TryFrom<&str> for Email {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<String> for Email {
|
impl From<Email> for String {
|
||||||
fn into(self) -> String {
|
fn from(val: Email) -> Self {
|
||||||
self.0.clone()
|
val.0.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let formattter = tracing_subscriber::fmt::Layer::new()
|
let formattter = tracing_subscriber::fmt::Layer::new()
|
||||||
.with_thread_names(true)
|
.with_thread_names(true)
|
||||||
.with_span_events(FmtSpan::FULL);
|
.with_span_events(FmtSpan::FULL);
|
||||||
let filter = EnvFilter::try_from_default_env()?;
|
let filter = EnvFilter::try_from_default_env().unwrap_or(EnvFilter::new("info"));
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
.with(formattter)
|
.with(formattter)
|
||||||
.with(filter)
|
.with(filter)
|
||||||
@@ -37,13 +37,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
} else {
|
} else {
|
||||||
PartialPunlockConfiguration::try_from_default_path()?
|
PartialPunlockConfiguration::try_from_default_path()?
|
||||||
};
|
};
|
||||||
|
|
||||||
let config: PunlockConfiguration = config.try_into()?;
|
let config: PunlockConfiguration = config.try_into()?;
|
||||||
|
|
||||||
config
|
config
|
||||||
.write_to_disk(USER_CONFIG_FILE_PATH.as_path())
|
.write_to_disk(USER_CONFIG_FILE_PATH.as_path())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let bitwarden = Bitwarden::new(config.email).authenticate().await?;
|
let bitwarden = Bitwarden::new(config.email)
|
||||||
|
.authenticate(config.domain)
|
||||||
|
.await?;
|
||||||
let store = UnmountedSecretStore::new(bitwarden)
|
let store = UnmountedSecretStore::new(bitwarden)
|
||||||
.into_platform_store()
|
.into_platform_store()
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -11,11 +11,12 @@ lazy_static! {
|
|||||||
ProjectDirs::from("dev", "kruhlmann", "punlock").unwrap();
|
ProjectDirs::from("dev", "kruhlmann", "punlock").unwrap();
|
||||||
pub static ref USER_CONFIG_FILE_PATH: PathBuf =
|
pub static ref USER_CONFIG_FILE_PATH: PathBuf =
|
||||||
PROJECT_DIRS.config_dir().join(CONFIG_FILE_NAME.to_string());
|
PROJECT_DIRS.config_dir().join(CONFIG_FILE_NAME.to_string());
|
||||||
pub static ref SYSTEM_CONFIG_PATH_CANDIDATES: Vec<PathBuf> = vec![
|
pub static ref HOME_DIRECTORY: PathBuf = dirs::home_dir().unwrap();
|
||||||
PathBuf::from(CONFIG_FILE_NAME.to_string()),
|
#[cfg(target_os = "linux")]
|
||||||
|
pub static ref RUNTIME_DIRECTORY: PathBuf = dirs::runtime_dir().unwrap();
|
||||||
|
pub static ref SYSTEM_CONFIG_PATH_CANDIDATES: Vec<PathBuf> = [PathBuf::from(CONFIG_FILE_NAME.to_string()),
|
||||||
USER_CONFIG_FILE_PATH.to_path_buf(),
|
USER_CONFIG_FILE_PATH.to_path_buf(),
|
||||||
PathBuf::from("/etc/punlock/").join("CONFIG_FILE_NAME"),
|
PathBuf::from("/etc/punlock/").join("CONFIG_FILE_NAME")]
|
||||||
]
|
|
||||||
.to_vec();
|
.to_vec();
|
||||||
pub static ref EMAIL_REGEX: Regex = Regex::new(r"^[^\s@]+@[^\s@]+\.[^\s@]+$").unwrap();
|
pub static ref EMAIL_REGEX: Regex = Regex::new(r"^[^\s@]+@[^\s@]+\.[^\s@]+$").unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
172
src/store.rs
172
src/store.rs
@@ -1,21 +1,21 @@
|
|||||||
use std::{path::PathBuf, process::Stdio};
|
use std::{os::unix::fs::PermissionsExt, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use futures::{StreamExt, stream::FuturesUnordered};
|
use futures::{StreamExt, stream::FuturesUnordered};
|
||||||
use tokio::{fs::File, io::AsyncWriteExt, process::Command};
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
use crate::{bitwarden::Bitwarden, config::PunlockConfigurationEntry, statics::PROJECT_DIRS};
|
use crate::{
|
||||||
|
bitwarden::Bitwarden,
|
||||||
|
config::PunlockConfigurationEntry,
|
||||||
|
statics::{self, HOME_DIRECTORY},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct UnmountedSecretStore {
|
pub struct UnmountedSecretStore {
|
||||||
bitwarden: Bitwarden<String>,
|
bitwarden: Bitwarden<String>,
|
||||||
root_path: PathBuf,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnmountedSecretStore {
|
impl UnmountedSecretStore {
|
||||||
pub fn new(bitwarden: Bitwarden<String>) -> Self {
|
pub fn new(bitwarden: Bitwarden<String>) -> Self {
|
||||||
Self {
|
Self { bitwarden }
|
||||||
bitwarden,
|
|
||||||
root_path: PROJECT_DIRS.cache_dir().to_owned(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +23,8 @@ impl UnmountedSecretStore {
|
|||||||
pub async fn into_platform_store(self) -> anyhow::Result<UnixSecretStore> {
|
pub async fn into_platform_store(self) -> anyhow::Result<UnixSecretStore> {
|
||||||
cfg_if::cfg_if! {
|
cfg_if::cfg_if! {
|
||||||
if #[cfg(target_os = "linux")] {
|
if #[cfg(target_os = "linux")] {
|
||||||
let store = UnixSecretStore::new(self.bitwarden, self.root_path).unmount().await?.mount().await?;
|
let root_path = statics::RUNTIME_DIRECTORY.join("punlock");
|
||||||
|
let store = UnixSecretStore::new(self.bitwarden, root_path).teardown().await?.setup().await?;
|
||||||
Ok(store)
|
Ok(store)
|
||||||
} else if #[cfg(target_os = "macos")] {
|
} else if #[cfg(target_os = "macos")] {
|
||||||
// mount_ramdisk_macos(mount_point)?;
|
// mount_ramdisk_macos(mount_point)?;
|
||||||
@@ -37,85 +38,132 @@ impl UnmountedSecretStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct UnixSecretStore {
|
pub struct UnixSecretStore {
|
||||||
bitwarden: Bitwarden<String>,
|
bitwarden: Arc<Bitwarden<String>>,
|
||||||
root_path: PathBuf,
|
root_path: Arc<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnixSecretStore {
|
impl UnixSecretStore {
|
||||||
pub fn new(bitwarden: Bitwarden<String>, root_path: PathBuf) -> Self {
|
pub fn new(bitwarden: Bitwarden<String>, root_path: PathBuf) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bitwarden,
|
bitwarden: Arc::new(bitwarden),
|
||||||
root_path,
|
root_path: Arc::new(root_path),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn write_secrets(
|
pub async fn write_secrets(&self, entries: &[PunlockConfigurationEntry]) -> anyhow::Result<()> {
|
||||||
&self,
|
|
||||||
entries: &Vec<PunlockConfigurationEntry>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let mut tasks = FuturesUnordered::new();
|
let mut tasks = FuturesUnordered::new();
|
||||||
|
|
||||||
for entry in entries.iter() {
|
for entry in entries.iter() {
|
||||||
tasks.push(async move {
|
let root = self.root_path.clone();
|
||||||
let secret = self.bitwarden.fetch(&entry).await?;
|
let bw = self.bitwarden.clone();
|
||||||
let path = self.root_path.join(&entry.path);
|
|
||||||
tokio::fs::create_dir_all(path.parent().unwrap_or(&path)).await.inspect_err(|error| tracing::error!(?error, "create secret directory"))?;
|
tasks.push(
|
||||||
let mut file = File::create(path).await.inspect_err(
|
async move {
|
||||||
|error| tracing::error!(?error, id = ?entry.id, path = ?entry.path, "create secret file"),
|
let secret = bw.fetch(entry).await.inspect_err(|error| tracing::error!(?error, ?entry, "item not found"))?;
|
||||||
)?;
|
let path = root.join(&entry.path);
|
||||||
file.write_all(secret.as_bytes()).await.inspect_err(
|
|
||||||
|error| tracing::error!(?error, id = ?entry.id, path = ?entry.path, "write secret"),
|
tokio::fs::create_dir_all(path.parent().unwrap_or(&path)).await?;
|
||||||
)?;
|
{
|
||||||
Ok::<(String, String), anyhow::Error>((entry.id.clone(), entry.path.clone()))
|
let mut file = tokio::fs::File::create(&path).await?;
|
||||||
})
|
file.write_all(secret.as_bytes()).await?;
|
||||||
|
if !secret.ends_with('\n') {
|
||||||
|
file.write_all(b"\n").await?;
|
||||||
|
}
|
||||||
|
file.flush().await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut perms = tokio::fs::metadata(&path).await?.permissions();
|
||||||
|
perms.set_readonly(true);
|
||||||
|
|
||||||
|
if !entry.public {
|
||||||
|
#[cfg(unix)]
|
||||||
|
perms.set_mode(0o400);
|
||||||
|
}
|
||||||
|
tokio::fs::set_permissions(&path, perms)
|
||||||
|
.await
|
||||||
|
.inspect(|_| tracing::debug!(?path, "set readonly"))
|
||||||
|
.inspect_err(|error| tracing::error!(?error, ?path, "remove runtime dir"))?;
|
||||||
|
|
||||||
|
if let Some(ref links) = entry.links {
|
||||||
|
for link in links {
|
||||||
|
let link_path: PathBuf = if PathBuf::from(link).is_absolute() {
|
||||||
|
PathBuf::from(link)
|
||||||
|
} else {
|
||||||
|
HOME_DIRECTORY.join(link)
|
||||||
|
};
|
||||||
|
tokio::fs::create_dir_all(link_path.parent().unwrap_or(&link_path))
|
||||||
|
.await?;
|
||||||
|
match tokio::fs::symlink_metadata(&link_path).await {
|
||||||
|
Ok(meta) if meta.file_type().is_symlink() => {
|
||||||
|
let current = tokio::fs::read_link(&link_path).await?;
|
||||||
|
if current == path {
|
||||||
|
tracing::debug!(?link_path, "skipping existing symlink");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tokio::fs::remove_file(&link_path).await?;
|
||||||
|
}
|
||||||
|
Ok(_) => tokio::fs::remove_file(&link_path).await?,
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = path.clone();
|
||||||
|
let dst = link_path.clone();
|
||||||
|
tokio::task::spawn_blocking(move || -> std::io::Result<()> {
|
||||||
|
#[cfg(unix)]
|
||||||
|
std::os::unix::fs::symlink(&src, &dst)?;
|
||||||
|
#[cfg(windows)]
|
||||||
|
std::os::windows::fs::symlink_file(&src, &dst)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))??;
|
||||||
|
|
||||||
|
tracing::info!(src =? path, destination = ?link_path, "created/updated symlink");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok::<_, anyhow::Error>((entry.id.clone(), entry.path.clone()))
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
while let Some(result) = tasks.next().await {
|
|
||||||
match result {
|
let mut count = 0;
|
||||||
Ok((id, path)) => tracing::info!(?id, ?path, "load secret"),
|
let mut success = 0;
|
||||||
Err(error) => tracing::error!(?error, "load secret"),
|
while let Some(res) = tasks.next().await {
|
||||||
|
count += 1;
|
||||||
|
match res {
|
||||||
|
Ok((id, path)) => {
|
||||||
|
success += 1;
|
||||||
|
tracing::info!(?id, ?path, "secret written")
|
||||||
|
}
|
||||||
|
Err(error) => tracing::error!(?error, "failed to write secret"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tracing::info!("wrote {success}/{count} secrets");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unmount(self) -> anyhow::Result<Self> {
|
async fn teardown(self) -> anyhow::Result<Self> {
|
||||||
if self.root_path.exists() {
|
if self.root_path.exists() {
|
||||||
Command::new("sudo")
|
tokio::fs::remove_dir_all(&*self.root_path)
|
||||||
.args(&["umount", self.root_path.to_str().unwrap()])
|
|
||||||
.stdout(Stdio::null())
|
|
||||||
.stderr(Stdio::null())
|
|
||||||
.status()
|
|
||||||
.await
|
.await
|
||||||
|
.inspect_err(
|
||||||
|
|error| tracing::error!(?error, path = ?self.root_path, "remove runtime dir"),
|
||||||
|
)
|
||||||
.ok();
|
.ok();
|
||||||
tokio::fs::remove_dir_all(&self.root_path).await.ok();
|
|
||||||
}
|
}
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn mount(self) -> anyhow::Result<Self> {
|
async fn setup(self) -> anyhow::Result<Self> {
|
||||||
tokio::fs::create_dir_all(&self.root_path)
|
tokio::fs::create_dir_all(&*self.root_path)
|
||||||
.await
|
.await
|
||||||
.inspect_err(
|
.inspect_err(
|
||||||
|error| tracing::error!(?error, path = ?self.root_path, "unable to create secret path"),
|
|error| tracing::error!(?error, path = ?self.root_path, "create runtime dir"),
|
||||||
)?;
|
)?;
|
||||||
let status = Command::new("sudo")
|
|
||||||
.args(&["mount", "-t", "tmpfs", "-o", "size=50M", "tmpfs"])
|
|
||||||
.arg(&self.root_path)
|
|
||||||
.status()
|
|
||||||
.await
|
|
||||||
.inspect_err(|error| tracing::error!(?error, "mount failed"))?;
|
|
||||||
if !status.success() {
|
|
||||||
anyhow::bail!("mount command failed with {}", status);
|
|
||||||
}
|
|
||||||
tracing::debug!(path = ?self.root_path, "tmpfs mounted");
|
|
||||||
|
|
||||||
let uid = users::get_current_uid();
|
|
||||||
let gid = users::get_current_gid();
|
|
||||||
Command::new("sudo")
|
|
||||||
.args(&["chown", &format!("{}:{}", uid, gid)])
|
|
||||||
.arg(&self.root_path)
|
|
||||||
.status()
|
|
||||||
.await
|
|
||||||
.inspect_err(|error| tracing::error!(?error, "chown failed"))?;
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user