LCOV - code coverage report
Current view: top level - compute_tools/src - rsyslog.rs (source / functions) Coverage Total Hit
Test: 62212f4d57a7ad0f69dc82a04629a0bbd5f7c824.info Lines: 0.0 % 70 0
Test Date: 2025-03-17 10:41:39 Functions: 0.0 % 8 0

            Line data    Source code
       1              : use std::fs;
       2              : use std::path::Path;
       3              : use std::process::Command;
       4              : use std::time::Duration;
       5              : use std::{fs::OpenOptions, io::Write};
       6              : 
       7              : use anyhow::{Context, Result};
       8              : use tracing::{error, info, instrument, warn};
       9              : 
      10            0 : fn get_rsyslog_pid() -> Option<String> {
      11            0 :     let output = Command::new("pgrep")
      12            0 :         .arg("rsyslogd")
      13            0 :         .output()
      14            0 :         .expect("Failed to execute pgrep");
      15            0 : 
      16            0 :     if !output.stdout.is_empty() {
      17            0 :         let pid = std::str::from_utf8(&output.stdout)
      18            0 :             .expect("Invalid UTF-8 in process output")
      19            0 :             .trim()
      20            0 :             .to_string();
      21            0 :         Some(pid)
      22              :     } else {
      23            0 :         None
      24              :     }
      25            0 : }
      26              : 
      27              : // Restart rsyslogd to apply the new configuration.
      28              : // This is necessary, because there is no other way to reload the rsyslog configuration.
      29              : //
      30              : // Rsyslogd shouldn't lose any messages, because of the restart,
      31              : // because it tracks the last read position in the log files
      32              : // and will continue reading from that position.
      33              : // TODO: test it properly
      34              : //
      35            0 : fn restart_rsyslog() -> Result<()> {
      36            0 :     let old_pid = get_rsyslog_pid().context("rsyslogd is not running")?;
      37            0 :     info!("rsyslogd is running with pid: {}, restart it", old_pid);
      38              : 
      39              :     // kill it to restart
      40            0 :     let _ = Command::new("pkill")
      41            0 :         .arg("rsyslogd")
      42            0 :         .output()
      43            0 :         .context("Failed to stop rsyslogd")?;
      44              : 
      45            0 :     Ok(())
      46            0 : }
      47              : 
      48            0 : pub fn configure_audit_rsyslog(
      49            0 :     log_directory: String,
      50            0 :     tag: &str,
      51            0 :     remote_endpoint: &str,
      52            0 : ) -> Result<()> {
      53            0 :     let config_content: String = format!(
      54            0 :         include_str!("config_template/compute_audit_rsyslog_template.conf"),
      55            0 :         log_directory = log_directory,
      56            0 :         tag = tag,
      57            0 :         remote_endpoint = remote_endpoint
      58            0 :     );
      59            0 : 
      60            0 :     info!("rsyslog config_content: {}", config_content);
      61              : 
      62            0 :     let rsyslog_conf_path = "/etc/rsyslog.d/compute_audit_rsyslog.conf";
      63            0 :     let mut file = OpenOptions::new()
      64            0 :         .create(true)
      65            0 :         .write(true)
      66            0 :         .truncate(true)
      67            0 :         .open(rsyslog_conf_path)?;
      68              : 
      69            0 :     file.write_all(config_content.as_bytes())?;
      70              : 
      71            0 :     info!(
      72            0 :         "rsyslog configuration file {} added successfully. Starting rsyslogd",
      73              :         rsyslog_conf_path
      74              :     );
      75              : 
      76              :     // start the service, using the configuration
      77            0 :     restart_rsyslog()?;
      78              : 
      79            0 :     Ok(())
      80            0 : }
      81              : 
      82              : #[instrument(skip_all)]
      83              : async fn pgaudit_gc_main_loop(log_directory: String) -> Result<()> {
      84              :     info!("running pgaudit GC main loop");
      85              :     loop {
      86              :         // Check log_directory for old pgaudit logs and delete them.
      87              :         // New log files are checked every 5 minutes, as set in pgaudit.log_rotation_age
      88              :         // Find files that were not modified in the last 15 minutes and delete them.
      89              :         // This should be enough time for rsyslog to process the logs and for us to catch the alerts.
      90              :         //
      91              :         // In case of a very high load, we might need to adjust this value and pgaudit.log_rotation_age.
      92              :         //
      93              :         // TODO: add some smarter logic to delete the files that are fully streamed according to rsyslog
      94              :         // imfile-state files, but for now just do a simple GC to avoid filling up the disk.
      95              :         let _ = Command::new("find")
      96              :             .arg(&log_directory)
      97              :             .arg("-name")
      98              :             .arg("audit*.log")
      99              :             .arg("-mmin")
     100              :             .arg("+15")
     101              :             .arg("-delete")
     102              :             .output()?;
     103              : 
     104              :         // also collect the metric for the size of the log directory
     105            0 :         async fn get_log_files_size(path: &Path) -> Result<u64> {
     106            0 :             let mut total_size = 0;
     107              : 
     108            0 :             for entry in fs::read_dir(path)? {
     109            0 :                 let entry = entry?;
     110            0 :                 let entry_path = entry.path();
     111            0 : 
     112            0 :                 if entry_path.is_file() && entry_path.to_string_lossy().ends_with("log") {
     113            0 :                     total_size += entry.metadata()?.len();
     114            0 :                 }
     115              :             }
     116              : 
     117            0 :             Ok(total_size)
     118            0 :         }
     119              : 
     120              :         let log_directory_size = get_log_files_size(Path::new(&log_directory))
     121              :             .await
     122            0 :             .unwrap_or_else(|e| {
     123            0 :                 warn!("Failed to get log directory size: {}", e);
     124            0 :                 0
     125            0 :             });
     126              :         crate::metrics::AUDIT_LOG_DIR_SIZE.set(log_directory_size as f64);
     127              :         tokio::time::sleep(Duration::from_secs(60)).await;
     128              :     }
     129              : }
     130              : 
     131              : // launch pgaudit GC thread to clean up the old pgaudit logs stored in the log_directory
     132            0 : pub fn launch_pgaudit_gc(log_directory: String) {
     133            0 :     tokio::spawn(async move {
     134            0 :         if let Err(e) = pgaudit_gc_main_loop(log_directory).await {
     135            0 :             error!("pgaudit GC main loop failed: {}", e);
     136            0 :         }
     137            0 :     });
     138            0 : }
        

Generated by: LCOV version 2.1-beta