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); |
