Skip to content

Commit fb766d1

Browse files
committed
Add support for writing client CAs when access-lists are updated
This commit adds the basic support necessary to produce the combined client CA files when certificates are updated.
1 parent e5bb50c commit fb766d1

File tree

3 files changed

+78
-9
lines changed

3 files changed

+78
-9
lines changed

backend/internal/access-list.js

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ const internalAccessList = {
8686
// re-fetch with expansions
8787
return internalAccessList.get(access, {
8888
id: data.id,
89-
expand: ['owner', 'items', 'clients', 'clientcas', 'proxy_hosts.access_list.[clientcas.certificate,clients,items]']
89+
expand: ['owner', 'items', 'clients', 'clientcas.certificate', 'proxy_hosts.access_list.[clientcas,clients,items]']
9090
}, true /* <- skip masking */);
9191
})
9292
.then((row) => {
@@ -247,7 +247,6 @@ const internalAccessList = {
247247
});
248248
}
249249
})
250-
.then(internalNginx.reload)
251250
.then(() => {
252251
// Add to audit log
253252
return internalAuditLog.add(access, {
@@ -261,10 +260,11 @@ const internalAccessList = {
261260
// re-fetch with expansions
262261
return internalAccessList.get(access, {
263262
id: data.id,
264-
expand: ['owner', 'items', 'clients', 'clientcas', 'proxy_hosts.[certificate,access_list.[clientcas.certificate,clients,items]]']
263+
expand: ['owner', 'items', 'clients', 'clientcas.certificate', 'proxy_hosts.[certificate,access_list.[clientcas,clients,items]]']
265264
}, true /* <- skip masking */);
266265
})
267266
.then((row) => {
267+
console.log(row);
268268
return internalAccessList.build(row)
269269
.then(() => {
270270
if (row.proxy_host_count) {
@@ -274,6 +274,11 @@ const internalAccessList = {
274274
.then(() => {
275275
return internalAccessList.maskItems(row);
276276
});
277+
})
278+
.then((row) => {
279+
return internalNginx.reload().then(() => {
280+
return row;
281+
});
277282
});
278283
},
279284

@@ -299,7 +304,7 @@ const internalAccessList = {
299304
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
300305
.where('access_list.is_deleted', 0)
301306
.andWhere('access_list.id', data.id)
302-
.withGraphFetched('[owner,items,clients,clientcas,proxy_hosts.[certificate,access_list.[clientcas.certificate,clients,items]]]')
307+
.allowGraph('[owner,items,clients,clientcas.certificate,proxy_hosts.[certificate,access_list.[clientcas,clients,items]]]')
303308
.first();
304309

305310
if (access_data.permission_visibility !== 'all') {
@@ -420,7 +425,7 @@ const internalAccessList = {
420425
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
421426
.where('access_list.is_deleted', 0)
422427
.groupBy('access_list.id')
423-
.withGraphFetched('[owner,items,clients,clientcas.certificate]')
428+
.allowGraph('[owner,items,clients,clientcas.certificate]')
424429
.orderBy('access_list.name', 'ASC');
425430

426431
if (access_data.permission_visibility !== 'all') {
@@ -477,6 +482,8 @@ const internalAccessList = {
477482
},
478483

479484
/**
485+
* Mask sensitive items in access list responses
486+
*
480487
* @param {Object} list
481488
* @returns {Object}
482489
*/
@@ -496,6 +503,24 @@ const internalAccessList = {
496503
});
497504
}
498505

506+
// Mask certificates in clientcas responses
507+
if (list && typeof list.clientcas !== 'undefined') {
508+
list.clientcas.map(function(val, idx) {
509+
if (typeof val.certificate !== 'undefined') {
510+
list.clientcas[idx].certificate.meta = {};
511+
}
512+
});
513+
}
514+
515+
// Mask certificates in ProxyHost responses (clear the meta field)
516+
if (list && typeof list.proxy_hosts !== 'undefined') {
517+
list.proxy_hosts.map(function(val, idx) {
518+
if (typeof val.certificate !== 'undefined') {
519+
list.proxy_hosts[idx].certificate.meta = {};
520+
}
521+
});
522+
}
523+
499524
return list;
500525
},
501526

@@ -508,17 +533,27 @@ const internalAccessList = {
508533
return '/data/access/' + list.id;
509534
},
510535

536+
/**
537+
* @param {Object} list
538+
* @param {Integer} list.id
539+
* @returns {String}
540+
*/
541+
getClientCAFilename: (list) => {
542+
return '/data/clientca/' + list.id;
543+
},
544+
511545
/**
512546
* @param {Object} list
513547
* @param {Integer} list.id
514548
* @param {String} list.name
515549
* @param {Array} list.items
550+
* @param {Array} list.clientcas
516551
* @returns {Promise}
517552
*/
518553
build: (list) => {
519-
logger.info('Building Access file #' + list.id + ' for: ' + list.name);
520554

521-
return new Promise((resolve, reject) => {
555+
const htPasswdBuild = new Promise((resolve, reject) => {
556+
logger.info('Building Access file #' + list.id + ' for: ' + list.name);
522557
let htpasswd_file = internalAccessList.getFilename(list);
523558

524559
// 1. remove any existing access file
@@ -566,6 +601,39 @@ const internalAccessList = {
566601
});
567602
}
568603
});
604+
605+
const caCertificateBuild = new Promise((resolve, reject) => {
606+
// TODO: we need to ensure this rebuild is run if any certificates change
607+
logger.info('Building Client CA file #' + list.id + ' for: ' + list.name);
608+
let clientca_file = internalAccessList.getClientCAFilename(list);
609+
610+
const certificate_bodies = list.clientcas
611+
.filter((clientca) => {
612+
return typeof clientca.certificate.meta !== 'undefined';
613+
})
614+
.map((clientca) => {
615+
return clientca.certificate.meta.certificate;
616+
});
617+
618+
// Unlink the original file (nginx retains file handle till reload)
619+
try {
620+
fs.unlinkSync(clientca_file);
621+
} catch (err) {
622+
// do nothing
623+
}
624+
625+
// Write the new file in one shot
626+
try {
627+
fs.writeFileSync(clientca_file, certificate_bodies.join('\n'), {encoding: 'utf8'});
628+
logger.success('Built Client CA file #' + list.id + ' for: ' + list.name);
629+
resolve(clientca_file);
630+
} catch (err) {
631+
reject(err);
632+
}
633+
});
634+
635+
// Execute both promises concurrently
636+
return Promise.all([htPasswdBuild, caCertificateBuild]);
569637
}
570638
};
571639

docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/20-paths.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mkdir -p \
2020
/data/custom_ssl \
2121
/data/logs \
2222
/data/access \
23+
/data/clientca \
2324
/data/nginx/default_host \
2425
/data/nginx/default_www \
2526
/data/nginx/proxy_host \

frontend/js/app/nginx/access/main.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ module.exports = Mn.View.extend({
7373
e.preventDefault();
7474
let query = this.ui.query.val();
7575

76-
this.fetch(['owner', 'items', 'clients', 'clientcas'], query)
76+
this.fetch(['owner', 'items', 'clients', 'clientcas.certificate'], query)
7777
.then(response => this.showData(response))
7878
.catch(err => {
7979
this.showError(err);
@@ -88,7 +88,7 @@ module.exports = Mn.View.extend({
8888
onRender: function () {
8989
let view = this;
9090

91-
view.fetch(['owner', 'items', 'clients', 'clientcas'])
91+
view.fetch(['owner', 'items', 'clients', 'clientcas.certificate'])
9292
.then(response => {
9393
if (!view.isDestroyed()) {
9494
if (response && response.length) {

0 commit comments

Comments
 (0)