WIP: started adding new host type ssl passthrough

This commit is contained in:
chaptergy
2021-10-10 23:49:07 +02:00
parent 85128f08f3
commit 5b1f0cead1
35 changed files with 1743 additions and 49 deletions

View File

@ -1,7 +1,8 @@
const _ = require('lodash');
const proxyHostModel = require('../models/proxy_host');
const redirectionHostModel = require('../models/redirection_host');
const deadHostModel = require('../models/dead_host');
const _ = require('lodash');
const proxyHostModel = require('../models/proxy_host');
const redirectionHostModel = require('../models/redirection_host');
const deadHostModel = require('../models/dead_host');
const sslPassthroughHostModel = require('../models/ssl_passthrough_host');
const internalHost = {
@ -81,6 +82,9 @@ const internalHost = {
.query()
.where('is_deleted', 0),
deadHostModel
.query()
.where('is_deleted', 0),
sslPassthroughHostModel
.query()
.where('is_deleted', 0)
];
@ -112,6 +116,12 @@ const internalHost = {
response_object.total_count += response_object.dead_hosts.length;
}
if (promises_results[3]) {
// SSL Passthrough Hosts
response_object.ssl_passthrough_hosts = internalHost._getHostsWithDomains(promises_results[3], domain_names);
response_object.total_count += response_object.ssl_passthrough_hosts.length;
}
return response_object;
});
},
@ -137,7 +147,11 @@ const internalHost = {
deadHostModel
.query()
.where('is_deleted', 0)
.andWhere('domain_names', 'like', '%' + hostname + '%')
.andWhere('domain_names', 'like', '%' + hostname + '%'),
sslPassthroughHostModel
.query()
.where('is_deleted', 0)
.andWhere('domain_name', '=', hostname),
];
return Promise.all(promises)
@ -165,6 +179,13 @@ const internalHost = {
}
}
if (promises_results[3]) {
// SSL Passthrough Hosts
if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[3], ignore_type === 'ssl_passthrough' && ignore_id ? ignore_id : 0)) {
is_taken = true;
}
}
return {
hostname: hostname,
is_taken: is_taken

View File

@ -1,10 +1,11 @@
const _ = require('lodash');
const fs = require('fs');
const logger = require('../logger').nginx;
const utils = require('../lib/utils');
const error = require('../lib/error');
const { Liquid } = require('liquidjs');
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
const _ = require('lodash');
const fs = require('fs');
const logger = require('../logger').nginx;
const utils = require('../lib/utils');
const error = require('../lib/error');
const { Liquid } = require('liquidjs');
const passthroughHostModel = require('../models/ssl_passthrough_host');
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
const internalNginx = {
@ -44,12 +45,21 @@ const internalNginx = {
nginx_err: null
});
if(host_type === 'ssl_passthrough_host'){
return passthroughHostModel
.query()
.patch({
meta: combined_meta
});
}
return model
.query()
.where('id', host.id)
.patch({
meta: combined_meta
});
})
.catch((err) => {
// Remove the error_log line because it's a docker-ism false positive that doesn't need to be reported.
@ -125,6 +135,8 @@ const internalNginx = {
if (host_type === 'default') {
return '/data/nginx/default_host/site.conf';
} else if (host_type === 'ssl_passthrough_host') {
return '/data/nginx/ssl_passthrough_host/hosts.conf';
}
return '/data/nginx/' + host_type + '/' + host_id + '.conf';
@ -199,7 +211,7 @@ const internalNginx = {
root: __dirname + '/../templates/'
});
return new Promise((resolve, reject) => {
return new Promise(async (resolve, reject) => {
let template = null;
let filename = internalNginx.getConfigName(host_type, host.id);
@ -214,7 +226,25 @@ const internalNginx = {
let origLocations;
// Manipulate the data a bit before sending it to the template
if (host_type !== 'default') {
if (host_type === 'ssl_passthrough_host') {
if(internalNginx.sslPassthroughEnabled()){
const allHosts = await passthroughHostModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.omit(['is_deleted']);
host = {
all_passthrough_hosts: allHosts.map((host) => {
// Replace dots in domain
host.escaped_name = host.domain_name.replace(/\./, '_');
host.forwarding_host = internalNginx.addIpv6Brackets(host.forwarding_host);
}),
}
} else {
internalNginx.deleteConfig(host_type, host)
}
} else if (host_type !== 'default') {
host.use_default_location = true;
if (typeof host.advanced_config !== 'undefined' && host.advanced_config) {
host.use_default_location = !internalNginx.advancedConfigHasDefaultLocation(host.advanced_config);
@ -429,6 +459,33 @@ const internalNginx = {
}
return true;
},
/**
* @returns {boolean}
*/
sslPassthroughEnabled: function () {
if (typeof process.env.ENABLE_SSL_PASSTHROUGH !== 'undefined') {
const enabled = process.env.ENABLE_SSL_PASSTHROUGH.toLowerCase();
return (enabled === 'on' || enabled === 'true' || enabled === '1' || enabled === 'yes');
}
return true;
},
/**
* Helper function to add brackets to an IP if it is IPv6
* @returns {string}
*/
addIpv6Brackets: function (ip) {
// Only run check if ipv6 is enabled
if (internalNginx.ipv6Enabled()) {
const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/gi;
if(ipv6Regex.test(ip)){
return `[${ip}]`
}
}
return ip;
}
};

View File

@ -0,0 +1,381 @@
const _ = require('lodash');
const error = require('../lib/error');
const passthroughHostModel = require('../models/ssl_passthrough_host');
const internalHost = require('./host');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
function omissions () {
return ['is_deleted'];
}
const internalPassthroughHost = {
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create: (access, data) => {
return access.can('ssl_passthrough_hosts:create', data)
.then(() => {
// Get a list of the domain names and check each of them against existing records
let domain_name_check_promises = [];
data.domain_names.map(function (domain_name) {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name));
});
return Promise.all(domain_name_check_promises)
.then((check_results) => {
check_results.map(function (result) {
if (result.is_taken) {
throw new error.ValidationError(result.hostname + ' is already in use');
}
});
});
}).then((/*access_data*/) => {
data.owner_user_id = access.token.getUserId(1);
if (typeof data.meta === 'undefined') {
data.meta = {};
}
return passthroughHostModel
.query()
.omit(omissions())
.insertAndFetch(data);
})
.then((row) => {
// Configure nginx
return internalNginx.configure(passthroughHostModel, 'ssl_passthrough_host', {})
.then(() => {
return internalPassthroughHost.get(access, {id: row.id, expand: ['owner']});
});
})
.then((row) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'ssl_passthrough_host',
object_id: row.id,
meta: data
})
.then(() => {
return row;
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @return {Promise}
*/
update: (access, data) => {
return access.can('ssl_passthrough_hosts:update', data.id)
.then((/*access_data*/) => {
// Get a list of the domain names and check each of them against existing records
let domain_name_check_promises = [];
if (typeof data.domain_names !== 'undefined') {
data.domain_names.map(function (domain_name) {
domain_name_check_promises.push(internalHost.isHostnameTaken(domain_name, 'ssl_passthrough', data.id));
});
return Promise.all(domain_name_check_promises)
.then((check_results) => {
check_results.map(function (result) {
if (result.is_taken) {
throw new error.ValidationError(result.hostname + ' is already in use');
}
});
});
}
}).then((/*access_data*/) => {
return internalPassthroughHost.get(access, {id: data.id});
})
.then((row) => {
if (row.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('SSL Passthrough Host could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
}
return passthroughHostModel
.query()
.omit(omissions())
.patchAndFetchById(row.id, data)
.then(() => {
return internalNginx.configure(passthroughHostModel, 'ssl_passthrough_host', {})
.then(() => {
return internalPassthroughHost.get(access, {id: row.id, expand: ['owner']});
});
})
.then((saved_row) => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'ssl_passthrough_host',
object_id: row.id,
meta: data
})
.then(() => {
return _.omit(saved_row, omissions());
});
});
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {Array} [data.expand]
* @param {Array} [data.omit]
* @return {Promise}
*/
get: (access, data) => {
if (typeof data === 'undefined') {
data = {};
}
return access.can('ssl_passthrough_hosts:get', data.id)
.then((access_data) => {
let query = passthroughHostModel
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowEager('[owner]')
.first();
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
}
// Custom omissions
if (typeof data.omit !== 'undefined' && data.omit !== null) {
query.omit(data.omit);
}
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.eager('[' + data.expand.join(', ') + ']');
}
return query;
})
.then((row) => {
if (row) {
return _.omit(row, omissions());
} else {
throw new error.ItemNotFoundError(data.id);
}
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
delete: (access, data) => {
return access.can('ssl_passthrough_hosts:delete', data.id)
.then(() => {
return internalPassthroughHost.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
}
return passthroughHostModel
.query()
.where('id', row.id)
.patch({
is_deleted: 1
})
.then(() => {
// Update Nginx Config
return internalNginx.configure(passthroughHostModel, 'ssl_passthrough_host', {})
.then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'ssl_passthrough_host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
enable: (access, data) => {
return access.can('ssl_passthrough_hosts:update', data.id)
.then(() => {
return internalPassthroughHost.get(access, {
id: data.id,
expand: ['owner']
});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) {
throw new error.ValidationError('Host is already enabled');
}
row.enabled = 1;
return passthroughHostModel
.query()
.where('id', row.id)
.patch({
enabled: 1
})
.then(() => {
// Configure nginx
return internalNginx.configure(passthroughHostModel, 'ssl_passthrough_host', {});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'enabled',
object_type: 'ssl_passthrough_host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Number} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
disable: (access, data) => {
return access.can('ssl_passthrough_hosts:update', data.id)
.then(() => {
return internalPassthroughHost.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) {
throw new error.ValidationError('Host is already disabled');
}
row.enabled = 0;
return passthroughHostModel
.query()
.where('id', row.id)
.patch({
enabled: 0
})
.then(() => {
// Update Nginx Config
return internalNginx.configure(passthroughHostModel, 'ssl_passthrough_host', {})
.then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'disabled',
object_type: 'ssl_passthrough_host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
return true;
});
},
/**
* All SSL Passthrough Hosts
*
* @param {Access} access
* @param {Array} [expand]
* @param {String} [search_query]
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('ssl_passthrough_hosts:list')
.then((access_data) => {
let query = passthroughHostModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.omit(['is_deleted'])
.allowEager('[owner]')
.orderBy('domain_name', 'ASC');
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
}
// Query is used for searching
if (typeof search_query === 'string') {
query.where(function () {
this.where('domain_name', 'like', '%' + search_query + '%');
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.eager('[' + expand.join(', ') + ']');
}
return query;
});
},
/**
* Report use
*
* @param {Number} user_id
* @param {String} visibility
* @returns {Promise}
*/
getCount: (user_id, visibility) => {
let query = passthroughHostModel
.query()
.count('id as count')
.where('is_deleted', 0);
if (visibility !== 'all') {
query.andWhere('owner_user_id', user_id);
}
return query.first()
.then((row) => {
return parseInt(row.count, 10);
});
}
};
module.exports = internalPassthroughHost;

View File

@ -62,14 +62,15 @@ const internalUser = {
return userPermissionModel
.query()
.insert({
user_id: user.id,
visibility: is_admin ? 'all' : 'user',
proxy_hosts: 'manage',
redirection_hosts: 'manage',
dead_hosts: 'manage',
streams: 'manage',
access_lists: 'manage',
certificates: 'manage'
user_id: user.id,
visibility: is_admin ? 'all' : 'user',
proxy_hosts: 'manage',
redirection_hosts: 'manage',
dead_hosts: 'manage',
ssl_passthrough_hosts: 'manage',
streams: 'manage',
access_lists: 'manage',
certificates: 'manage'
})
.then(() => {
return internalUser.get(access, {id: user.id, expand: ['permissions']});