Collecting Linux audit logs
Sending auditd records to Qorionix: the two routes in, which fields are extracted, and which record types reach the shipped execution detections.
The Linux audit daemon is the closest thing Linux has to the Windows Security log. It records who ran what, who became root, who opened which file and which system calls a process made, in a format that has not changed in twenty years and is not designed to be read by humans. Qorionix has a parser for it, called auditd, and this article covers what that parser does and does not extract.
The two routes in
There is no Linux-specific collector and no auditd plugin to install. The parser is reached the same two ways every other parser is reached.
| Route | How | Trade |
|---|---|---|
| HTTP Event Collector | Issue an ingest token with parser_kind set to auditd and post lines to the collector as text/plain, one record per line. Any shipper that can POST will do — a cron job with curl, Vector, Fluent Bit, rsyslog's HTTP output. | Authenticated with a token, encrypted, and it tells you whether the events were stored. This is the route to prefer. |
| Syslog over TLS | Register the host's public address in the console as a syslog source with the parser set to auditd, install the Qorionix CA on the host, then configure audisp-syslog (or rsyslog reading /var/log/audit/audit.log) to forward with the omfwd TLS snippet the wizard gives you — gtls driver, x509/name verification, port 6514. | Encrypted and verified towards us, but attributed by source address only: the host does not authenticate itself. Use it when the host already ships syslog and you want one forwarder for everything. |
See Ingest tokens for issuing the token and Collecting syslog for the address-to-tenant mapping the second route needs. One token per format, as always: a token used for both auditd and plain syslog will run the auditd parser over both.
What the parser extracts
The parser reads key=value pairs across the whole line, honouring double quotes so comm="sudo" and exe="/usr/bin/sudo" parse correctly. From those pairs it fills six things.
| Audit field | Becomes | Note |
|---|---|---|
msg=audit(<epoch>:<seq>) | The event timestamp and metadata.audit_id | The record's own clock, including the fractional part, so the four or five records a single action emits keep their order. The audit id is extracted too, which is what you join them back together on. |
type= | The record's verb, and its category | EXECVE and an execve SYSCALL become process / process_launch; USER_LOGIN becomes authentication; the rest keep the type verbatim in upper case. See the next section. |
res= or success= | outcome | The USER_* family writes res=success|failed; the SYSCALL family writes success=yes|no. Both are read. This used to be res= only, which left every SYSCALL record unknown. |
uid= | user_id | The numeric user id, as written: 0, 1000. Not a username. auid and euid are not read. |
hostname= | The host | Rarely present in real records. When it is absent and the record arrived over syslog, the sending address is used instead. |
exe= | asset_id | The executable path. Yes, this is unusual — see below. |
On an execution record two more fields are filled in, because OCSF Process Activity needs them and because the detection rules read them: metadata.process_name from exe, comm or the first argument, and metadata.command_line. An EXECVE record names no executable at all — the exe is on the paired SYSCALL record — so its command line is rebuilt from the argv vector, a0 a1 a2 …, which is the only thing an EXECVE record actually contains.
The asset_id behaviour is worth a sentence of its own, because it is surprising. Auditd records name a program, not a machine. With no hostname in the record, the normalisation layer's only candidate for what this event is about is the executable path, so asset_id on an auditd event typically holds something like /usr/bin/sudo. If you want to filter by machine, the sending host lands in source_ip, not in asset_id.
Where each record type lands
The searchable category is chosen from the record's type. Only the types whose meaning is unambiguous are mapped; everything else keeps its type as the action and is classified by the generic fallback, which is the honest answer for a record family whose meaning depends on audit rules we cannot see.
| Record type | Category | Action |
|---|---|---|
EXECVE | process | process_launch. Matched by the shipped execution rules. |
SYSCALL where the syscall is execve or execveat | process | process_launch. Recognised from arch= plus syscall=; see below. |
SYSCALL for any other syscall | Generic fallback (usually api or file) | SYSCALL, verbatim and upper case. |
USER_LOGIN, USER_AUTH, USER_ACCT, CRED_ACQ | authentication | login_success or login_failed. These behave like a Windows 4624 or 4625 and are matched by the same brute-force rules. |
USER_LOGOUT | authentication | logout |
ADD_USER, DEL_USER, ADD_GROUP, DEL_GROUP, USER_CHAUTHTOK, USER_ROLE_CHANGE | iam | user_created, user_deleted, role_assigned, permission_revoked, password_changed |
PATH, CWD, PROCTITLE, and everything else | Generic fallback | The record type, verbatim and upper case. |
One further quirk, mentioned so that it does not read as inconsistency. For records that fall through to the generic classifier, the fallback looks at the recorded path and treats something that looks like a filename as file activity, so a record whose exe contains a dot — /usr/bin/python3.11, /opt/app/run.sh — is categorised file rather than api. Same record type, different category, decided by whether the binary's path happens to contain a full stop. It is a side effect of a heuristic that is right far more often than it is wrong, and on auditd data specifically it produces this odd split. It no longer affects execution records, which are categorised by type before the fallback runs.
Search these events in upper case
The search language compares values case-sensitively with =, and auditd record types are stored exactly as auditd writes them, which is upper case. This trips people up on the first query.
# Every execution record, from both EXECVE and execve SYSCALL records
category = process AND action = process_launch
# Non-execution system calls. Note the case — these keep the auditd spelling.
action = SYSCALL AND user_id = "0"
# Wrong — matches nothing; the search language is case-sensitive with =
action = syscall
# Logins recorded by auditd, failed only
category = authentication AND outcome = failure AND source_module = "siem-ingest"
# Anything an audit rule tagged with a key you defined
raw_log contains "key=\"privileged\""
# Failed system calls — now answerable with outcome, not just raw_log
action = SYSCALL AND outcome = failureNote the case rule still applies to the record types that keep their auditd spelling: the search language compares case-sensitively with =, and auditd writes its types in upper case. Execution records no longer need it, because process_launch is the platform's own spelling.
The fifteen searchable fields carry a summary of an auditd record; the record itself is intact in raw_log, and contains is how you reach the other forty fields. A detection rule has the better deal — it can address any extracted pair directly as metadata.ocsf.unmapped.<field>, so metadata.ocsf.unmapped.comm and metadata.ocsf.unmapped.key are both available to a rule and neither is available to a search.
What to audit, if you are starting from nothing
We are not going to reproduce an auditd tutorial, but the question what should I turn on has a short answer for a small organisation, and turning on everything is the wrong one — a busy server with unfiltered syscall auditing will produce tens of thousands of records a minute and cost you both disk and ingest.
- Privilege escalation. Audit
execvefor/usr/bin/sudoand/bin/su, tagged with a key you will recognise. This is the highest value per line of any rule you can write. - Changes to accounts and authentication config. Watch
/etc/passwd,/etc/shadow,/etc/sudoersand/etc/sudoers.d/. Small volume, high signal, and directly useful in an audit. - Changes to the audit configuration itself. Watch
/etc/audit/and/etc/audit/rules.d/. Somebody turning off logging is a finding. - SSH authorised keys. Watch
~/.ssh/authorized_keyswhere you can. A key appended to a service account is a classic persistence step and produces exactly one line. - Only then consider broad syscall auditing, and only on the machines that justify it. Check your event volume against your plan's retention before you turn it on — see Log retention.
The first of those — auditing execve for sudo and su — produces SYSCALL and EXECVE records that now arrive as category = process, action = process_launch, with a real outcome, and are seen by the shipped execution rules. The file-watch rules produce SYSCALL and PATH records that arrive with the record type as the action and an outcome read from success=, and are found by searching on the audit key you tagged them with.
What to read next
- Sending events with the HTTP Event Collector — the preferred route in, with a copyable request.
- Searching your logs — the fifteen fields, and why
raw_log containsdoes so much work on this source. - What a firewall log actually becomes — the same analysis for the three appliance parsers.