ensureCertificate: copy certs from db to disk as needed

This commit is contained in:
Girish Ramakrishnan
2021-05-07 21:56:26 -07:00
parent 593a61f51b
commit b3a805faff

View File

@@ -265,47 +265,56 @@ function getFallbackCertificatePathSync(domain) {
return { certFilePath, keyFilePath };
}
function getAppCertificatePathSync(vhost) {
assert.strictEqual(typeof vhost, 'string');
const certFilePath = path.join(paths.NGINX_CERT_DIR, `${vhost}.user.cert`);
const keyFilePath = path.join(paths.NGINX_CERT_DIR, `${vhost}.user.key`);
return { certFilePath, keyFilePath };
}
function getAcmeCertificatePathSync(vhost, domainObject) {
assert.strictEqual(typeof vhost, 'string'); // this can contain wildcard domain (for alias domains)
assert.strictEqual(typeof domainObject, 'object');
let certName, certFilePath, keyFilePath, csrFilePath;
if (vhost !== domainObject.domain && domainObject.tlsConfig.wildcard) { // bare domain is not part of wildcard SAN
certName = domains.makeWildcard(vhost).replace('*.', '_.');
certFilePath = path.join(paths.NGINX_CERT_DIR, `${certName}.cert`);
keyFilePath = path.join(paths.NGINX_CERT_DIR, `${certName}.key`);
csrFilePath = path.join(paths.NGINX_CERT_DIR, `${certName}.csr`);
} else {
certName = vhost;
certFilePath = path.join(paths.NGINX_CERT_DIR, `${vhost}.cert`);
keyFilePath = path.join(paths.NGINX_CERT_DIR, `${vhost}.key`);
csrFilePath = path.join(paths.NGINX_CERT_DIR, `${vhost}.csr`);
}
return { certName, certFilePath, keyFilePath, csrFilePath };
}
function setAppCertificate(location, domainObject, certificate, callback) {
assert.strictEqual(typeof location, 'string');
assert.strictEqual(typeof domainObject, 'object');
assert.strictEqual(typeof certificate, 'object');
assert.strictEqual(typeof callback, 'function');
let fqdn = domains.fqdn(location, domainObject);
const fqdn = domains.fqdn(location, domainObject);
const { certFilePath, keyFilePath } = getAppCertificatePathSync(fqdn);
if (certificate.cert && certificate.key) {
if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, `${fqdn}.user.cert`), certificate.cert)) return safe.error;
if (!safe.fs.writeFileSync(path.join(paths.NGINX_CERT_DIR, `${fqdn}.user.key`), certificate.key)) return safe.error;
if (!safe.fs.writeFileSync(certFilePath, certificate.cert)) return callback(safe.error);
if (!safe.fs.writeFileSync(keyFilePath, certificate.key)) return callback(safe.error);
} else { // remove existing cert/key
if (!safe.fs.unlinkSync(path.join(paths.NGINX_CERT_DIR, `${fqdn}.user.cert`))) debug('Error removing cert: ' + safe.error.message);
if (!safe.fs.unlinkSync(path.join(paths.NGINX_CERT_DIR, `${fqdn}.user.key`))) debug('Error removing key: ' + safe.error.message);
if (!safe.fs.unlinkSync(certFilePath)) debug(`Error removing cert: ${safe.error.message}`);
if (!safe.fs.unlinkSync(keyFilePath)) debug(`Error removing key: ${safe.error.message}`);
}
reload(callback);
}
function getAcmeCertificatePath(vhost, domainObject, callback) {
assert.strictEqual(typeof vhost, 'string'); // this can contain wildcard domain (for alias domains)
assert.strictEqual(typeof domainObject, 'object');
assert.strictEqual(typeof callback, 'function');
let certFilePath, keyFilePath;
if (vhost !== domainObject.domain && domainObject.tlsConfig.wildcard) { // bare domain is not part of wildcard SAN
let certName = domains.makeWildcard(vhost).replace('*.', '_.');
certFilePath = path.join(paths.NGINX_CERT_DIR, `${certName}.cert`);
keyFilePath = path.join(paths.NGINX_CERT_DIR, `${certName}.key`);
if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) return callback(null, { certFilePath, keyFilePath });
} else {
certFilePath = path.join(paths.NGINX_CERT_DIR, `${vhost}.cert`);
keyFilePath = path.join(paths.NGINX_CERT_DIR, `${vhost}.key`);
if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) return callback(null, { certFilePath, keyFilePath });
}
callback(null);
}
function getCertificatePath(fqdn, domain, callback) {
assert.strictEqual(typeof fqdn, 'string');
assert.strictEqual(typeof domain, 'string');
@@ -318,39 +327,67 @@ function getCertificatePath(fqdn, domain, callback) {
domains.get(domain, function (error, domainObject) {
if (error) return callback(error);
// user cert always wins
let certFilePath = path.join(paths.NGINX_CERT_DIR, `${fqdn}.user.cert`);
let keyFilePath = path.join(paths.NGINX_CERT_DIR, `${fqdn}.user.key`);
if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) return callback(null, { certFilePath, keyFilePath });
const appCertPath = getAppCertificatePathSync(fqdn); // user cert always wins
if (fs.existsSync(appCertPath.certFilePath) && fs.existsSync(appCertPath.keyFilePath)) return callback(null, appCertPath);
if (domainObject.tlsConfig.provider === 'fallback') return callback(null, getFallbackCertificatePathSync(domain));
getAcmeCertificatePath(fqdn, domainObject, function (error, result) {
if (error || result) return callback(error, result);
const acmeCertPath = getAcmeCertificatePathSync(fqdn, domainObject);
if (fs.existsSync(acmeCertPath.certFilePath) && fs.existsSync(acmeCertPath.keyFilePath)) return callback(null, acmeCertPath);
return callback(null, getFallbackCertificatePathSync(domain));
});
return callback(null, getFallbackCertificatePathSync(domain));
});
}
async function checkAppCertificate(vhost, domainObject) {
assert.strictEqual(typeof vhost, 'string'); // this can contain wildcard domain (for alias domains)
assert.strictEqual(typeof domainObject, 'object');
const subdomain = vhost.substr(0, vhost.length - domainObject.domain.length - 1);
const certificate = await apps.getCertificate(subdomain, domainObject);
if (!certificate) return null;
const { certFilePath, keyFilePath } = getAppCertificatePathSync(vhost);
if (!safe.fs.writeFileSync(certFilePath, certificate.cert)) throw new BoxError(BoxError.FS_ERROR, `Failed to write certificate: ${safe.error.message}`);
if (!safe.fs.writeFileSync(keyFilePath, certificate.key)) throw new BoxError(BoxError.FS_ERROR, `Failed to write key: ${safe.error.message}`);
return { certFilePath, keyFilePath };
}
async function checkAcmeCertificate(vhost, domainObject) {
assert.strictEqual(typeof vhost, 'string'); // this can contain wildcard domain (for alias domains)
assert.strictEqual(typeof domainObject, 'object');
const { certName, certFilePath, keyFilePath, csrFilePath } = getAcmeCertificatePathSync(vhost, domainObject);
const privateKey = await blobs.get(`${blobs.CERT_PREFIX}-${certName}.key`);
const cert = await blobs.get(`${blobs.CERT_PREFIX}-${certName}.cert`);
const csr = await blobs.get(`${blobs.CERT_PREFIX}-${certName}.csr`);
if (!privateKey || !cert) return null;
if (!safe.fs.writeFileSync(keyFilePath, privateKey)) throw new BoxError(BoxError.FS_ERROR, `Failed to write private key: ${safe.error.message}`);
if (!safe.fs.writeFileSync(certFilePath, cert)) throw new BoxError(BoxError.FS_ERROR, `Failed to write certificate: ${safe.error.message}`);
if (csr) safe.fs.writeFileSync(csrFilePath, csr);
return { certFilePath, keyFilePath };
}
function ensureCertificate(vhost, domain, auditSource, callback) {
assert.strictEqual(typeof vhost, 'string');
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof auditSource, 'object');
assert.strictEqual(typeof callback, 'function');
domains.get(domain, function (error, domainObject) {
domains.get(domain, async function (error, domainObject) {
if (error) return callback(error);
// user cert always wins
let certFilePath = path.join(paths.NGINX_CERT_DIR, `${vhost}.user.cert`);
let keyFilePath = path.join(paths.NGINX_CERT_DIR, `${vhost}.user.key`);
if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) {
debug(`ensureCertificate: ${vhost} will use custom app certs`);
return callback(null, { certFilePath, keyFilePath }, { renewed: false });
}
let bundle;
[error, bundle] = await safe(checkAppCertificate(vhost, domainObject));
if (error) return callback(error);
if (bundle) return callback(null, bundle, { renewed: false });
if (domainObject.tlsConfig.provider === 'fallback') {
debug(`ensureCertificate: ${vhost} will use fallback certs`);
@@ -358,37 +395,36 @@ function ensureCertificate(vhost, domain, auditSource, callback) {
return callback(null, getFallbackCertificatePathSync(domain), { renewed: false });
}
getAcmeApi(domainObject, function (error, acmeApi, apiOptions) {
getAcmeApi(domainObject, async function (error, acmeApi, apiOptions) {
if (error) return callback(error);
getAcmeCertificatePath(vhost, domainObject, function (_error, currentBundle) {
if (currentBundle) {
debug(`ensureCertificate: ${vhost} certificate already exists at ${currentBundle.keyFilePath}`);
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 });
debug(`ensureCertificate: ${vhost} cert requires renewal`);
} else {
debug(`ensureCertificate: ${vhost} cert does not exist`);
if (!isExpiringSync(currentBundle.certFilePath, 24 * 30) && 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`);
}
debug('ensureCertificate: getting certificate for %s with options %j', vhost, _.omit(apiOptions, 'accountKeyPem'));
acmeApi.getCertificate(vhost, domain, apiOptions, function (error, certFilePath, keyFilePath) {
debug(`ensureCertificate: error: ${error ? error.message : 'null'} cert: ${certFilePath || 'null'}`);
eventlog.add(currentBundle ? eventlog.ACTION_CERTIFICATE_RENEWAL : eventlog.ACTION_CERTIFICATE_NEW, auditSource, { domain: vhost, errorMessage: error ? error.message : '' });
if (error && currentBundle && !isExpiringSync(currentBundle.certFilePath, 0)) {
debug('ensureCertificate: continue using existing bundle since renewal failed');
return callback(null, currentBundle, { renewed: false });
}
debug('ensureCertificate: getting certificate for %s with options %j', vhost, _.omit(apiOptions, 'accountKeyPem'));
if (certFilePath && keyFilePath) return callback(null, { certFilePath, keyFilePath }, { renewed: true });
acmeApi.getCertificate(vhost, domain, apiOptions, function (error, certFilePath, keyFilePath) {
debug(`ensureCertificate: error: ${error ? error.message : 'null'} cert: ${certFilePath || 'null'}`);
debug(`ensureCertificate: renewal of ${vhost} failed. using fallback certificates for ${domain}`);
eventlog.add(currentBundle ? eventlog.ACTION_CERTIFICATE_RENEWAL : eventlog.ACTION_CERTIFICATE_NEW, auditSource, { domain: vhost, errorMessage: error ? error.message : '' });
if (error && currentBundle && !isExpiringSync(currentBundle.certFilePath, 0)) {
debug('ensureCertificate: continue using existing bundle since renewal failed');
return callback(null, currentBundle, { renewed: false });
}
if (certFilePath && keyFilePath) return callback(null, { certFilePath, keyFilePath }, { renewed: true });
debug(`ensureCertificate: renewal of ${vhost} failed. using fallback certificates for ${domain}`);
callback(null, getFallbackCertificatePathSync(domain), { renewed: false });
});
callback(null, getFallbackCertificatePathSync(domain), { renewed: false });
});
});
});