diff --git a/src/notifications.js b/src/notifications.js index ae9fa4414..b9a66e103 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -247,6 +247,7 @@ async function onEvent(id, action, source, data) { case eventlog.ACTION_CERTIFICATE_RENEWAL: case eventlog.ACTION_CERTIFICATE_NEW: if (!data.errorMessage) return; + if (!data.notAfter || (data.notAfter - new Date() >= (60 * 60 * 24 * 10 * 1000))) return; // more than 10 days left to expire return await certificateRenewalError(id, data.domain, data.errorMessage); case eventlog.ACTION_BACKUP_FINISH: diff --git a/src/reverseproxy.js b/src/reverseproxy.js index e2bac7f5d..591fce36e 100644 --- a/src/reverseproxy.js +++ b/src/reverseproxy.js @@ -96,19 +96,18 @@ function getAcmeApi(domainObject, callback) { }); } -function isExpiringSync(certFilePath, hours) { +function getExpiryDate(certFilePath) { assert.strictEqual(typeof certFilePath, 'string'); - assert.strictEqual(typeof hours, 'number'); - if (!fs.existsSync(certFilePath)) return 2; // not found + if (!fs.existsSync(certFilePath)) return null; // not found - const result = safe.child_process.spawnSync('/usr/bin/openssl', [ 'x509', '-checkend', String(60 * 60 * hours), '-in', certFilePath ]); + const result = safe.child_process.spawnSync('/usr/bin/openssl', [ 'x509', '-enddate', '-noout', certFilePath ]); + if (!result) return null; // some error - if (!result) return 3; // some error + const notAfter = result.stdout.toString('utf8').trim().split('=')[1]; + debug(`expiryDate: ${certFilePath} notAfter=${notAfter}`); - debug('isExpiringSync: %s %s %s', certFilePath, result.stdout.toString('utf8').trim(), result.status); - - return result.status === 1; // 1 - expired 0 - not expired + return new Date(notAfter); } // We used to check for the must-staple in the cert using openssl x509 -text -noout -in ${certFilePath} | grep -q status_request @@ -417,12 +416,14 @@ function ensureCertificate(vhost, domain, auditSource, callback) { getAcmeApi(domainObject, async function (error, acmeApi, apiOptions) { if (error) return callback(error); + let notAfter = null; const [, currentBundle] = await safe(checkAcmeCertificate(vhost, domainObject)); if (currentBundle) { debug(`ensureCertificate: ${vhost} certificate already exists at ${currentBundle.keyFilePath}`); - - if (!isExpiringSync(currentBundle.certFilePath, 24 * 30) && providerMatchesSync(domainObject, currentBundle.certFilePath, apiOptions)) return callback(null, currentBundle, { renewed: false }); + notAfter = getExpiryDate(currentBundle.certFilePath); + const isExpiring = (notAfter - new Date()) <= (60 * 60 * 24 * 30 * 1000); // expiring in a month + if (!isExpiring && providerMatchesSync(domainObject, currentBundle.certFilePath, apiOptions)) return callback(null, currentBundle, { renewed: false }); debug(`ensureCertificate: ${vhost} cert requires renewal`); } else { debug(`ensureCertificate: ${vhost} cert does not exist`); @@ -434,9 +435,9 @@ function ensureCertificate(vhost, domain, auditSource, callback) { acmeApi.getCertificate(vhost, domain, acmePaths, apiOptions, async function (error) { debug(`ensureCertificate: error: ${error ? error.message : 'null'} cert: ${acmePaths.certFilePath || 'null'}`); - eventlog.add(currentBundle ? eventlog.ACTION_CERTIFICATE_RENEWAL : eventlog.ACTION_CERTIFICATE_NEW, auditSource, { domain: vhost, errorMessage: error ? error.message : '' }); + eventlog.add(currentBundle ? eventlog.ACTION_CERTIFICATE_RENEWAL : eventlog.ACTION_CERTIFICATE_NEW, auditSource, { domain: vhost, errorMessage: error ? error.message : '', notAfter }); - if (error && currentBundle && !isExpiringSync(currentBundle.certFilePath, 0)) { + if (error && currentBundle && (notAfter - new Date() > 0)) { // still some life left in this certificate debug('ensureCertificate: continue using existing bundle since renewal failed'); return callback(null, currentBundle, { renewed: false }); } @@ -749,10 +750,14 @@ function renewCerts(options, auditSource, progressCallback, callback) { async function cleanupCerts() { const filenames = await fs.promises.readdir(paths.NGINX_CERT_DIR); const certFilenames = filenames.filter(f => f.endsWith('.cert')); + const now = new Date(); for (const certFilename of certFilenames) { const certFilePath = path.join(paths.NGINX_CERT_DIR, certFilename); - if (isExpiringSync(certFilePath, - 24 * 30 * 6)) { // expired 6 months ago + const notAfter = getExpiryDate(certFilePath); + if (!notAfter) continue; // some error + + if (now - notAfter >= (60 * 60 * 24 * 30 * 6 * 1000)) { // expired 6 months ago const fqdn = certFilename.replace(/\.cert$/, ''); debug(`cleanupCerts: deleting certs of ${fqdn}`);