Central Logging, AIDE and AnsibleLesson 2 of 3
Detecting changed files with AIDE
Build an AIDE baseline, define what to watch, read a check report and update the database after an approved change.
aideaide --initaide --checkaide --updateaide --compare/etc/aide.confrpm -Vrpm -Vasha256sumsystemctl enable --now
After this lesson you can
- Explain what AIDE records, what it cannot see, and why the database must be trusted.
- Initialise the database, add a watch rule for a directory, and run a check.
- Read the summary, the one-line change codes and the detail section, and decide between 'approved change' and 'incident'.
The idea
AIDE (Advanced Intrusion Detection Environment) answers one question: has any file I care about changed since I last said everything was fine? It records attributes of chosen files in a database, and later compares the filesystem to the database and reports the differences.
It does not prevent anything, and it does not tell you who or why. Its value is that unexpected change becomes visible: a modified sshd_config, a replaced binary, a permission bit flipped on /etc/shadow.
What it records
Each rule names attributes to store. The common ones:
| Letter | Attribute | Letter | Attribute |
|---|---|---|---|
p | permissions | s | size |
u / g | owner / group | m | modification time |
n | number of links | a / c | access / change time |
i | inode | sha256, sha512 | content hash |
selinux | SELinux context | xattrs, acl | extended attributes, ACLs |
Rules group attributes: LAB = p+u+g+s+m+sha256 defines a group named LAB, and /opt/webapp/conf LAB applies it to everything under that path. RHEL’s /etc/aide.conf ships with ready-made groups and watches /boot, /etc, /usr and other system trees; read it before you change it.
The database is the trust anchor
If an attacker can change both the file and the database, AIDE reports nothing. So in production the baseline is copied somewhere the machine cannot write (read-only media, another host), and the check runs from that copy. On the exam the point is simpler: create the baseline while the system is in a known good state, and update it only after you have understood each reported change.
| File | Role |
|---|---|
/etc/aide.conf | Rules and groups; where the databases live |
/var/lib/aide/aide.db.gz | The baseline AIDE compares against (database_in) |
/var/lib/aide/aide.db.new.gz | Where --init and --update write the new database (database_out) |
/var/log/aide/ | Log directory, if you configure one |
Commands
aide --init # scan and write a brand-new database (aide.db.new.gz)
aide --check # compare filesystem with the baseline; read-only
aide --update # compare AND write a new database that includes today's state
aide --compare # compare database_in with database_new, no filesystem scan
The exit status of --check is a bit mask (1 = new files, 2 = removed, 4 = changed), so 0 means clean and anything else means differences or an error. That makes it easy to put in a scheduled job.
Try it
Real output from AIDE 0.18 (second lines of long hash values omitted), with a small test configuration that watches a directory called watch with the LAB group above. The commands are the same on RHEL; only the configuration path and database location differ.
aide --init
aide --check | head -2
Start timestamp: 2026-11-04 17:03:06 +0300 (AIDE 0.18.6)
AIDE successfully initialized database.
New AIDE database written to /tmp/ai/db/aide.db.new.gz
Start timestamp: 2026-11-04 17:03:06 +0300 (AIDE 0.18.6)
AIDE found NO differences between database and filesystem. Looks okay!!
Now make three changes: append to a file, change another file’s mode, create a new file.
echo "changed" >> watch/app.conf
chmod 600 watch/data.txt
echo new > watch/extra.txt
aide --check | sed -n 2,40p
AIDE found differences between database and filesystem!!
Summary:
Total number of entries: 4
Added entries: 1
Removed entries: 0
Changed entries: 3
---------------------------------------------------
Added entries:
---------------------------------------------------
f+++++++++++++++++: /tmp/ai/watch/extra.txt
---------------------------------------------------
Changed entries:
---------------------------------------------------
d = ... m : /tmp/ai/watch
f > ... m H : /tmp/ai/watch/app.conf
f = p.. . . : /tmp/ai/watch/data.txt
---------------------------------------------------
Detailed information about changes:
---------------------------------------------------
Directory: /tmp/ai/watch
Mtime : 2026-11-04 17:02:59 +0300 | 2026-11-04 17:03:06 +0300
File: /tmp/ai/watch/app.conf
Size : 6 | 14
Mtime : 2026-11-04 17:02:59 +0300 | 2026-11-04 17:03:06 +0300
SHA256 : tqmNnOmi2RSSiPo99C03fD5Cc3r9za9x | WCgELHbZ3cq8+a/GEyqkTTl52MXU6zll
File: /tmp/ai/watch/data.txt
Perm : -rw-r--r-- | -rw-------
Reading the report
The report has three levels, and you read them in order.
- Summary: how many added, removed and changed. Zero everywhere means clean.
- One-line codes: the first letter is the type (
ffile,ddirectory,llink). Then a column per attribute: a letter (p,m,s,Hfor hash) means that attribute changed, a dot means unchanged,=and>describe the size relationship.f+++++++++++++++++means a file that is new. - Detailed information: the old value on the left, the new on the right, only for attributes that changed.
The three entries tell three different stories. extra.txt appeared (a new file). app.conf changed in content (size and hash). data.txt changed only in permissions: no content change, but a mode change on a sensitive file is exactly what you want to notice. The directory line is an automatic side effect: adding a file changes the parent’s mtime.
Accepting changes
aide --update | head -3
cp -p /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
aide --check | head -2
--update reports the same differences as --check and also writes a database containing the current state. Do it only after reviewing the report. Updating blindly turns AIDE into a tool that approves everything.
AIDE versus rpm -V
rpm -V PACKAGE (or rpm -Va for everything) compares installed files with the package database. It needs no setup and covers every file a package owns, but it cannot see files that belong to no package, and the database is itself on the machine. AIDE covers any path you choose and can be baselined at a moment you control. They answer slightly different questions, so use both: rpm -Va for “does this still match the package?”, AIDE for “has anything changed since the baseline?”
When AIDE misbehaves
| Symptom | Cause | Fix |
|---|---|---|
Couldn't open file ... aide.db.gz | Baseline missing | aide --init, then copy aide.db.new.gz to aide.db.gz |
| Same differences every run | Database not replaced after --update | Copy the new database over the old one |
| Thousands of changes after updates | Packages changed /usr, /boot | Expected after dnf update; run --update after patching, and review |
| Report full of noise from one directory | Cache, logs or spool watched | Exclude with !/path or watch only attributes that matter |
| Rule has no effect | Path typed wrong, regular expression not anchored, exclusion placed after a broader rule | Check with aide --check after a small test change |
| Check is very slow | Hashing everything under /usr | Use fewer or lighter hashes for large trees |
Going further
Scheduling. A check is only useful if it runs. A systemd timer calling aide --check and mailing or logging the result is the usual approach; systemd-run --on-calendar is a quick way to test it. Beyond the exam: the baseline should be signed or stored off the machine, and a SIEM should consume the report. AIDE sees changes only when it runs, so an attacker who changes and restores a file between checks leaves no trace.
Practice
0 of 3 done
Do these on your own system. They get harder. Check each result, then tick it off.
- Warm-up
Baseline, change, detect
Install AIDE and create the baseline with the default configuration. Run a check and confirm it reports no differences. Then create
/etc/aide-lab-markercontaining one line, run the check again, and confirm the new file is reported. (Use a file in a directory the default rules watch;/etcis.)Check your result
sudo ls -l /var/lib/aide/aide.db.gzexists.- The first
sudo aide --checkends withAIDE found NO differences between database and filesystem. Looks okay!!(on a freshly installed machine; see the note on first runs). - After the change, the report lists
/etc/aide-lab-markerunder Added entries and/etcunder changed directories.
Hint
aide --initwrites/var/lib/aide/aide.db.new.gz. AIDE readsaide.db.gz, so you must copy the new file over the old name.aide --checkcompares; it never changes the database.Solution
sudo dnf install -y aide sudo aide --init sudo cp -p /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz sudo aide --check | head -3 echo "marker" | sudo tee /etc/aide-lab-marker sudo aide --check | sed -n 1,20p - Practice
Watch a directory, ignore the noise
An application keeps its settings in
/opt/webapp/confand writes cache files into/opt/webapp/cache. Add rules so AIDE watches the permissions, owner, size, mtime and SHA-256 of everything underconf, and ignorescache. Rebuild the baseline. Prove it: changeconf/app.conf(reported), create a file incache(not reported), thenchmod 600a second file inconf(reported as a permission change only).Check your result
grep -n webapp /etc/aide.confshows a watch line and an exclusion line (starting with!).- The check reports
app.confwith a changed size/hash and the second file with onlyp(permissions) in its change code. - Nothing under
/opt/webapp/cacheappears in the report.
Hint
A rule is
PATH GROUP. A path prefixed with!is excluded. Define a group with your own attribute list near the top of the file, or reuse one that already exists. Exclusion lines should come first in practice, and paths are regular expressions anchored at the start.Solution
sudo mkdir -p /opt/webapp/conf /opt/webapp/cache echo 'port=8080' | sudo tee /opt/webapp/conf/app.conf echo 'x' | sudo tee /opt/webapp/conf/other.conf sudo tee -a /etc/aide.conf <<'EOT' # EX342 lab: web application WEBAPP = p+u+g+s+m+sha256 !/opt/webapp/cache /opt/webapp/conf WEBAPP EOT sudo aide --init && sudo cp -p /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz echo 'port=9090' | sudo tee /opt/webapp/conf/app.conf echo junk | sudo tee /opt/webapp/cache/tmp1 sudo chmod 600 /opt/webapp/conf/other.conf sudo aide --check | sed -n 1,30p - Challenge
Approved change or intrusion?
Setup (with a baseline that covers
/etc):sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config sudo useradd -m -s /bin/bash svc-backup2 sudo chmod 644 /etc/shadow sudo touch -d '2020-01-01' /etc/hostsRun a check and classify every reported item as expected maintenance (you can explain it) or suspicious (you cannot, or it weakens security). Fix the suspicious ones, then update the baseline so that only the approved changes are accepted. Finish with a clean check.
Check your result
ls -l /etc/shadowshows mode----------(000) again.grep -i '^PermitRootLogin' /etc/ssh/sshd_configis notyes.sudo aide --checkends withLooks okay!!.- Your notes name the shadow permission change as the clearest sign of tampering.
Hint
/etc/passwd,/etc/shadow,/etc/group,/etc/gshadowchange when you add a user, which is a normal change./etc/shadowbecoming world-readable is not: the change code showsp(permissions). Comparerpm -V setupfor the files that belong to a package. Mtime-only changes on a file are usually harmless but still worth a comment.Solution
sudo aide --check | sed -n 1,40p rpm -V setup openssh-server sudo chmod 000 /etc/shadow sudo sed -i 's/^PermitRootLogin yes/#PermitRootLogin prohibit-password/' /etc/ssh/sshd_config sudo systemctl reload sshd sudo aide --update | head -3 sudo cp -p /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz sudo aide --check | head -2