Mercurial > prosody-modules
comparison mod_audit/mod_audit.lua @ 5331:e00e3e2c72a3
mod_audit: Add expiration of entries, and handling of full archive stores
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Fri, 07 Apr 2023 15:21:54 +0100 |
| parents | 7e3862a26e55 |
| children | c35f3c1762b5 |
comparison
equal
deleted
inserted
replaced
| 5330:071d05b13a06 | 5331:e00e3e2c72a3 |
|---|---|
| 1 module:set_global(); | 1 module:set_global(); |
| 2 | 2 |
| 3 local audit_log_limit = module:get_option_number("audit_log_limit", 10000); | 3 local time_now = os.time; |
| 4 local cleanup_after = module:get_option_string("audit_log_expires_after", "2w"); | 4 local parse_duration = require "util.human.io".parse_duration; |
| 5 local ip = require "util.ip"; | |
| 6 local st = require "util.stanza"; | |
| 7 local moduleapi = require "core.moduleapi"; | |
| 8 | |
| 9 local host_wide_user = "@"; | |
| 10 | |
| 11 local cleanup_after = module:get_option_string("audit_log_expires_after", "28d"); | |
| 12 if cleanup_after == "never" then | |
| 13 cleanup_after = nil; | |
| 14 else | |
| 15 cleanup_after = parse_duration(cleanup_after); | |
| 16 end | |
| 5 | 17 |
| 6 local attach_ips = module:get_option_boolean("audit_log_ips", true); | 18 local attach_ips = module:get_option_boolean("audit_log_ips", true); |
| 7 local attach_ipv4_prefix = module:get_option_number("audit_log_ipv4_prefix", nil); | 19 local attach_ipv4_prefix = module:get_option_number("audit_log_ipv4_prefix", nil); |
| 8 local attach_ipv6_prefix = module:get_option_number("audit_log_ipv6_prefix", nil); | 20 local attach_ipv6_prefix = module:get_option_number("audit_log_ipv6_prefix", nil); |
| 9 | 21 |
| 14 if have_geoip and attach_location then | 26 if have_geoip and attach_location then |
| 15 geoip4_country = geoip.open(module:get_option_string("geoip_ipv4_country", "/usr/share/GeoIP/GeoIP.dat")); | 27 geoip4_country = geoip.open(module:get_option_string("geoip_ipv4_country", "/usr/share/GeoIP/GeoIP.dat")); |
| 16 geoip6_country = geoip.open(module:get_option_string("geoip_ipv6_country", "/usr/share/GeoIP/GeoIPv6.dat")); | 28 geoip6_country = geoip.open(module:get_option_string("geoip_ipv6_country", "/usr/share/GeoIP/GeoIPv6.dat")); |
| 17 end | 29 end |
| 18 | 30 |
| 19 local time_now = os.time; | |
| 20 local ip = require "util.ip"; | |
| 21 local st = require "util.stanza"; | |
| 22 local moduleapi = require "core.moduleapi"; | |
| 23 | |
| 24 local host_wide_user = "@"; | |
| 25 | 31 |
| 26 local stores = {}; | 32 local stores = {}; |
| 27 | 33 |
| 28 local function get_store(self, host) | 34 local function get_store(self, host) |
| 29 local store = rawget(self, host); | 35 local store = rawget(self, host); |
| 34 rawset(self, host, store); | 40 rawset(self, host, store); |
| 35 return store; | 41 return store; |
| 36 end | 42 end |
| 37 | 43 |
| 38 setmetatable(stores, { __index = get_store }); | 44 setmetatable(stores, { __index = get_store }); |
| 45 | |
| 46 local function prune_audit_log(host) | |
| 47 local before = os.time() - cleanup_after; | |
| 48 module:context(host):log("debug", "Pruning audit log for entries older than %s", os.date("%Y-%m-%d %R:%S", before)); | |
| 49 local ok, err = stores[host]:delete(nil, { ["end"] = before }); | |
| 50 if not ok then | |
| 51 module:context(host):log("error", "Unable to prune audit log: %s", err); | |
| 52 return; | |
| 53 end | |
| 54 local sum = tonumber(ok); | |
| 55 if sum then | |
| 56 module:context(host):log("debug", "Pruned %d expired audit log entries", sum); | |
| 57 return sum > 0; | |
| 58 end | |
| 59 module:context(host):log("debug", "Pruned expired audit log entries"); | |
| 60 return true; | |
| 61 end | |
| 39 | 62 |
| 40 local function get_ip_network(ip_addr) | 63 local function get_ip_network(ip_addr) |
| 41 local _ip = ip.new_ip(ip_addr); | 64 local _ip = ip.new_ip(ip_addr); |
| 42 local proto = _ip.proto; | 65 local proto = _ip.proto; |
| 43 local network; | 66 local network; |
| 109 stanza:add_child(child); | 132 stanza:add_child(child); |
| 110 end | 133 end |
| 111 end | 134 end |
| 112 end | 135 end |
| 113 | 136 |
| 114 local id, err = stores[host]:append(nil, nil, stanza, extra and extra.timestamp or time_now(), user_key); | 137 local store = stores[host]; |
| 115 if err then | 138 local id, err = store:append(nil, nil, stanza, extra and extra.timestamp or time_now(), user_key); |
| 116 module:log("error", "failed to persist audit event: %s", err); | 139 if not id then |
| 117 return | 140 if err == "quota-limit" then |
| 141 local limit = store.caps and store.caps.quota or 1000; | |
| 142 local truncate_to = math.floor(limit * 0.99); | |
| 143 if type(cleanup_after) == "number" then | |
| 144 module:log("debug", "Audit log has reached quota - forcing prune"); | |
| 145 if prune_audit_log(host) then | |
| 146 -- Retry append | |
| 147 id, err = store:append(nil, nil, stanza, extra and extra.timestamp or time_now(), user_key); | |
| 148 end | |
| 149 end | |
| 150 if not id and (store.caps and store.caps.truncate) then | |
| 151 module:log("debug", "Audit log has reached quota - truncating"); | |
| 152 local truncated = store:delete(nil, { | |
| 153 truncate = truncate_to; | |
| 154 }); | |
| 155 if truncated then | |
| 156 -- Retry append | |
| 157 id, err = store:append(nil, nil, stanza, extra and extra.timestamp or time_now(), user_key); | |
| 158 end | |
| 159 end | |
| 160 end | |
| 161 if not id then | |
| 162 module:log("error", "Failed to persist audit event: %s", err); | |
| 163 return; | |
| 164 end | |
| 118 else | 165 else |
| 119 module:log("debug", "persisted audit event %s as %s", stanza:top_tag(), id); | 166 module:log("debug", "Persisted audit event %s as %s", stanza:top_tag(), id); |
| 120 end | 167 end |
| 121 end | 168 end |
| 122 | 169 |
| 123 function moduleapi.audit(module, user, event_type, extra) | 170 function moduleapi.audit(module, user, event_type, extra) |
| 124 audit(module.host, user, "mod_" .. module:get_name(), event_type, extra); | 171 audit(module.host, user, "mod_" .. module:get_name(), event_type, extra); |
| 125 end | 172 end |
| 126 | 173 |
| 127 function module.command(_arg) | 174 function module.command(_arg) |
| 128 local jid = require "util.jid"; | 175 local jid = require "util.jid"; |
| 129 local arg = require "util.argparse".parse(_arg, { value_params = { "limit" } }); | 176 local arg = require "util.argparse".parse(_arg, { |
| 177 value_params = { "limit" }; | |
| 178 }); | |
| 179 | |
| 180 for k, v in pairs(arg) do print("U", k, v) end | |
| 130 local query_user, host = jid.prepped_split(arg[1]); | 181 local query_user, host = jid.prepped_split(arg[1]); |
| 182 | |
| 183 if arg.prune then | |
| 184 local sm = require "core.storagemanager"; | |
| 185 if host then | |
| 186 sm.initialize_host(host); | |
| 187 prune_audit_log(host); | |
| 188 else | |
| 189 for _host in pairs(prosody.hosts) do | |
| 190 sm.initialize_host(_host); | |
| 191 prune_audit_log(_host); | |
| 192 end | |
| 193 end | |
| 194 return; | |
| 195 end | |
| 196 | |
| 131 if not host then | 197 if not host then |
| 132 print("EE: Please supply the host for which you want to show events"); | 198 print("EE: Please supply the host for which you want to show events"); |
| 133 return 1; | 199 return 1; |
| 134 elseif not prosody.hosts[host] then | 200 elseif not prosody.hosts[host] then |
| 135 print("EE: Unknown host: "..host); | 201 print("EE: Unknown host: "..host); |
| 211 end | 277 end |
| 212 end | 278 end |
| 213 print(string.rep("-", width)); | 279 print(string.rep("-", width)); |
| 214 print(("%d records displayed"):format(c)); | 280 print(("%d records displayed"):format(c)); |
| 215 end | 281 end |
| 282 | |
| 283 function module.add_host(host_module) | |
| 284 host_module:depends("cron"); | |
| 285 host_module:daily("Prune audit logs", function () | |
| 286 prune_audit_log(host_module.host); | |
| 287 end); | |
| 288 end |
