From c97e6ada5bfba22bfe25587a572795c29ba0f275 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Wed, 12 Dec 2018 09:47:12 +1000 Subject: [PATCH] Support for upstream ssl proxy hosts --- docker-compose.yml | 1 + rootfs/etc/nginx/conf.d/default.conf | 5 ++- rootfs/etc/nginx/conf.d/include/proxy.conf | 2 +- rootfs/etc/nginx/nginx.conf | 5 +++ src/backend/internal/proxy-host.js | 12 +++++- .../20181213013211_forward_scheme.js | 37 ++++++++++++++++++ src/backend/schema/endpoints/proxy-hosts.json | 28 ++++++++++++++ src/backend/templates/proxy_host.conf | 5 ++- src/frontend/js/app/nginx/dead/form.js | 2 +- src/frontend/js/app/nginx/proxy/form.ejs | 24 +++++++++++- src/frontend/js/app/nginx/proxy/form.js | 26 +++++++++---- src/frontend/js/app/nginx/proxy/list/item.ejs | 2 +- src/frontend/js/app/nginx/redirection/form.js | 2 +- src/frontend/js/i18n/messages.json | 4 +- src/frontend/js/models/proxy-host.js | 38 ++++++++++--------- 15 files changed, 155 insertions(+), 38 deletions(-) create mode 100644 src/backend/migrations/20181213013211_forward_scheme.js diff --git a/docker-compose.yml b/docker-compose.yml index ac0f64f9..2ff821b2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,7 @@ services: - NODE_ENV=development - FORCE_COLOR=1 volumes: + - ./data:/data - ./data/letsencrypt:/etc/letsencrypt - .:/app - ./rootfs/etc/nginx:/etc/nginx diff --git a/rootfs/etc/nginx/conf.d/default.conf b/rootfs/etc/nginx/conf.d/default.conf index f24cbaf7..490e2868 100644 --- a/rootfs/etc/nginx/conf.d/default.conf +++ b/rootfs/etc/nginx/conf.d/default.conf @@ -8,8 +8,9 @@ server { include conf.d/include/block-exploits.conf; - set $server 127.0.0.1; - set $port 81; + set $forward_scheme http; + set $server 127.0.0.1; + set $port 81; location /health { access_log off; diff --git a/rootfs/etc/nginx/conf.d/include/proxy.conf b/rootfs/etc/nginx/conf.d/include/proxy.conf index d02653af..b84a4513 100644 --- a/rootfs/etc/nginx/conf.d/include/proxy.conf +++ b/rootfs/etc/nginx/conf.d/include/proxy.conf @@ -3,4 +3,4 @@ proxy_set_header Host $host; proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; -proxy_pass http://$server:$port; +proxy_pass $forward_scheme://$server:$port; diff --git a/rootfs/etc/nginx/nginx.conf b/rootfs/etc/nginx/nginx.conf index 1b6f4fd3..14efa598 100644 --- a/rootfs/etc/nginx/nginx.conf +++ b/rootfs/etc/nginx/nginx.conf @@ -54,6 +54,11 @@ http { # Dynamically generated resolvers file include /etc/nginx/conf.d/include/resolvers.conf; + # Default upstream scheme + map $host $forward_scheme { + default http; + } + # Files generated by NPM include /etc/nginx/conf.d/*.conf; include /data/nginx/proxy_host/*.conf; diff --git a/src/backend/internal/proxy-host.js b/src/backend/internal/proxy-host.js index 7c627b7b..407b01ae 100644 --- a/src/backend/internal/proxy-host.js +++ b/src/backend/internal/proxy-host.js @@ -48,6 +48,11 @@ const internalProxyHost = { // At this point the domains should have been checked data.owner_user_id = access.token.getUserId(1); + // Ignoring upstream ssl errors only applies when upstream scheme is https + if (data.forward_scheme === 'http') { + data.ignore_invalid_upstream_ssl = false; + } + return proxyHostModel .query() .omit(omissions()) @@ -163,7 +168,12 @@ const internalProxyHost = { // Add domain_names to the data in case it isn't there, so that the audit log renders correctly. The order is important here. data = _.assign({}, { domain_names: row.domain_names - },data); + }, data); + + // Ignoring upstream ssl errors only applies when upstream scheme is https + if (typeof data.forward_scheme !== 'undefined' && data.forward_scheme === 'http') { + data.ignore_invalid_upstream_ssl = false; + } return proxyHostModel .query() diff --git a/src/backend/migrations/20181213013211_forward_scheme.js b/src/backend/migrations/20181213013211_forward_scheme.js new file mode 100644 index 00000000..6b74a4de --- /dev/null +++ b/src/backend/migrations/20181213013211_forward_scheme.js @@ -0,0 +1,37 @@ +'use strict'; + +const migrate_name = 'forward_scheme'; +const logger = require('../logger').migrate; + +/** + * Migrate + * + * @see http://knexjs.org/#Schema + * + * @param {Object} knex + * @param {Promise} Promise + * @returns {Promise} + */ +exports.up = function (knex/*, Promise*/) { + logger.info('[' + migrate_name + '] Migrating Up...'); + + return knex.schema.table('proxy_host', function (proxy_host) { + proxy_host.string('forward_scheme').notNull().defaultTo('http'); + proxy_host.integer('ignore_invalid_upstream_ssl').notNull().unsigned().defaultTo(0); + }) + .then(() => { + logger.info('[' + migrate_name + '] proxy_host Table altered'); + }); +}; + +/** + * Undo Migrate + * + * @param {Object} knex + * @param {Promise} Promise + * @returns {Promise} + */ +exports.down = function (knex, Promise) { + logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); + return Promise.resolve(true); +}; diff --git a/src/backend/schema/endpoints/proxy-hosts.json b/src/backend/schema/endpoints/proxy-hosts.json index 4cfb596f..3f457ef7 100644 --- a/src/backend/schema/endpoints/proxy-hosts.json +++ b/src/backend/schema/endpoints/proxy-hosts.json @@ -18,6 +18,10 @@ "domain_names": { "$ref": "../definitions.json#/definitions/domain_names" }, + "forward_scheme": { + "type": "string", + "enum": ["http", "https"] + }, "forward_host": { "type": "string", "minLength": 1, @@ -48,6 +52,11 @@ "example": true, "type": "boolean" }, + "ignore_invalid_upstream_ssl": { + "description": "Ignore invalid upstream SSL certificates", + "example": true, + "type": "boolean" + }, "access_list_id": { "$ref": "../definitions.json#/definitions/access_list_id" }, @@ -71,6 +80,9 @@ "domain_names": { "$ref": "#/definitions/domain_names" }, + "forward_scheme": { + "$ref": "#/definitions/forward_scheme" + }, "forward_host": { "$ref": "#/definitions/forward_host" }, @@ -95,6 +107,9 @@ "allow_websocket_upgrade": { "$ref": "#/definitions/allow_websocket_upgrade" }, + "ignore_invalid_upstream_ssl": { + "$ref": "#/definitions/ignore_invalid_upstream_ssl" + }, "access_list_id": { "$ref": "#/definitions/access_list_id" }, @@ -138,6 +153,7 @@ "additionalProperties": false, "required": [ "domain_names", + "forward_scheme", "forward_host", "forward_port" ], @@ -145,6 +161,9 @@ "domain_names": { "$ref": "#/definitions/domain_names" }, + "forward_scheme": { + "$ref": "#/definitions/forward_scheme" + }, "forward_host": { "$ref": "#/definitions/forward_host" }, @@ -169,6 +188,9 @@ "allow_websocket_upgrade": { "$ref": "#/definitions/allow_websocket_upgrade" }, + "ignore_invalid_upstream_ssl": { + "$ref": "#/definitions/ignore_invalid_upstream_ssl" + }, "access_list_id": { "$ref": "#/definitions/access_list_id" }, @@ -203,6 +225,9 @@ "domain_names": { "$ref": "#/definitions/domain_names" }, + "forward_scheme": { + "$ref": "#/definitions/forward_scheme" + }, "forward_host": { "$ref": "#/definitions/forward_host" }, @@ -227,6 +252,9 @@ "allow_websocket_upgrade": { "$ref": "#/definitions/allow_websocket_upgrade" }, + "ignore_invalid_upstream_ssl": { + "$ref": "#/definitions/ignore_invalid_upstream_ssl" + }, "access_list_id": { "$ref": "#/definitions/access_list_id" }, diff --git a/src/backend/templates/proxy_host.conf b/src/backend/templates/proxy_host.conf index b8a301d7..911fb3cc 100644 --- a/src/backend/templates/proxy_host.conf +++ b/src/backend/templates/proxy_host.conf @@ -1,8 +1,9 @@ {% include "_header_comment.conf" %} server { - set $server "{{ forward_host }}"; - set $port {{ forward_port }}; + set $forward_scheme {{ forward_scheme }}; + set $server "{{ forward_host }}"; + set $port {{ forward_port }}; {% include "_listen.conf" %} {% include "_certificates.conf" %} diff --git a/src/frontend/js/app/nginx/dead/form.js b/src/frontend/js/app/nginx/dead/form.js index 39d08e83..881778e5 100644 --- a/src/frontend/js/app/nginx/dead/form.js +++ b/src/frontend/js/app/nginx/dead/form.js @@ -83,7 +83,7 @@ module.exports = Mn.View.extend({ data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1'; } else { - data.certificate_id = parseInt(data.certificate_id, 0); + data.certificate_id = parseInt(data.certificate_id, 10); } let method = App.Api.Nginx.DeadHosts.create; diff --git a/src/frontend/js/app/nginx/proxy/form.ejs b/src/frontend/js/app/nginx/proxy/form.ejs index eca0ca6d..6cda34fa 100644 --- a/src/frontend/js/app/nginx/proxy/form.ejs +++ b/src/frontend/js/app/nginx/proxy/form.ejs @@ -20,7 +20,16 @@ -
+
+
+ + +
+
+
@@ -50,7 +59,7 @@
-
+
+ +
+
+ +
+
+
diff --git a/src/frontend/js/app/nginx/proxy/form.js b/src/frontend/js/app/nginx/proxy/form.js index 378b9b6f..89410e34 100644 --- a/src/frontend/js/app/nginx/proxy/form.js +++ b/src/frontend/js/app/nginx/proxy/form.js @@ -26,10 +26,20 @@ module.exports = Mn.View.extend({ access_list_select: 'select[name="access_list_id"]', ssl_forced: 'input[name="ssl_forced"]', http2_support: 'input[name="http2_support"]', + forward_scheme: 'select[name="forward_scheme"]', + ignore_ssl: 'input[name="ignore_invalid_upstream_ssl"]', letsencrypt: '.letsencrypt' }, events: { + 'change @ui.forward_scheme': function () { + let val = this.ui.forward_scheme.val(); + this.ui.ignore_ssl + .prop('disabled', val === 'http') + .parents('.form-group') + .css('opacity', val === 'https' ? 1 : 0.5); + }, + 'change @ui.certificate_select': function () { let id = this.ui.certificate_select.val(); if (id === 'new') { @@ -43,8 +53,6 @@ module.exports = Mn.View.extend({ .prop('disabled', !enabled) .parents('.form-group') .css('opacity', enabled ? 1 : 0.5); - - this.ui.http2_support.prop('disabled', !enabled); }, 'click @ui.save': function (e) { @@ -59,10 +67,11 @@ module.exports = Mn.View.extend({ let data = this.ui.form.serializeJSON(); // Manipulate - data.forward_port = parseInt(data.forward_port, 10); - data.block_exploits = !!data.block_exploits; - data.caching_enabled = !!data.caching_enabled; - data.allow_websocket_upgrade = !!data.allow_websocket_upgrade; + data.forward_port = parseInt(data.forward_port, 10); + data.block_exploits = !!data.block_exploits; + data.caching_enabled = !!data.caching_enabled; + data.allow_websocket_upgrade = !!data.allow_websocket_upgrade; + data.ignore_invalid_upstream_ssl = data.forward_scheme === 'https' ? !!data.ignore_invalid_upstream_ssl : false; if (typeof data.ssl_forced !== 'undefined' && data.ssl_forced === '1') { data.ssl_forced = true; @@ -92,7 +101,7 @@ module.exports = Mn.View.extend({ data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1'; } else { - data.certificate_id = parseInt(data.certificate_id, 0); + data.certificate_id = parseInt(data.certificate_id, 10); } let method = App.Api.Nginx.ProxyHosts.create; @@ -147,7 +156,6 @@ module.exports = Mn.View.extend({ }); // Access Lists - this.ui.letsencrypt.hide(); this.ui.access_list_select.selectize({ valueField: 'id', labelField: 'name', @@ -207,6 +215,8 @@ module.exports = Mn.View.extend({ view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); } }); + + this.ui.forward_scheme.trigger('change'); }, initialize: function (options) { diff --git a/src/frontend/js/app/nginx/proxy/list/item.ejs b/src/frontend/js/app/nginx/proxy/list/item.ejs index d6252ddb..d91a598b 100644 --- a/src/frontend/js/app/nginx/proxy/list/item.ejs +++ b/src/frontend/js/app/nginx/proxy/list/item.ejs @@ -23,7 +23,7 @@
-
<%- forward_host %>:<%- forward_port %>
+
<%- forward_scheme %>://<%- forward_host %>:<%- forward_port %>
<%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %>
diff --git a/src/frontend/js/app/nginx/redirection/form.js b/src/frontend/js/app/nginx/redirection/form.js index 971082e0..182ebf68 100644 --- a/src/frontend/js/app/nginx/redirection/form.js +++ b/src/frontend/js/app/nginx/redirection/form.js @@ -86,7 +86,7 @@ module.exports = Mn.View.extend({ data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1'; } else { - data.certificate_id = parseInt(data.certificate_id, 0); + data.certificate_id = parseInt(data.certificate_id, 10); } let method = App.Api.Nginx.RedirectionHosts.create; diff --git a/src/frontend/js/i18n/messages.json b/src/frontend/js/i18n/messages.json index f96c300a..c091439d 100644 --- a/src/frontend/js/i18n/messages.json +++ b/src/frontend/js/i18n/messages.json @@ -93,6 +93,7 @@ "empty": "There are no Proxy Hosts", "add": "Add Proxy Host", "form-title": "{id, select, undefined{New} other{Edit}} Proxy Host", + "forward-scheme": "Scheme", "forward-host": "Forward Hostname / IP", "forward-port": "Forward Port", "delete": "Delete Proxy Host", @@ -100,7 +101,8 @@ "help-title": "What is a Proxy Host?", "help-content": "A Proxy Host is the incoming endpoint for a web service that you want to forward.\nIt provides optional SSL termination for your service that might not have SSL support built in.\nProxy Hosts are the most common use for the Nginx Proxy Manager.", "access-list": "Access List", - "allow-websocket-upgrade": "Allow Websocket HTTP Upgrades" + "allow-websocket-upgrade": "Websockets Support", + "ignore-invalid-upstream-ssl": "Ignore Invalid SSL" }, "redirection-hosts": { "title": "Redirection Hosts", diff --git a/src/frontend/js/models/proxy-host.js b/src/frontend/js/models/proxy-host.js index 2fe7d001..41e45d05 100644 --- a/src/frontend/js/models/proxy-host.js +++ b/src/frontend/js/models/proxy-host.js @@ -7,25 +7,27 @@ const model = Backbone.Model.extend({ defaults: function () { return { - id: undefined, - created_on: null, - modified_on: null, - domain_names: [], - forward_host: '', - forward_port: null, - access_list_id: 0, - certificate_id: 0, - ssl_forced: false, - caching_enabled: false, - allow_websocket_upgrade: false, - block_exploits: false, - http2_support: false, - advanced_config: '', - meta: {}, + id: undefined, + created_on: null, + modified_on: null, + domain_names: [], + forward_scheme: 'http', + forward_host: '', + forward_port: null, + access_list_id: 0, + certificate_id: 0, + ssl_forced: false, + caching_enabled: false, + allow_websocket_upgrade: false, + block_exploits: false, + http2_support: false, + ignore_invalid_upstream_ssl: false, + advanced_config: '', + meta: {}, // The following are expansions: - owner: null, - access_list: null, - certificate: null + owner: null, + access_list: null, + certificate: null }; } });