From 02d3093d88e828f0014399feadcad1ee1b1ba80c Mon Sep 17 00:00:00 2001 From: chaptergy Date: Tue, 12 Oct 2021 15:25:46 +0200 Subject: [PATCH] Finalizes SSL Passthrough hosts --- backend/internal/nginx.js | 53 ++++++++++++++++--- backend/routes/api/main.js | 2 +- backend/setup.js | 2 +- docker/rootfs/etc/nginx/nginx.conf | 2 +- docs/advanced-config/README.md | 25 +++++++++ frontend/js/app/controller.js | 2 +- .../js/app/nginx/ssl-passthrough/form.ejs | 2 +- frontend/js/app/nginx/ssl-passthrough/form.js | 8 +-- .../app/nginx/ssl-passthrough/list/main.ejs | 2 +- .../js/app/nginx/ssl-passthrough/main.ejs | 8 +-- frontend/js/app/nginx/ssl-passthrough/main.js | 3 +- frontend/js/i18n/messages.json | 2 +- frontend/scss/custom.scss | 9 ++++ 13 files changed, 95 insertions(+), 25 deletions(-) diff --git a/backend/internal/nginx.js b/backend/internal/nginx.js index 2c0c5f42..0bf31ac2 100644 --- a/backend/internal/nginx.js +++ b/backend/internal/nginx.js @@ -25,6 +25,7 @@ const internalNginx = { */ configure: (model, host_type, host) => { let combined_meta = {}; + const sslPassthroughEnabled = internalNginx.sslPassthroughEnabled(); return internalNginx.test() .then(() => { @@ -33,7 +34,25 @@ const internalNginx = { return internalNginx.deleteConfig(host_type, host); // Don't throw errors, as the file may not exist at all }) .then(() => { - return internalNginx.generateConfig(host_type, host); + if(host_type === 'ssl_passthrough_host' && !sslPassthroughEnabled){ + // ssl passthrough is disabled + const meta = { + nginx_online: false, + nginx_err: 'SSL passthrough is not enabled in environment' + }; + + return passthroughHostModel + .query() + .where('is_deleted', 0) + .andWhere('enabled', 1) + .patch({ + meta + }).then(() => { + return internalNginx.deleteConfig('ssl_passthrough_host', host, false); + }); + } else { + return internalNginx.generateConfig(host_type, host); + } }) .then(() => { // Test nginx again and update meta with result @@ -46,11 +65,17 @@ const internalNginx = { }); if(host_type === 'ssl_passthrough_host'){ - return passthroughHostModel - .query() - .patch({ - meta: combined_meta - }); + // If passthrough is disabled we have already marked the hosts as offline + if (sslPassthroughEnabled) { + return passthroughHostModel + .query() + .where('is_deleted', 0) + .andWhere('enabled', 1) + .patch({ + meta: combined_meta + }); + } + return Promise.resolve(); } return model @@ -84,6 +109,18 @@ const internalNginx = { nginx_err: valid_lines.join('\n') }); + if(host_type === 'ssl_passthrough_host'){ + return passthroughHostModel + .query() + .where('is_deleted', 0) + .andWhere('enabled', 1) + .patch({ + meta: combined_meta + }).then(() => { + return internalNginx.deleteConfig('ssl_passthrough_host', host, true); + }); + } + return model .query() .where('id', host.id) @@ -241,7 +278,7 @@ const internalNginx = { }), } } else { - internalNginx.deleteConfig(host_type, host) + internalNginx.deleteConfig(host_type, host, false) } } else if (host_type !== 'default') { @@ -470,7 +507,7 @@ const internalNginx = { return (enabled === 'on' || enabled === 'true' || enabled === '1' || enabled === 'yes'); } - return true; + return false; }, /** diff --git a/backend/routes/api/main.js b/backend/routes/api/main.js index 48b095a8..3ccf9398 100644 --- a/backend/routes/api/main.js +++ b/backend/routes/api/main.js @@ -42,7 +42,7 @@ router.use('/nginx/certificates', require('./nginx/certificates')); router.get('/ssl-passthrough-enabled', (req, res/*, next*/) => { res.status(200).send({ - status: 'OK', + status: 'OK', ssl_passthrough_enabled: internalNginx.sslPassthroughEnabled() }); }); diff --git a/backend/setup.js b/backend/setup.js index 12fec3dc..f0eb8f89 100644 --- a/backend/setup.js +++ b/backend/setup.js @@ -230,7 +230,7 @@ const setupLogrotation = () => { * @returns {Promise} */ const setupSslPassthrough = () => { - return internalNginx.configure(passthroughHostModel, 'ssl_passthrough_host', {}).then(() => internalNginx.reload()); + return internalNginx.configure(passthroughHostModel, 'ssl_passthrough_host', {}); }; module.exports = function () { diff --git a/docker/rootfs/etc/nginx/nginx.conf b/docker/rootfs/etc/nginx/nginx.conf index 4b9fef5e..b56682d3 100644 --- a/docker/rootfs/etc/nginx/nginx.conf +++ b/docker/rootfs/etc/nginx/nginx.conf @@ -85,7 +85,7 @@ http { stream { # Files generated by NPM - include /data/nginx/ssl_passthrough_host/hosts.conf; + include /data/nginx/ssl_passthrough_host/hosts[.]conf; include /data/nginx/stream/*.conf; # Custom diff --git a/docs/advanced-config/README.md b/docs/advanced-config/README.md index c7b51a84..b0595c31 100644 --- a/docs/advanced-config/README.md +++ b/docs/advanced-config/README.md @@ -172,3 +172,28 @@ value by specifying it as a Docker environment variable. The default if not spec X_FRAME_OPTIONS: "sameorigin" ... ``` + +## SSL Passthrough + +SSL Passthrough will allow you to proxy a server without [SSL termination](https://en.wikipedia.org/wiki/TLS_termination_proxy). This means the SSL encryption of the server will be passed right through the proxy, retaining the original certificate. + +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](https://en.wikipedia.org/wiki/Server_Name_Indication) to know where to send this network 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 a HTTPS requests. + +Due 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. + +To enable SSL Passthrough on your npm instance you need to do two things: add the environment variable `ENABLE_SSL_PASSTHROUGH` with the value `"true"`, and expose port 444 instead of 443 to the outside as port 443. + +```yml +version: '3' +services: + app: + ... + ports: + - '80:80' + - '81:81' + - '443:444' # Expose internal port 444 instead of 443 as SSL port + environment: + ... + ENABLE_SSL_PASSTHROUGH: "true" # Enable SSL Passthrough + ... +``` \ No newline at end of file diff --git a/frontend/js/app/controller.js b/frontend/js/app/controller.js index abac53ad..72accc01 100644 --- a/frontend/js/app/controller.js +++ b/frontend/js/app/controller.js @@ -253,7 +253,7 @@ module.exports = { * * @param model */ - showNginxSslPassthroughConfirm: function (model) { + showNginxSslPassthroughDeleteConfirm: function (model) { if (Cache.User.isAdmin() || Cache.User.canManage('ssl_passthrough_hosts')) { require(['./main', './nginx/ssl-passthrough/delete'], function (App, View) { App.UI.showModalDialog(new View({model: model})); diff --git a/frontend/js/app/nginx/ssl-passthrough/form.ejs b/frontend/js/app/nginx/ssl-passthrough/form.ejs index 6ba35865..30a26a54 100644 --- a/frontend/js/app/nginx/ssl-passthrough/form.ejs +++ b/frontend/js/app/nginx/ssl-passthrough/form.ejs @@ -8,7 +8,7 @@
- +
diff --git a/frontend/js/app/nginx/ssl-passthrough/form.js b/frontend/js/app/nginx/ssl-passthrough/form.js index 4fea26f1..522c3703 100644 --- a/frontend/js/app/nginx/ssl-passthrough/form.js +++ b/frontend/js/app/nginx/ssl-passthrough/form.js @@ -12,11 +12,11 @@ module.exports = Mn.View.extend({ className: 'modal-dialog', ui: { - form: 'form', + form: 'form', forwarding_host: 'input[name="forwarding_host"]', - buttons: '.modal-footer button', - cancel: 'button.cancel', - save: 'button.save' + buttons: '.modal-footer button', + cancel: 'button.cancel', + save: 'button.save' }, events: { diff --git a/frontend/js/app/nginx/ssl-passthrough/list/main.ejs b/frontend/js/app/nginx/ssl-passthrough/list/main.ejs index 79268f06..80c7d173 100644 --- a/frontend/js/app/nginx/ssl-passthrough/list/main.ejs +++ b/frontend/js/app/nginx/ssl-passthrough/list/main.ejs @@ -1,6 +1,6 @@   - <%- i18n('ssl-passthrough-hosts', 'domain-name') %> + <%- i18n('all-hosts', 'domain-name') %> <%- i18n('str', 'destination') %> <%- i18n('str', 'status') %> <% if (canManage) { %> diff --git a/frontend/js/app/nginx/ssl-passthrough/main.ejs b/frontend/js/app/nginx/ssl-passthrough/main.ejs index 24adfbfd..5fd4429c 100644 --- a/frontend/js/app/nginx/ssl-passthrough/main.ejs +++ b/frontend/js/app/nginx/ssl-passthrough/main.ejs @@ -1,17 +1,17 @@
-
+

<%- i18n('ssl-passthrough-hosts', 'title') %>

-
- Disabled +
+ <%= i18n('ssl-passthrough-hosts', 'is-disabled-warning', {url: 'https://nginxproxymanager.com/advanced-config/#ssl-passthrough'}) %>
diff --git a/frontend/js/app/nginx/ssl-passthrough/main.js b/frontend/js/app/nginx/ssl-passthrough/main.js index af86d91f..0ceb08be 100644 --- a/frontend/js/app/nginx/ssl-passthrough/main.js +++ b/frontend/js/app/nginx/ssl-passthrough/main.js @@ -43,7 +43,6 @@ module.exports = Mn.View.extend({ 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 { @@ -65,7 +64,7 @@ module.exports = Mn.View.extend({ 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, - btn_color: 'blue', + btn_color: 'dark', permission: 'ssl-passthrough-hosts', action: function () { App.Controller.showNginxSslPassthroughForm(); diff --git a/frontend/js/i18n/messages.json b/frontend/js/i18n/messages.json index a7747197..eda9087d 100644 --- a/frontend/js/i18n/messages.json +++ b/frontend/js/i18n/messages.json @@ -121,11 +121,11 @@ "empty": "There are no SSL Passthrough Hosts", "add": "Add SSL Passthrough Hosts", "form-title": "{id, select, undefined{New} other{Edit}} SSL Passthrough Host", - "domain-name": "Domain Name", "forwarding-host": "Forward Host", "forwarding-port": "Forward Port", "delete": "Delete SSL Passthrough Host", "delete-confirm": "Are you sure you want to delete this SSL Passthrough Host?", + "is-disabled-warning": "SSL Passthrough Hosts are not enabled in the environment. Please see the docs for more information.", "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." }, diff --git a/frontend/scss/custom.scss b/frontend/scss/custom.scss index 4037dcf6..284798f5 100644 --- a/frontend/scss/custom.scss +++ b/frontend/scss/custom.scss @@ -12,6 +12,15 @@ a:hover { color: darken($primary-color, 10%); } +.alert-danger a { + color: #6b1110; + text-decoration: underline; +} + +a:hover { + color: darken(#6b1110, 10%); +} + .dropdown-header { padding-left: 1rem; }