use std::fs::{self, OpenOptions};
use std::io::Write;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use tempfile::TempDir;
pub struct Askpass {
_dir: TempDir,
script_path: PathBuf,
}
impl Askpass {
pub fn new(password: &str) -> std::io::Result<Self> {
let mut builder = tempfile::Builder::new();
builder.prefix("mlstack-askpass-");
#[cfg(unix)]
builder.permissions(fs::Permissions::from_mode(0o700));
let dir = builder.tempdir()?;
let pw_path = dir.path().join("pw");
{
let mut f = OpenOptions::new()
.write(true)
.create_new(true)
.open(&pw_path)?;
f.write_all(password.as_bytes())?;
#[cfg(unix)]
f.set_permissions(fs::Permissions::from_mode(0o600))?;
}
let script_path = dir.path().join("askpass.sh");
{
let mut f = OpenOptions::new()
.write(true)
.create_new(true)
.open(&script_path)?;
writeln!(f, "#!/bin/sh")?;
writeln!(f, "pw_dir=$(dirname \"$0\")")?;
writeln!(f, "exec cat \"$pw_dir/pw\"")?;
#[cfg(unix)]
f.set_permissions(fs::Permissions::from_mode(0o700))?;
}
Ok(Self {
_dir: dir,
script_path,
})
}
pub fn path(&self) -> &str {
self.script_path
.to_str()
.expect("tempdir paths are valid UTF-8")
}
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
#[test]
fn script_cats_password_and_is_secure() {
let helper = Askpass::new("s3cr!t'pass").unwrap();
let script = std::fs::read_to_string(helper.path()).unwrap();
assert!(script.contains("#!/bin/sh"));
assert!(script.contains("exec cat "));
assert!(!script.contains("s3cr"));
use std::os::unix::fs::PermissionsExt;
let mode = std::fs::metadata(helper.path())
.unwrap()
.permissions()
.mode();
assert_eq!(mode & 0o777, 0o700);
let dir = std::path::Path::new(helper.path()).parent().unwrap();
let pw_mode = std::fs::metadata(dir.join("pw"))
.unwrap()
.permissions()
.mode();
assert_eq!(pw_mode & 0o777, 0o600);
}
#[test]
fn askpass_executes_and_returns_password() {
let helper = Askpass::new("hunter2").unwrap();
let out = std::process::Command::new(helper.path())
.output()
.expect("askpass script runs");
assert!(out.status.success(), "askpass exited non-zero");
assert_eq!(String::from_utf8_lossy(&out.stdout), "hunter2");
}
}