From d18b04effe8178669f4c156a7521c868a3d11b0a Mon Sep 17 00:00:00 2001 From: Wildan M Date: Wed, 15 Nov 2023 06:32:39 +0700 Subject: [PATCH] Ability to use shared SSL --- package.json | 2 +- src/executor/nginx.js | 31 +++++++++++++++++++++++++++---- src/executor/runner.js | 41 ++++++++++++++++++++++++++++++++++++++--- src/util.js | 27 +++++++++++++++++++++++++++ sudoutil.js | 8 ++++++++ 5 files changed, 101 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index ada937c..b6181c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "domcloud-bridge", - "version": "0.31.1", + "version": "0.32.0", "description": "Deployment runner for DOM Cloud", "main": "app.js", "engines": { diff --git a/src/executor/nginx.js b/src/executor/nginx.js index 63a06be..3c538e4 100644 --- a/src/executor/nginx.js +++ b/src/executor/nginx.js @@ -275,8 +275,6 @@ class NginxExecutor { await spawnSudoUtil('NGINX_GET', [domain]); return await new Promise((resolve, reject) => { var src = cat(tmpFile).toString(); - // https://github.com/virtualmin/virtualmin-nginx/issues/18 - src = src.replace(/ default_server/g, ''); NginxConfFile.createFromSource(src, (err, conf) => { if (err) return reject(err); @@ -291,8 +289,33 @@ class NginxExecutor { spawnSudoUtil('NGINX_SET', [domain]).then(() => { resolve("Done updated\n" + node.toString()); }).catch((err) => { - if (err && err.stderr && err.stderr.includes('nginx: [emerg]')) { - } + reject(err); + }) + }); + }); + }) + } + /** + * @param {string} domain + * @param {any} info + */ + async setDirect(domain, info) { + return await executeLock('nginx', async () => { + await spawnSudoUtil('NGINX_GET', [domain]); + return await new Promise((resolve, reject) => { + var src = cat(tmpFile).toString(); + NginxConfFile.createFromSource(src, (err, conf) => { + if (err) + return reject(err); + const node = conf.nginx.server[0]; + if (!node) { + return reject(new Error(`Cannot find domain ${domain}`)); + } + this.applyInfo(node, info); + writeTo(tmpFile, conf.toString()); + spawnSudoUtil('NGINX_SET', [domain]).then(() => { + resolve("Done updated\n" + node.toString()); + }).catch((err) => { reject(err); }) }); diff --git a/src/executor/runner.js b/src/executor/runner.js index a2fa5fb..5b91e19 100644 --- a/src/executor/runner.js +++ b/src/executor/runner.js @@ -1,5 +1,6 @@ import path from "path"; import { + detectCanShareSSL, escapeShell, getDbName, getLtsPhp, @@ -661,13 +662,47 @@ export async function runConfigSubdomain(config, domaindata, subdomain, sshExec, await sshExec(`mkdir -p ~/.local/bin; echo -e "\\u23\\u21/bin/bash\\n$(which php${phpVer}) \\u22\\u24\\u40\\u22" > ~/.local/bin/php; chmod +x ~/.local/bin/php`, false); break; case 'ssl': + // ssl now also fix any misconfigurations if (process.env.MODE === 'dev') { break; } - if (['off', 'always', 'enforce', 'on'].includes(value)) { + let regenerateSsl = false; + let expectedSslMode = null; + if (['off', 'always', 'on'].includes(value)) { + expectedSslMode = expectedSslMode; + } else if (value == 'letsencrypt') { + regenerateSsl = true; + } + var nginxNodes = await nginxExec.get(subdomain); + var nginxInfos = nginxExec.extractInfo(nginxNodes, subdomain); + var sharedSSL = regenerateSsl ? null : detectCanShareSSL(subdomain); + var changed = false; + var expectCert = sharedSSL ? path.join(sharedSSL, 'ssl.cert') : domaindata['SSL cert file']; + var expectKey = sharedSSL ? path.join(sharedSSL, 'ssl.key') : domaindata['SSL key file']; + if (!expectCert || !expectKey) { + expectedSslMode = 'off'; + } + if (expectCert != nginxInfos.ssl_certificate) { + nginxInfos.ssl_certificate = expectCert + changed = true; + } + if (expectKey != nginxInfos.ssl_certificate_key) { + nginxInfos.ssl_certificate_key = expectKey + changed = true; + } + if (domaindata['HTML directory'] != nginxInfos.root) { + nginxInfos.root = domaindata['HTML directory'] + changed = true; + } + if (expectedSslMode && expectedSslMode != ["", "off", "always", "on"][nginxInfos.ssl]) { + nginxInfos.config.ssl = expectedSslMode + changed = true; + } + if (changed) { await writeLog("$> Applying nginx ssl config on " + subdomain); - await writeLog(await nginxExec.setSsl(subdomain, value, "")); - } else if (value == 'letsencrypt' || !value) { + await writeLog(await nginxExec.setDirect(subdomain, nginxInfos)); + } + if (regenerateSsl || (!expectedSslMode && !sharedSSL)) { await writeLog("$> generating ssl cert with let's encrypt"); await spawnSudoUtil('OPENSSL_CLEAN'); await virtExec("generate-letsencrypt-cert", { diff --git a/src/util.js b/src/util.js index a59d63f..6d63eaa 100644 --- a/src/util.js +++ b/src/util.js @@ -15,6 +15,10 @@ let pythonVersionsList = []; * @type {Record} */ let pythonVersionsMap = {}; +/** + * @type {Record} + */ +let sslWildcardsMap = {}; const pythonConstants = { // https://raw.githubusercontent.com/indygreg/python-build-standalone/latest-release/latest-release.json tag: "20230507", @@ -41,6 +45,13 @@ export const initUtils = () => { const rev = fs.readFileSync('.git/HEAD').toString().trim(); revision = rev.indexOf(':') === -1 ? rev : fs.readFileSync('.git/' + rev.substring(5)).toString().trim(); revision = revision.substring(0, 7); + sslWildcardsMap = process.env.SSL_WILDCARDS ? process.env.SSL_WILDCARDS.split(',').reduce((a, b) => { + var splits = b.split(':'); + if (splits.length == 2) { + a[splits[0].toLowerCase()] = splits[1]; + } + return a; + }, {}) : {}; axios.get('https://www.php.net/releases/?json').then(res => { Object.values(res.data).forEach(v => { v.supported_versions.forEach((/** @type {string} */ ver) => { @@ -482,3 +493,19 @@ export function writeTo(path, content) { encoding: 'utf-8' }); } + +export function detectCanShareSSL(subdomain) { + const subdomainParts = subdomain.split('.'); + for (const domain of Object.keys(sslWildcardsMap)) { + + // Split the domain strings into arrays of subdomains + const domainParts = domain.split('.'); + + // Check if the subdomain has exactly one more part than the domain + if (subdomainParts.length === domainParts.length + 1 && + subdomain.endsWith(`.${domain}`)) { + return sslWildcardsMap[domain] + } + } + return null; +} diff --git a/sudoutil.js b/sudoutil.js index f037844..f3f06b7 100755 --- a/sudoutil.js +++ b/sudoutil.js @@ -174,6 +174,14 @@ switch (cli.args.shift()) { arg = cli.args.shift(); exec(`${env.BASH_KILL} ${arg}`, { shell: '' }).code; exit(0); + case 'SHELL_EXISTS': + arg = cli.args.shift(); + for (const path of cli.args) { + if (!existsSync(path)) { + exit(1); + } + } + exit(0); case 'SHELL_SUDO': arg = cli.args.shift(); var sudo = spawn(env.BASH_SUDO, ['-u', arg, '-i', ...cli.args], {