Certificates ui section and permissions

This commit is contained in:
Jamie Curnow
2018-08-02 19:48:47 +10:00
parent 66e25e315b
commit 1c57ccdc87
65 changed files with 1697 additions and 109 deletions

View File

@ -0,0 +1,183 @@
'use strict';
const _ = require('lodash');
const error = require('../lib/error');
const certificateModel = require('../models/certificate');
function omissions () {
return ['is_deleted'];
}
const internalCertificate = {
/**
* @param {Access} access
* @param {Object} data
* @returns {Promise}
*/
create: (access, data) => {
return access.can('certificates:create', data)
.then(access_data => {
// TODO
return {};
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Integer} data.id
* @param {String} [data.email]
* @param {String} [data.name]
* @return {Promise}
*/
update: (access, data) => {
return access.can('certificates:update', data.id)
.then(access_data => {
// TODO
return {};
});
},
/**
* @param {Access} access
* @param {Object} data
* @param {Integer} data.id
* @param {Array} [data.expand]
* @param {Array} [data.omit]
* @return {Promise}
*/
get: (access, data) => {
if (typeof data === 'undefined') {
data = {};
}
if (typeof data.id === 'undefined' || !data.id) {
data.id = access.token.get('attrs').id;
}
return access.can('certificates:get', data.id)
.then(access_data => {
let query = certificateModel
.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.get('attrs').id);
}
// 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 {Integer} data.id
* @param {String} [data.reason]
* @returns {Promise}
*/
delete: (access, data) => {
return access.can('certificates:delete', data.id)
.then(() => {
return internalCertificate.get(access, {id: data.id});
})
.then(row => {
if (!row) {
throw new error.ItemNotFoundError(data.id);
}
return certificateModel
.query()
.where('id', row.id)
.patch({
is_deleted: 1
});
})
.then(() => {
return true;
});
},
/**
* All Lists
*
* @param {Access} access
* @param {Array} [expand]
* @param {String} [search_query]
* @returns {Promise}
*/
getAll: (access, expand, search_query) => {
return access.can('certificates:list')
.then(access_data => {
let query = certificateModel
.query()
.where('is_deleted', 0)
.groupBy('id')
.omit(['is_deleted'])
.allowEager('[owner]')
.orderBy('name', 'ASC');
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.get('attrs').id);
}
// Query is used for searching
if (typeof search_query === 'string') {
query.where(function () {
this.where('name', 'like', '%' + search_query + '%');
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.eager('[' + expand.join(', ') + ']');
}
return query;
});
},
/**
* Report use
*
* @param {Integer} user_id
* @param {String} visibility
* @returns {Promise}
*/
getCount: (user_id, visibility) => {
let query = certificateModel
.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 = internalCertificate;

View File

@ -4,6 +4,7 @@ const _ = require('lodash');
const error = require('../lib/error');
const deadHostModel = require('../models/dead_host');
const internalHost = require('./host');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
function omissions () {
@ -49,6 +50,13 @@ const internalDeadHost = {
.omit(omissions())
.insertAndFetch(data);
})
.then(row => {
// Configure nginx
return internalNginx.configure(deadHostModel, 'dead_host', row)
.then(() => {
return internalDeadHost.get(access, {id: row.id, expand: ['owner']});
});
})
.then(row => {
// Add to audit log
return internalAuditLog.add(access, {
@ -58,7 +66,7 @@ const internalDeadHost = {
meta: data
})
.then(() => {
return _.omit(row, omissions());
return row;
});
});
},
@ -192,6 +200,13 @@ const internalDeadHost = {
.patch({
is_deleted: 1
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('dead_host', row)
.then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
row.meta = internalHost.cleanMeta(row.meta);

View File

@ -1,18 +1,94 @@
'use strict';
const fs = require('fs');
const Liquid = require('liquidjs');
const logger = require('../logger').nginx;
const utils = require('../lib/utils');
const error = require('../lib/error');
const _ = require('lodash');
const fs = require('fs');
const Liquid = require('liquidjs');
const logger = require('../logger').nginx;
const utils = require('../lib/utils');
const error = require('../lib/error');
const internalSsl = require('./ssl');
const debug_mode = process.env.NODE_ENV !== 'production';
const internalNginx = {
/**
* This will:
* - test the nginx config first to make sure it's OK
* - create / recreate the config for the host
* - test again
* - IF OK: update the meta with online status
* - IF BAD: update the meta with offline status and remove the config entirely
* - then reload nginx
*
* @param {Object} model
* @param {String} host_type
* @param {Object} host
* @returns {Promise}
*/
configure: (model, host_type, host) => {
return internalNginx.test()
.then(() => {
// Nginx is OK
// We're deleting this config regardless.
return internalNginx.deleteConfig(host_type, host); // Don't throw errors, as the file may not exist at all
})
.then(() => {
if (host.ssl && !internalSsl.hasValidSslCerts(host_type, host)) {
return internalSsl.configureSsl(host_type, host);
}
})
.then(() => {
return internalNginx.generateConfig(host_type, host);
})
.then(() => {
// Test nginx again and update meta with result
return internalNginx.test()
.then(() => {
// nginx is ok
return model
.query()
.where('id', host.id)
.patch({
meta: _.assign({}, host.meta, {
nginx_online: true,
nginx_err: null
})
});
})
.catch(err => {
if (debug_mode) {
logger.error('Nginx test failed:', err.message);
}
// config is bad, update meta and delete config
return model
.query()
.where('id', host.id)
.patch({
meta: _.assign({}, host.meta, {
nginx_online: false,
nginx_err: err.message
})
})
.then(() => {
return internalNginx.deleteConfig(host_type, host, true);
});
});
})
.then(() => {
return internalNginx.reload();
});
},
/**
* @returns {Promise}
*/
test: () => {
logger.info('Testing Nginx configuration');
if (debug_mode) {
logger.info('Testing Nginx configuration');
}
return utils.exec('/usr/sbin/nginx -t');
},
@ -43,8 +119,13 @@ const internalNginx = {
* @returns {Promise}
*/
generateConfig: (host_type, host) => {
host_type = host_type.replace(new RegExp('-', 'g'), '_');
if (debug_mode) {
logger.info('Generating ' + host_type + ' Config:', host);
}
let renderEngine = Liquid();
host_type = host_type.replace(new RegExp('-', 'g'), '_');
return new Promise((resolve, reject) => {
let template = null;
@ -56,14 +137,23 @@ const internalNginx = {
return;
}
return renderEngine
renderEngine
.parseAndRender(template, host)
.then(config_text => {
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
return true;
if (debug_mode) {
logger.success('Wrote config:', filename, config_text);
}
resolve(true);
})
.catch(err => {
throw new error.ConfigurationError(err.message);
if (debug_mode) {
logger.warn('Could not write ' + filename + ':', err.message);
}
reject(new error.ConfigurationError(err.message));
});
});
},
@ -75,10 +165,22 @@ const internalNginx = {
* @returns {Promise}
*/
deleteConfig: (host_type, host, throw_errors) => {
host_type = host_type.replace(new RegExp('-', 'g'), '_');
return new Promise((resolve, reject) => {
try {
fs.unlinkSync(internalNginx.getConfigName(host_type, host.id));
let config_file = internalNginx.getConfigName(host_type, host.id);
if (debug_mode) {
logger.warn('Deleting nginx config: ' + config_file);
}
fs.unlinkSync(config_file);
} catch (err) {
if (debug_mode) {
logger.warn('Could not delete config:', err.message);
}
if (throw_errors) {
reject(err);
}

View File

@ -4,6 +4,7 @@ const _ = require('lodash');
const error = require('../lib/error');
const proxyHostModel = require('../models/proxy_host');
const internalHost = require('./host');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
function omissions () {
@ -50,6 +51,15 @@ const internalProxyHost = {
.insertAndFetch(data);
})
.then(row => {
// Configure nginx
return internalNginx.configure(proxyHostModel, 'proxy_host', row)
.then(() => {
return internalProxyHost.get(access, {id: row.id, expand: ['owner']});
});
})
.then(row => {
data.meta = _.assign({}, data.meta || {}, row.meta);
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
@ -58,7 +68,7 @@ const internalProxyHost = {
meta: data
})
.then(() => {
return _.omit(row, omissions());
return row;
});
});
},
@ -192,6 +202,13 @@ const internalProxyHost = {
.patch({
is_deleted: 1
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('proxy_host', row)
.then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
row.meta = internalHost.cleanMeta(row.meta);

View File

@ -4,6 +4,7 @@ const _ = require('lodash');
const error = require('../lib/error');
const redirectionHostModel = require('../models/redirection_host');
const internalHost = require('./host');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
function omissions () {
@ -49,6 +50,13 @@ const internalRedirectionHost = {
.omit(omissions())
.insertAndFetch(data);
})
.then(row => {
// Configure nginx
return internalNginx.configure(redirectionHostModel, 'redirection_host', row)
.then(() => {
return internalRedirectionHost.get(access, {id: row.id, expand: ['owner']});
});
})
.then(row => {
// Add to audit log
return internalAuditLog.add(access, {
@ -58,7 +66,7 @@ const internalRedirectionHost = {
meta: data
})
.then(() => {
return _.omit(row, omissions());
return row;
});
});
},
@ -192,6 +200,13 @@ const internalRedirectionHost = {
.patch({
is_deleted: 1
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('redirection_host', row)
.then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
row.meta = internalHost.cleanMeta(row.meta);

View File

@ -17,6 +17,7 @@ const internalSsl = {
interval_processing: false,
initTimer: () => {
logger.info('Let\'s Encrypt Renewal Timer initialized');
internalSsl.interval = setInterval(internalSsl.processExpiringHosts, internalSsl.interval_timeout);
},
@ -51,7 +52,7 @@ const internalSsl = {
*/
hasValidSslCerts: (host_type, host) => {
host_type = host_type.replace(new RegExp('-', 'g'), '_');
let le_path = '/etc/letsencrypt/live/' + host_type + '_' + host.id;
let le_path = '/etc/letsencrypt/live/' + host_type + '-' + host.id;
return fs.existsSync(le_path + '/fullchain.pem') && fs.existsSync(le_path + '/privkey.pem');
},

View File

@ -3,6 +3,7 @@
const _ = require('lodash');
const error = require('../lib/error');
const streamModel = require('../models/stream');
const internalNginx = require('./nginx');
const internalAuditLog = require('./audit-log');
function omissions () {
@ -31,6 +32,13 @@ const internalStream = {
.omit(omissions())
.insertAndFetch(data);
})
.then(row => {
// Configure nginx
return internalNginx.configure(streamModel, 'stream', row)
.then(() => {
return internalStream.get(access, {id: row.id, expand: ['owner']});
});
})
.then(row => {
// Add to audit log
return internalAuditLog.add(access, {
@ -40,7 +48,7 @@ const internalStream = {
meta: data
})
.then(() => {
return _.omit(row, omissions());
return row;
});
});
},
@ -153,6 +161,13 @@ const internalStream = {
.patch({
is_deleted: 1
})
.then(() => {
// Delete Nginx Config
return internalNginx.deleteConfig('stream', row)
.then(() => {
return internalNginx.reload();
});
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {

View File

@ -70,7 +70,8 @@ const internalUser = {
redirection_hosts: 'manage',
dead_hosts: 'manage',
streams: 'manage',
access_lists: 'manage'
access_lists: 'manage',
certificates: 'manage'
})
.then(() => {
return internalUser.get(access, {id: user.id, expand: ['permissions']});