ensureCertificate: copy certs from db to disk as needed
This commit is contained in:
@@ -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 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user