diff --git a/backend/app.js b/backend/app.js index 8f4890c3d..bbeb1c02d 100644 --- a/backend/app.js +++ b/backend/app.js @@ -74,12 +74,10 @@ app.use(function (err, req, res, next) { } // Not every error is worth logging - but this is good for now until it gets annoying. - if (typeof err.stack !== 'undefined' && err.stack) { - if (process.env.NODE_ENV === 'development' || process.env.DEBUG) { - log.debug(err.stack); - } else if (typeof err.public == 'undefined' || !err.public) { - log.warn(err.message); - } + if (process.env.NODE_ENV === 'development' || process.env.DEBUG) { + log.debug(err); + } else if (typeof err.stack !== 'undefined' && err.stack && (typeof err.public == 'undefined' || !err.public)) { + log.warn(err.message); } res diff --git a/backend/internal/host.js b/backend/internal/host.js index f37b943d7..9c91b4169 100644 --- a/backend/internal/host.js +++ b/backend/internal/host.js @@ -206,14 +206,21 @@ const internalHost = { if (existing_rows && existing_rows.length) { existing_rows.map(function (existing_row) { - existing_row.domain_names.map(function (existing_hostname) { + + function checkHostname(existing_hostname) { // Does this domain match? if (existing_hostname.toLowerCase() === hostname.toLowerCase()) { if (!ignore_id || ignore_id !== existing_row.id) { is_taken = true; } } - }); + } + + if (existing_row.domain_names) { + existing_row.domain_names.map(checkHostname); + } else if (existing_row.domain_name) { + checkHostname(existing_row.domain_name); + } }); } diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index 9215df9a4..2c0c5f42f 100644 --- a/backend/internal/nginx.js +++ b/backend/internal/nginx.js @@ -236,8 +236,8 @@ const internalNginx = { host = { all_passthrough_hosts: allHosts.map((host) => { // Replace dots in domain - host.escaped_name = host.domain_name.replace(/\./, '_'); host.forwarding_host = internalNginx.addIpv6Brackets(host.forwarding_host); + return host; }), } } else { diff --git a/backend/internal/ssl-passthrough-host.js b/backend/internal/ssl-passthrough-host.js index a4f0d57de..a6b3f1576 100644 --- a/backend/internal/ssl-passthrough-host.js +++ b/backend/internal/ssl-passthrough-host.js @@ -19,20 +19,12 @@ const internalPassthroughHost = { create: (access, data) => { return access.can('ssl_passthrough_hosts:create', data) .then(() => { - // Get a list of the domain names and check each of them against existing records - let domain_name_check_promises = []; - - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name)); - }); - - return Promise.all(domain_name_check_promises) - .then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); + // Get the domain name and check it against existing records + return internalHost.isHostnameTaken(data.domain_name) + .then((result) => { + if (result.is_taken) { + throw new error.ValidationError(result.hostname + ' is already in use'); + } }); }).then((/*access_data*/) => { data.owner_user_id = access.token.getUserId(1); @@ -57,7 +49,7 @@ const internalPassthroughHost = { // Add to audit log return internalAuditLog.add(access, { action: 'created', - object_type: 'ssl_passthrough_host', + object_type: 'ssl-passthrough-host', object_id: row.id, meta: data }) @@ -76,21 +68,13 @@ const internalPassthroughHost = { update: (access, data) => { return access.can('ssl_passthrough_hosts:update', data.id) .then((/*access_data*/) => { - // Get a list of the domain names and check each of them against existing records - let domain_name_check_promises = []; - - if (typeof data.domain_names !== 'undefined') { - data.domain_names.map(function (domain_name) { - domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'ssl_passthrough', data.id)); - }); - - return Promise.all(domain_name_check_promises) - .then((check_results) => { - check_results.map(function (result) { - if (result.is_taken) { - throw new error.ValidationError(result.hostname + ' is already in use'); - } - }); + // Get the domain name and check it against existing records + if (typeof data.domain_name !== 'undefined') { + return internalHost.isHostnameTaken(data.domain_name, 'ssl_passthrough', data.id) + .then((result) => { + if (result.is_taken) { + throw new error.ValidationError(result.hostname + ' is already in use'); + } }); } }).then((/*access_data*/) => { @@ -116,7 +100,7 @@ const internalPassthroughHost = { // Add to audit log return internalAuditLog.add(access, { action: 'updated', - object_type: 'ssl_passthrough_host', + object_type: 'ssl-passthrough-host', object_id: row.id, meta: data }) @@ -207,7 +191,7 @@ const internalPassthroughHost = { // Add to audit log return internalAuditLog.add(access, { action: 'deleted', - object_type: 'ssl_passthrough_host', + object_type: 'ssl-passthrough-host', object_id: row.id, meta: _.omit(row, omissions()) }); @@ -256,7 +240,7 @@ const internalPassthroughHost = { // Add to audit log return internalAuditLog.add(access, { action: 'enabled', - object_type: 'ssl_passthrough_host', + object_type: 'ssl-passthrough-host', object_id: row.id, meta: _.omit(row, omissions()) }); @@ -305,7 +289,7 @@ const internalPassthroughHost = { // Add to audit log return internalAuditLog.add(access, { action: 'disabled', - object_type: 'ssl_passthrough_host', + object_type: 'ssl-passthrough-host', object_id: row.id, meta: _.omit(row, omissions()) }); diff --git a/backend/migrations/20211010141200_ssl_passthrough_host.js b/backend/migrations/20211010141200_ssl_passthrough_host.js index 9a92442b7..325386861 100644 --- a/backend/migrations/20211010141200_ssl_passthrough_host.js +++ b/backend/migrations/20211010141200_ssl_passthrough_host.js @@ -20,13 +20,30 @@ exports.up = function (knex/*, Promise*/) { table.integer('owner_user_id').notNull().unsigned(); table.integer('is_deleted').notNull().unsigned().defaultTo(0); table.string('domain_name').notNull(); - table.string('forward_ip').notNull(); + table.string('forwarding_host').notNull(); table.integer('forwarding_port').notNull().unsigned(); + table.integer('enabled').notNull().unsigned().defaultTo(1); table.json('meta').notNull(); + }).then(() => { + logger.info('[' + migrate_name + '] Table created'); }) - .then(() => { - logger.info('[' + migrate_name + '] Table created'); - }); + .then(() => { + return knex.schema.table('user_permission', (table) => { + table.string('ssl_passthrough_hosts').notNull(); + }) + .then(() => { + return knex('user_permission').update('ssl_passthrough_hosts', knex.ref('streams')); + }) + .then(() => { + return knex.schema.alterTable('user_permission', (table) => { + table.string('ssl_passthrough_hosts').notNullable().alter(); + }); + }) + .then(() => { + logger.info('[' + migrate_name + '] permissions updated'); + }); + }) + ; }; /** @@ -39,8 +56,12 @@ exports.up = function (knex/*, Promise*/) { exports.down = function (knex/*, Promise*/) { logger.info('[' + migrate_name + '] Migrating Down...'); - return knex.schema.dropTable('stream') + return knex.schema.dropTable('stream').then(() => { + return knex.schema.table('user_permission', (table) => { + table.dropColumn('ssl_passthrough_hosts'); + }) + }) .then(function () { - logger.info('[' + migrate_name + '] Table altered'); + logger.info('[' + migrate_name + '] Table altered and permissions updated'); }); }; diff --git a/backend/routes/api/nginx/ssl_passthrough_hosts.js b/backend/routes/api/nginx/ssl_passthrough_hosts.js index 5eb75f710..dfa2eac30 100644 --- a/backend/routes/api/nginx/ssl_passthrough_hosts.js +++ b/backend/routes/api/nginx/ssl_passthrough_hosts.js @@ -73,7 +73,7 @@ router * /api/nginx/ssl-passthrough-hosts/123 */ router - .route('/:ssl_passthrough_host_id') + .route('/:host_id') .options((req, res) => { res.sendStatus(204); }) @@ -86,7 +86,7 @@ router */ .get((req, res, next) => { validator({ - required: ['ssl_passthrough_host_id'], + required: ['host_id'], additionalProperties: false, properties: { host_id: { diff --git a/backend/schema/endpoints/ssl-passthrough-hosts.json b/backend/schema/endpoints/ssl-passthrough-hosts.json index 12306d08d..5c206023d 100644 --- a/backend/schema/endpoints/ssl-passthrough-hosts.json +++ b/backend/schema/endpoints/ssl-passthrough-hosts.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "endpoints/ssl-passthough-hosts", + "$id": "endpoints/ssl-passthrough-hosts", "title": "SSL Passthrough Hosts", "description": "Endpoints relating to SSL Passthrough Hosts", "stability": "stable", diff --git a/backend/setup.js b/backend/setup.js index 4a2f9489b..769ca0600 100644 --- a/backend/setup.js +++ b/backend/setup.js @@ -107,14 +107,15 @@ const setupDefaultUser = () => { }) .then(() => { return userPermissionModel.query().insert({ - user_id: user.id, - visibility: 'all', - proxy_hosts: 'manage', - redirection_hosts: 'manage', - dead_hosts: 'manage', - streams: 'manage', - access_lists: 'manage', - certificates: 'manage', + user_id: user.id, + visibility: 'all', + proxy_hosts: 'manage', + redirection_hosts: 'manage', + dead_hosts: 'manage', + ssl_passthrough_hosts: 'manage', + streams: 'manage', + access_lists: 'manage', + certificates: 'manage', }); }); }) @@ -229,7 +230,7 @@ const setupLogrotation = () => { * @returns {Promise} */ const setupSslPassthrough = () => { - return internalNginx.configure(passthroughHostModel, 'ssl_passthrough_host', {}); + return internalNginx.configure(passthroughHostModel, 'ssl_passthrough_host', {}).then(() => internalNginx.reload()); }; module.exports = function () { diff --git a/backend/templates/ssl_passthrough_host.conf b/backend/templates/ssl_passthrough_host.conf index 9ee872d41..6dd2b34b5 100644 --- a/backend/templates/ssl_passthrough_host.conf +++ b/backend/templates/ssl_passthrough_host.conf @@ -4,16 +4,16 @@ map $ssl_preread_server_name $name { {% for host in all_passthrough_hosts %} -{% if enabled %} - {{ host.domain_name }} ssl_passthrough_{{ host.escaped_name }} +{% if host.enabled %} + {{ host.domain_name }} ssl_passthrough_{{ host.domain_name }}; {% endif %} {% endfor %} default https_default_backend; } {% for host in all_passthrough_hosts %} -{% if enabled %} -upstream ssl_passthrough_{{ host.escaped_name }} { +{% if host.enabled %} +upstream ssl_passthrough_{{ host.domain_name }} { server {{host.forwarding_host}}:{{host.forwarding_port}}; } {% endif %} @@ -34,6 +34,8 @@ server { proxy_pass $name; ssl_preread on; + error_log /data/logs/ssl-passthrough-hosts_error.log warn; + # Custom include /data/nginx/custom/server_ssl_passthrough[.]conf; } \ No newline at end of file diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 4914cd101..4d2e3a1b7 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -23,7 +23,7 @@ services: DB_MYSQL_USER: "npm" DB_MYSQL_PASSWORD: "npm" DB_MYSQL_NAME: "npm" - ENABLE_SSL_PASSTHROUGH: "true" + # ENABLE_SSL_PASSTHROUGH: "true" # DB_SQLITE_FILE: "/data/database.sqlite" # DISABLE_IPV6: "true" volumes: @@ -41,6 +41,8 @@ services: container_name: npm_db networks: - nginx_proxy_manager + ports: + - 33306:3306 environment: MYSQL_ROOT_PASSWORD: "npm" MYSQL_DATABASE: "npm" diff --git a/frontend/js/app/api.js b/frontend/js/app/api.js index af47a1334..d689acd3b 100644 --- a/frontend/js/app/api.js +++ b/frontend/js/app/api.js @@ -516,6 +516,15 @@ module.exports = { }, SslPassthroughHosts: { + /** + * @param {Array} [expand] + * @param {String} [query] + * @returns {Promise} + */ + getFeatureEnabled: function () { + return fetch('get', 'ssl-passthrough-enabled'); + }, + /** * @param {Array} [expand] * @param {String} [query] diff --git a/frontend/js/app/nginx/ssl-passthrough/form.ejs b/frontend/js/app/nginx/ssl-passthrough/form.ejs index 312000283..6ba358651 100644 --- a/frontend/js/app/nginx/ssl-passthrough/form.ejs +++ b/frontend/js/app/nginx/ssl-passthrough/form.ejs @@ -21,7 +21,7 @@
- +
diff --git a/frontend/js/app/nginx/ssl-passthrough/form.js b/frontend/js/app/nginx/ssl-passthrough/form.js index ffaf27558..4fea26f1c 100644 --- a/frontend/js/app/nginx/ssl-passthrough/form.js +++ b/frontend/js/app/nginx/ssl-passthrough/form.js @@ -14,9 +14,7 @@ module.exports = Mn.View.extend({ ui: { form: 'form', forwarding_host: 'input[name="forwarding_host"]', - type_error: '.forward-type-error', buttons: '.modal-footer button', - switches: '.custom-switch-input', cancel: 'button.cancel', save: 'button.save' }, @@ -38,7 +36,6 @@ module.exports = Mn.View.extend({ let data = this.ui.form.serializeJSON(); // Manipulate - data.incoming_port = parseInt(data.incoming_port, 10); data.forwarding_port = parseInt(data.forwarding_port, 10); let method = App.Api.Nginx.SslPassthroughHosts.create; diff --git a/frontend/js/app/nginx/ssl-passthrough/main.ejs b/frontend/js/app/nginx/ssl-passthrough/main.ejs index cf29c2d7d..24adfbfd9 100644 --- a/frontend/js/app/nginx/ssl-passthrough/main.ejs +++ b/frontend/js/app/nginx/ssl-passthrough/main.ejs @@ -10,6 +10,9 @@
+
+ Disabled +
diff --git a/frontend/js/app/nginx/ssl-passthrough/main.js b/frontend/js/app/nginx/ssl-passthrough/main.js index c4419206b..af86d91fd 100644 --- a/frontend/js/app/nginx/ssl-passthrough/main.js +++ b/frontend/js/app/nginx/ssl-passthrough/main.js @@ -11,10 +11,11 @@ module.exports = Mn.View.extend({ template: template, ui: { - list_region: '.list-region', - add: '.add-item', - help: '.help', - dimmer: '.dimmer' + list_region: '.list-region', + add: '.add-item', + help: '.help', + dimmer: '.dimmer', + disabled_info: '#ssl-passthrough-disabled-info' }, regions: { @@ -39,6 +40,16 @@ module.exports = Mn.View.extend({ onRender: function () { let view = this; + view.ui.disabled_info.hide(); + + App.Api.Nginx.SslPassthroughHosts.getFeatureEnabled().then((response) => { + console.debug(response) + if (response.ssl_passthrough_enabled === false) { + view.ui.disabled_info.show(); + } else { + view.ui.disabled_info.hide(); + } + }); App.Api.Nginx.SslPassthroughHosts.getAll(['owner']) .then(response => { @@ -53,7 +64,7 @@ module.exports = Mn.View.extend({ view.showChildView('list_region', new EmptyView({ title: App.i18n('ssl-passthrough-hosts', 'empty'), subtitle: App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), - link: manage ? App.i18n('ssl_passthrough_hosts', 'add') : null, + link: manage ? App.i18n('ssl-passthrough-hosts', 'add') : null, btn_color: 'blue', permission: 'ssl-passthrough-hosts', action: function () { diff --git a/frontend/js/app/ui/menu/main.ejs b/frontend/js/app/ui/menu/main.ejs index 671b4e3be..ae45fe570 100644 --- a/frontend/js/app/ui/menu/main.ejs +++ b/frontend/js/app/ui/menu/main.ejs @@ -20,6 +20,10 @@ <%- i18n('streams', 'title') %> <% } %> + <% if (canShow('ssl_passthrough_hosts')) { %> + <%- i18n('ssl-passthrough-hosts', 'title') %> + <% } %> + <% if (canShow('dead_hosts')) { %> <%- i18n('dead-hosts', 'title') %> <% } %> diff --git a/frontend/js/app/user/permissions.ejs b/frontend/js/app/user/permissions.ejs index b61617960..592c104bd 100644 --- a/frontend/js/app/user/permissions.ejs +++ b/frontend/js/app/user/permissions.ejs @@ -31,9 +31,9 @@
<% - var list = ['proxy-hosts', 'redirection-hosts', 'dead-hosts', 'streams', 'access-lists', 'certificates']; + var list = ['proxy-hosts', 'redirection-hosts', 'dead-hosts', 'streams', 'ssl-passthrough-hosts', 'access-lists', 'certificates']; list.map(function(item) { - var perm = item.replace('-', '_'); + var perm = item.replace(/-/g, '_'); %>
diff --git a/frontend/js/app/user/permissions.js b/frontend/js/app/user/permissions.js index af8049ce8..b03d2db62 100644 --- a/frontend/js/app/user/permissions.js +++ b/frontend/js/app/user/permissions.js @@ -29,12 +29,13 @@ module.exports = Mn.View.extend({ if (view.model.isAdmin()) { // Force some attributes for admin data = _.assign({}, data, { - access_lists: 'manage', - dead_hosts: 'manage', - proxy_hosts: 'manage', - redirection_hosts: 'manage', - streams: 'manage', - certificates: 'manage' + access_lists: 'manage', + dead_hosts: 'manage', + proxy_hosts: 'manage', + redirection_hosts: 'manage', + ssl_passthrough_hosts: 'manage', + streams: 'manage', + certificates: 'manage' }); } diff --git a/frontend/js/i18n/messages.json b/frontend/js/i18n/messages.json index 94a7f2801..a77471971 100644 --- a/frontend/js/i18n/messages.json +++ b/frontend/js/i18n/messages.json @@ -72,6 +72,7 @@ "enable-ssl": "Enable SSL", "force-ssl": "Force SSL", "http2-support": "HTTP/2 Support", + "domain-name": "Domain Name", "domain-names": "Domain Names", "cert-provider": "Certificate Provider", "block-exploits": "Block Common Exploits", @@ -125,8 +126,8 @@ "forwarding-port": "Forward Port", "delete": "Delete SSL Passthrough Host", "delete-confirm": "Are you sure you want to delete this SSL Passthrough Host?", - "help-title": "What is a SSL Passthrough Host?", - "help-content": "An SSL Passthrough Host will allow you to proxy a server without SSL termination. This means the SSL encryption of the server will be passed right through the proxy, retaining the upstream certificates.\nThough this also means the proxy does not know anything about the traffic, and it just relies on an SSL feature called Server Name Indication, to know where to send this packet. This also means, if the client does not provide this additional information, accessing the site through the proxy won't be possible. However most modern browsers include this information in HTTP requests.\n\nHowever using SSL Passthrough comes with a performance penalty, since all hosts (including normal proxy hosts) now have to pass through this additional step of checking the destination. If you do not need your service to be available on port 443, it is recommended to use a stream host instead." + "help-title": "What is an SSL Passthrough Host?", + "help-content": "An SSL Passthrough Host will allow you to proxy a server without SSL termination. This means the SSL encryption of the server will be passed right through the proxy, retaining the upstream certificate.\n Because of the SSL encryption the proxy does not know anything about the traffic, and it just relies on an SSL feature called Server Name Indication to know where to send this packet. This also means if the client does not provide this additional information, accessing the site through the proxy won't be possible. But most modern browsers include this information in HTTP requests.\n\nDue to nginx constraints using SSL Passthrough comes with a performance penalty for other hosts, since all hosts (including normal proxy hosts) now have to pass through this additional step and basically being proxied twice. If you want to retain the upstream SSL certificate but do not need your service to be available on port 443, it is recommended to use a stream host instead." }, "proxy-hosts": { "title": "Proxy Hosts",