quickseal 0.1.4

Create Kubernetes SealedSecrets with a single secure command
Documentation
//! Integration with kubeseal binary for sealing secrets

use crate::error::SealingError;
use crate::models::{SealedSecretManifest, SecretManifest, SecretRequest};
use crate::security::{CleanupGuard, SecureTempFile};
use std::process::Command;

/// Check if kubeseal binary exists in PATH
pub fn check_kubeseal_exists() -> Result<(), SealingError> {
    match which::which("kubeseal") {
        Ok(_) => Ok(()),
        Err(_) => Err(SealingError::KubesealNotFound {
            message: "kubeseal binary not found in PATH. Please install kubeseal.".to_string(),
        }),
    }
}

/// Seal a secret using kubeseal binary
/// This function:
/// 1. Creates a secure temporary file
/// 2. Writes the plaintext secret YAML
/// 3. Executes kubeseal
/// 4. Parses the output
/// 5. Validates the output
/// 6. Ensures cleanup (via SecureTempFile Drop)
pub fn seal_secret(request: &SecretRequest) -> Result<SealedSecretManifest, anyhow::Error> {
    // Check kubeseal exists first
    check_kubeseal_exists()?;

    // Create secret manifest
    let manifest = SecretManifest::from_request(request);
    let yaml = manifest.to_yaml()?;

    // Create secure temp file with automatic cleanup
    let mut temp_file =
        SecureTempFile::new().map_err(|e| SealingError::TempFileCreationFailed {
            error: e.to_string(),
        })?;

    // Setup cleanup guard to ensure temp file cleanup even on panic
    let temp_path = temp_file.path().to_path_buf();
    let _cleanup = CleanupGuard::new(move || {
        // SecureTempFile Drop will handle cleanup, this is extra safety
        if temp_path.exists() {
            let _ = std::fs::remove_file(&temp_path);
        }
    });

    // Write secret YAML to temp file
    temp_file
        .write_all(yaml.as_bytes())
        .map_err(|e| SealingError::TempFileCreationFailed {
            error: e.to_string(),
        })?;

    // Execute kubeseal
    let output = Command::new("kubeseal")
        .arg("--format")
        .arg("yaml")
        .stdin(
            std::fs::File::open(temp_file.path())
                .map_err(|e| SealingError::Other(e.to_string()))?,
        )
        .output()
        .map_err(|e| SealingError::KubesealExecutionFailed {
            stderr: e.to_string(),
            exit_code: -1,
        })?;

    // Check exit status
    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(SealingError::KubesealExecutionFailed {
            stderr: stderr.to_string(),
            exit_code: output.status.code().unwrap_or(-1),
        }
        .into());
    }

    // Parse output
    let stdout = String::from_utf8_lossy(&output.stdout);
    let sealed_secret = SealedSecretManifest::from_yaml(&stdout).map_err(|e| {
        SealingError::OutputValidationFailed {
            reason: format!("Failed to parse kubeseal output: {}", e),
        }
    })?;

    // Validate output matches input
    sealed_secret
        .validate_against_request(request)
        .map_err(|e| SealingError::OutputValidationFailed {
            reason: e.to_string(),
        })?;

    // Temp file will be automatically cleaned up when it goes out of scope
    Ok(sealed_secret)
}

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

    #[test]
    fn test_check_kubeseal_exists() {
        // This test will pass if kubeseal is installed, fail otherwise
        // In CI, kubeseal should be installed as a prerequisite
        let result = check_kubeseal_exists();
        // Just verify the function doesn't panic
        let _ = result;
    }
}