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 : }
|