Merge remote-tracking branch 'origin/develop' into FEAT/open-id-connect-authentication

This commit is contained in:
Samuel Oechsler
2024-10-30 20:30:36 +01:00
182 changed files with 7882 additions and 5228 deletions

View File

@ -269,7 +269,7 @@ const internalAccessList = {
return query.then(utils.omitRow(omissions()));
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
}
if (!skip_masking && typeof row.items !== 'undefined' && row.items) {
@ -296,7 +296,7 @@ const internalAccessList = {
return internalAccessList.get(access, {id: data.id, expand: ['proxy_hosts', 'items', 'clients']});
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
}

View File

@ -3,27 +3,29 @@ const fs = require('fs');
const https = require('https');
const tempWrite = require('temp-write');
const moment = require('moment');
const archiver = require('archiver');
const path = require('path');
const { isArray } = require('lodash');
const logger = require('../logger').ssl;
const config = require('../lib/config');
const error = require('../lib/error');
const utils = require('../lib/utils');
const certbot = require('../lib/certbot');
const certificateModel = require('../models/certificate');
const tokenModel = require('../models/token');
const dnsPlugins = require('../global/certbot-dns-plugins.json');
const internalAuditLog = require('./audit-log');
const internalNginx = require('./nginx');
const internalHost = require('./host');
const certbot = require('../lib/certbot');
const archiver = require('archiver');
const path = require('path');
const { isArray } = require('lodash');
const letsencryptStaging = config.useLetsencryptStaging();
const letsencryptServer = config.useLetsencryptServer();
const letsencryptConfig = '/etc/letsencrypt.ini';
const certbotCommand = 'certbot';
function omissions() {
return ['is_deleted'];
return ['is_deleted', 'owner.is_deleted'];
}
const internalCertificate = {
@ -207,6 +209,7 @@ const internalCertificate = {
.patchAndFetchById(certificate.id, {
expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss')
})
.then(utils.omitRow(omissions()))
.then((saved_row) => {
// Add cert data for audit log
saved_row.meta = _.assign({}, saved_row.meta, {
@ -323,7 +326,7 @@ const internalCertificate = {
return query.then(utils.omitRow(omissions()));
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
}
// Custom omissions
@ -412,7 +415,7 @@ const internalCertificate = {
return internalCertificate.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
}
@ -730,29 +733,29 @@ const internalCertificate = {
return utils.exec('openssl x509 -in ' + certificate_file + ' -subject -noout')
.then((result) => {
// Examples:
// subject=CN = *.jc21.com
// subject=CN = something.example.com
const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim;
const match = regex.exec(result);
if (typeof match[1] === 'undefined') {
throw new error.ValidationError('Could not determine subject from certificate: ' + result);
if (match && typeof match[1] !== 'undefined') {
certData['cn'] = match[1];
}
certData['cn'] = match[1];
})
.then(() => {
return utils.exec('openssl x509 -in ' + certificate_file + ' -issuer -noout');
})
.then((result) => {
// Examples:
// issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
// issuer=C = US, O = Let's Encrypt, CN = E5
// issuer=O = NginxProxyManager, CN = NginxProxyManager Intermediate CA","O = NginxProxyManager, CN = NginxProxyManager Intermediate CA
const regex = /^(?:issuer=)?(.*)$/gim;
const match = regex.exec(result);
if (typeof match[1] === 'undefined') {
throw new error.ValidationError('Could not determine issuer from certificate: ' + result);
if (match && typeof match[1] !== 'undefined') {
certData['issuer'] = match[1];
}
certData['issuer'] = match[1];
})
.then(() => {
return utils.exec('openssl x509 -in ' + certificate_file + ' -dates -noout');
@ -827,17 +830,18 @@ const internalCertificate = {
requestLetsEncryptSsl: (certificate) => {
logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
const cmd = certbotCommand + ' certonly ' +
'--config "' + letsencryptConfig + '" ' +
const cmd = `${certbotCommand} certonly ` +
`--config '${letsencryptConfig}' ` +
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
'--cert-name "npm-' + certificate.id + '" ' +
`--cert-name "npm-${certificate.id}" ` +
'--agree-tos ' +
'--authenticator webroot ' +
'--email "' + certificate.meta.letsencrypt_email + '" ' +
`--email '${certificate.meta.letsencrypt_email}' ` +
'--preferred-challenges "dns,http" ' +
'--domains "' + certificate.domain_names.join(',') + '" ' +
(letsencryptStaging ? '--staging' : '');
`--domains "${certificate.domain_names.join(',')}" ` +
(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') +
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
logger.info('Command:', cmd);
@ -868,25 +872,26 @@ const internalCertificate = {
const hasConfigArg = certificate.meta.dns_provider !== 'route53';
let mainCmd = certbotCommand + ' certonly ' +
'--config "' + letsencryptConfig + '" ' +
`--config '${letsencryptConfig}' ` +
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
'--cert-name "npm-' + certificate.id + '" ' +
`--cert-name 'npm-${certificate.id}' ` +
'--agree-tos ' +
'--email "' + certificate.meta.letsencrypt_email + '" ' +
'--domains "' + certificate.domain_names.join(',') + '" ' +
'--authenticator ' + dnsPlugin.full_plugin_name + ' ' +
`--email '${certificate.meta.letsencrypt_email}' ` +
`--domains '${certificate.domain_names.join(',')}' ` +
`--authenticator '${dnsPlugin.full_plugin_name}' ` +
(
hasConfigArg
? '--' + dnsPlugin.full_plugin_name + '-credentials "' + credentialsLocation + '"'
? `--${dnsPlugin.full_plugin_name}-credentials '${credentialsLocation}' `
: ''
) +
(
certificate.meta.propagation_seconds !== undefined
? ' --' + dnsPlugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds
? `--${dnsPlugin.full_plugin_name}-propagation-seconds '${certificate.meta.propagation_seconds}' `
: ''
) +
(letsencryptStaging ? ' --staging' : '');
(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') +
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
// Prepend the path to the credentials file as an environment variable
if (certificate.meta.dns_provider === 'route53') {
@ -963,14 +968,15 @@ const internalCertificate = {
logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
const cmd = certbotCommand + ' renew --force-renewal ' +
'--config "' + letsencryptConfig + '" ' +
`--config '${letsencryptConfig}' ` +
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
'--cert-name "npm-' + certificate.id + '" ' +
`--cert-name 'npm-${certificate.id}' ` +
'--preferred-challenges "dns,http" ' +
'--no-random-sleep-on-renew ' +
'--disable-hook-validation ' +
(letsencryptStaging ? '--staging' : '');
(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') +
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
logger.info('Command:', cmd);
@ -995,13 +1001,14 @@ const internalCertificate = {
logger.info(`Renewing Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
let mainCmd = certbotCommand + ' renew --force-renewal ' +
'--config "' + letsencryptConfig + '" ' +
`--config "${letsencryptConfig}" ` +
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
'--cert-name "npm-' + certificate.id + '" ' +
`--cert-name 'npm-${certificate.id}' ` +
'--disable-hook-validation ' +
'--no-random-sleep-on-renew ' +
(letsencryptStaging ? ' --staging' : '');
(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') +
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
// Prepend the path to the credentials file as an environment variable
if (certificate.meta.dns_provider === 'route53') {
@ -1027,12 +1034,13 @@ const internalCertificate = {
logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
const mainCmd = certbotCommand + ' revoke ' +
'--config "' + letsencryptConfig + '" ' +
`--config '${letsencryptConfig}' ` +
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
'--cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' +
`--cert-path '/etc/letsencrypt/live/npm-${certificate.id}/fullchain.pem' ` +
'--delete-after-revoke ' +
(letsencryptStaging ? '--staging' : '');
(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') +
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
// Don't fail command if file does not exist
const delete_credentialsCmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`;

View File

@ -48,6 +48,12 @@ const internalDeadHost = {
data.owner_user_id = access.token.getUserId(1);
data = internalHost.cleanSslHstsData(data);
// Fix for db field not having a default value
// for this optional field.
if (typeof data.advanced_config === 'undefined') {
data.advanced_config = '';
}
return deadHostModel
.query()
.insertAndFetch(data)
@ -233,7 +239,7 @@ const internalDeadHost = {
return query.then(utils.omitRow(omissions()));
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
}
// Custom omissions
@ -257,7 +263,7 @@ const internalDeadHost = {
return internalDeadHost.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
}
@ -305,7 +311,7 @@ const internalDeadHost = {
});
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) {
throw new error.ValidationError('Host is already enabled');
@ -351,7 +357,7 @@ const internalDeadHost = {
return internalDeadHost.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) {
throw new error.ValidationError('Host is already disabled');

View File

@ -181,7 +181,9 @@ const internalNginx = {
* @param {Object} host
* @returns {Promise}
*/
generateConfig: (host_type, host) => {
generateConfig: (host_type, host_row) => {
// Prevent modifying the original object:
let host = JSON.parse(JSON.stringify(host_row));
const nice_host_type = internalNginx.getFileFriendlyHostType(host_type);
if (config.debug()) {

View File

@ -8,7 +8,7 @@ const internalAuditLog = require('./audit-log');
const internalCertificate = require('./certificate');
function omissions () {
return ['is_deleted'];
return ['is_deleted', 'owner.is_deleted'];
}
const internalProxyHost = {
@ -48,6 +48,12 @@ const internalProxyHost = {
data.owner_user_id = access.token.getUserId(1);
data = internalHost.cleanSslHstsData(data);
// Fix for db field not having a default value
// for this optional field.
if (typeof data.advanced_config === 'undefined') {
data.advanced_config = '';
}
return proxyHostModel
.query()
.insertAndFetch(data)
@ -239,7 +245,7 @@ const internalProxyHost = {
return query.then(utils.omitRow(omissions()));
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
}
row = internalHost.cleanRowCertificateMeta(row);
@ -264,7 +270,7 @@ const internalProxyHost = {
return internalProxyHost.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
}
@ -312,7 +318,7 @@ const internalProxyHost = {
});
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) {
throw new error.ValidationError('Host is already enabled');
@ -358,7 +364,7 @@ const internalProxyHost = {
return internalProxyHost.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) {
throw new error.ValidationError('Host is already disabled');

View File

@ -48,6 +48,12 @@ const internalRedirectionHost = {
data.owner_user_id = access.token.getUserId(1);
data = internalHost.cleanSslHstsData(data);
// Fix for db field not having a default value
// for this optional field.
if (typeof data.advanced_config === 'undefined') {
data.advanced_config = '';
}
return redirectionHostModel
.query()
.insertAndFetch(data)
@ -232,7 +238,7 @@ const internalRedirectionHost = {
return query.then(utils.omitRow(omissions()));
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
}
row = internalHost.cleanRowCertificateMeta(row);
@ -257,7 +263,7 @@ const internalRedirectionHost = {
return internalRedirectionHost.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
}
@ -305,7 +311,7 @@ const internalRedirectionHost = {
});
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) {
throw new error.ValidationError('Host is already enabled');
@ -351,7 +357,7 @@ const internalRedirectionHost = {
return internalRedirectionHost.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) {
throw new error.ValidationError('Host is already disabled');

View File

@ -128,7 +128,7 @@ const internalStream = {
return query.then(utils.omitRow(omissions()));
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
}
// Custom omissions
@ -152,7 +152,7 @@ const internalStream = {
return internalStream.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
}
@ -200,7 +200,7 @@ const internalStream = {
});
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (row.enabled) {
throw new error.ValidationError('Host is already enabled');
@ -246,7 +246,7 @@ const internalStream = {
return internalStream.get(access, {id: data.id});
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
} else if (!row.enabled) {
throw new error.ValidationError('Host is already disabled');

View File

@ -194,7 +194,7 @@ const internalUser = {
return query.then(utils.omitRow(omissions()));
})
.then((row) => {
if (!row) {
if (!row || !row.id) {
throw new error.ItemNotFoundError(data.id);
}
// Custom omissions