mod_audit*: modules for audit logging in prosody
These are to be seen as proof-of-concept for now.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_audit/README.md Tue Apr 26 22:32:44 2022 +0200
@@ -0,0 +1,27 @@
+---
+summary: Audit Logging
+rockspec: {}
+...
+
+This module provides infrastructure for audit logging inside Prosody.
+
+## What is audit logging?
+
+Audit logs will contain security sensitive events, both for server-wide
+incidents as well as user-specific.
+
+This module, however, only provides the infrastructure for audit logging. It
+does not, by itself, generate such logs. For that, other modules, such as
+`mod_audit_auth` or `mod_audit_register` need to be loaded.
+
+## A note on privacy
+
+Audit logging is intended to ensure the security of a system. As such, its
+contents are often at the same time highly sensitive (containing user names
+and IP addresses, for instance) and allowed to be stored under common privacy
+regulations.
+
+Before using these modules, you may want to ensure that you are legally
+allowed to store the data for the amount of time these modules will store it.
+Note that it is currently not possible to store different event types with
+different expiration times.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_audit/mod_audit.lua Tue Apr 26 22:32:44 2022 +0200
@@ -0,0 +1,86 @@
+module:set_global();
+
+local time_now = os.time;
+local st = require "util.stanza";
+
+local host_wide_user = "@";
+
+local stores = {};
+
+local function get_store(self, host)
+ local store = rawget(self, host);
+ if store then
+ return store
+ end
+ local store = module:context(host):open_store("audit", "archive");
+ rawset(self, host, store);
+ return store;
+end
+
+setmetatable(stores, { __index = get_store });
+
+
+local function session_extra(session)
+ local attr = {
+ xmlns = "xmpp:prosody.im/audit",
+ };
+ if session.id then
+ attr.id = session.id;
+ end
+ if session.type then
+ attr.type = session.type;
+ end
+ local stanza = st.stanza("session", attr);
+ if session.ip then
+ stanza:text_tag("remote-ip", session.ip);
+ end
+ return stanza
+end
+
+local function audit(host, user, source, event_type, extra)
+ if not host or host == "*" then
+ error("cannot log audit events for global");
+ end
+ local user = user or host_wide_user;
+
+ local attr = {
+ ["source"] = source,
+ ["type"] = event_type,
+ };
+ if user ~= host_wide_user then
+ attr.user = user;
+ end
+ local stanza = st.stanza("audit-event", attr);
+ if extra ~= nil then
+ if extra.session then
+ local child = session_extra(extra.session);
+ if child then
+ stanza:add_child(child);
+ end
+ end
+ if extra.custom then
+ for _, child in extra.custom do
+ if not st.is_stanza(child) then
+ error("all extra.custom items must be stanzas")
+ end
+ stanza:add_child(child);
+ end
+ end
+ end
+
+ local id, err = stores[host]:append(nil, nil, stanza, time_now(), user);
+ if err then
+ module:log("error", "failed to persist audit event: %s", err);
+ return
+ else
+ module:log("debug", "persisted audit event %s as %s", stanza:top_tag(), id);
+ end
+end
+
+local module_api = getmetatable(module).__index;
+
+function module_api:audit(user, event_type, extra)
+ audit(self.host, user, "mod_" .. self:get_name(), event_type, extra);
+end
+
+module:hook("audit", audit, 0);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_audit_auth/README.md Tue Apr 26 22:32:44 2022 +0200
@@ -0,0 +1,9 @@
+---
+summary: Store authentication events in the audit log
+rockspec:
+ dependencies:
+ - mod_audit
+...
+
+This module stores authentication failures and authentication successes in the
+audit log provided by `mod_audit`.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_audit_auth/mod_audit_auth.lua Tue Apr 26 22:32:44 2022 +0200
@@ -0,0 +1,15 @@
+module:depends("audit");
+
+module:hook("authentication-failure", function(event)
+ local session = event.session;
+ module:audit(session.sasl_handler.username, "authentication-failure", {
+ session = session,
+ });
+end)
+
+module:hook("authentication-success", function(event)
+ local session = event.session;
+ module:audit(session.sasl_handler.username, "authentication-success", {
+ session = session,
+ });
+end)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_audit_register/README.md Tue Apr 26 22:32:44 2022 +0200
@@ -0,0 +1,9 @@
+---
+summary: Store registration events in the audit log
+rockspec:
+ dependencies:
+ - mod_audit
+...
+
+This module stores successful user registrations in the audit log provided by
+`mod_audit`.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_audit_register/mod_audit_register.lua Tue Apr 26 22:32:44 2022 +0200
@@ -0,0 +1,22 @@
+module:depends("audit");
+
+local st = require "util.stanza";
+
+module:hook("user-registered", function(event)
+ local session = event.session;
+ local custom = {};
+ local invite = event.validated_invite or (event.session and event.session.validated_invite);
+ if invite then
+ table.insert(custom, st.stanza(
+ "invite-used",
+ {
+ xmlns = "xmpp:prosody.im/audit",
+ token = invite.token,
+ }
+ ))
+ end
+ module:audit(event.username, "user-registered", {
+ session = session,
+ custom = custom,
+ });
+end);