Support for upstream ssl proxy hosts

This commit is contained in:
Jamie Curnow 2018-12-12 09:47:12 +10:00
parent cd40ca7f0a
commit c97e6ada5b
15 changed files with 155 additions and 38 deletions

View File

@ -11,6 +11,7 @@ services:
- NODE_ENV=development - NODE_ENV=development
- FORCE_COLOR=1 - FORCE_COLOR=1
volumes: volumes:
- ./data:/data
- ./data/letsencrypt:/etc/letsencrypt - ./data/letsencrypt:/etc/letsencrypt
- .:/app - .:/app
- ./rootfs/etc/nginx:/etc/nginx - ./rootfs/etc/nginx:/etc/nginx

View File

@ -8,8 +8,9 @@ server {
include conf.d/include/block-exploits.conf; include conf.d/include/block-exploits.conf;
set $server 127.0.0.1; set $forward_scheme http;
set $port 81; set $server 127.0.0.1;
set $port 81;
location /health { location /health {
access_log off; access_log off;

View File

@ -3,4 +3,4 @@ proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://$server:$port; proxy_pass $forward_scheme://$server:$port;

View File

@ -54,6 +54,11 @@ http {
# Dynamically generated resolvers file # Dynamically generated resolvers file
include /etc/nginx/conf.d/include/resolvers.conf; include /etc/nginx/conf.d/include/resolvers.conf;
# Default upstream scheme
map $host $forward_scheme {
default http;
}
# Files generated by NPM # Files generated by NPM
include /etc/nginx/conf.d/*.conf; include /etc/nginx/conf.d/*.conf;
include /data/nginx/proxy_host/*.conf; include /data/nginx/proxy_host/*.conf;

View File

@ -48,6 +48,11 @@ const internalProxyHost = {
// At this point the domains should have been checked // At this point the domains should have been checked
data.owner_user_id = access.token.getUserId(1); 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 return proxyHostModel
.query() .query()
.omit(omissions()) .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. // 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({}, { data = _.assign({}, {
domain_names: row.domain_names 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 return proxyHostModel
.query() .query()

View File

@ -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);
};

View File

@ -18,6 +18,10 @@
"domain_names": { "domain_names": {
"$ref": "../definitions.json#/definitions/domain_names" "$ref": "../definitions.json#/definitions/domain_names"
}, },
"forward_scheme": {
"type": "string",
"enum": ["http", "https"]
},
"forward_host": { "forward_host": {
"type": "string", "type": "string",
"minLength": 1, "minLength": 1,
@ -48,6 +52,11 @@
"example": true, "example": true,
"type": "boolean" "type": "boolean"
}, },
"ignore_invalid_upstream_ssl": {
"description": "Ignore invalid upstream SSL certificates",
"example": true,
"type": "boolean"
},
"access_list_id": { "access_list_id": {
"$ref": "../definitions.json#/definitions/access_list_id" "$ref": "../definitions.json#/definitions/access_list_id"
}, },
@ -71,6 +80,9 @@
"domain_names": { "domain_names": {
"$ref": "#/definitions/domain_names" "$ref": "#/definitions/domain_names"
}, },
"forward_scheme": {
"$ref": "#/definitions/forward_scheme"
},
"forward_host": { "forward_host": {
"$ref": "#/definitions/forward_host" "$ref": "#/definitions/forward_host"
}, },
@ -95,6 +107,9 @@
"allow_websocket_upgrade": { "allow_websocket_upgrade": {
"$ref": "#/definitions/allow_websocket_upgrade" "$ref": "#/definitions/allow_websocket_upgrade"
}, },
"ignore_invalid_upstream_ssl": {
"$ref": "#/definitions/ignore_invalid_upstream_ssl"
},
"access_list_id": { "access_list_id": {
"$ref": "#/definitions/access_list_id" "$ref": "#/definitions/access_list_id"
}, },
@ -138,6 +153,7 @@
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
"domain_names", "domain_names",
"forward_scheme",
"forward_host", "forward_host",
"forward_port" "forward_port"
], ],
@ -145,6 +161,9 @@
"domain_names": { "domain_names": {
"$ref": "#/definitions/domain_names" "$ref": "#/definitions/domain_names"
}, },
"forward_scheme": {
"$ref": "#/definitions/forward_scheme"
},
"forward_host": { "forward_host": {
"$ref": "#/definitions/forward_host" "$ref": "#/definitions/forward_host"
}, },
@ -169,6 +188,9 @@
"allow_websocket_upgrade": { "allow_websocket_upgrade": {
"$ref": "#/definitions/allow_websocket_upgrade" "$ref": "#/definitions/allow_websocket_upgrade"
}, },
"ignore_invalid_upstream_ssl": {
"$ref": "#/definitions/ignore_invalid_upstream_ssl"
},
"access_list_id": { "access_list_id": {
"$ref": "#/definitions/access_list_id" "$ref": "#/definitions/access_list_id"
}, },
@ -203,6 +225,9 @@
"domain_names": { "domain_names": {
"$ref": "#/definitions/domain_names" "$ref": "#/definitions/domain_names"
}, },
"forward_scheme": {
"$ref": "#/definitions/forward_scheme"
},
"forward_host": { "forward_host": {
"$ref": "#/definitions/forward_host" "$ref": "#/definitions/forward_host"
}, },
@ -227,6 +252,9 @@
"allow_websocket_upgrade": { "allow_websocket_upgrade": {
"$ref": "#/definitions/allow_websocket_upgrade" "$ref": "#/definitions/allow_websocket_upgrade"
}, },
"ignore_invalid_upstream_ssl": {
"$ref": "#/definitions/ignore_invalid_upstream_ssl"
},
"access_list_id": { "access_list_id": {
"$ref": "#/definitions/access_list_id" "$ref": "#/definitions/access_list_id"
}, },

View File

@ -1,8 +1,9 @@
{% include "_header_comment.conf" %} {% include "_header_comment.conf" %}
server { server {
set $server "{{ forward_host }}"; set $forward_scheme {{ forward_scheme }};
set $port {{ forward_port }}; set $server "{{ forward_host }}";
set $port {{ forward_port }};
{% include "_listen.conf" %} {% include "_listen.conf" %}
{% include "_certificates.conf" %} {% include "_certificates.conf" %}

View File

@ -83,7 +83,7 @@ module.exports = Mn.View.extend({
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1'; data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';
} else { } else {
data.certificate_id = parseInt(data.certificate_id, 0); data.certificate_id = parseInt(data.certificate_id, 10);
} }
let method = App.Api.Nginx.DeadHosts.create; let method = App.Api.Nginx.DeadHosts.create;

View File

@ -20,7 +20,16 @@
<input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>" required> <input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>" required>
</div> </div>
</div> </div>
<div class="col-sm-8 col-md-8"> <div class="col-sm-3 col-md-3">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'forward-scheme') %><span class="form-required">*</span></label>
<select name="forward_scheme" class="form-control custom-select" placeholder="http">
<option value="http" <%- forward_scheme === 'http' ? 'selected' : '' %>>http</option>
<option value="https" <%- forward_scheme === 'https' ? 'selected' : '' %>>https</option>
</select>
</div>
</div>
<div class="col-sm-5 col-md-5">
<div class="form-group"> <div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><span class="form-required">*</span></label> <label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><span class="form-required">*</span></label>
<input type="text" name="forward_host" class="form-control text-monospace" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="50" required> <input type="text" name="forward_host" class="form-control text-monospace" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="50" required>
@ -50,7 +59,7 @@
</label> </label>
</div> </div>
</div> </div>
<div class="col-sm-12 col-md-12"> <div class="col-sm-6 col-md-6">
<div class="form-group"> <div class="form-group">
<label class="custom-switch"> <label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="allow_websocket_upgrade" value="1"<%- allow_websocket_upgrade ? ' checked' : '' %>> <input type="checkbox" class="custom-switch-input" name="allow_websocket_upgrade" value="1"<%- allow_websocket_upgrade ? ' checked' : '' %>>
@ -59,6 +68,17 @@
</label> </label>
</div> </div>
</div> </div>
<div class="col-sm-6 col-md-6">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="ignore_invalid_upstream_ssl" value="1"<%- ignore_invalid_upstream_ssl ? ' checked' : '' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('proxy-hosts', 'ignore-invalid-upstream-ssl') %></span>
</label>
</div>
</div>
<div class="col-sm-12 col-md-12"> <div class="col-sm-12 col-md-12">
<div class="form-group"> <div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'access-list') %></label> <label class="form-label"><%- i18n('proxy-hosts', 'access-list') %></label>

View File

@ -26,10 +26,20 @@ module.exports = Mn.View.extend({
access_list_select: 'select[name="access_list_id"]', access_list_select: 'select[name="access_list_id"]',
ssl_forced: 'input[name="ssl_forced"]', ssl_forced: 'input[name="ssl_forced"]',
http2_support: 'input[name="http2_support"]', http2_support: 'input[name="http2_support"]',
forward_scheme: 'select[name="forward_scheme"]',
ignore_ssl: 'input[name="ignore_invalid_upstream_ssl"]',
letsencrypt: '.letsencrypt' letsencrypt: '.letsencrypt'
}, },
events: { 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 () { 'change @ui.certificate_select': function () {
let id = this.ui.certificate_select.val(); let id = this.ui.certificate_select.val();
if (id === 'new') { if (id === 'new') {
@ -43,8 +53,6 @@ module.exports = Mn.View.extend({
.prop('disabled', !enabled) .prop('disabled', !enabled)
.parents('.form-group') .parents('.form-group')
.css('opacity', enabled ? 1 : 0.5); .css('opacity', enabled ? 1 : 0.5);
this.ui.http2_support.prop('disabled', !enabled);
}, },
'click @ui.save': function (e) { 'click @ui.save': function (e) {
@ -59,10 +67,11 @@ module.exports = Mn.View.extend({
let data = this.ui.form.serializeJSON(); let data = this.ui.form.serializeJSON();
// Manipulate // Manipulate
data.forward_port = parseInt(data.forward_port, 10); data.forward_port = parseInt(data.forward_port, 10);
data.block_exploits = !!data.block_exploits; data.block_exploits = !!data.block_exploits;
data.caching_enabled = !!data.caching_enabled; data.caching_enabled = !!data.caching_enabled;
data.allow_websocket_upgrade = !!data.allow_websocket_upgrade; 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') { if (typeof data.ssl_forced !== 'undefined' && data.ssl_forced === '1') {
data.ssl_forced = true; data.ssl_forced = true;
@ -92,7 +101,7 @@ module.exports = Mn.View.extend({
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1'; data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';
} else { } else {
data.certificate_id = parseInt(data.certificate_id, 0); data.certificate_id = parseInt(data.certificate_id, 10);
} }
let method = App.Api.Nginx.ProxyHosts.create; let method = App.Api.Nginx.ProxyHosts.create;
@ -147,7 +156,6 @@ module.exports = Mn.View.extend({
}); });
// Access Lists // Access Lists
this.ui.letsencrypt.hide();
this.ui.access_list_select.selectize({ this.ui.access_list_select.selectize({
valueField: 'id', valueField: 'id',
labelField: 'name', labelField: 'name',
@ -207,6 +215,8 @@ module.exports = Mn.View.extend({
view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id'));
} }
}); });
this.ui.forward_scheme.trigger('change');
}, },
initialize: function (options) { initialize: function (options) {

View File

@ -23,7 +23,7 @@
</div> </div>
</td> </td>
<td> <td>
<div class="text-monospace"><%- forward_host %>:<%- forward_port %></div> <div class="text-monospace"><%- forward_scheme %>://<%- forward_host %>:<%- forward_port %></div>
</td> </td>
<td> <td>
<div><%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %></div> <div><%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %></div>

View File

@ -86,7 +86,7 @@ module.exports = Mn.View.extend({
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1'; data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';
} else { } else {
data.certificate_id = parseInt(data.certificate_id, 0); data.certificate_id = parseInt(data.certificate_id, 10);
} }
let method = App.Api.Nginx.RedirectionHosts.create; let method = App.Api.Nginx.RedirectionHosts.create;

View File

@ -93,6 +93,7 @@
"empty": "There are no Proxy Hosts", "empty": "There are no Proxy Hosts",
"add": "Add Proxy Host", "add": "Add Proxy Host",
"form-title": "{id, select, undefined{New} other{Edit}} Proxy Host", "form-title": "{id, select, undefined{New} other{Edit}} Proxy Host",
"forward-scheme": "Scheme",
"forward-host": "Forward Hostname / IP", "forward-host": "Forward Hostname / IP",
"forward-port": "Forward Port", "forward-port": "Forward Port",
"delete": "Delete Proxy Host", "delete": "Delete Proxy Host",
@ -100,7 +101,8 @@
"help-title": "What is a Proxy Host?", "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.", "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", "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": { "redirection-hosts": {
"title": "Redirection Hosts", "title": "Redirection Hosts",

View File

@ -7,25 +7,27 @@ const model = Backbone.Model.extend({
defaults: function () { defaults: function () {
return { return {
id: undefined, id: undefined,
created_on: null, created_on: null,
modified_on: null, modified_on: null,
domain_names: [], domain_names: [],
forward_host: '', forward_scheme: 'http',
forward_port: null, forward_host: '',
access_list_id: 0, forward_port: null,
certificate_id: 0, access_list_id: 0,
ssl_forced: false, certificate_id: 0,
caching_enabled: false, ssl_forced: false,
allow_websocket_upgrade: false, caching_enabled: false,
block_exploits: false, allow_websocket_upgrade: false,
http2_support: false, block_exploits: false,
advanced_config: '', http2_support: false,
meta: {}, ignore_invalid_upstream_ssl: false,
advanced_config: '',
meta: {},
// The following are expansions: // The following are expansions:
owner: null, owner: null,
access_list: null, access_list: null,
certificate: null certificate: null
}; };
} }
}); });