mirror of
https://github.com/rkivalin/certforge.git
synced 2026-10-14 00:00:44 +00:00
Omit --name= from systemd-creds encrypt so the encrypted credential can be loaded under any name via LoadCredentialEncrypted=. Previously, the embedded name had to match the credential name in the service unit, causing decryption failures when services used different names. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
84 lines
2.6 KiB
Rust
84 lines
2.6 KiB
Rust
use std::path::Path;
|
|
use tokio::process::Command;
|
|
|
|
use crate::error::{Error, Result};
|
|
|
|
/// Load a secret from either a systemd encrypted credential file or a plain file.
|
|
///
|
|
/// If `credential` is set, decrypts it at runtime via `systemd-creds decrypt`.
|
|
/// If only `path` is set, reads the file directly.
|
|
pub async fn load_secret(credential: Option<&Path>, path: Option<&Path>) -> Result<Vec<u8>> {
|
|
if let Some(cred_path) = credential {
|
|
decrypt_credential(cred_path).await
|
|
} else if let Some(file_path) = path {
|
|
std::fs::read(file_path).map_err(|_| Error::CredentialNotFound {
|
|
name: file_path.display().to_string(),
|
|
path: file_path.to_path_buf(),
|
|
})
|
|
} else {
|
|
Err(Error::Config(
|
|
"neither credential nor path configured".into(),
|
|
))
|
|
}
|
|
}
|
|
|
|
/// Decrypt an encrypted credential file using `systemd-creds decrypt`.
|
|
async fn decrypt_credential(path: &Path) -> Result<Vec<u8>> {
|
|
let output = Command::new("systemd-creds")
|
|
.arg("decrypt")
|
|
.arg(path)
|
|
.arg("-")
|
|
.output()
|
|
.await
|
|
.map_err(|e| Error::SystemdCreds(format!("failed to run systemd-creds: {e}")))?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
return Err(Error::SystemdCreds(format!(
|
|
"systemd-creds decrypt {} failed: {stderr}",
|
|
path.display()
|
|
)));
|
|
}
|
|
|
|
Ok(output.stdout)
|
|
}
|
|
|
|
/// Encrypt data and write it to an encrypted credential file.
|
|
///
|
|
/// Credentials are not bound to a specific name, allowing services to
|
|
/// load them under any credential name via LoadCredentialEncrypted=.
|
|
pub async fn encrypt_credential(data: &[u8], path: &Path) -> Result<()> {
|
|
if let Some(parent) = path.parent() {
|
|
std::fs::create_dir_all(parent)?;
|
|
}
|
|
|
|
let mut child = Command::new("systemd-creds")
|
|
.arg("encrypt")
|
|
.arg("-")
|
|
.arg(path)
|
|
.stdin(std::process::Stdio::piped())
|
|
.stdout(std::process::Stdio::piped())
|
|
.stderr(std::process::Stdio::piped())
|
|
.spawn()
|
|
.map_err(|e| Error::SystemdCreds(format!("failed to run systemd-creds: {e}")))?;
|
|
|
|
use tokio::io::AsyncWriteExt;
|
|
if let Some(mut stdin) = child.stdin.take() {
|
|
stdin.write_all(data).await?;
|
|
// Drop stdin to close it so systemd-creds can proceed
|
|
}
|
|
|
|
let output = child.wait_with_output().await?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
return Err(Error::SystemdCreds(format!(
|
|
"systemd-creds encrypt to {} failed: {stderr}",
|
|
path.display()
|
|
)));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|