Mercurial > prosody-modules
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); |