# HG changeset patch # User Jonas Schäfer # Date 1651005164 -7200 # Node ID 530d116b7f68d9cde89e6ed081f8af54babaddb8 # Parent f4a9e804c4577657c7a69c754b77a7cfdadec4fd mod_audit*: modules for audit logging in prosody These are to be seen as proof-of-concept for now. diff -r f4a9e804c457 -r 530d116b7f68 mod_audit/README.md --- /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. diff -r f4a9e804c457 -r 530d116b7f68 mod_audit/mod_audit.lua --- /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); diff -r f4a9e804c457 -r 530d116b7f68 mod_audit_auth/README.md --- /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`. diff -r f4a9e804c457 -r 530d116b7f68 mod_audit_auth/mod_audit_auth.lua --- /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) diff -r f4a9e804c457 -r 530d116b7f68 mod_audit_register/README.md --- /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`. diff -r f4a9e804c457 -r 530d116b7f68 mod_audit_register/mod_audit_register.lua --- /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);