diff --git a/README.quickstart b/README.quickstart index 87adc63..92e42dc 100644 --- a/README.quickstart +++ b/README.quickstart @@ -20,21 +20,28 @@ compared with the AIDE database. Prior to running a check manually, ensure that the AIDE binary and database have not been modified without your knowledge. - - Caution! - - With the default setup, an AIDE check is not run periodically as a - cron job. It cannot be guaranteed that the AIDE binaries, config - file and database are intact. It is not recommended that you run - automated AIDE checks without verifying AIDE yourself frequently. - In addition to that, AIDE does not implement any password or - encryption protection for its own files. - - It is up to you how to put a file integrity checker to good effect - and how to set up automated checks if you think it adds a level of - safety (e.g. detecting failed/incomplete compromises or unauthorized - modification of special files). On a compromised system, the - intruder could disable the automated check. Or he could replace the - AIDE binary, config file and database easily when they are not - located on read-only media. + +6) To schedule daily integrity checks, enable the systemd timer: + + systemctl enable --now aide-check.timer + + View results with: journalctl -u aide-check + Check timer status with: systemctl status aide-check.timer + + The timer runs daily with low CPU/IO priority to minimize impact + on production workloads. It is disabled by default — only enable + it after initializing the database (steps 2-4). + + Caution! + + It cannot be guaranteed that the AIDE binaries, config file and + database are intact. It is not recommended that you run automated + AIDE checks without verifying AIDE yourself frequently. In addition + to that, AIDE does not implement any password or encryption + protection for its own files. + + It is up to you how to put a file integrity checker to good effect. + On a compromised system, the intruder could disable the automated + check. Or he could replace the AIDE binary, config file and database + easily when they are not located on read-only media. diff --git a/aide-check.service b/aide-check.service new file mode 100644 index 0000000..8946759 --- /dev/null +++ b/aide-check.service @@ -0,0 +1,10 @@ +[Unit] +Description=AIDE file integrity check +Documentation=man:aide(1) man:aide.conf(5) + +[Service] +Type=oneshot +ExecStart=/usr/sbin/aide --check +SuccessExitStatus=0 1 2 3 4 5 6 7 +Nice=19 +IOSchedulingClass=idle diff --git a/aide-check.timer b/aide-check.timer new file mode 100644 index 0000000..5bbfa1e --- /dev/null +++ b/aide-check.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Daily AIDE file integrity check +Documentation=man:aide(1) man:aide.conf(5) + +[Timer] +OnCalendar=daily +AccuracySec=1h +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/aide-include-permission-checks.patch b/aide-include-permission-checks.patch new file mode 100644 index 0000000..012b640 --- /dev/null +++ b/aide-include-permission-checks.patch @@ -0,0 +1,66 @@ +diff --git a/src/conf_eval.c b/src/conf_eval.c +index 5774ce6..6503709 100644 +--- a/src/conf_eval.c ++++ b/src/conf_eval.c +@@ -580,9 +580,9 @@ static void include_file(const char* file, bool execute, int include_depth, char + } + } + +-void check_permissions(const char* path, struct stat *st, int linenumber, char *filename, char* linebuf) { ++static void check_permissions(const char* path, struct stat *st, const char *directive, int linenumber, char *filename, char* linebuf) { + if ((st->st_uid != geteuid() && st->st_uid != 0) || (st->st_mode & 002) != 0 || (st->st_mode & 020) != 0) { +- LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'@@x_include': bad ownership or modes for '%s' (please ensure it is neither group- nor world-writable and owned by the current user or root)", path) ++ LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'%s': bad ownership or modes for '%s' (please ensure it is neither group- nor world-writable and owned by the current user or root)", directive, path) + exit(INVALID_CONFIGURELINE_ERROR); + } + } +@@ -611,13 +611,13 @@ static void include_directory(const char* dir, const char* rx, bool execute, cha + + struct stat fs; + +- if (execute) { +- if (stat(dir,&fs) == -1) { +- LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'@@x_include': stat for '%s' failed: %s", dir, strerror(errno)) +- exit(INVALID_CONFIGURELINE_ERROR); +- } +- check_permissions(dir, &fs, linenumber, filename, linebuf); ++ /* stat() follows symlinks; we intentionally check the target's ownership ++ * and mode rather than the symlink node itself */ ++ if (stat(dir,&fs) == -1) { ++ LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'%s': stat for '%s' failed: %s", execute?"@@x_include":"@@include", dir, strerror(errno)) ++ exit(INVALID_CONFIGURELINE_ERROR); + } ++ check_permissions(dir, &fs, execute?"@@x_include":"@@include", linenumber, filename, linebuf); + + n = scandir(dir, &namelist, dirfilter, alphasort); + if (n == -1) { +@@ -660,9 +660,8 @@ static void include_directory(const char* dir, const char* rx, bool execute, cha + log_msg(LOG_LEVEL_DEBUG,"%s: skip '%s' (reason: file name does not match regex '%s')", dir, namelist[i]->d_name, rx); + } else { + int exec = execute && S_IXUSR&fs.st_mode; +- if (exec) { +- check_permissions(filepath, &fs, linenumber, filename, linebuf); +- } ++ /* pass directive name (not exec flag) so the error names the directive the user wrote */ ++ check_permissions(filepath, &fs, execute?"@@x_include":"@@include", linenumber, filename, linebuf); + log_msg(LOG_LEVEL_CONFIG,"%s: %s '%s'", dir, exec?"execute":"include", namelist[i]->d_name); + include_file(filepath, exec, include_depth, nested_rule_prefix); + } +@@ -701,14 +700,15 @@ static void eval_include_statement(include_statement statement, int include_dept + } else { + struct stat fs; + if (lstat(path,&fs) == -1) { +- LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'@@include': lstat for '%s' failed: %s", path, strerror(errno)) ++ LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'%s': lstat for '%s' failed: %s", statement.execute?"@@x_include":"@@include", path, strerror(errno)) + exit(INVALID_CONFIGURELINE_ERROR); + } + if (S_ISREG(fs.st_mode)) { ++ check_permissions(path, &fs, statement.execute?"@@x_include":"@@include", linenumber, filename, linebuf); + LOG_CONFIG_FORMAT_LINE_PREFIX(LOG_LEVEL_CONFIG, "include file '%s' (depth: %d)", path, include_depth) + include_file(path, statement.execute && S_IXUSR&fs.st_mode, include_depth, rule_prefix); + } else { +- LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'@@include': '%s' is not a regular file", path); ++ LOG_CONFIG_FORMAT_LINE(LOG_LEVEL_ERROR, "'%s': '%s' is not a regular file", statement.execute?"@@x_include":"@@include", path); + exit(INVALID_CONFIGURELINE_ERROR); + } + } diff --git a/aide.conf b/aide.conf index 1f8a145..ee198b7 100644 --- a/aide.conf +++ b/aide.conf @@ -23,8 +23,8 @@ database_add_metadata=yes # Warn about unrestricted rules during config check (default: false) config_check_warn_unrestricted_rules=false -# Number of workers for parallel processing (default: 1, can use percentage) -num_workers=1 +# Number of workers for parallel processing (AIDE default: 1, overridden to 4 here) +num_workers=4 # Default. log_level=warning @@ -124,6 +124,10 @@ LOG = p+ftype+u+g+n+ANF+ARF+selinux+xattrs # but we want to know when the data inside them changes - updated with modern hash DATAONLY = ftype+p+l+n+u+g+s+acl+selinux+xattrs+sha256 +# Read /etc/aide.d/*.conf files +@@include /etc/aide.d ^[a-zA-Z0-9_-]+\.conf$ + + # Next decide what directories/files you want in the database. /boot NORMAL diff --git a/aide.spec b/aide.spec index 991d093..d6eddb5 100644 --- a/aide.spec +++ b/aide.spec @@ -1,7 +1,7 @@ Summary: Intrusion detection environment Name: aide Version: 0.19.2 -Release: 6%{?dist} +Release: 7%{?dist} URL: https://github.com/aide/aide License: GPLv2+ @@ -16,7 +16,10 @@ Source4: README.quickstart Source5: aide.logrotate Source6: aide-tmpfiles.conf Source7: aide-migrate-config +Source8: aide-check.service +Source9: aide-check.timer Patch0: aide-0.19.2-syslog-format.patch +Patch1: aide-include-permission-checks.patch BuildRequires: gcc BuildRequires: make @@ -69,6 +72,11 @@ mkdir -p -m0700 %{buildroot}%{_localstatedir}/lib/aide # Install tmpfiles config install -Dpm0644 %{SOURCE6} %{buildroot}%{_tmpfilesdir}/aide.conf install -Dpm0755 %{SOURCE7} %{buildroot}%{_sbindir}/aide-migrate-config +# Create /etc/aide.d/ +mkdir -p -m0700 %{buildroot}%{_sysconfdir}/aide.d +# Install systemd timer and service for scheduled integrity checks +install -Dpm0644 %{SOURCE8} %{buildroot}%{_unitdir}/aide-check.service +install -Dpm0644 %{SOURCE9} %{buildroot}%{_unitdir}/aide-check.timer %files %license COPYING @@ -80,17 +88,35 @@ install -Dpm0755 %{SOURCE7} %{buildroot}%{_sbindir}/aide-migrate-config %{_mandir}/man5/*.5* %config(noreplace) %attr(0600,root,root) %{_sysconfdir}/aide.conf %config(noreplace) %{_sysconfdir}/logrotate.d/aide +%dir %attr(0700,root,root) %{_sysconfdir}/aide.d %dir %attr(0700,root,root) %{_localstatedir}/lib/aide %dir %attr(0700,root,root) %{_localstatedir}/log/aide %{_tmpfilesdir}/aide.conf +%{_unitdir}/aide-check.service +%{_unitdir}/aide-check.timer %post +%systemd_post aide-check.timer if [ $1 -ge 2 ]; then /usr/sbin/aide-migrate-config /etc/aide.conf 2>&1 | \ tee -a /var/log/aide/aide-migrate.log || : fi +%preun +%systemd_preun aide-check.timer + +%postun +%systemd_postun_with_restart aide-check.timer + %changelog +* Tue Jun 03 2026 Patrik Koncity - 0.19.2-7 +- Support for included files in /etc/aide.d/ +Resolves: RHEL-178122 +- Increase default values for num_workers +Resolves: RHEL-178123 +- Add pre-configured systemd timer for aide check +Resolves: RHEL-178121 + * Tue May 26 2026 Attila Lakatos - 0.19.2-6 - Add aide-migrate-config to automate config migration from pre-0.19 syntax Resolves: RHEL-178317