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