Audit Log - sparkycz1/honeypot-shelf GitHub Wiki

๐Ÿ“ Audit Log

Who did what, to which honeypot, and whether it actually worked โ€” superadmin-only, and it never forgets on purpose.

What gets recorded

Every route or background job that mutates something โ€” or that refuses to because a safeguard tripped (a typed confirmation that didn't match, a missing pinned host key, a bad self-registration token, a failed or locked-out login) โ€” writes one AuditLogEntry: actor, action, outcome, source IP, and a plain-language summary. A background job (the scheduler, a retention purge) logs under a fixed actor label instead of a user, since it has no request to read one from.

In the reader's language. The summary is always recorded in English โ€” exports, syslog, the Wazuh rules and the REST API stay English, so a machine-consumed record never varies by who triggered it. On the Audit log page an English reader sees that summary; any other language shows the translated label of the action code instead (audit.action_label.<code> in each locale file, falling back to the parent code โ€” honeypot.power.reboot โ†’ honeypot.power โ€” and then to the English summary), with the full English summary as the cell's tooltip. The details column is shown as key: value ยท key: value. A test fails when a new audit action has no label in every shipped language (tests/test_audit_labels_and_nav.py).

The Audit log page itself live-refreshes over WebSocket the moment a new entry is written โ€” no need to reload to see something that just happened. See Honeypot Management's "Live updates over WebSocket" for how that mechanism works across the app.

Tamper-evident by construction

Every entry is hash-chained: entry_hash covers this entry's own fields plus the previous entry's entry_hash, so altering or deleting an entry breaks the chain from that point on. Concurrent writers (the web app and every Celery worker child can all log at once) serialize through a single locked row for the duration of one entry's write, so two concurrent entries can never both link to the same previous hash.

Settings โ†’ Security โ†’ "Verify chain" walks the whole table and reports the first broken link, if any โ€” a proactive integrity check, not something that only fires when someone goes looking.

Logging commits independently of whatever the caller is doing, after its own commit โ€” a failed audit write can never roll back the action it describes, and a rejected/blocked action still gets its own record even though nothing else was worth committing.

Retention and export

Kept audit_log_retention_days (Settings โ†’ Checks & retention, default 90) before the daily purge job deletes old entries โ€” None means keep forever. Exportable as CSV or JSON (/audit/export) for archival or compliance, with the same spreadsheet-formula-injection guard the REST API's own event export uses (a leading =/+/-/@ gets a defusing prefix, since a raw one opens as a live formula in Excel/Sheets).

Forwarding to your own SIEM

A separate, optional target (Settings โ†’ Integrations, app.audit_syslog) mirrors every entry to a syslog server over UDP/TCP/ TCP-over-TLS as it's written โ€” best-effort, a delivery failure is logged and swallowed, never blocking the write it's mirroring. This is the global, audit-only target โ€” never a honeypot alert; those have their own three targets entirely, see Honeypot Management.

Wazuh users: wazuh/honeypotshelf_decoders.xml + wazuh/honeypotshelf_rules.xml (repo root) ship a ready-made decoder + rule set (id range 107000-107099) for both this feed and the honeypot-alert one โ€” covers every audit action this app emits and every OpenCanary module, plus brute-force correlation rules for repeated failed logins. Both files are plain XML with no comments (some Wazuh manager versions' own upload/file-editor path chokes on comments and non-ASCII characters), so the install steps live here instead:

  1. Copy honeypotshelf_decoders.xml to /var/ossec/etc/decoders/ and honeypotshelf_rules.xml to /var/ossec/etc/rules/ โ€” or point a <decoder_dir>/<rule_dir> pair in ossec.conf's <ruleset> block at wherever you keep this wazuh folder instead:
    <ruleset>
      <decoder_dir>etc/decoders</decoder_dir>
      <rule_dir>etc/rules</rule_dir>
      ...
      <decoder_dir>/path/to/wazuh</decoder_dir>
      <rule_dir>/path/to/wazuh</rule_dir>
    </ruleset>
  2. Point Honeypot Shelf's syslog target(s) at this manager (or an intermediate syslog-ng/rsyslog relay forwarding to it) โ€” Settings โ†’ Integrations for the global audit target, each Company's own page and/or "All honeypots" โ†’ Integrations for alerts.
  3. /var/ossec/bin/wazuh-control restart (or just restart wazuh-manager) to load both files.
  4. Verify with /var/ossec/bin/wazuh-logtest against a captured line before relying on it in production, as always.

The rules file matches on the <decoded_as> names the decoders produce (honeypotshelf-audit / honeypotshelf-honeypot-alert โ€” each decoder needs <use_own_name>true</use_own_name>, or Wazuh reports the parent decoder's name instead and <decoded_as> never matches) and on the fields both decoders expose from each feed's JSON body via JSON_Decoder โ€” every top-level key in the payload, referenced by its own name with no data. prefix (JSON_Decoder doesn't add one): event, id, timestamp, action, actor, ip, outcome, target_type, target_id, target_label, summary, details for the audit feed; event, id, timestamp, companies, honeypot, honeypot_ip, type โ€” OpenCanary's own numeric logtype id, as a string โ€” label, src_ip, src_port, dst_port, source, raw for the honeypot-alert feed).

Watch out for Wazuh's 13 reserved "static" field names (srcip, dstip, srcport, dstport, protocol, user, status, id, url, data, extra_data, action, hostname) โ€” the audit feed's own action key collides with one. A rule can't match it via <field name="action">...</field> (Wazuh rejects the whole rule file at upload with a bare "XML syntax error"/code 1113, no line number); it needs the dedicated <action type="pcre2">...</action> element instead โ€” same attributes, different tag. honeypotshelf_rules.xml already does this.

Severity levels: every honeypot-alert rule (107001, 107045- 107069) sits at level 8 or higher โ€” a honeypot has no legitimate traffic, so any interaction with one is inherently an attacker doing something, never routine noise. Credential submissions/login attempts step up to 9, and the brute-force correlation rules (107007, 107050) stay at 10. The audit-feed rules (107000-107043) stay mostly informational (level 3-6) and only escalate to 8+ for genuinely security-relevant events (failed/locked-out logins, CSRF rejection, rate limiting, destructive actions) โ€” ordinary CRUD by a logged-in admin is expected, routine activity, not something worth an analyst's attention.

โš ๏ธ **GitHub.com Fallback** โš ๏ธ