quickseal 0.1.4

Create Kubernetes SealedSecrets with a single secure command
Documentation
//! Secure temporary file handling with automatic cleanup

use std::fs::File;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use tempfile::NamedTempFile;
use zeroize::Zeroize;

/// Secure temporary file wrapper that:
/// 1. Creates files with 0600 permissions (owner read/write only)
/// 2. Overwrites content with zeros before deletion
/// 3. Automatically deletes on drop
pub struct SecureTempFile {
    file: NamedTempFile,
    path: PathBuf,
}

impl SecureTempFile {
    /// Create a new secure temporary file
    pub fn new() -> io::Result<Self> {
        let file = tempfile::Builder::new()
            .prefix("seal-secret-")
            .suffix(".yaml")
            .rand_bytes(16)
            .tempfile()?;

        let path = file.path().to_path_buf();

        // Set restrictive permissions (Unix only)
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            let mut perms = std::fs::metadata(&path)?.permissions();
            perms.set_mode(0o600); // Owner read/write only
            std::fs::set_permissions(&path, perms)?;
        }

        Ok(Self { file, path })
    }

    /// Write data to the temporary file
    pub fn write_all(&mut self, data: &[u8]) -> io::Result<()> {
        self.file.write_all(data)?;
        self.file.flush()?;
        Ok(())
    }

    /// Get the path to the temporary file
    pub fn path(&self) -> &Path {
        &self.path
    }

    /// Explicitly delete the file (called automatically on drop)
    pub fn delete(self) -> io::Result<()> {
        // Drop will handle cleanup
        Ok(())
    }
}

impl Drop for SecureTempFile {
    fn drop(&mut self) {
        // Attempt to overwrite file contents with zeros before deletion
        if let Ok(metadata) = std::fs::metadata(&self.path) {
            let file_size = metadata.len() as usize;
            if file_size > 0 && file_size < 10 * 1024 * 1024 {
                // Only overwrite if < 10MB
                let mut zeros = vec![0u8; file_size];
                if let Ok(mut file) = File::create(&self.path) {
                    let _ = file.write_all(&zeros);
                    let _ = file.flush();
                }
                zeros.zeroize();
            }
        }
        // NamedTempFile will automatically delete on drop
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_secure_temp_file_creation() -> io::Result<()> {
        let temp_file = SecureTempFile::new()?;
        assert!(temp_file.path().exists());
        Ok(())
    }

    #[test]
    fn test_secure_temp_file_write() -> io::Result<()> {
        let mut temp_file = SecureTempFile::new()?;
        temp_file.write_all(b"test data")?;
        assert!(temp_file.path().exists());
        Ok(())
    }

    #[test]
    fn test_secure_temp_file_cleanup() -> io::Result<()> {
        let path;
        {
            let temp_file = SecureTempFile::new()?;
            path = temp_file.path().to_path_buf();
            assert!(path.exists());
        }
        // File should be deleted after drop
        assert!(!path.exists());
        Ok(())
    }
}