diff --git a/rootfs/etc/nginx/nginx.conf b/rootfs/etc/nginx/nginx.conf index fb446246..8dddd301 100644 --- a/rootfs/etc/nginx/nginx.conf +++ b/rootfs/etc/nginx/nginx.conf @@ -51,7 +51,9 @@ http { access_log /data/logs/default.log proxy; include /etc/nginx/conf.d/*.conf; - include /data/nginx/*.conf; + include /data/nginx/proxy_host/*.conf; + include /data/nginx/redirection_host/*.conf; + include /data/nginx/dead_host/*.conf; } stream { diff --git a/rootfs/etc/services.d/nginx/run b/rootfs/etc/services.d/nginx/run index 0ae11026..7bf1449a 100755 --- a/rootfs/etc/services.d/nginx/run +++ b/rootfs/etc/services.d/nginx/run @@ -2,7 +2,7 @@ mkdir -p /tmp/nginx \ /data/{nginx,logs,access} \ - /data/nginx/stream \ + /data/nginx/{proxy_host,redirection_host,stream,dead_host} \ /var/lib/nginx/cache/{public,private} chown root /tmp/nginx diff --git a/src/backend/internal/audit-log.js b/src/backend/internal/audit-log.js index cb4b8c1d..926773ec 100644 --- a/src/backend/internal/audit-log.js +++ b/src/backend/internal/audit-log.js @@ -1,19 +1,10 @@ 'use strict'; +const error = require('../lib/error'); const auditLogModel = require('../models/audit-log'); const internalAuditLog = { - /** - * Internal use only - * - * @param {Object} data - * @returns {Promise} - */ - create: data => { - // TODO - }, - /** * All logs * @@ -28,16 +19,14 @@ const internalAuditLog = { let query = auditLogModel .query() .orderBy('created_on', 'DESC') - .limit(100); + .limit(100) + .allowEager('[user]'); // Query is used for searching if (typeof search_query === 'string') { - /* query.where(function () { - this.where('name', 'like', '%' + search_query + '%') - .orWhere('email', 'like', '%' + search_query + '%'); + this.where('meta', 'like', '%' + search_query + '%'); }); - */ } if (typeof expand !== 'undefined' && expand !== null) { @@ -46,6 +35,44 @@ const internalAuditLog = { return query; }); + }, + + /** + * This method should not be publicly used, it doesn't check certain things. It will be assumed + * that permission to add to audit log is already considered, however the access token is used for + * default user id determination. + * + * @param {Access} access + * @param {Object} data + * @param {String} data.action + * @param {Integer} [data.user_id] + * @param {Integer} [data.object_id] + * @param {Integer} [data.object_type] + * @param {Object} [data.meta] + * @returns {Promise} + */ + add: (access, data) => { + return new Promise((resolve, reject) => { + // Default the user id + if (typeof data.user_id === 'undefined' || !data.user_id) { + data.user_id = access.token.get('attrs').id; + } + + if (typeof data.action === 'undefined' || !data.action) { + reject(new error.InternalValidationError('Audit log entry must contain an Action')); + } else { + // Make sure at least 1 of the IDs are set and action + resolve(auditLogModel + .query() + .insert({ + user_id: data.user_id, + action: data.action, + object_type: data.object_type || '', + object_id: data.object_id || 0, + meta: data.meta || {} + })); + } + }); } }; diff --git a/src/backend/internal/dead-host.js b/src/backend/internal/dead-host.js index cadbc73f..8630f8d0 100644 --- a/src/backend/internal/dead-host.js +++ b/src/backend/internal/dead-host.js @@ -1,9 +1,10 @@ 'use strict'; -const _ = require('lodash'); -const error = require('../lib/error'); -const deadHostModel = require('../models/dead_host'); -const internalHost = require('./host'); +const _ = require('lodash'); +const error = require('../lib/error'); +const deadHostModel = require('../models/dead_host'); +const internalHost = require('./host'); +const internalAuditLog = require('./audit-log'); function omissions () { return ['is_deleted']; @@ -49,7 +50,16 @@ const internalDeadHost = { .insertAndFetch(data); }) .then(row => { - return _.omit(row, omissions()); + // Add to audit log + return internalAuditLog.add(access, { + action: 'created', + object_type: 'dead-host', + object_id: row.id, + meta: data + }) + .then(() => { + return _.omit(row, omissions()); + }); }); }, @@ -97,7 +107,17 @@ const internalDeadHost = { .patchAndFetchById(row.id, data) .then(saved_row => { saved_row.meta = internalHost.cleanMeta(saved_row.meta); - return _.omit(saved_row, omissions()); + + // Add to audit log + return internalAuditLog.add(access, { + action: 'updated', + object_type: 'dead-host', + object_id: row.id, + meta: data + }) + .then(() => { + return _.omit(saved_row, omissions()); + }); }); }); }, @@ -171,6 +191,17 @@ const internalDeadHost = { .where('id', row.id) .patch({ is_deleted: 1 + }) + .then(() => { + // Add to audit log + row.meta = internalHost.cleanMeta(row.meta); + + return internalAuditLog.add(access, { + action: 'deleted', + object_type: 'dead-host', + object_id: row.id, + meta: _.omit(row, omissions()) + }); }); }) .then(() => { @@ -200,7 +231,15 @@ const internalDeadHost = { }); }) .then(row => { - return _.pick(row.meta, internalHost.allowed_ssl_files); + return internalAuditLog.add(access, { + action: 'updated', + object_type: 'dead-host', + object_id: row.id, + meta: data + }) + .then(() => { + return _.pick(row.meta, internalHost.allowed_ssl_files); + }); }); }, diff --git a/src/backend/internal/nginx.js b/src/backend/internal/nginx.js new file mode 100644 index 00000000..de163ff0 --- /dev/null +++ b/src/backend/internal/nginx.js @@ -0,0 +1,92 @@ +'use strict'; + +const fs = require('fs'); +const Liquid = require('liquidjs'); +const logger = require('../logger').nginx; +const utils = require('../lib/utils'); +const error = require('../lib/error'); + +const internalNginx = { + + /** + * @returns {Promise} + */ + test: () => { + logger.info('Testing Nginx configuration'); + return utils.exec('/usr/sbin/nginx -t'); + }, + + /** + * @returns {Promise} + */ + reload: () => { + return internalNginx.test() + .then(() => { + logger.info('Reloading Nginx'); + return utils.exec('/usr/sbin/nginx -s reload'); + }); + }, + + /** + * @param {String} host_type + * @param {Integer} host_id + * @returns {String} + */ + getConfigName: (host_type, host_id) => { + host_type = host_type.replace(new RegExp('-', 'g'), '_'); + return '/data/nginx/' + host_type + '/' + host_id + '.conf'; + }, + + /** + * @param {String} host_type + * @param {Object} host + * @returns {Promise} + */ + generateConfig: (host_type, host) => { + let renderEngine = Liquid(); + host_type = host_type.replace(new RegExp('-', 'g'), '_'); + + return new Promise((resolve, reject) => { + let template = null; + let filename = internalNginx.getConfigName(host_type, host.id); + try { + template = fs.readFileSync(__dirname + '/../templates/' + host_type + '.conf', {encoding: 'utf8'}); + } catch (err) { + reject(new error.ConfigurationError(err.message)); + return; + } + + return renderEngine + .parseAndRender(template, host) + .then(config_text => { + fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); + return true; + }) + .catch(err => { + throw new error.ConfigurationError(err.message); + }); + }); + }, + + /** + * @param {String} host_type + * @param {Object} host + * @param {Boolean} [throw_errors] + * @returns {Promise} + */ + deleteConfig: (host_type, host, throw_errors) => { + return new Promise((resolve, reject) => { + try { + fs.unlinkSync(internalNginx.getConfigName(host_type, host.id)); + } catch (err) { + if (throw_errors) { + reject(err); + } + } + + resolve(); + }); + } +}; + +module.exports = internalNginx; diff --git a/src/backend/internal/proxy-host.js b/src/backend/internal/proxy-host.js index ded58d94..391d6ace 100644 --- a/src/backend/internal/proxy-host.js +++ b/src/backend/internal/proxy-host.js @@ -1,9 +1,10 @@ 'use strict'; -const _ = require('lodash'); -const error = require('../lib/error'); -const proxyHostModel = require('../models/proxy_host'); -const internalHost = require('./host'); +const _ = require('lodash'); +const error = require('../lib/error'); +const proxyHostModel = require('../models/proxy_host'); +const internalHost = require('./host'); +const internalAuditLog = require('./audit-log'); function omissions () { return ['is_deleted']; @@ -49,7 +50,16 @@ const internalProxyHost = { .insertAndFetch(data); }) .then(row => { - return _.omit(row, omissions()); + // Add to audit log + return internalAuditLog.add(access, { + action: 'created', + object_type: 'proxy-host', + object_id: row.id, + meta: data + }) + .then(() => { + return _.omit(row, omissions()); + }); }); }, @@ -97,7 +107,17 @@ const internalProxyHost = { .patchAndFetchById(row.id, data) .then(saved_row => { saved_row.meta = internalHost.cleanMeta(saved_row.meta); - return _.omit(saved_row, omissions()); + + // Add to audit log + return internalAuditLog.add(access, { + action: 'updated', + object_type: 'proxy-host', + object_id: row.id, + meta: data + }) + .then(() => { + return _.omit(saved_row, omissions()); + }); }); }); }, @@ -171,6 +191,17 @@ const internalProxyHost = { .where('id', row.id) .patch({ is_deleted: 1 + }) + .then(() => { + // Add to audit log + row.meta = internalHost.cleanMeta(row.meta); + + return internalAuditLog.add(access, { + action: 'deleted', + object_type: 'proxy-host', + object_id: row.id, + meta: _.omit(row, omissions()) + }); }); }) .then(() => { @@ -200,7 +231,15 @@ const internalProxyHost = { }); }) .then(row => { - return _.pick(row.meta, internalHost.allowed_ssl_files); + return internalAuditLog.add(access, { + action: 'updated', + object_type: 'proxy-host', + object_id: row.id, + meta: data + }) + .then(() => { + return _.pick(row.meta, internalHost.allowed_ssl_files); + }); }); }, diff --git a/src/backend/internal/redirection-host.js b/src/backend/internal/redirection-host.js index 7f0d711f..32ecd64c 100644 --- a/src/backend/internal/redirection-host.js +++ b/src/backend/internal/redirection-host.js @@ -4,6 +4,7 @@ const _ = require('lodash'); const error = require('../lib/error'); const redirectionHostModel = require('../models/redirection_host'); const internalHost = require('./host'); +const internalAuditLog = require('./audit-log'); function omissions () { return ['is_deleted']; @@ -49,7 +50,16 @@ const internalRedirectionHost = { .insertAndFetch(data); }) .then(row => { - return _.omit(row, omissions()); + // Add to audit log + return internalAuditLog.add(access, { + action: 'created', + object_type: 'redirection-host', + object_id: row.id, + meta: data + }) + .then(() => { + return _.omit(row, omissions()); + }); }); }, @@ -97,7 +107,17 @@ const internalRedirectionHost = { .patchAndFetchById(row.id, data) .then(saved_row => { saved_row.meta = internalHost.cleanMeta(saved_row.meta); - return _.omit(saved_row, omissions()); + + // Add to audit log + return internalAuditLog.add(access, { + action: 'updated', + object_type: 'redirection-host', + object_id: row.id, + meta: data + }) + .then(() => { + return _.omit(saved_row, omissions()); + }); }); }); }, @@ -171,6 +191,17 @@ const internalRedirectionHost = { .where('id', row.id) .patch({ is_deleted: 1 + }) + .then(() => { + // Add to audit log + row.meta = internalHost.cleanMeta(row.meta); + + return internalAuditLog.add(access, { + action: 'deleted', + object_type: 'redirection-host', + object_id: row.id, + meta: _.omit(row, omissions()) + }); }); }) .then(() => { @@ -200,7 +231,15 @@ const internalRedirectionHost = { }); }) .then(row => { - return _.pick(row.meta, internalHost.allowed_ssl_files); + return internalAuditLog.add(access, { + action: 'updated', + object_type: 'redirection-host', + object_id: row.id, + meta: data + }) + .then(() => { + return _.pick(row.meta, internalHost.allowed_ssl_files); + }); }); }, diff --git a/src/backend/internal/ssl.js b/src/backend/internal/ssl.js new file mode 100644 index 00000000..efa09309 --- /dev/null +++ b/src/backend/internal/ssl.js @@ -0,0 +1,163 @@ +'use strict'; + +const fs = require('fs'); +const Liquid = require('liquidjs'); +const timestamp = require('unix-timestamp'); +const internalNginx = require('./nginx'); +const logger = require('../logger').ssl; +const utils = require('../lib/utils'); +const error = require('../lib/error'); + +timestamp.round = true; + +const internalSsl = { + + interval_timeout: 1000 * 60 * 60 * 12, // 12 hours + interval: null, + interval_processing: false, + + initTimer: () => { + internalSsl.interval = setInterval(internalSsl.processExpiringHosts, internalSsl.interval_timeout); + }, + + /** + * Triggered by a timer, this will check for expiring hosts and renew their ssl certs if required + */ + processExpiringHosts: () => { + if (!internalSsl.interval_processing) { + logger.info('Renewing SSL certs close to expiry...'); + return utils.exec('/usr/bin/certbot renew -q') + .then(result => { + logger.info(result); + internalSsl.interval_processing = false; + + return internalNginx.reload() + .then(() => { + logger.info('Renew Complete'); + return result; + }); + }) + .catch(err => { + logger.error(err); + internalSsl.interval_processing = false; + }); + } + }, + + /** + * @param {String} host_type + * @param {Object} host + * @returns {Boolean} + */ + hasValidSslCerts: (host_type, host) => { + host_type = host_type.replace(new RegExp('-', 'g'), '_'); + let le_path = '/etc/letsencrypt/live/' + host_type + '_' + host.id; + + return fs.existsSync(le_path + '/fullchain.pem') && fs.existsSync(le_path + '/privkey.pem'); + }, + + /** + * @param {String} host_type + * @param {Object} host + * @returns {Promise} + */ + requestSsl: (host_type, host) => { + logger.info('Requesting SSL certificates for ' + host_type + ' #' + host.id); + + // TODO + + return utils.exec('/usr/bin/letsencrypt certonly --agree-tos --email "' + host.letsencrypt_email + '" -n -a webroot -d "' + host.hostname + '"') + .then(result => { + logger.info(result); + return result; + }); + }, + + /** + * @param {String} host_type + * @param {Object} host + * @returns {Promise} + */ + renewSsl: (host_type, host) => { + logger.info('Renewing SSL certificates for ' + host_type + ' #' + host.id); + + // TODO + + return utils.exec('/usr/bin/certbot renew --force-renewal --disable-hook-validation --cert-name "' + host.hostname + '"') + .then(result => { + logger.info(result); + return result; + }); + }, + + /** + * @param {String} host_type + * @param {Object} host + * @returns {Promise} + */ + deleteCerts: (host_type, host) => { + logger.info('Deleting SSL certificates for ' + host_type + ' #' + host.id); + + // TODO + + return utils.exec('/usr/bin/certbot delete -n --cert-name "' + host.hostname + '"') + .then(result => { + logger.info(result); + }) + .catch(err => { + logger.error(err); + }); + }, + + /** + * @param {String} host_type + * @param {Object} host + * @returns {Promise} + */ + generateSslSetupConfig: (host_type, host) => { + host_type = host_type.replace(new RegExp('-', 'g'), '_'); + + let renderEngine = Liquid(); + let template = null; + let filename = internalNginx.getConfigName(host_type, host); + + return new Promise((resolve, reject) => { + try { + template = fs.readFileSync(__dirname + '/../templates/letsencrypt.conf', {encoding: 'utf8'}); + } catch (err) { + reject(new error.ConfigurationError(err.message)); + return; + } + + return renderEngine + .parseAndRender(template, host) + .then(config_text => { + fs.writeFileSync(filename, config_text, {encoding: 'utf8'}); + return template_data; + }) + .catch(err => { + throw new error.ConfigurationError(err.message); + }); + }); + }, + + /** + * @param {String} host_type + * @param {Object} host + * @returns {Promise} + */ + configureSsl: (host_type, host) => { + + // TODO + + return internalSsl.generateSslSetupConfig(host) + .then(data => { + return internalNginx.reload() + .then(() => { + return internalSsl.requestSsl(data); + }); + }); + } +}; + +module.exports = internalSsl; diff --git a/src/backend/internal/stream.js b/src/backend/internal/stream.js index 603b8fd7..bb8c9c3a 100644 --- a/src/backend/internal/stream.js +++ b/src/backend/internal/stream.js @@ -1,8 +1,9 @@ 'use strict'; -const _ = require('lodash'); -const error = require('../lib/error'); -const streamModel = require('../models/stream'); +const _ = require('lodash'); +const error = require('../lib/error'); +const streamModel = require('../models/stream'); +const internalAuditLog = require('./audit-log'); function omissions () { return ['is_deleted']; @@ -31,7 +32,16 @@ const internalStream = { .insertAndFetch(data); }) .then(row => { - return _.omit(row, omissions()); + // Add to audit log + return internalAuditLog.add(access, { + action: 'created', + object_type: 'stream', + object_id: row.id, + meta: data + }) + .then(() => { + return _.omit(row, omissions()); + }); }); }, @@ -60,7 +70,16 @@ const internalStream = { .omit(omissions()) .patchAndFetchById(row.id, data) .then(saved_row => { - return _.omit(saved_row, omissions()); + // Add to audit log + return internalAuditLog.add(access, { + action: 'updated', + object_type: 'stream', + object_id: row.id, + meta: data + }) + .then(() => { + return _.omit(saved_row, omissions()); + }); }); }); }, @@ -133,6 +152,15 @@ const internalStream = { .where('id', row.id) .patch({ is_deleted: 1 + }) + .then(() => { + // Add to audit log + return internalAuditLog.add(access, { + action: 'deleted', + object_type: 'stream', + object_id: row.id, + meta: _.omit(row, omissions()) + }); }); }) .then(() => { diff --git a/src/backend/internal/user.js b/src/backend/internal/user.js index 9cbf63d0..6a91e98e 100644 --- a/src/backend/internal/user.js +++ b/src/backend/internal/user.js @@ -7,6 +7,7 @@ const userPermissionModel = require('../models/user_permission'); const authModel = require('../models/auth'); const gravatar = require('gravatar'); const internalToken = require('./token'); +const internalAuditLog = require('./audit-log'); function omissions () { return ['is_deleted']; @@ -74,6 +75,18 @@ const internalUser = { .then(() => { return internalUser.get(access, {id: user.id, expand: ['permissions']}); }); + }) + .then(user => { + // Add to audit log + return internalAuditLog.add(access, { + action: 'created', + object_type: 'user', + object_id: user.id, + meta: user + }) + .then(() => { + return user; + }); }); }, @@ -136,6 +149,18 @@ const internalUser = { }) .then(() => { return internalUser.get(access, {id: data.id}); + }) + .then(user => { + // Add to audit log + return internalAuditLog.add(access, { + action: 'updated', + object_type: 'user', + object_id: user.id, + meta: data + }) + .then(() => { + return user; + }); }); }, @@ -236,6 +261,15 @@ const internalUser = { .where('id', user.id) .patch({ is_deleted: 1 + }) + .then(() => { + // Add to audit log + return internalAuditLog.add(access, { + action: 'deleted', + object_type: 'user', + object_id: user.id, + meta: _.omit(user, omissions()) + }); }); }) .then(() => { @@ -389,6 +423,19 @@ const internalUser = { meta: {} }); } + }) + .then(() => { + // Add to Audit Log + return internalAuditLog.add(access, { + action: 'updated', + object_type: 'user', + object_id: user.id, + meta: { + name: user.name, + password_changed: true, + auth_type: data.type + } + }); }); }) .then(() => { @@ -435,8 +482,21 @@ const internalUser = { } }) .then(permissions => { - return true; + // Add to Audit Log + return internalAuditLog.add(access, { + action: 'updated', + object_type: 'user', + object_id: user.id, + meta: { + name: user.name, + permissions: permissions + } + }); + }); + }) + .then(() => { + return true; }); }, diff --git a/src/backend/lib/error.js b/src/backend/lib/error.js index 1580c45c..070952f1 100644 --- a/src/backend/lib/error.js +++ b/src/backend/lib/error.js @@ -50,6 +50,15 @@ module.exports = { this.public = false; }, + ConfigurationError: function (message, previous) { + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + this.previous = previous; + this.message = message; + this.status = 400; + this.public = true; + }, + CacheError: function (message, previous) { Error.captureStackTrace(this, this.constructor); this.name = this.constructor.name; diff --git a/src/backend/lib/utils.js b/src/backend/lib/utils.js new file mode 100644 index 00000000..28919b18 --- /dev/null +++ b/src/backend/lib/utils.js @@ -0,0 +1,22 @@ +'use strict'; + +const exec = require('child_process').exec; + +module.exports = { + + /** + * @param {String} cmd + * @returns {Promise} + */ + exec: function (cmd) { + return new Promise((resolve, reject) => { + exec(cmd, function (err, stdout, stderr) { + if (err && typeof err === 'object') { + reject(err); + } else { + resolve(stdout.trim()); + } + }); + }); + } +}; diff --git a/src/backend/logger.js b/src/backend/logger.js index aeb4c70a..584287bf 100644 --- a/src/backend/logger.js +++ b/src/backend/logger.js @@ -1,8 +1,10 @@ const {Signale} = require('signale'); module.exports = { - global: new Signale({scope: 'Global '}), - migrate: new Signale({scope: 'Migrate '}), - express: new Signale({scope: 'Express '}), - access: new Signale({scope: 'Access '}) + global: new Signale({scope: 'Global '}), + migrate: new Signale({scope: 'Migrate '}), + express: new Signale({scope: 'Express '}), + access: new Signale({scope: 'Access '}), + nginx: new Signale({scope: 'Nginx '}), + ssl: new Signale({scope: 'SSL '}) }; diff --git a/src/backend/migrations/20180618015850_initial.js b/src/backend/migrations/20180618015850_initial.js index 3ce8d018..153f3536 100644 --- a/src/backend/migrations/20180618015850_initial.js +++ b/src/backend/migrations/20180618015850_initial.js @@ -165,7 +165,8 @@ exports.up = function (knex/*, Promise*/) { table.dateTime('created_on').notNull(); table.dateTime('modified_on').notNull(); table.integer('user_id').notNull().unsigned(); - // TODO + table.string('object_type').notNull().defaultTo(''); + table.integer('object_id').notNull().unsigned().defaultTo(0); table.string('action').notNull(); table.json('meta').notNull(); }); diff --git a/src/backend/models/audit-log.js b/src/backend/models/audit-log.js index e60273c8..d9f312ee 100644 --- a/src/backend/models/audit-log.js +++ b/src/backend/models/audit-log.js @@ -5,6 +5,7 @@ const db = require('../db'); const Model = require('objection').Model; +const User = require('./user'); Model.knex(db); @@ -25,6 +26,26 @@ class AuditLog extends Model { static get tableName () { return 'audit_log'; } + + static get jsonAttributes () { + return ['meta']; + } + + static get relationMappings () { + return { + user: { + relation: Model.HasOneRelation, + modelClass: User, + join: { + from: 'audit_log.user_id', + to: 'user.id' + }, + modify: function (qb) { + qb.omit(['id', 'created_on', 'modified_on', 'roles']); + } + } + }; + } } module.exports = AuditLog; diff --git a/src/backend/templates/dead_host.conf b/src/backend/templates/dead_host.conf new file mode 100644 index 00000000..d136541a --- /dev/null +++ b/src/backend/templates/dead_host.conf @@ -0,0 +1,19 @@ +# <%- hostname %> +server { + listen 80; + <%- typeof ssl !== 'undefined' && ssl ? 'listen 443 ssl;' : '' %> + + server_name <%- hostname %>; + + access_log /config/logs/<%- hostname %>.log proxy; + +<% if (typeof ssl !== 'undefined' && ssl) { -%> + include conf.d/include/ssl-ciphers.conf; + ssl_certificate /etc/letsencrypt/live/<%- hostname %>/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/<%- hostname %>/privkey.pem; +<% } -%> + + <%- typeof advanced !== 'undefined' && advanced ? advanced : '' %> + + return 404; +} diff --git a/src/backend/templates/letsencrypt.conf b/src/backend/templates/letsencrypt.conf new file mode 100644 index 00000000..f870f2ea --- /dev/null +++ b/src/backend/templates/letsencrypt.conf @@ -0,0 +1,11 @@ +# Letsencrypt Verification Temporary Host: <%- hostname %> +server { + listen 80; + server_name <%- hostname %>; + + access_log /config/logs/letsencrypt.log proxy; + + location / { + root /config/letsencrypt-acme-challenge; + } +} diff --git a/src/backend/templates/proxy_host.conf b/src/backend/templates/proxy_host.conf new file mode 100644 index 00000000..4f320360 --- /dev/null +++ b/src/backend/templates/proxy_host.conf @@ -0,0 +1,33 @@ +# <%- hostname %> +server { + listen 80; + <%- typeof ssl !== 'undefined' && ssl ? 'listen 443 ssl;' : '' %> + + server_name <%- hostname %>; + + access_log /config/logs/<%- hostname %>.log proxy; + + set $server <%- forward_server %>; + set $port <%- forward_port %>; + + <%- typeof asset_caching !== 'undefined' && asset_caching ? 'include conf.d/include/assets.conf;' : '' %> + <%- typeof block_exploits !== 'undefined' && block_exploits ? 'include conf.d/include/block-exploits.conf;' : '' %> + +<% if (typeof ssl !== 'undefined' && ssl) { -%> + include conf.d/include/letsencrypt-acme-challenge.conf; + include conf.d/include/ssl-ciphers.conf; + ssl_certificate /etc/letsencrypt/live/<%- hostname %>/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/<%- hostname %>/privkey.pem; +<% } -%> + +<%- typeof advanced !== 'undefined' && advanced ? advanced : '' %> + + location / { + <% if (typeof access_list_id !== 'undefined' && access_list_id) { -%> + auth_basic "Authorization required"; + auth_basic_user_file /config/access/<%- access_list_id %>; + <% } -%> + <%- typeof force_ssl !== 'undefined' && force_ssl ? 'include conf.d/include/force-ssl.conf;' : '' %> + include conf.d/include/proxy.conf; + } +} diff --git a/src/backend/templates/redirection_host.conf b/src/backend/templates/redirection_host.conf new file mode 100644 index 00000000..1c4f91b5 --- /dev/null +++ b/src/backend/templates/redirection_host.conf @@ -0,0 +1,22 @@ +# <%- hostname %> +server { + listen 80; + <%- typeof ssl !== 'undefined' && ssl ? 'listen 443 ssl;' : '' %> + + server_name <%- hostname %>; + + access_log /config/logs/<%- hostname %>.log proxy; + + <%- typeof asset_caching !== 'undefined' && asset_caching ? 'include conf.d/include/assets.conf;' : '' %> + <%- typeof block_exploits !== 'undefined' && block_exploits ? 'include conf.d/include/block-exploits.conf;' : '' %> + +<% if (typeof ssl !== 'undefined' && ssl) { -%> + include conf.d/include/ssl-ciphers.conf; + ssl_certificate /etc/letsencrypt/live/<%- hostname %>/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/<%- hostname %>/privkey.pem; +<% } -%> + + <%- typeof advanced !== 'undefined' && advanced ? advanced : '' %> + + return 301 $scheme://<%- forward_host %>$request_uri; +} diff --git a/src/backend/templates/stream.conf b/src/backend/templates/stream.conf new file mode 100644 index 00000000..49994a26 --- /dev/null +++ b/src/backend/templates/stream.conf @@ -0,0 +1,11 @@ +# <%- incoming_port %> - <%- protocols.join(',').toUpperCase() %> +<% +protocols.forEach(function (protocol) { +%> +server { + listen <%- incoming_port %> <%- protocol === 'tcp' ? '' : protocol %>; + proxy_pass <%- forward_server %>:<%- forward_port %>; +} +<% +}); +%> diff --git a/src/frontend/js/app/audit-log/list/item.ejs b/src/frontend/js/app/audit-log/list/item.ejs index bd4d19e0..499b8070 100644 --- a/src/frontend/js/app/audit-log/list/item.ejs +++ b/src/frontend/js/app/audit-log/list/item.ejs @@ -1,32 +1,72 @@
' + content.join('
') + '
' + }; + } +}); diff --git a/src/frontend/js/app/nginx/access/main.ejs b/src/frontend/js/app/nginx/access/main.ejs index 140cd490..c245ff4a 100644 --- a/src/frontend/js/app/nginx/access/main.ejs +++ b/src/frontend/js/app/nginx/access/main.ejs @@ -3,6 +3,7 @@