diff --git a/box.js b/box.js index cf52e7ca3..d1298bf01 100755 --- a/box.js +++ b/box.js @@ -52,7 +52,7 @@ async function main() { process.on('SIGHUP', async function () { debug('Received SIGHUP. Re-reading configs.'); const conf = await settings.getDirectoryServerConfig(); - if (conf.enabled) await directoryServer.handleCertChanged(); + if (conf.enabled) await directoryServer.checkCertificate(); }); process.on('SIGINT', async function () { diff --git a/src/directoryserver.js b/src/directoryserver.js index 9317afb17..92f2e1a87 100644 --- a/src/directoryserver.js +++ b/src/directoryserver.js @@ -4,7 +4,7 @@ exports = module.exports = { start, stop, - handleCertChanged, + checkCertificate, validateConfig, applyConfig @@ -366,14 +366,14 @@ async function stop() { gServer = null; } -async function handleCertChanged() { +async function checkCertificate() { const certificate = await reverseProxy.getDirectoryServerCertificate(); if (certificate.cert === gCertificate.cert) { - debug('handleCertChanged: certificate has not changed'); + debug('checkCertificate: certificate has not changed'); return; } - debug('handleCertChanged: certificate changed. restarting'); + debug('checkCertificate: certificate changed. restarting'); await stop(); await start(); } diff --git a/src/mail.js b/src/mail.js index f03d5f8b6..7eeb22d1d 100644 --- a/src/mail.js +++ b/src/mail.js @@ -32,7 +32,7 @@ exports = module.exports = { startMail, restartMail, - handleCertChanged, + checkCertificate, getMailAuth, sendTestMail, @@ -812,15 +812,14 @@ async function restartMailIfActivated() { await restartMail(); } -async function handleCertChanged() { - debug('handleCertChanged: will restart if activated'); +async function checkCertificate() { const certificate = await reverseProxy.getMailCertificate(); const cert = safe.fs.readFileSync(`${paths.MAIL_CONFIG_DIR}/tls_cert.pem`, { encoding: 'utf8' }); if (cert === certificate.cert) { - debug('handleCertChanged: certificate has not changed'); + debug('checkCertificate: certificate has not changed'); return; } - debug('handleCertChanged: certificate has changed'); + debug('checkCertificate: certificate has changed'); await restartMailIfActivated(); } diff --git a/src/reverseproxy.js b/src/reverseproxy.js index eafa7a7a4..d8841339c 100644 --- a/src/reverseproxy.js +++ b/src/reverseproxy.js @@ -158,8 +158,7 @@ function validateCertificate(subdomain, domain, certificate) { } async function notifyCertChange() { - // let other parts of code know about any cert changes. apptask can trigger a renewal, provider can change, for example - await mail.handleCertChanged(); + await mail.checkCertificate(); await shell.promises.sudo('notifyCertChange', [ RESTART_SERVICE_CMD, 'box' ], {}); // directory server const allApps = (await apps.list()).filter(app => app.runState !== apps.RSTATE_STOPPED); for (const app of allApps) { @@ -216,7 +215,7 @@ async function setFallbackCertificate(domain, certificate) { if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, `${domain}.host.key`), certificate.key)) throw new BoxError(BoxError.FS_ERROR, safe.error.message); await reload(); - await notifyCertChange(); + await notifyCertChange(); // if domain uses fallback certs, propagate immediately } async function restoreFallbackCertificates() { @@ -285,39 +284,51 @@ async function getDirectoryServerCertificate() { return await getCertificate({ domain: settings.dashboardDomain(), fqdn: settings.dashboardFqdn(), certificate: null, type: apps.LOCATION_TYPE_DIRECTORY_SERVER }); } -// write if contents mismatch +// write if contents mismatch (thus preserving mtime) function writeFileSync(filePath, data) { assert.strictEqual(typeof filePath, 'string'); assert.strictEqual(typeof data, 'string'); const curData = safe.fs.readFileSync(filePath, { encoding: 'utf8' }); - if (curData === data) return; + if (curData === data) return false; if (!safe.fs.writeFileSync(filePath, data)) throw new BoxError(BoxError.FS_ERROR, safe.error.message); + return true; } async function setupTlsAddon(app) { assert.strictEqual(typeof app, 'object'); const certificateDir = `${paths.PLATFORM_DATA_DIR}/tls/${app.id}`; - - // clean up any certs of old locations - const filenames = safe.fs.readdirSync(certificateDir) || []; - for (const filename of filenames) { - safe.fs.unlinkSync(path.join(certificateDir, filename)); - } - + const contents = []; for (const location of getAppLocationsSync(app)) { const certificate = await getCertificate(location); - writeFileSync(`${certificateDir}/${location.fqdn}.cert`, certificate.cert); - writeFileSync(`${certificateDir}/${location.fqdn}.key`, certificate.key); + contents.push({ filename: `${location.fqdn}.cert`, data: certificate.cert }); + contents.push({ filename: `${location.fqdn}.key`, data: certificate.key }); if (location.type === apps.LOCATION_TYPE_PRIMARY) { // backward compat - writeFileSync(`${certificateDir}/tls_cert.pem`, certificate.cert); - writeFileSync(`${certificateDir}/tls_key.pem`, certificate.key); + contents.push({ filename: 'tls_cert.pem', data: certificate.cert }); + contents.push({ filename: 'tls_key.pem', data: certificate.key }); } } - await docker.restartContainer(app.id); + let changed = 0; + for (const content of contents) { + if (writeFileSync(`${certificateDir}/${content.filename}`, content.data)) ++changed; + } + debug(`setupTlsAddon: ${changed} files changed`); + + // clean up any certs of old locations + const filenamesInUse = new Set(contents.map(c => c.filename)); + const filenames = safe.fs.readdirSync(certificateDir) || []; + let removed = 0; + for (const filename of filenames) { + if (filenamesInUse.has(filename)) continue; + safe.fs.unlinkSync(path.join(certificateDir, filename)); + ++removed; + } + debug(`setupTlsAddon: ${removed} files removed`); + + if (changed || removed) await docker.restartContainer(app.id); } // writes latest certificate to disk and returns the path @@ -402,8 +413,6 @@ async function ensureCertificate(location, auditSource) { const [error] = await safe(acme2.getCertificate(fqdn, domainObject)); debug(`ensureCertificate: error: ${error ? error.message : 'null'}`); - if (!error) await notifyCertChange(); - await safe(eventlog.add(eventlog.ACTION_CERTIFICATE_NEW, auditSource, { domain: fqdn, errorMessage: error?.message || '' })); } @@ -635,6 +644,8 @@ async function checkCerts(options, auditSource, progressCallback) { await writeDashboardConfig(settings.dashboardDomain()); await notifyCertChange(); // this allows user to "rebuild" using UI just in case we crashed and went out of sync safe.fs.unlinkSync(paths.REVERSE_PROXY_REBUILD_FILE); + } else { + await notifyCertChange(); // propagate any cert changes to services } await cleanupCerts(locations, auditSource, progressCallback);