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 |