#![forbid(unsafe_code)]
use std::path::Path;
use crate::constants::{SECRET_DIR_MODE_UNIX, SECRET_FILE_MODE_UNIX};
use crate::errors::{SshCliError, SshCliResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SecretProtection {
Applied,
Unsupported,
}
impl SecretProtection {
#[must_use]
pub const fn is_applied(self) -> bool {
matches!(self, Self::Applied)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Applied => "applied",
Self::Unsupported => "unsupported",
}
}
}
#[must_use]
pub const fn secret_protection_supported() -> bool {
cfg!(unix)
}
pub fn set_secret_file_mode_checked(path: &Path) -> SshCliResult<SecretProtection> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(path)
.map_err(SshCliError::Io)?
.permissions();
perms.set_mode(SECRET_FILE_MODE_UNIX);
std::fs::set_permissions(path, perms).map_err(SshCliError::Io)?;
Ok(SecretProtection::Applied)
}
#[cfg(not(unix))]
{
let _ = path;
Ok(SecretProtection::Unsupported)
}
}
pub fn set_secret_dir_mode_checked(path: &Path) -> SshCliResult<SecretProtection> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(path)
.map_err(SshCliError::Io)?
.permissions();
perms.set_mode(SECRET_DIR_MODE_UNIX);
std::fs::set_permissions(path, perms).map_err(SshCliError::Io)?;
Ok(SecretProtection::Applied)
}
#[cfg(not(unix))]
{
let _ = path;
Ok(SecretProtection::Unsupported)
}
}
pub fn set_secret_file_mode(path: &Path) -> SshCliResult<()> {
warn_if_unprotected(path, set_secret_file_mode_checked(path)?);
Ok(())
}
pub fn set_secret_dir_mode(path: &Path) -> SshCliResult<()> {
warn_if_unprotected(path, set_secret_dir_mode_checked(path)?);
Ok(())
}
fn warn_if_unprotected(path: &Path, outcome: SecretProtection) {
if !outcome.is_applied() {
tracing::warn!(
path = %path.display(),
"secret path left at platform-default permissions: this build cannot restrict access on this OS"
);
}
}
pub fn write_secret_file_atomic(path: &Path, data: &[u8]) -> SshCliResult<()> {
use std::io::Write;
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(SshCliError::Io)?;
}
let parent = path.parent().unwrap_or_else(|| Path::new("."));
let mut tmp = tempfile::NamedTempFile::new_in(parent).map_err(SshCliError::Io)?;
tmp.write_all(data).map_err(SshCliError::Io)?;
tmp.as_file().sync_all().map_err(SshCliError::Io)?;
set_secret_file_mode(tmp.path())?;
tmp.persist(path).map_err(|e| SshCliError::Io(e.error))?;
set_secret_file_mode(path)?;
Ok(())
}
#[must_use]
pub const fn secret_file_mode() -> u32 {
SECRET_FILE_MODE_UNIX
}
#[must_use]
pub const fn secret_dir_mode() -> u32 {
SECRET_DIR_MODE_UNIX
}