reverseproxy: use async exec
This commit is contained in:
@@ -60,7 +60,6 @@ const acme2 = require('./acme2.js'),
|
||||
settings = require('./settings.js'),
|
||||
shell = require('./shell.js'),
|
||||
tasks = require('./tasks.js'),
|
||||
util = require('util'),
|
||||
validator = require('validator');
|
||||
|
||||
const NGINX_APPCONFIG_EJS = fs.readFileSync(__dirname + '/nginxconfig.ejs', { encoding: 'utf8' });
|
||||
@@ -73,13 +72,13 @@ function nginxLocation(s) {
|
||||
return `~ ^(?!(${re.slice(1)}))`; // negative regex assertion - https://stackoverflow.com/questions/16302897/nginx-location-not-equal-to-regex
|
||||
}
|
||||
|
||||
function getCertificateDatesSync(cert) {
|
||||
async function getCertificateDates(cert) {
|
||||
assert.strictEqual(typeof cert, 'string');
|
||||
|
||||
const result = safe.child_process.spawnSync('/usr/bin/openssl', [ 'x509', '-startdate', '-enddate', '-subject', '-noout' ], { input: cert, encoding: 'utf8' });
|
||||
if (!result) return { startDate: null, endDate: null } ; // some error
|
||||
const [error, result] = await safe(shell.promises.exec('getCertificateDates', 'openssl x509 -startdate -enddate -subject -noout', { input: cert, encoding: 'utf8' }));
|
||||
if (error) return { startDate: null, endDate: null } ; // some error
|
||||
|
||||
const lines = result.stdout.trim().split('\n');
|
||||
const lines = result.trim().split('\n');
|
||||
const notBefore = lines[0].split('=')[1];
|
||||
const notBeforeDate = new Date(notBefore);
|
||||
|
||||
@@ -104,17 +103,17 @@ async function isOcspEnabled(certFilePath) {
|
||||
|
||||
// We used to check for the must-staple in the cert using openssl x509 -text -noout -in ${certFilePath} | grep -q status_request
|
||||
// however, we cannot set the must-staple because first request to nginx fails because of it's OCSP caching behavior
|
||||
const result = safe.child_process.execSync(`openssl x509 -in ${certFilePath} -noout -ocsp_uri`, { encoding: 'utf8' });
|
||||
return result && result.length > 0; // no error and has uri
|
||||
const [error, result] = await safe(shell.promises.exec('isOscpEnabled', `openssl x509 -in ${certFilePath} -noout -ocsp_uri`, { encoding: 'utf8' }));
|
||||
return !error && result.length > 0; // no error and has uri
|
||||
}
|
||||
|
||||
// checks if the certificate matches the options provided by user (like wildcard, le-staging etc)
|
||||
function providerMatchesSync(domainObject, cert) {
|
||||
async function providerMatches(domainObject, cert) {
|
||||
assert.strictEqual(typeof domainObject, 'object');
|
||||
assert.strictEqual(typeof cert, 'string');
|
||||
|
||||
const subjectAndIssuer = safe.child_process.execSync('/usr/bin/openssl x509 -noout -subject -issuer', { encoding: 'utf8', input: cert });
|
||||
if (!subjectAndIssuer) return false; // something bad happenned
|
||||
const [error, subjectAndIssuer] = await safe(shell.promises.exec('providerMatches', 'openssl x509 -noout -subject -issuer', { encoding: 'utf8', input: cert }));
|
||||
if (error) return false; // something bad happenned
|
||||
|
||||
const subject = subjectAndIssuer.match(/^subject=(.*)$/m)[1];
|
||||
const domain = subject.substr(subject.indexOf('=') + 1).trim(); // subject can be /CN=, CN=, CN = and other forms
|
||||
@@ -131,7 +130,7 @@ function providerMatchesSync(domainObject, cert) {
|
||||
|
||||
const mismatch = issuerMismatch || wildcardMismatch;
|
||||
|
||||
debug(`providerMatchesSync: subject=${subject} domain=${domain} issuer=${issuer} `
|
||||
debug(`providerMatches: subject=${subject} domain=${domain} issuer=${issuer} `
|
||||
+ `wildcard=${isWildcardCert}/${wildcard} prod=${isLetsEncryptProd}/${prod} `
|
||||
+ `issuerMismatch=${issuerMismatch} wildcardMismatch=${wildcardMismatch} match=${!mismatch}`);
|
||||
|
||||
@@ -140,7 +139,7 @@ function providerMatchesSync(domainObject, cert) {
|
||||
|
||||
// note: https://tools.ietf.org/html/rfc4346#section-7.4.2 (certificate_list) requires that the
|
||||
// servers certificate appears first (and not the intermediate cert)
|
||||
function validateCertificate(subdomain, domain, certificate) {
|
||||
async function validateCertificate(subdomain, domain, certificate) {
|
||||
assert.strictEqual(typeof subdomain, 'string');
|
||||
assert.strictEqual(typeof domain, 'string');
|
||||
assert(certificate && typeof certificate, 'object');
|
||||
@@ -148,29 +147,28 @@ function validateCertificate(subdomain, domain, certificate) {
|
||||
const { cert, key } = certificate;
|
||||
|
||||
// check for empty cert and key strings
|
||||
if (!cert && key) return new BoxError(BoxError.BAD_FIELD, 'missing cert');
|
||||
if (cert && !key) return new BoxError(BoxError.BAD_FIELD, 'missing key');
|
||||
if (!cert && key) throw new BoxError(BoxError.BAD_FIELD, 'missing cert');
|
||||
if (cert && !key) throw new BoxError(BoxError.BAD_FIELD, 'missing key');
|
||||
|
||||
// -checkhost checks for SAN or CN exclusively. SAN takes precedence and if present, ignores the CN.
|
||||
const fqdn = dns.fqdn(subdomain, domain);
|
||||
|
||||
let result = safe.child_process.execSync(`openssl x509 -noout -checkhost "${fqdn}"`, { encoding: 'utf8', input: cert });
|
||||
if (result === null) return new BoxError(BoxError.BAD_FIELD, 'Unable to get certificate subject:' + safe.error.message);
|
||||
|
||||
if (result.indexOf('does match certificate') === -1) return new BoxError(BoxError.BAD_FIELD, `Certificate is not valid for this domain. Expecting ${fqdn}`);
|
||||
const [checkHostError, checkHostOutput] = await safe(shell.promises.exec('validateCertificate', `openssl x509 -noout -checkhost "${fqdn}"`, { encoding: 'utf8', input: cert }));
|
||||
console.log(checkHostError, checkHostOutput);
|
||||
if (checkHostError) throw new BoxError(BoxError.BAD_FIELD, 'Could not validate certificate');
|
||||
if (checkHostOutput.indexOf('does match certificate') === -1) throw new BoxError(BoxError.BAD_FIELD, `Certificate is not valid for this domain. Expecting ${fqdn}`);
|
||||
|
||||
// check if public key in the cert and private key matches. pkey below works for RSA and ECDSA keys
|
||||
const pubKeyFromCert = safe.child_process.execSync('openssl x509 -noout -pubkey', { encoding: 'utf8', input: cert });
|
||||
if (pubKeyFromCert === null) return new BoxError(BoxError.BAD_FIELD, `Unable to get public key from certificate: ${safe.error.message}`);
|
||||
const [pubKeyError1, pubKeyFromCert] = await safe(shell.promises.exec('validateCertificate', 'openssl x509 -noout -pubkey', { encoding: 'utf8', input: cert }));
|
||||
if (pubKeyError1) throw new BoxError(BoxError.BAD_FIELD, 'Could not get public key from cert');
|
||||
const [pubKeyError2, pubKeyFromKey] = await safe(shell.promises.exec('validateCertificate', 'openssl pkey -pubout', { encoding: 'utf8', input: key }));
|
||||
if (pubKeyError2) throw new BoxError(BoxError.BAD_FIELD, 'Could not get public key from private key');
|
||||
|
||||
const pubKeyFromKey = safe.child_process.execSync('openssl pkey -pubout', { encoding: 'utf8', input: key });
|
||||
if (pubKeyFromKey === null) return new BoxError(BoxError.BAD_FIELD, `Unable to get public key from private key: ${safe.error.message}`);
|
||||
|
||||
if (pubKeyFromCert !== pubKeyFromKey) return new BoxError(BoxError.BAD_FIELD, 'Public key does not match the certificate.');
|
||||
if (pubKeyFromCert !== pubKeyFromKey) throw new BoxError(BoxError.BAD_FIELD, 'Public key does not match the certificate.');
|
||||
|
||||
// check expiration
|
||||
result = safe.child_process.execSync('openssl x509 -checkend 0', { encoding: 'utf8', input: cert });
|
||||
if (!result) return new BoxError(BoxError.BAD_FIELD, 'Certificate has expired.');
|
||||
const [error] = await safe(shell.promises.exec('validateCertificate', 'openssl x509 -checkend 0', { encoding: 'utf8', input: cert }));
|
||||
if (error) throw new BoxError(BoxError.BAD_FIELD, 'Certificate has expired');
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -209,8 +207,8 @@ async function generateFallbackCertificate(domain) {
|
||||
const configFile = path.join(os.tmpdir(), 'openssl-' + crypto.randomBytes(4).readUInt32LE(0) + '.conf');
|
||||
safe.fs.writeFileSync(configFile, opensslConfWithSan, 'utf8');
|
||||
// the days field is chosen to be less than 825 days per apple requirement (https://support.apple.com/en-us/HT210176)
|
||||
const certCommand = util.format(`openssl req -x509 -newkey rsa:2048 -keyout ${keyFilePath} -out ${certFilePath} -days 800 -subj /CN=*.${cn} -extensions SAN -config ${configFile} -nodes`);
|
||||
if (!safe.child_process.execSync(certCommand)) throw new BoxError(BoxError.OPENSSL_ERROR, safe.error.message);
|
||||
const certCommand = `openssl req -x509 -newkey rsa:2048 -keyout ${keyFilePath} -out ${certFilePath} -days 800 -subj /CN=*.${cn} -extensions SAN -config ${configFile} -nodes`;
|
||||
await shell.promises.exec('generateFallbackCertificate', certCommand, {});
|
||||
safe.fs.unlinkSync(configFile);
|
||||
|
||||
const cert = safe.fs.readFileSync(certFilePath, 'utf8');
|
||||
@@ -267,11 +265,11 @@ function getAcmeCertificateNameSync(fqdn, domainObject) {
|
||||
}
|
||||
}
|
||||
|
||||
function needsRenewalSync(cert, options) {
|
||||
async function needsRenewal(cert, options) {
|
||||
assert.strictEqual(typeof cert, 'string');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
const { startDate, endDate } = getCertificateDatesSync(cert);
|
||||
const { startDate, endDate } = await getCertificateDates(cert);
|
||||
const now = new Date();
|
||||
|
||||
let isExpiring;
|
||||
@@ -433,7 +431,9 @@ async function ensureCertificate(location, options, auditSource) {
|
||||
const cert = await blobs.getString(`${blobs.CERT_PREFIX}-${certName}.cert`);
|
||||
|
||||
if (key && cert) {
|
||||
if (providerMatchesSync(domainObject, cert) && !needsRenewalSync(cert, options)) {
|
||||
const sameProvider = await providerMatches(domainObject, cert);
|
||||
const outdated = await needsRenewal(cert, options);
|
||||
if (sameProvider && !outdated) {
|
||||
debug(`ensureCertificate: ${fqdn} acme cert exists and is up to date`);
|
||||
return;
|
||||
}
|
||||
@@ -629,7 +629,7 @@ async function cleanupCerts(locations, auditSource, progressCallback) {
|
||||
if (certNamesInUse.has(certName)) continue;
|
||||
|
||||
const cert = await blobs.getString(certId);
|
||||
const { endDate } = getCertificateDatesSync(cert);
|
||||
const { endDate } = await getCertificateDates(cert);
|
||||
if (!endDate) continue; // some error
|
||||
|
||||
if (now - endDate >= (60 * 60 * 24 * 30 * 6 * 1000)) { // expired 6 months ago and not in use
|
||||
@@ -736,10 +736,7 @@ async function writeDefaultConfig(options) {
|
||||
|
||||
const cn = 'cloudron-' + (new Date()).toISOString(); // randomize date a bit to keep firefox happy
|
||||
// the days field is chosen to be less than 825 days per apple requirement (https://support.apple.com/en-us/HT210176)
|
||||
if (!safe.child_process.execSync(`openssl req -x509 -newkey rsa:2048 -keyout ${keyFilePath} -out ${certFilePath} -days 800 -subj /CN=${cn} -nodes`)) {
|
||||
debug(`writeDefaultConfig: could not generate certificate: ${safe.error.message}`);
|
||||
throw new BoxError(BoxError.OPENSSL_ERROR, safe.error);
|
||||
}
|
||||
await shell.promises.exec('writeDefaultConfig', `openssl req -x509 -newkey rsa:2048 -keyout ${keyFilePath} -out ${certFilePath} -days 800 -subj /CN=${cn} -nodes`, {});
|
||||
}
|
||||
|
||||
const data = {
|
||||
|
||||
Reference in New Issue
Block a user