Audit Log items, backend stuff, help pages

This commit is contained in:
Jamie Curnow
2018-08-01 21:18:17 +10:00
parent a43c2d74bf
commit 66e25e315b
47 changed files with 936 additions and 134 deletions

View File

@ -1,19 +1,10 @@
'use strict';
const error = require('../lib/error');
const auditLogModel = require('../models/audit-log');
const internalAuditLog = {
/**
* Internal use only
*
* @param {Object} data
* @returns {Promise}
*/
create: data => {
// TODO
},
/**
* All logs
*
@ -28,16 +19,14 @@ const internalAuditLog = {
let query = auditLogModel
.query()
.orderBy('created_on', 'DESC')
.limit(100);
.limit(100)
.allowEager('[user]');
// Query is used for searching
if (typeof search_query === 'string') {
/*
query.where(function () {
this.where('name', 'like', '%' + search_query + '%')
.orWhere('email', 'like', '%' + search_query + '%');
this.where('meta', 'like', '%' + search_query + '%');
});
*/
}
if (typeof expand !== 'undefined' && expand !== null) {
@ -46,6 +35,44 @@ const internalAuditLog = {
return query;
});
},
/**
* This method should not be publicly used, it doesn't check certain things. It will be assumed
* that permission to add to audit log is already considered, however the access token is used for
* default user id determination.
*
* @param {Access} access
* @param {Object} data
* @param {String} data.action
* @param {Integer} [data.user_id]
* @param {Integer} [data.object_id]
* @param {Integer} [data.object_type]
* @param {Object} [data.meta]
* @returns {Promise}
*/
add: (access, data) => {
return new Promise((resolve, reject) => {
// Default the user id
if (typeof data.user_id === 'undefined' || !data.user_id) {
data.user_id = access.token.get('attrs').id;
}
if (typeof data.action === 'undefined' || !data.action) {
reject(new error.InternalValidationError('Audit log entry must contain an Action'));
} else {
// Make sure at least 1 of the IDs are set and action
resolve(auditLogModel
.query()
.insert({
user_id: data.user_id,
action: data.action,
object_type: data.object_type || '',
object_id: data.object_id || 0,
meta: data.meta || {}
}));
}
});
}
};

View File

@ -1,9 +1,10 @@
'use strict';
const _ = require('lodash');
const error = require('../lib/error');
const deadHostModel = require('../models/dead_host');
const internalHost = require('./host');
const _ = require('lodash');
const error = require('../lib/error');
const deadHostModel = require('../models/dead_host');
const internalHost = require('./host');
const internalAuditLog = require('./audit-log');
function omissions () {
return ['is_deleted'];
@ -49,7 +50,16 @@ const internalDeadHost = {
.insertAndFetch(data);
})
.then(row => {
return _.omit(row, omissions());
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'dead-host',
object_id: row.id,
meta: data
})
.then(() => {
return _.omit(row, omissions());
});
});
},
@ -97,7 +107,17 @@ const internalDeadHost = {
.patchAndFetchById(row.id, data)
.then(saved_row => {
saved_row.meta = internalHost.cleanMeta(saved_row.meta);
return _.omit(saved_row, omissions());
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'dead-host',
object_id: row.id,
meta: data
})
.then(() => {
return _.omit(saved_row, omissions());
});
});
});
},
@ -171,6 +191,17 @@ const internalDeadHost = {
.where('id', row.id)
.patch({
is_deleted: 1
})
.then(() => {
// Add to audit log
row.meta = internalHost.cleanMeta(row.meta);
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'dead-host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
@ -200,7 +231,15 @@ const internalDeadHost = {
});
})
.then(row => {
return _.pick(row.meta, internalHost.allowed_ssl_files);
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'dead-host',
object_id: row.id,
meta: data
})
.then(() => {
return _.pick(row.meta, internalHost.allowed_ssl_files);
});
});
},

View File

@ -0,0 +1,92 @@
'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 internalNginx = {
/**
* @returns {Promise}
*/
test: () => {
logger.info('Testing Nginx configuration');
return utils.exec('/usr/sbin/nginx -t');
},
/**
* @returns {Promise}
*/
reload: () => {
return internalNginx.test()
.then(() => {
logger.info('Reloading Nginx');
return utils.exec('/usr/sbin/nginx -s reload');
});
},
/**
* @param {String} host_type
* @param {Integer} host_id
* @returns {String}
*/
getConfigName: (host_type, host_id) => {
host_type = host_type.replace(new RegExp('-', 'g'), '_');
return '/data/nginx/' + host_type + '/' + host_id + '.conf';
},
/**
* @param {String} host_type
* @param {Object} host
* @returns {Promise}
*/
generateConfig: (host_type, host) => {
let renderEngine = Liquid();
host_type = host_type.replace(new RegExp('-', 'g'), '_');
return new Promise((resolve, reject) => {
let template = null;
let filename = internalNginx.getConfigName(host_type, host.id);
try {
template = fs.readFileSync(__dirname + '/../templates/' + host_type + '.conf', {encoding: 'utf8'});
} catch (err) {
reject(new error.ConfigurationError(err.message));
return;
}
return renderEngine
.parseAndRender(template, host)
.then(config_text => {
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
return true;
})
.catch(err => {
throw new error.ConfigurationError(err.message);
});
});
},
/**
* @param {String} host_type
* @param {Object} host
* @param {Boolean} [throw_errors]
* @returns {Promise}
*/
deleteConfig: (host_type, host, throw_errors) => {
return new Promise((resolve, reject) => {
try {
fs.unlinkSync(internalNginx.getConfigName(host_type, host.id));
} catch (err) {
if (throw_errors) {
reject(err);
}
}
resolve();
});
}
};
module.exports = internalNginx;

View File

@ -1,9 +1,10 @@
'use strict';
const _ = require('lodash');
const error = require('../lib/error');
const proxyHostModel = require('../models/proxy_host');
const internalHost = require('./host');
const _ = require('lodash');
const error = require('../lib/error');
const proxyHostModel = require('../models/proxy_host');
const internalHost = require('./host');
const internalAuditLog = require('./audit-log');
function omissions () {
return ['is_deleted'];
@ -49,7 +50,16 @@ const internalProxyHost = {
.insertAndFetch(data);
})
.then(row => {
return _.omit(row, omissions());
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'proxy-host',
object_id: row.id,
meta: data
})
.then(() => {
return _.omit(row, omissions());
});
});
},
@ -97,7 +107,17 @@ const internalProxyHost = {
.patchAndFetchById(row.id, data)
.then(saved_row => {
saved_row.meta = internalHost.cleanMeta(saved_row.meta);
return _.omit(saved_row, omissions());
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'proxy-host',
object_id: row.id,
meta: data
})
.then(() => {
return _.omit(saved_row, omissions());
});
});
});
},
@ -171,6 +191,17 @@ const internalProxyHost = {
.where('id', row.id)
.patch({
is_deleted: 1
})
.then(() => {
// Add to audit log
row.meta = internalHost.cleanMeta(row.meta);
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'proxy-host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
@ -200,7 +231,15 @@ const internalProxyHost = {
});
})
.then(row => {
return _.pick(row.meta, internalHost.allowed_ssl_files);
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'proxy-host',
object_id: row.id,
meta: data
})
.then(() => {
return _.pick(row.meta, internalHost.allowed_ssl_files);
});
});
},

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 internalAuditLog = require('./audit-log');
function omissions () {
return ['is_deleted'];
@ -49,7 +50,16 @@ const internalRedirectionHost = {
.insertAndFetch(data);
})
.then(row => {
return _.omit(row, omissions());
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'redirection-host',
object_id: row.id,
meta: data
})
.then(() => {
return _.omit(row, omissions());
});
});
},
@ -97,7 +107,17 @@ const internalRedirectionHost = {
.patchAndFetchById(row.id, data)
.then(saved_row => {
saved_row.meta = internalHost.cleanMeta(saved_row.meta);
return _.omit(saved_row, omissions());
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'redirection-host',
object_id: row.id,
meta: data
})
.then(() => {
return _.omit(saved_row, omissions());
});
});
});
},
@ -171,6 +191,17 @@ const internalRedirectionHost = {
.where('id', row.id)
.patch({
is_deleted: 1
})
.then(() => {
// Add to audit log
row.meta = internalHost.cleanMeta(row.meta);
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'redirection-host',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {
@ -200,7 +231,15 @@ const internalRedirectionHost = {
});
})
.then(row => {
return _.pick(row.meta, internalHost.allowed_ssl_files);
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'redirection-host',
object_id: row.id,
meta: data
})
.then(() => {
return _.pick(row.meta, internalHost.allowed_ssl_files);
});
});
},

163
src/backend/internal/ssl.js Normal file
View File

@ -0,0 +1,163 @@
'use strict';
const fs = require('fs');
const Liquid = require('liquidjs');
const timestamp = require('unix-timestamp');
const internalNginx = require('./nginx');
const logger = require('../logger').ssl;
const utils = require('../lib/utils');
const error = require('../lib/error');
timestamp.round = true;
const internalSsl = {
interval_timeout: 1000 * 60 * 60 * 12, // 12 hours
interval: null,
interval_processing: false,
initTimer: () => {
internalSsl.interval = setInterval(internalSsl.processExpiringHosts, internalSsl.interval_timeout);
},
/**
* Triggered by a timer, this will check for expiring hosts and renew their ssl certs if required
*/
processExpiringHosts: () => {
if (!internalSsl.interval_processing) {
logger.info('Renewing SSL certs close to expiry...');
return utils.exec('/usr/bin/certbot renew -q')
.then(result => {
logger.info(result);
internalSsl.interval_processing = false;
return internalNginx.reload()
.then(() => {
logger.info('Renew Complete');
return result;
});
})
.catch(err => {
logger.error(err);
internalSsl.interval_processing = false;
});
}
},
/**
* @param {String} host_type
* @param {Object} host
* @returns {Boolean}
*/
hasValidSslCerts: (host_type, host) => {
host_type = host_type.replace(new RegExp('-', 'g'), '_');
let le_path = '/etc/letsencrypt/live/' + host_type + '_' + host.id;
return fs.existsSync(le_path + '/fullchain.pem') && fs.existsSync(le_path + '/privkey.pem');
},
/**
* @param {String} host_type
* @param {Object} host
* @returns {Promise}
*/
requestSsl: (host_type, host) => {
logger.info('Requesting SSL certificates for ' + host_type + ' #' + host.id);
// TODO
return utils.exec('/usr/bin/letsencrypt certonly --agree-tos --email "' + host.letsencrypt_email + '" -n -a webroot -d "' + host.hostname + '"')
.then(result => {
logger.info(result);
return result;
});
},
/**
* @param {String} host_type
* @param {Object} host
* @returns {Promise}
*/
renewSsl: (host_type, host) => {
logger.info('Renewing SSL certificates for ' + host_type + ' #' + host.id);
// TODO
return utils.exec('/usr/bin/certbot renew --force-renewal --disable-hook-validation --cert-name "' + host.hostname + '"')
.then(result => {
logger.info(result);
return result;
});
},
/**
* @param {String} host_type
* @param {Object} host
* @returns {Promise}
*/
deleteCerts: (host_type, host) => {
logger.info('Deleting SSL certificates for ' + host_type + ' #' + host.id);
// TODO
return utils.exec('/usr/bin/certbot delete -n --cert-name "' + host.hostname + '"')
.then(result => {
logger.info(result);
})
.catch(err => {
logger.error(err);
});
},
/**
* @param {String} host_type
* @param {Object} host
* @returns {Promise}
*/
generateSslSetupConfig: (host_type, host) => {
host_type = host_type.replace(new RegExp('-', 'g'), '_');
let renderEngine = Liquid();
let template = null;
let filename = internalNginx.getConfigName(host_type, host);
return new Promise((resolve, reject) => {
try {
template = fs.readFileSync(__dirname + '/../templates/letsencrypt.conf', {encoding: 'utf8'});
} catch (err) {
reject(new error.ConfigurationError(err.message));
return;
}
return renderEngine
.parseAndRender(template, host)
.then(config_text => {
fs.writeFileSync(filename, config_text, {encoding: 'utf8'});
return template_data;
})
.catch(err => {
throw new error.ConfigurationError(err.message);
});
});
},
/**
* @param {String} host_type
* @param {Object} host
* @returns {Promise}
*/
configureSsl: (host_type, host) => {
// TODO
return internalSsl.generateSslSetupConfig(host)
.then(data => {
return internalNginx.reload()
.then(() => {
return internalSsl.requestSsl(data);
});
});
}
};
module.exports = internalSsl;

View File

@ -1,8 +1,9 @@
'use strict';
const _ = require('lodash');
const error = require('../lib/error');
const streamModel = require('../models/stream');
const _ = require('lodash');
const error = require('../lib/error');
const streamModel = require('../models/stream');
const internalAuditLog = require('./audit-log');
function omissions () {
return ['is_deleted'];
@ -31,7 +32,16 @@ const internalStream = {
.insertAndFetch(data);
})
.then(row => {
return _.omit(row, omissions());
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'stream',
object_id: row.id,
meta: data
})
.then(() => {
return _.omit(row, omissions());
});
});
},
@ -60,7 +70,16 @@ const internalStream = {
.omit(omissions())
.patchAndFetchById(row.id, data)
.then(saved_row => {
return _.omit(saved_row, omissions());
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'stream',
object_id: row.id,
meta: data
})
.then(() => {
return _.omit(saved_row, omissions());
});
});
});
},
@ -133,6 +152,15 @@ const internalStream = {
.where('id', row.id)
.patch({
is_deleted: 1
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'stream',
object_id: row.id,
meta: _.omit(row, omissions())
});
});
})
.then(() => {

View File

@ -7,6 +7,7 @@ const userPermissionModel = require('../models/user_permission');
const authModel = require('../models/auth');
const gravatar = require('gravatar');
const internalToken = require('./token');
const internalAuditLog = require('./audit-log');
function omissions () {
return ['is_deleted'];
@ -74,6 +75,18 @@ const internalUser = {
.then(() => {
return internalUser.get(access, {id: user.id, expand: ['permissions']});
});
})
.then(user => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'created',
object_type: 'user',
object_id: user.id,
meta: user
})
.then(() => {
return user;
});
});
},
@ -136,6 +149,18 @@ const internalUser = {
})
.then(() => {
return internalUser.get(access, {id: data.id});
})
.then(user => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'user',
object_id: user.id,
meta: data
})
.then(() => {
return user;
});
});
},
@ -236,6 +261,15 @@ const internalUser = {
.where('id', user.id)
.patch({
is_deleted: 1
})
.then(() => {
// Add to audit log
return internalAuditLog.add(access, {
action: 'deleted',
object_type: 'user',
object_id: user.id,
meta: _.omit(user, omissions())
});
});
})
.then(() => {
@ -389,6 +423,19 @@ const internalUser = {
meta: {}
});
}
})
.then(() => {
// Add to Audit Log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'user',
object_id: user.id,
meta: {
name: user.name,
password_changed: true,
auth_type: data.type
}
});
});
})
.then(() => {
@ -435,8 +482,21 @@ const internalUser = {
}
})
.then(permissions => {
return true;
// Add to Audit Log
return internalAuditLog.add(access, {
action: 'updated',
object_type: 'user',
object_id: user.id,
meta: {
name: user.name,
permissions: permissions
}
});
});
})
.then(() => {
return true;
});
},