From b57d1e5a66ebf9d3579c551089540e07d17a5f34 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Fri, 16 Feb 2018 16:57:54 +1000 Subject: [PATCH] Added Stream forwarding support --- manager/package.json | 2 +- manager/src/backend/internal/host.js | 61 ++++++++++++------ manager/src/backend/internal/nginx.js | 4 ++ .../src/backend/schema/endpoints/hosts.json | 34 +++++++++- manager/src/backend/templates/stream.conf.ejs | 11 ++++ manager/src/frontend/js/app/controller.js | 11 ++++ .../src/frontend/js/app/dashboard/main.ejs | 3 +- manager/src/frontend/js/app/dashboard/main.js | 8 ++- manager/src/frontend/js/app/dashboard/row.ejs | 37 +++++++---- manager/src/frontend/js/app/dashboard/row.js | 3 + .../src/frontend/js/app/host/stream_form.ejs | 55 ++++++++++++++++ .../src/frontend/js/app/host/stream_form.js | 63 +++++++++++++++++++ manager/src/frontend/js/models/host.js | 4 +- rootfs/etc/nginx/nginx.conf | 4 ++ rootfs/etc/services.d/nginx/run | 2 +- 15 files changed, 264 insertions(+), 38 deletions(-) create mode 100644 manager/src/backend/templates/stream.conf.ejs create mode 100644 manager/src/frontend/js/app/host/stream_form.ejs create mode 100644 manager/src/frontend/js/app/host/stream_form.js diff --git a/manager/package.json b/manager/package.json index 4f3bedc3..d6acfa0b 100644 --- a/manager/package.json +++ b/manager/package.json @@ -1,6 +1,6 @@ { "name": "nginx-proxy-manager", - "version": "1.0.1", + "version": "1.1.0", "description": "Nginx proxt with built in Web based management", "main": "src/backend/index.js", "dependencies": { diff --git a/manager/src/backend/internal/host.js b/manager/src/backend/internal/host.js index 1cdb667d..de3a24a8 100644 --- a/manager/src/backend/internal/host.js +++ b/manager/src/backend/internal/host.js @@ -44,19 +44,31 @@ const internalHost = { */ create: payload => { return new Promise((resolve, reject) => { - // Enforce lowercase hostnames - payload.hostname = payload.hostname.toLowerCase(); + let existing_host = false; - // 1. Check that the hostname doesn't already exist - let existing_host = db.hosts.findOne({hostname: payload.hostname}); + if (payload.type === 'stream') { + // Check that the incoming port doesn't already exist + existing_host = db.hosts.findOne({incoming_port: payload.incoming_port}); + + if (payload.incoming_port === 80 || payload.incoming_port === 81 || payload.incoming_port === 443) { + reject(new error.ConfigurationError('Port ' + payload.incoming_port + ' is reserved')); + return; + } + + } else { + payload.hostname = payload.hostname.toLowerCase(); + + // Check that the hostname doesn't already exist + existing_host = db.hosts.findOne({hostname: payload.hostname}); + } if (existing_host) { reject(new error.ValidationError('Hostname already exists')); } else { - // 2. Add host to db + // Add host to db let host = db.hosts.save(payload); - // 3. Fire the config generation for this host + // Fire the config generation for this host internalHost.configure(host, true) .then((/*result*/) => { resolve(host); @@ -98,10 +110,16 @@ const internalHost = { } // Check that the hostname doesn't already exist - let other_host = db.hosts.findOne({hostname: payload.hostname}); + let other_host = false; + + if (typeof payload.incoming_port !== 'undefined') { + other_host = db.hosts.findOne({incoming_port: payload.incoming_port}); + } else { + other_host = db.hosts.findOne({hostname: payload.hostname}); + } if (other_host && other_host._id !== id) { - reject(new error.ValidationError('Hostname already exists')); + reject(new error.ValidationError((other_host.type === 'stream' ? 'Source Stream Port' : 'Hostname') + ' already exists')); } else { // 2. Update host db.hosts.update({_id: id}, payload, {multi: false, upsert: false}); @@ -126,17 +144,22 @@ const internalHost = { return data; }) .then(data => { - if ( - (data.original.ssl && !data.updated.ssl) || // ssl was enabled and is now disabled - (data.original.ssl && data.original.hostname !== data.updated.hostname) // hostname was changed for a previously ssl-enabled host - ) { - // SSL was turned off or hostname for ssl has changed so we should remove certs for the original - return internalSsl.deleteCerts(data.original) - .then(() => { - db.hosts.update({_id: data.updated._id}, {ssl_expires: 0}, {multi: false, upsert: false}); - data.updated.ssl_expires = 0; - return data; - }); + if (data.updated.type !== 'stream') { + if ( + (data.original.ssl && !data.updated.ssl) || // ssl was enabled and is now disabled + (data.original.ssl && data.original.hostname !== data.updated.hostname) // hostname was changed for a previously ssl-enabled host + ) { + // SSL was turned off or hostname for ssl has changed so we should remove certs for the original + return internalSsl.deleteCerts(data.original) + .then(() => { + db.hosts.update({_id: data.updated._id}, {ssl_expires: 0}, { + multi: false, + upsert: false + }); + data.updated.ssl_expires = 0; + return data; + }); + } } return data; diff --git a/manager/src/backend/internal/nginx.js b/manager/src/backend/internal/nginx.js index 1aa40688..c05d50d8 100644 --- a/manager/src/backend/internal/nginx.js +++ b/manager/src/backend/internal/nginx.js @@ -32,6 +32,10 @@ const internalNginx = { * @returns {String} */ getConfigName: host => { + if (host.type === 'stream') { + return '/config/nginx/stream/' + host.incoming_port + '.conf'; + } + return '/config/nginx/' + host.hostname + '.conf'; }, diff --git a/manager/src/backend/schema/endpoints/hosts.json b/manager/src/backend/schema/endpoints/hosts.json index 6e0ed9f9..1949e4f6 100644 --- a/manager/src/backend/schema/endpoints/hosts.json +++ b/manager/src/backend/schema/endpoints/hosts.json @@ -12,7 +12,7 @@ }, "type": { "type": "string", - "pattern": "^(proxy|redirection|404)$" + "pattern": "^(proxy|redirection|404|stream)$" }, "hostname": { "$ref": "../definitions.json#/definitions/hostname" @@ -59,6 +59,17 @@ "access_list": { "type": "object", "readonly": true + }, + "incoming_port": { + "type": "integer", + "minumum": 1, + "maxiumum": 65535 + }, + "protocols": { + "type": "array", + "items": { + "type": "string" + } } }, "links": [ @@ -86,8 +97,7 @@ "schema": { "type": "object", "required": [ - "type", - "hostname" + "type" ], "properties": { "type": { @@ -125,6 +135,12 @@ }, "access_list_id": { "$ref": "#/definitions/access_list_id" + }, + "incoming_port": { + "$ref": "#/definitions/incoming_port" + }, + "protocols": { + "$ref": "#/definitions/protocols" } } }, @@ -181,6 +197,12 @@ }, "access_list_id": { "$ref": "#/definitions/access_list_id" + }, + "incoming_port": { + "$ref": "#/definitions/incoming_port" + }, + "protocols": { + "$ref": "#/definitions/protocols" } } }, @@ -247,6 +269,12 @@ }, "advanced": { "$ref": "#/definitions/advanced" + }, + "incoming_port": { + "$ref": "#/definitions/incoming_port" + }, + "protocols": { + "$ref": "#/definitions/protocols" } } } diff --git a/manager/src/backend/templates/stream.conf.ejs b/manager/src/backend/templates/stream.conf.ejs new file mode 100644 index 00000000..49994a26 --- /dev/null +++ b/manager/src/backend/templates/stream.conf.ejs @@ -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/manager/src/frontend/js/app/controller.js b/manager/src/frontend/js/app/controller.js index 789fd951..5893f5c9 100644 --- a/manager/src/frontend/js/app/controller.js +++ b/manager/src/frontend/js/app/controller.js @@ -86,6 +86,17 @@ module.exports = { }); }, + /** + * Show Stream Host Form + * + * @param model + */ + showStreamHostForm: function (model) { + require(['./main', './host/stream_form'], function (App, View) { + App.UI.showModalDialog(new View({model: model})); + }); + }, + /** * Show Delete Host Confirmation * diff --git a/manager/src/frontend/js/app/dashboard/main.ejs b/manager/src/frontend/js/app/dashboard/main.ejs index 6759348b..6df4f241 100644 --- a/manager/src/frontend/js/app/dashboard/main.ejs +++ b/manager/src/frontend/js/app/dashboard/main.ejs @@ -1,6 +1,6 @@ - + @@ -13,6 +13,7 @@
  • Proxy Host
  • Redirection Host
  • 404 Host
  • +
  • Stream Host
  • diff --git a/manager/src/frontend/js/app/dashboard/main.js b/manager/src/frontend/js/app/dashboard/main.js index 8d1709f6..7897a665 100644 --- a/manager/src/frontend/js/app/dashboard/main.js +++ b/manager/src/frontend/js/app/dashboard/main.js @@ -28,7 +28,8 @@ module.exports = Mn.View.extend({ ui: { new_proxy: 'th .new-proxy', new_redirection: 'th .new-redirection', - new_404: 'th .new-404' + new_404: 'th .new-404', + new_stream: 'th .new-stream' }, events: { @@ -45,6 +46,11 @@ module.exports = Mn.View.extend({ 'click @ui.new_404': function (e) { e.preventDefault(); Controller.show404HostForm(new HostModel.Model); + }, + + 'click @ui.new_stream': function (e) { + e.preventDefault(); + Controller.showStreamHostForm(new HostModel.Model); } }, diff --git a/manager/src/frontend/js/app/dashboard/row.ejs b/manager/src/frontend/js/app/dashboard/row.ejs index 6943c806..5741057e 100644 --- a/manager/src/frontend/js/app/dashboard/row.ejs +++ b/manager/src/frontend/js/app/dashboard/row.ejs @@ -1,7 +1,14 @@ - + diff --git a/manager/src/frontend/js/app/dashboard/row.js b/manager/src/frontend/js/app/dashboard/row.js index 23eab1d0..0c403f99 100644 --- a/manager/src/frontend/js/app/dashboard/row.js +++ b/manager/src/frontend/js/app/dashboard/row.js @@ -32,6 +32,9 @@ module.exports = Mn.View.extend({ case '404': Controller.show404HostForm(this.model); break; + case 'stream': + Controller.showStreamHostForm(this.model); + break; } }, diff --git a/manager/src/frontend/js/app/host/stream_form.ejs b/manager/src/frontend/js/app/host/stream_form.ejs new file mode 100644 index 00000000..9a52b3c2 --- /dev/null +++ b/manager/src/frontend/js/app/host/stream_form.ejs @@ -0,0 +1,55 @@ + diff --git a/manager/src/frontend/js/app/host/stream_form.js b/manager/src/frontend/js/app/host/stream_form.js new file mode 100644 index 00000000..fd0ff38e --- /dev/null +++ b/manager/src/frontend/js/app/host/stream_form.js @@ -0,0 +1,63 @@ +'use strict'; + +import Mn from 'backbone.marionette'; + +const _ = require('lodash'); +const template = require('./stream_form.ejs'); +const Controller = require('../controller'); +const Api = require('../api'); +const App = require('../main'); + +require('jquery-serializejson'); + +module.exports = Mn.View.extend({ + template: template, + + ui: { + form: 'form', + buttons: 'form button' + }, + + events: { + 'submit @ui.form': function (e) { + e.preventDefault(); + let data = _.extend({}, this.ui.form.serializeJSON()); + + data.type = 'stream'; + + // Ports are integers + data.incoming_port = parseInt(data.incoming_port, 10); + data.forward_port = parseInt(data.forward_port, 10); + + if (typeof data.protocols === 'undefined' || !data.protocols.length) { + alert('You must select one or more Protocols'); + return; + } + + this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); + let method = Api.Hosts.create; + + if (this.model.get('_id')) { + // edit + method = Api.Hosts.update; + data._id = this.model.get('_id'); + } + + method(data) + .then((/*result*/) => { + App.UI.closeModal(); + Controller.showDashboard(); + }) + .catch((err) => { + alert(err.message); + this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); + }); + } + }, + + templateContext: { + hasStreamProtocol: function (protocol) { + return this.protocols.indexOf(protocol) !== -1; + } + } +}); diff --git a/manager/src/frontend/js/models/host.js b/manager/src/frontend/js/models/host.js index fe3dcfcb..8856b3dd 100644 --- a/manager/src/frontend/js/models/host.js +++ b/manager/src/frontend/js/models/host.js @@ -20,7 +20,9 @@ const model = Backbone.Model.extend({ letsencrypt_email: '', accept_tos: false, access_list_id: '', - advanced: '' + advanced: '', + incoming_port: 0, + protocols: [] }; } }); diff --git a/rootfs/etc/nginx/nginx.conf b/rootfs/etc/nginx/nginx.conf index 54c3f279..6007c413 100644 --- a/rootfs/etc/nginx/nginx.conf +++ b/rootfs/etc/nginx/nginx.conf @@ -53,3 +53,7 @@ http { include /etc/nginx/conf.d/*.conf; include /config/nginx/*.conf; } + +stream { + include /config/nginx/stream/*.conf; +} diff --git a/rootfs/etc/services.d/nginx/run b/rootfs/etc/services.d/nginx/run index 24052218..29663be5 100755 --- a/rootfs/etc/services.d/nginx/run +++ b/rootfs/etc/services.d/nginx/run @@ -1,5 +1,5 @@ #!/usr/bin/with-contenv bash -mkdir -p /tmp/nginx /config/{nginx,logs,access} /var/lib/nginx/cache/{public,private} +mkdir -p /tmp/nginx /config/{nginx,logs,access} /config/nginx/stream /var/lib/nginx/cache/{public,private} chown root /tmp/nginx exec nginx
    HostnameSource Destination SSL Access List <%- hostname %> + <% if (type === 'stream') { %> + <%- incoming_port %> + <%- protocols.join(', ').toUpperCase() %> + <% } else { %> + <%- hostname %> + <% } %> + - <% if (type === 'proxy') { %> + <% if (type === 'proxy' || type === 'stream') { %> <%- forward_server %>:<%- forward_port %> <% } else if (type === 'redirection') { %> <%- forward_host %> @@ -11,19 +18,27 @@ - <% if (ssl && force_ssl) { %> - Forced - <% } else if (ssl) { %> - Enabled + <% if (type === 'stream') { %> + - <% } else { %> - No + <% if (ssl && force_ssl) { %> + Forced + <% } else if (ssl) { %> + Enabled + <% } else { %> + No + <% } %> <% } %> - <% if (access_list) { %> - <%- access_list.name %> + <% if (type === 'stream') { %> + - <% } else { %> - None + <% if (access_list) { %> + <%- access_list.name %> + <% } else { %> + None + <% } %> <% } %> @@ -31,7 +46,7 @@ <% } %> - +