Add support for adding Client Certificates to access-lists

Client certificate support is added as a new separate type of option for
access-lists.

This commit is the support code to enable access-lists to contain
Client Certificate references.
This commit is contained in:
Will Rouesnel
2023-05-27 01:43:15 +10:00
parent d5b3e53140
commit e5bb50c164
15 changed files with 374 additions and 41 deletions

View File

@ -1,15 +1,16 @@
const _ = require('lodash');
const fs = require('fs');
const batchflow = require('batchflow');
const logger = require('../logger').access;
const error = require('../lib/error');
const utils = require('../lib/utils');
const accessListModel = require('../models/access_list');
const accessListAuthModel = require('../models/access_list_auth');
const accessListClientModel = require('../models/access_list_client');
const proxyHostModel = require('../models/proxy_host');
const internalAuditLog = require('./audit-log');
const internalNginx = require('./nginx');
const _ = require('lodash');
const fs = require('fs');
const batchflow = require('batchflow');
const logger = require('../logger').access;
const error = require('../lib/error');
const utils = require('../lib/utils');
const accessListModel = require('../models/access_list');
const accessListAuthModel = require('../models/access_list_auth');
const accessListClientModel = require('../models/access_list_client');
const accessListClientCAsModel = require('../models/access_list_clientcas');
const proxyHostModel = require('../models/proxy_host');
const internalAuditLog = require('./audit-log');
const internalNginx = require('./nginx');
function omissions () {
return ['is_deleted'];
@ -66,13 +67,26 @@ const internalAccessList = {
});
}
// Now add the client certificate references
if (typeof data.clientcas !== 'undefined' && data.clientcas) {
data.clientcas.map((certificate_id) => {
promises.push(accessListClientCAsModel
.query()
.insert({
access_list_id: row.id,
certificate_id: certificate_id
})
);
});
}
return Promise.all(promises);
})
.then(() => {
// re-fetch with expansions
return internalAccessList.get(access, {
id: data.id,
expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.[clients,items]']
expand: ['owner', 'items', 'clients', 'clientcas', 'proxy_hosts.access_list.[clientcas.certificate,clients,items]']
}, true /* <- skip masking */);
})
.then((row) => {
@ -204,6 +218,35 @@ const internalAccessList = {
});
}
})
.then(() => {
// Check for client certificates and add/update/remove them
if (typeof data.clientcas !== 'undefined' && data.clientcas) {
let promises = [];
data.clientcas.map(function (certificate_id) {
promises.push(accessListClientCAsModel
.query()
.insert({
access_list_id: data.id,
certificate_id: certificate_id
})
);
});
let query = accessListClientCAsModel
.query()
.delete()
.where('access_list_id', data.id);
return query
.then(() => {
// Add new items
if (promises.length) {
return Promise.all(promises);
}
});
}
})
.then(internalNginx.reload)
.then(() => {
// Add to audit log
@ -218,7 +261,7 @@ const internalAccessList = {
// re-fetch with expansions
return internalAccessList.get(access, {
id: data.id,
expand: ['owner', 'items', 'clients', 'proxy_hosts.[certificate,access_list.[clients,items]]']
expand: ['owner', 'items', 'clients', 'clientcas', 'proxy_hosts.[certificate,access_list.[clientcas.certificate,clients,items]]']
}, true /* <- skip masking */);
})
.then((row) => {
@ -256,7 +299,7 @@ const internalAccessList = {
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
.where('access_list.is_deleted', 0)
.andWhere('access_list.id', data.id)
.allowGraph('[owner,items,clients,proxy_hosts.[certificate,access_list.[clients,items]]]')
.withGraphFetched('[owner,items,clients,clientcas,proxy_hosts.[certificate,access_list.[clientcas.certificate,clients,items]]]')
.first();
if (access_data.permission_visibility !== 'all') {
@ -294,7 +337,7 @@ const internalAccessList = {
delete: (access, data) => {
return access.can('access_lists:delete', data.id)
.then(() => {
return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items', 'clients']});
return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items', 'clients', 'clientcas']});
})
.then((row) => {
if (!row) {
@ -377,7 +420,7 @@ const internalAccessList = {
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
.where('access_list.is_deleted', 0)
.groupBy('access_list.id')
.allowGraph('[owner,items,clients]')
.withGraphFetched('[owner,items,clients,clientcas.certificate]')
.orderBy('access_list.name', 'ASC');
if (access_data.permission_visibility !== 'all') {

View File

@ -0,0 +1,50 @@
const migrate_name = 'client_certificates';
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.createTable('access_list_clientcas', (table) => {
table.increments().primary();
table.dateTime('created_on').notNull();
table.dateTime('modified_on').notNull();
table.integer('access_list_id').notNull().unsigned();
table.integer('certificate_id').notNull().unsigned();
table.json('meta').notNull();
})
.then(function () {
logger.info('[' + migrate_name + '] access_list_clientcas Table created');
})
.then(() => {
logger.info('[' + migrate_name + '] Migrating Up Complete');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema.dropTable('access_list_clientcas')
.then(() => {
logger.info('[' + migrate_name + '] access_list_clientcas Table dropped');
})
.then(() => {
logger.info('[' + migrate_name + '] Migrating Down Complete');
});
};

View File

@ -1,12 +1,13 @@
// Objection Docs:
// http://vincit.github.io/objection.js/
const db = require('../db');
const Model = require('objection').Model;
const User = require('./user');
const AccessListAuth = require('./access_list_auth');
const AccessListClient = require('./access_list_client');
const now = require('./now_helper');
const db = require('../db');
const Model = require('objection').Model;
const User = require('./user');
const AccessListAuth = require('./access_list_auth');
const AccessListClient = require('./access_list_client');
const AccessListClientCAs = require('./access_list_clientcas');
const now = require('./now_helper');
Model.knex(db);
@ -68,6 +69,14 @@ class AccessList extends Model {
to: 'access_list_client.access_list_id'
}
},
clientcas: {
relation: Model.HasManyRelation,
modelClass: AccessListClientCAs,
join: {
from: 'access_list.id',
to: 'access_list_clientcas.access_list_id'
}
},
proxy_hosts: {
relation: Model.HasManyRelation,
modelClass: ProxyHost,

View File

@ -0,0 +1,62 @@
// Objection Docs:
// http://vincit.github.io/objection.js/
const db = require('../db');
const Model = require('objection').Model;
const now = require('./now_helper');
Model.knex(db);
class AccessListClientCAs extends Model {
$beforeInsert () {
this.created_on = now();
this.modified_on = now();
// Default for meta
if (typeof this.meta === 'undefined') {
this.meta = {};
}
}
$beforeUpdate () {
this.modified_on = now();
}
static get name () {
return 'AccessListClientCAs';
}
static get tableName () {
return 'access_list_clientcas';
}
static get jsonAttributes () {
return ['meta'];
}
static get relationMappings () {
return {
access_list: {
relation: Model.HasOneRelation,
modelClass: require('./access_list'),
join: {
from: 'access_list_clientcas.access_list_id',
to: 'access_list.id'
},
modify: function (qb) {
qb.where('access_list.is_deleted', 0);
}
},
certificate: {
relation: Model.HasOneRelation,
modelClass: require('./certificate'),
join: {
from: 'access_list_clientcas.certificate_id',
to: 'certificate.id'
}
}
};
}
}
module.exports = AccessListClientCAs;

View File

@ -142,6 +142,13 @@
}
}
},
"clientcas": {
"type": "array",
"minItems": 0,
"items": {
"type": "integer"
}
},
"meta": {
"$ref": "#/definitions/meta"
}
@ -209,6 +216,13 @@
}
}
}
},
"clientcas": {
"type": "array",
"minItems": 0,
"items": {
"type": "integer"
}
}
}
},