comparison mod_checkcerts/mod_checkcerts.lua @ 1098:cbbeac61f1ab

mod_checkcerts: Add timestamp parsing, format time until expiry more human-readable, adjust check intervals to time left.
author Kim Alvefur <zash@zash.se>
date Sun, 30 Jun 2013 20:14:28 +0200
parents a8203db13ca2
children d9fcf9d8e787
comparison
equal deleted inserted replaced
1097:608d9019b0de 1098:cbbeac61f1ab
1 local ssl = require"ssl"; 1 local ssl = require"ssl";
2 local load_cert = ssl.x509 and ssl.x509.load 2 local datetime_parse = require"util.datetime".parse;
3 or ssl.cert_from_pem; -- COMPAT mw/luasec-hg 3 local load_cert = ssl.x509 and ssl.x509.load;
4 local st = require"util.stanza" 4 local st = require"util.stanza"
5
6 -- These are in days.
7 local nag_time = module:get_option_number("checkcerts_notify", 7) * 86400;
5 8
6 if not load_cert then 9 if not load_cert then
7 module:log("error", "This version of LuaSec (%s) does not support certificate checking", ssl._VERSION); 10 module:log("error", "This version of LuaSec (%s) does not support certificate checking", ssl._VERSION);
8 return 11 return
9 end 12 end
10 13
11 local last_check = 0; 14 local pat = "^([JFMAONSD][ceupao][glptbvyncr]) ?(%d%d?) (%d%d):(%d%d):(%d%d) (%d%d%d%d) GMT$";
15 local months = {Jan=1,Feb=2,Mar=3,Apr=4,May=5,Jun=6,Jul=7,Aug=8,Sep=9,Oct=10,Nov=11,Dec=12};
16 local function parse_x509_datetime(s)
17 local month, day, hour, min, sec, year = s:match(pat); month = months[month];
18 return datetime_parse(("%04d-%02d-%02dT%02d:%02d:%02dZ"):format(year, month, day, hour, min, sec));
19 end
20
21 local timeunits = {"minute",60,"hour",3600,"day",86400,"week",604800,"month",2629746,"year",31556952,};
22 local function humantime(timediff)
23 local ret = {};
24 for i=#timeunits,2,-2 do
25 if timeunits[i] < timediff then
26 local n = math.floor(timediff / timeunits[i]);
27 if n > 0 and #ret < 2 then
28 ret[#ret+1] = ("%d %s%s"):format(n, timeunits[i-1], n ~= 1 and "s" or "");
29 timediff = timediff - n*timeunits[i];
30 end
31 end
32 end
33 return table.concat(ret, " and ")
34 end
12 35
13 local function check_certs_validity() 36 local function check_certs_validity()
14 local now = os.time(); 37 local now = os.time();
15 38
16 if last_check > now - 21600 then
17 return
18 else
19 last_check = now;
20 end
21 -- First, let's find out what certificate this host uses. 39 -- First, let's find out what certificate this host uses.
22 local ssl_config = config.rawget(module.host, "core", "ssl"); 40 local ssl_config = config.rawget(module.host, "ssl");
23 if not ssl_config then 41 if not ssl_config then
24 local base_host = module.host:match("%.(.*)"); 42 local base_host = module.host:match("%.(.*)");
25 ssl_config = config.get(base_host, "core", "ssl"); 43 ssl_config = config.get(base_host, "ssl");
26 end 44 end
27 45
28 if ssl_config.certificate then 46 if ssl_config and ssl_config.certificate then
29 local certfile = ssl_config.certificate; 47 local certfile = ssl_config.certificate;
30 local cert;
31
32 local fh = io.open(certfile); -- Load the file. 48 local fh = io.open(certfile); -- Load the file.
33 cert = fh and fh:read"*a"; 49 cert = fh and fh:read"*a";
34 fh:close(); 50 fh = fh and fh:close();
35 cert = cert and load_cert(cert); -- And parse 51 local cert = cert and load_cert(cert); -- And parse
36 if not cert then return end
37 -- No error reporting, certmanager should complain already
38 52
39 local valid_at = cert.valid_at or cert.validat; 53 if not cert then
40 if not valid_at then return end -- Broken or uncommon LuaSec version? 54 module:log("warn", "No certificate configured for this host, please fix this and reload this module to check expiry");
41 55 return
42 -- This might be wrong if the certificate has NotBefore in the future. 56 end
43 -- However this is unlikely to happen with CA-issued certs in the wild. 57 local expires_at = parse_x509_datetime(cert:notafter());
44 local notafter = cert.notafter and cert:notafter(); 58 local expires_in = os.difftime(expires_at, now);
45 if not valid_at(cert, now) then 59 local fmt = "Certificate %s expires in %s"
46 module:log("error", "The certificate %s has expired", certfile); 60 local nag_admin = expires_in < nag_time;
47 module:send(st.message({from=module.host,to=admin,type="chat"},("Certificate for host %s has expired!"):format(module.host))); 61 local log_warn = expires_in < nag_time * 2;
48 elseif not valid_at(cert, now+86400*7) then 62 local timediff = expires_in;
49 module:log("warn", "The certificate %s will expire %s", certfile, notafter or "this week"); 63 if expires_in < 0 then
64 fmt = "Certificate %s expired %s ago";
65 timediff = -timediff;
66 end
67 timediff = humantime(timediff);
68 module:log(log_warn and "warn" or "info", fmt, certfile, timediff);
69 if nag_admin then
70 local body = fmt:format("for host ".. module.host, timediff);
50 for _,admin in ipairs(module:get_option_array("admins", {})) do 71 for _,admin in ipairs(module:get_option_array("admins", {})) do
51 module:send(st.message({from=module.host,to=admin,type="chat"},("Certificate for host %s will expire %s!"):format(module.host, notafter or "this week"))); 72 module:send(st.message({ from = module.host, to = admin, type = "chat" }, body));
52 end 73 end
53 elseif not valid_at(cert, now+86400*30) then
54 module:log("warn", "The certificate %s will expire later this month", certfile);
55 else
56 module:log("info", "The certificate %s is valid until %s", certfile, notafter or "later");
57 end 74 end
75 return math.max(86400, expires_in / 3);
58 end 76 end
59 end 77 end
60 78
61 module:hook_global("config-reloaded", check_certs_validity); 79 module:add_timer(1, check_certs_validity);
62 module:add_timer(1, function()
63 check_certs_validity();
64 return math.random(14400, 86400);
65 end);