Fix certificate ordering logic

* app certs set by user are always preferred
* If fallback, choose fallback certs. ignore others
* If LE, try to pick LE certs. Otherwise, provider fallback.

Fixes #724
This commit is contained in:
Girish Ramakrishnan
2020-08-07 22:59:57 -07:00
parent 60b3fceea6
commit 1f1c94de70
5 changed files with 41 additions and 70 deletions

View File

@@ -27,7 +27,7 @@ exports = module.exports = {
removeAppConfigs: removeAppConfigs,
// exported for testing
_getCertApi: getCertApi
_getAcmeApi: getAcmeApi
};
var acme2 = require('./cert/acme2.js'),
@@ -41,7 +41,6 @@ var acme2 = require('./cert/acme2.js'),
domains = require('./domains.js'),
ejs = require('ejs'),
eventlog = require('./eventlog.js'),
fallback = require('./cert/fallback.js'),
fs = require('fs'),
mail = require('./mail.js'),
os = require('os'),
@@ -58,15 +57,13 @@ var acme2 = require('./cert/acme2.js'),
var NGINX_APPCONFIG_EJS = fs.readFileSync(__dirname + '/nginxconfig.ejs', { encoding: 'utf8' }),
RELOAD_NGINX_CMD = path.join(__dirname, 'scripts/reloadnginx.sh');
function getCertApi(domainObject, callback) {
function getAcmeApi(domainObject, callback) {
assert.strictEqual(typeof domainObject, 'object');
assert.strictEqual(typeof callback, 'function');
if (domainObject.tlsConfig.provider === 'fallback') return callback(null, fallback, { fallback: true });
const api = acme2;
var api = acme2;
var options = { prod: false, performHttpAuthorization: false, wildcard: false, email: '' };
let options = { prod: false, performHttpAuthorization: false, wildcard: false, email: '' };
options.prod = domainObject.tlsConfig.provider.match(/.*-prod/) !== null; // matches 'le-prod' or 'letsencrypt-prod'
options.performHttpAuthorization = domainObject.provider.match(/noop|manual|wildcard/) !== null;
options.wildcard = !!domainObject.tlsConfig.wildcard;
@@ -105,8 +102,6 @@ function providerMatchesSync(domainObject, certFilePath, apiOptions) {
if (!fs.existsSync(certFilePath)) return false; // not found
if (apiOptions.fallback) return certFilePath.includes('.host.cert');
const subjectAndIssuer = safe.child_process.execSync(`/usr/bin/openssl x509 -noout -subject -issuer -in "${certFilePath}"`, { encoding: 'utf8' });
if (!subjectAndIssuer) return false; // something bad happenned
@@ -251,15 +246,12 @@ function setAppCertificateSync(location, domainObject, certificate) {
return null;
}
function getCertificateByHostname(hostname, domainObject, callback) {
function getAcmeCertificate(hostname, domainObject, callback) {
assert.strictEqual(typeof hostname, 'string');
assert.strictEqual(typeof domainObject, 'object');
assert.strictEqual(typeof callback, 'function');
let certFilePath = path.join(paths.APP_CERTS_DIR, `${hostname}.user.cert`);
let keyFilePath = path.join(paths.APP_CERTS_DIR, `${hostname}.user.key`);
if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) return callback(null, { certFilePath, keyFilePath });
let certFilePath, keyFilePath;
if (hostname !== domainObject.domain && domainObject.tlsConfig.wildcard) { // bare domain is not part of wildcard SAN
let certName = domains.makeWildcard(hostname).replace('*.', '_.');
@@ -282,10 +274,22 @@ function getCertificate(fqdn, domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
// 1. user cert always wins
// 2. if using fallback provider, return that cert
// 3. look for LE certs
domains.get(domain, function (error, domainObject) {
if (error) return callback(error);
getCertificateByHostname(fqdn, domainObject, function (error, result) {
// user cert always wins
let certFilePath = path.join(paths.APP_CERTS_DIR, `${fqdn}.user.cert`);
let keyFilePath = path.join(paths.APP_CERTS_DIR, `${fqdn}.user.key`);
if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) return callback(null, { certFilePath, keyFilePath });
if (domainObject.tlsConfig.provider === 'fallback') return getFallbackCertificate(domain, callback);
getAcmeCertificate(fqdn, domainObject, function (error, result) {
if (error || result) return callback(error, result);
return getFallbackCertificate(domain, callback);
@@ -313,14 +317,27 @@ function ensureCertificate(vhost, domain, auditSource, callback) {
domains.get(domain, function (error, domainObject) {
if (error) return callback(error);
getCertApi(domainObject, function (error, api, apiOptions) {
// user cert always wins
let certFilePath = path.join(paths.APP_CERTS_DIR, `${vhost}.user.cert`);
let keyFilePath = path.join(paths.APP_CERTS_DIR, `${vhost}.user.key`);
if (fs.existsSync(certFilePath) && fs.existsSync(keyFilePath)) return callback(null, { certFilePath, keyFilePath }, { renewed: false });
if (domainObject.tlsConfig.provider === 'fallback') {
return getFallbackCertificate(domain, function (error, bundle) {
if (error) return callback(error);
callback(null, bundle, { renewed: false });
});
}
getAcmeApi(domainObject, function (error, acmeApi, apiOptions) {
if (error) return callback(error);
getCertificateByHostname(vhost, domainObject, function (_error, currentBundle) {
getAcmeCertificate(vhost, domainObject, function (_error, currentBundle) {
if (currentBundle) {
debug(`ensureCertificate: ${vhost} certificate already exists at ${currentBundle.keyFilePath}`);
if (currentBundle.certFilePath.endsWith('.user.cert')) return callback(null, currentBundle, { renewed: false }); // user certs cannot be renewed
if (!isExpiringSync(currentBundle.certFilePath, 24 * 30) && providerMatchesSync(domainObject, currentBundle.certFilePath, apiOptions)) return callback(null, currentBundle, { renewed: false });
debug(`ensureCertificate: ${vhost} cert require renewal`);
} else {
@@ -329,7 +346,7 @@ function ensureCertificate(vhost, domain, auditSource, callback) {
debug('ensureCertificate: getting certificate for %s with options %j', vhost, apiOptions);
api.getCertificate(vhost, domain, apiOptions, function (error, certFilePath, keyFilePath) {
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 : '' });
@@ -346,7 +363,6 @@ function ensureCertificate(vhost, domain, auditSource, callback) {
debug(`ensureCertificate: renewal of ${vhost} failed. using fallback certificates for ${domain}`);
// if no cert was returned use fallback. the fallback provider will not provide any for example
getFallbackCertificate(domain, function (error, bundle) {
if (error) return callback(error);