Skip to content

SSL passthrough hosts #1479

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Finalizes SSL Passthrough hosts
  • Loading branch information
chaptergy committed Oct 12, 2021
commit 02d3093d88e828f0014399feadcad1ee1b1ba80c
53 changes: 45 additions & 8 deletions backend/internal/nginx.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const internalNginx = {
*/
configure: (model, host_type, host) => {
let combined_meta = {};
const sslPassthroughEnabled = internalNginx.sslPassthroughEnabled();

return internalNginx.test()
.then(() => {
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -241,7 +278,7 @@ const internalNginx = {
}),
}
} else {
internalNginx.deleteConfig(host_type, host)
internalNginx.deleteConfig(host_type, host, false)
}

} else if (host_type !== 'default') {
Expand Down Expand Up @@ -470,7 +507,7 @@ const internalNginx = {
return (enabled === 'on' || enabled === 'true' || enabled === '1' || enabled === 'yes');
}

return true;
return false;
},

/**
Expand Down
2 changes: 1 addition & 1 deletion backend/routes/api/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
});
});
Expand Down
2 changes: 1 addition & 1 deletion backend/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
2 changes: 1 addition & 1 deletion docker/rootfs/etc/nginx/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions docs/advanced-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
...
```
2 changes: 1 addition & 1 deletion frontend/js/app/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}));
Expand Down
2 changes: 1 addition & 1 deletion frontend/js/app/nginx/ssl-passthrough/form.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('all-hosts', '___domain-names') %> <span class="form-required">*</span></label>
<label class="form-label"><%- i18n('all-hosts', '___domain-name') %> <span class="form-required">*</span></label>
<input type="text" name="domain_name" class="form-control" id="input-___domain" placeholder="example.com" value="<%- domain_name %>" required>
</div>
</div>
Expand Down
8 changes: 4 additions & 4 deletions frontend/js/app/nginx/ssl-passthrough/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
2 changes: 1 addition & 1 deletion frontend/js/app/nginx/ssl-passthrough/list/main.ejs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<thead>
<th width="30">&nbsp;</th>
<th><%- i18n('ssl-passthrough-hosts', '___domain-name') %></th>
<th><%- i18n('all-hosts', '___domain-name') %></th>
<th><%- i18n('str', 'destination') %></th>
<th><%- i18n('str', 'status') %></th>
<% if (canManage) { %>
Expand Down
8 changes: 4 additions & 4 deletions frontend/js/app/nginx/ssl-passthrough/main.ejs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<div class="card">
<div class="card-status bg-blue"></div>
<div class="card-status bg-dark"></div>
<div class="card-header">
<h3 class="card-title"><%- i18n('ssl-passthrough-hosts', 'title') %></h3>
<div class="card-options">
<a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a>
<% if (showAddButton) { %>
<a href="#" class="btn btn-outline-blue btn-sm ml-2 add-item"><%- i18n('ssl-passthrough-hosts', 'add') %></a>
<a href="#" class="btn btn-outline-dark btn-sm ml-2 add-item"><%- i18n('ssl-passthrough-hosts', 'add') %></a>
<% } %>
</div>
</div>
<div class="card-body no-padding min-100">
<div id="ssl-passthrough-disabled-info">
Disabled
<div id="ssl-passthrough-disabled-info" class="alert alert-danger rounded-0 mb-0">
<%= i18n('ssl-passthrough-hosts', 'is-disabled-warning', {url: 'https://nginxproxymanager.com/advanced-config/#ssl-passthrough'}) %>
</div>
<div class="dimmer active">
<div class="loader"></div>
Expand Down
3 changes: 1 addition & 2 deletions frontend/js/app/nginx/ssl-passthrough/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion frontend/js/i18n/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href=\"{url}\" target=\"_blank\">the docs</a> 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."
},
Expand Down
9 changes: 9 additions & 0 deletions frontend/scss/custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down