diff --git a/backend/index.js b/backend/index.js index 82f9be5f..129cf060 100644 --- a/backend/index.js +++ b/backend/index.js @@ -9,7 +9,7 @@ async function appStart () { const apiValidator = require('./lib/validator/api'); const internalCertificate = require('./internal/certificate'); const internalIpRanges = require('./internal/ip_ranges'); - const ddnsResolver = require('./lib/ddns_resolver/ddns_resolver'); + const ddnsResolver = require('./lib/ddns_resolver'); return migrate.latest() .then(setup) diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index 77933e73..2f5dc376 100644 --- a/backend/internal/nginx.js +++ b/backend/internal/nginx.js @@ -4,6 +4,7 @@ const logger = require('../logger').nginx; const config = require('../lib/config'); const utils = require('../lib/utils'); const error = require('../lib/error'); +const ddnsResolver = require('../lib/ddns_resolver'); const internalNginx = { @@ -131,6 +132,31 @@ const internalNginx = { return '/data/nginx/' + internalNginx.getFileFriendlyHostType(host_type) + '/' + host_id + '.conf'; }, + /** + * Resolves any ddns addresses that need to be resolved for clients in the host's access list. + * @param {Object} host + * @returns {Promise} + */ + resolveDDNSAddresses: (host) => { + const promises = []; + if (typeof host.access_list !== 'undefined' && typeof host.access_list.clients !== 'undefined') { + for (const client of host.access_list.clients) { + if (ddnsResolver.requiresResolution(client.address)) { + const p = ddnsResolver.resolveAddress(client.address) + .then((resolvedIP) => { + client.address = `${resolvedIP}; # ${client.address}`; + return Promise.resolve(); + }); + promises.push(p); + } + } + } + if (promises.length) { + return Promise.all(promises); + } + return Promise.resolve(); + }, + /** * Generates custom locations * @param {Object} host @@ -201,6 +227,12 @@ const internalNginx = { return; } + // Resolve ddns addresses if needed + let resolverPromise = Promise.resolve(); + if (host_type === 'proxy_host') { + resolverPromise = internalNginx.resolveDDNSAddresses(host); + } + let locationsPromise; let origLocations; @@ -215,8 +247,10 @@ const internalNginx = { if (host.locations) { //logger.info ('host.locations = ' + JSON.stringify(host.locations, null, 2)); origLocations = [].concat(host.locations); - locationsPromise = internalNginx.renderLocations(host).then((renderedLocations) => { - host.locations = renderedLocations; + locationsPromise = resolverPromise.then(() => { + return internalNginx.renderLocations(host).then((renderedLocations) => { + host.locations = renderedLocations; + }); }); // Allow someone who is using / custom location path to use it, and skip the default / location @@ -227,7 +261,7 @@ const internalNginx = { }); } else { - locationsPromise = Promise.resolve(); + locationsPromise = resolverPromise; } // Set the IPv6 setting for the host diff --git a/backend/lib/ddns_resolver/ddns_resolver.js b/backend/lib/ddns_resolver.js similarity index 74% rename from backend/lib/ddns_resolver/ddns_resolver.js rename to backend/lib/ddns_resolver.js index fc759133..b339ca47 100644 --- a/backend/lib/ddns_resolver/ddns_resolver.js +++ b/backend/lib/ddns_resolver.js @@ -1,41 +1,7 @@ -const error = require('../error') -const logger = require('../../logger').global; -const internalAccessList = require('../../internal/access-list'); -const internalNginx = require('../../internal/nginx'); -const spawn = require('child_process').spawn; - -const cmdHelper = { - /** - * Run the given command. Safer than using exec since args are passed as a list instead of in shell mode as a single string. - * @param {string} cmd The command to run - * @param {string} args The args to pass to the command - * @returns Promise that resolves to stdout or an object with error code and stderr if there's an error - */ - run: (cmd, args) => { - return new Promise((resolve, reject) => { - let stdout = ''; - let stderr = ''; - const proc = spawn(cmd, args); - proc.stdout.on('data', (data) => { - stdout += data; - }); - proc.stderr.on('data', (data) => { - stderr += data; - }); - - proc.on('close', (exitCode) => { - if (!exitCode) { - resolve(stdout.trim()); - } else { - reject({ - exitCode: exitCode, - stderr: stderr - }); - } - }); - }); - } -}; +const error = require('./error') +const logger = require('../logger').ddns; +const internalAccessList = require('../internal/access-list'); +const utils = require('./utils'); const ddnsResolver = { /** @@ -99,15 +65,13 @@ const ddnsResolver = { /** Private **/ // Properties _initialized: false, - _updateIntervalMs: 1000 * 60 * 60, // 1 hr default (overriden with $DDNS_UPDATE_INTERVAL env var) + _updateIntervalMs: 60 * 60 * 1000, // 1 hr default (overriden with $DDNS_UPDATE_INTERVAL env var) /** * cache mapping host to (ip address, last updated time) */ _cache: new Map(), _interval: null, // reference to created interval id _processingDDNSUpdate: false, - - _originalGenerateConfig: null, // Used for patching config generation to resolve hosts // Methods @@ -126,16 +90,6 @@ const ddnsResolver = { logger.warn(`[DDNS] invalid value for update interval: '${process.env.DDNS_UPDATE_INTERVAL}'`); } } - - // Patch nginx config generation if needed (check env var) - if (typeof process.env.DDNS_UPDATE_PATCH !== 'undefined') { - const enabled = Number(process.env.DDNS_UPDATE_PATCH.toLowerCase()); - if (!isNaN(enabled) && enabled) { - logger.info('Patching nginx config generation'); - ddnsResolver._originalGenerateConfig = internalNginx.generateConfig; - internalNginx.generateConfig = ddnsResolver._patchedGenerateConfig; - } - } ddnsResolver._initialized = true; }, @@ -146,7 +100,7 @@ const ddnsResolver = { */ _queryHost: (host) => { logger.info('Looking up IP for ', host); - return cmdHelper.run('getent', ['hosts', host]) + return utils.execSafe('getent', ['hosts', host]) .then((result) => { if (result.length < 8) { logger.error('IP lookup returned invalid output: ', result); @@ -162,35 +116,12 @@ const ddnsResolver = { }); }, - _patchedGenerateConfig: (host_type, host) => { - const promises = []; - if (host_type === 'proxy_host') { - if (typeof host.access_list !== 'undefined' && typeof host.access_list.clients !== 'undefined') { - for (const client of host.access_list.clients) { - if (ddnsResolver.requiresResolution(client.address)) { - const p = ddnsResolver.resolveAddress(client.address) - .then((resolvedIP) => { - client.address = `${resolvedIP}; # ${client.address}`; - return Promise.resolve(); - }); - promises.push(p); - } - } - } - } - if (promises.length) { - return Promise.all(promises) - .then(() => { - return ddnsResolver._originalGenerateConfig(host_type, host); - }); - } - return ddnsResolver._originalGenerateConfig(host_type, host); - }, - /** * Triggered by a timer, will check for and update ddns hosts in access list clients */ _checkForDDNSUpdates: () => { + const internalNginx = require('../internal/nginx'); // Prevent circular import + logger.info('Checking for DDNS updates...'); if (!ddnsResolver._processingDDNSUpdate) { ddnsResolver._processingDDNSUpdate = true; diff --git a/backend/lib/utils.js b/backend/lib/utils.js index bcdb3341..188a8cbe 100644 --- a/backend/lib/utils.js +++ b/backend/lib/utils.js @@ -4,6 +4,7 @@ const execFile = require('child_process').execFile; const { Liquid } = require('liquidjs'); const logger = require('../logger').global; const error = require('./error'); +const spawn = require('child_process').spawn; module.exports = { @@ -26,6 +27,37 @@ module.exports = { return stdout; }, + /** + * Run the given command. Safer than using exec since args are passed as a list instead of in shell mode as a single string. + * @param {string} cmd The command to run + * @param {string} args The args to pass to the command + * @returns Promise that resolves to stdout or an object with error code and stderr if there's an error + */ + execSafe: (cmd, args) => { + return new Promise((resolve, reject) => { + let stdout = ''; + let stderr = ''; + const proc = spawn(cmd, args); + proc.stdout.on('data', (data) => { + stdout += data; + }); + proc.stderr.on('data', (data) => { + stderr += data; + }); + + proc.on('close', (exitCode) => { + if (!exitCode) { + resolve(stdout.trim()); + } else { + reject({ + exitCode: exitCode, + stderr: stderr + }); + } + }); + }); + }, + /** * @param {String} cmd * @param {Array} args diff --git a/backend/logger.js b/backend/logger.js index 0ebb07c5..78553f51 100644 --- a/backend/logger.js +++ b/backend/logger.js @@ -10,5 +10,6 @@ module.exports = { certbot: new Signale({scope: 'Certbot '}), import: new Signale({scope: 'Importer '}), setup: new Signale({scope: 'Setup '}), - ip_ranges: new Signale({scope: 'IP Ranges'}) + ip_ranges: new Signale({scope: 'IP Ranges'}), + ddns: new Signale({scope: 'DDNS '}) };