Compare commits

..

28 Commits

Author SHA1 Message Date
dependabot[bot]
4397f57a51 Bump @eslint/plugin-kit from 0.3.2 to 0.3.5 in /test
Bumps [@eslint/plugin-kit](https://github.com/eslint/rewrite/tree/HEAD/packages/plugin-kit) from 0.3.2 to 0.3.5.
- [Release notes](https://github.com/eslint/rewrite/releases)
- [Changelog](https://github.com/eslint/rewrite/blob/main/packages/plugin-kit/CHANGELOG.md)
- [Commits](https://github.com/eslint/rewrite/commits/plugin-kit-v0.3.5/packages/plugin-kit)

---
updated-dependencies:
- dependency-name: "@eslint/plugin-kit"
  dependency-version: 0.3.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-22 02:38:28 +00:00
jc21
5039738aa3 Merge pull request #4696 from NginxProxyManager/dependabot/npm_and_yarn/test/tmp-0.2.4
Bump tmp from 0.2.3 to 0.2.4 in /test
2025-08-22 12:34:03 +10:00
jc21
4451be8f1c Merge pull request #4722 from NginxProxyManager/dependabot/npm_and_yarn/frontend/cipher-base-1.0.6
Bump cipher-base from 1.0.4 to 1.0.6 in /frontend
2025-08-22 12:22:49 +10:00
jc21
bee2fd1978 Merge pull request #4723 from NginxProxyManager/dependabot/npm_and_yarn/frontend/sha.js-2.4.12
Bump sha.js from 2.4.11 to 2.4.12 in /frontend
2025-08-22 12:22:39 +10:00
dependabot[bot]
c8adbdfc15 Bump sha.js from 2.4.11 to 2.4.12 in /frontend
Bumps [sha.js](https://github.com/crypto-browserify/sha.js) from 2.4.11 to 2.4.12.
- [Changelog](https://github.com/browserify/sha.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/sha.js/compare/v2.4.11...v2.4.12)

---
updated-dependencies:
- dependency-name: sha.js
  dependency-version: 2.4.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-21 15:45:37 +00:00
dependabot[bot]
aff4182ab8 Bump cipher-base from 1.0.4 to 1.0.6 in /frontend
Bumps [cipher-base](https://github.com/crypto-browserify/cipher-base) from 1.0.4 to 1.0.6.
- [Changelog](https://github.com/browserify/cipher-base/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/cipher-base/compare/v1.0.4...v1.0.6)

---
updated-dependencies:
- dependency-name: cipher-base
  dependency-version: 1.0.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-21 15:13:31 +00:00
Jamie Curnow
8c9d2745e2 Fix remote execution bug where email address can contain malicious code
Some checks failed
Close stale issues and PRs / stale (push) Has been cancelled
also convert almost all cmd execs for certificates to properly escape arguments
2025-08-20 10:57:24 +10:00
dependabot[bot]
076d14b5e4 Bump tmp from 0.2.3 to 0.2.4 in /test
Bumps [tmp](https://github.com/raszi/node-tmp) from 0.2.3 to 0.2.4.
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/compare/v0.2.3...v0.2.4)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 0.2.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-06 17:12:10 +00:00
Jamie Curnow
54d463ac36 Safer and flexible boolean env vars 2025-07-09 21:27:50 +10:00
Jamie Curnow
a23dc24021 Tweak ownership output 2025-07-09 21:01:21 +10:00
Jamie Curnow
4f9df893c8 Ownership script shakeup
- Don't touch a file to determine if we need to run
- Instead, check ownership of each location and skip it if we are happy
- Keeping SKIP_CERTBOT_OWNERSHIP flag
- More vebose logging of outcomes
2025-07-09 20:30:27 +10:00
Jamie Curnow
304b38e82b Fix ownership if statement 2025-07-09 18:19:50 +10:00
jc21
1b0929ade6 Merge branch 'master' into develop 2025-07-09 16:36:26 +10:00
Jamie Curnow
ddbafb62a6 bump version 2025-07-09 16:33:50 +10:00
Jamie Curnow
9a0383bc73 Move SKIP_CERTBOT_OWNERSHIP check around the entire certbot code 2025-07-09 16:30:45 +10:00
jc21
307cb94e84 Merge pull request #4651 from NginxProxyManager/develop
v2.12.5
2025-07-09 14:22:26 +10:00
jc21
63ae924fbc Merge branch 'master' into develop 2025-07-09 13:16:38 +10:00
Jamie Curnow
1710a263c0 Bump version 2025-07-09 13:15:15 +10:00
Jamie Curnow
1357774f21 Add SKIP_CERTBOT_OWNERSHIP env var support to skip certbot folder ownership 2025-07-09 13:14:27 +10:00
Jamie Curnow
5f54490d86 Set SETUPTOOLS_USE_DISTUTILS for all plugin installs, seems like they all need it. 2025-07-09 12:35:20 +10:00
Jamie Curnow
c97b8a339d Some auto formatting changes suggested by ide 2025-07-09 11:34:57 +10:00
Jamie Curnow
ed1d90ee7f Fix powerdns dns plugin install, deps are outrageously old ;(
Some checks failed
Close stale issues and PRs / stale (push) Has been cancelled
2025-07-09 11:34:19 +10:00
Jamie Curnow
70894e55b8 Remove cloudflare dep for certbot plugin, tested 2025-07-09 09:36:57 +10:00
Jamie Curnow
817021a43d Update s6 overlay
Some checks failed
Close stale issues and PRs / stale (push) Has been cancelled
2025-07-08 17:32:23 +10:00
Jamie Curnow
36e3449a56 Update cloudflare dependency 2025-07-08 17:14:20 +10:00
Jamie Curnow
db9f25638f Update PR comments to highlight verification requirements 2025-07-08 17:08:31 +10:00
jc21
ddd3355d95 Merge pull request #4645 from NginxProxyManager/revert-4574-develop
Revert "Update 'global/certbot-dns-plugins.json' to apply SSL certs for CloudFlare."
2025-07-08 11:19:53 +10:00
jc21
b84762b5b9 Merge pull request #4605 from NginxProxyManager/develop
v2.12.4
2025-07-01 11:12:08 +10:00
30 changed files with 453 additions and 334 deletions

View File

@@ -1 +1 @@
2.12.4
2.12.6

15
Jenkinsfile vendored
View File

@@ -241,12 +241,17 @@ pipeline {
}
steps {
script {
npmGithubPrComment("""Docker Image for build ${BUILD_NUMBER} is available on
[DockerHub](https://cloud.docker.com/repository/docker/nginxproxymanager/${IMAGE}-dev)
as `nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}`
npmGithubPrComment("""Docker Image for build ${BUILD_NUMBER} is available on [DockerHub](https://cloud.docker.com/repository/docker/nginxproxymanager/${IMAGE}-dev):
```
nginxproxymanager/${IMAGE}-dev:${BRANCH_LOWER}
```
**Note:** ensure you backup your NPM instance before testing this image! Especially if there are database changes
**Note:** this is a different docker image namespace than the official image
> [!NOTE]
> Ensure you backup your NPM instance before testing this image! Especially if there are database changes.
> This is a different docker image namespace than the official image.
> [!WARNING]
> Changes and additions to DNS Providers require verification by at least 2 members of the community!
""", true)
}
}

View File

@@ -1,7 +1,7 @@
<p align="center">
<img src="https://nginxproxymanager.com/github.png">
<br><br>
<img src="https://img.shields.io/badge/version-2.12.4-green.svg?style=for-the-badge">
<img src="https://img.shields.io/badge/version-2.12.6-green.svg?style=for-the-badge">
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
</a>

View File

@@ -1,5 +1,5 @@
const _ = require('lodash');
const fs = require('fs');
const fs = require('node:fs');
const batchflow = require('batchflow');
const logger = require('../logger').access;
const error = require('../lib/error');
@@ -38,7 +38,7 @@ const internalAccessList = {
.then((row) => {
data.id = row.id;
let promises = [];
const promises = [];
// Now add the items
data.items.map((item) => {
@@ -116,7 +116,7 @@ const internalAccessList = {
.then((row) => {
if (row.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('Access List could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
throw new error.InternalValidationError(`Access List could not be updated, IDs do not match: ${row.id} !== ${data.id}`);
}
})
.then(() => {
@@ -135,10 +135,10 @@ const internalAccessList = {
.then(() => {
// Check for items and add/update/remove them
if (typeof data.items !== 'undefined' && data.items) {
let promises = [];
let items_to_keep = [];
const promises = [];
const items_to_keep = [];
data.items.map(function (item) {
data.items.map((item) => {
if (item.password) {
promises.push(accessListAuthModel
.query()
@@ -154,7 +154,7 @@ const internalAccessList = {
}
});
let query = accessListAuthModel
const query = accessListAuthModel
.query()
.delete()
.where('access_list_id', data.id);
@@ -175,9 +175,9 @@ const internalAccessList = {
.then(() => {
// Check for clients and add/update/remove them
if (typeof data.clients !== 'undefined' && data.clients) {
let promises = [];
const promises = [];
data.clients.map(function (client) {
data.clients.map((client) => {
if (client.address) {
promises.push(accessListClientModel
.query()
@@ -190,7 +190,7 @@ const internalAccessList = {
}
});
let query = accessListClientModel
const query = accessListClientModel
.query()
.delete()
.where('access_list_id', data.id);
@@ -249,7 +249,7 @@ const internalAccessList = {
return access.can('access_lists:get', data.id)
.then((access_data) => {
let query = accessListModel
const query = accessListModel
.query()
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
.leftJoin('proxy_host', function() {
@@ -267,7 +267,7 @@ const internalAccessList = {
}
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.withGraphFetched('[' + data.expand.join(', ') + ']');
query.withGraphFetched(`[${data.expand.join(', ')}]`);
}
return query.then(utils.omitRow(omissions()));
@@ -327,7 +327,7 @@ const internalAccessList = {
// 3. reconfigure those hosts, then reload nginx
// set the access_list_id to zero for these items
row.proxy_hosts.map(function (val, idx) {
row.proxy_hosts.map((_val, idx) => {
row.proxy_hosts[idx].access_list_id = 0;
});
@@ -340,11 +340,11 @@ const internalAccessList = {
})
.then(() => {
// delete the htpasswd file
let htpasswd_file = internalAccessList.getFilename(row);
const htpasswd_file = internalAccessList.getFilename(row);
try {
fs.unlinkSync(htpasswd_file);
} catch (err) {
} catch (_err) {
// do nothing
}
})
@@ -374,7 +374,7 @@ const internalAccessList = {
getAll: (access, expand, search_query) => {
return access.can('access_lists:list')
.then((access_data) => {
let query = accessListModel
const query = accessListModel
.query()
.select('access_list.*', accessListModel.raw('COUNT(proxy_host.id) as proxy_host_count'))
.leftJoin('proxy_host', function() {
@@ -393,19 +393,19 @@ const internalAccessList = {
// Query is used for searching
if (typeof search_query === 'string') {
query.where(function () {
this.where('name', 'like', '%' + search_query + '%');
this.where('name', 'like', `%${search_query}%`);
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.withGraphFetched('[' + expand.join(', ') + ']');
query.withGraphFetched(`[${expand.join(', ')}]`);
}
return query.then(utils.omitRows(omissions()));
})
.then((rows) => {
if (rows) {
rows.map(function (row, idx) {
rows.map((row, idx) => {
if (typeof row.items !== 'undefined' && row.items) {
rows[idx] = internalAccessList.maskItems(row);
}
@@ -424,7 +424,7 @@ const internalAccessList = {
* @returns {Promise}
*/
getCount: (user_id, visibility) => {
let query = accessListModel
const query = accessListModel
.query()
.count('id as count')
.where('is_deleted', 0);
@@ -445,7 +445,7 @@ const internalAccessList = {
*/
maskItems: (list) => {
if (list && typeof list.items !== 'undefined') {
list.items.map(function (val, idx) {
list.items.map((val, idx) => {
let repeat_for = 8;
let first_char = '*';
@@ -468,7 +468,7 @@ const internalAccessList = {
* @returns {String}
*/
getFilename: (list) => {
return '/data/access/' + list.id;
return `/data/access/${list.id}`;
},
/**
@@ -479,15 +479,15 @@ const internalAccessList = {
* @returns {Promise}
*/
build: (list) => {
logger.info('Building Access file #' + list.id + ' for: ' + list.name);
logger.info(`Building Access file #${list.id} for: ${list.name}`);
return new Promise((resolve, reject) => {
let htpasswd_file = internalAccessList.getFilename(list);
const htpasswd_file = internalAccessList.getFilename(list);
// 1. remove any existing access file
try {
fs.unlinkSync(htpasswd_file);
} catch (err) {
} catch (_err) {
// do nothing
}
@@ -504,14 +504,14 @@ const internalAccessList = {
if (list.items.length) {
return new Promise((resolve, reject) => {
batchflow(list.items).sequential()
.each((i, item, next) => {
.each((_i, item, next) => {
if (typeof item.password !== 'undefined' && item.password.length) {
logger.info('Adding: ' + item.username);
logger.info(`Adding: ${item.username}`);
utils.execFile('openssl', ['passwd', '-apr1', item.password])
.then((res) => {
try {
fs.appendFileSync(htpasswd_file, item.username + ':' + res + '\n', {encoding: 'utf8'});
fs.appendFileSync(htpasswd_file, `${item.username}:${res}\n`, {encoding: 'utf8'});
} catch (err) {
reject(err);
}
@@ -528,7 +528,7 @@ const internalAccessList = {
reject(err);
})
.end((results) => {
logger.success('Built Access file #' + list.id + ' for: ' + list.name);
logger.success(`Built Access file #${list.id} for: ${list.name}`);
resolve(results);
});
});

View File

@@ -1,6 +1,6 @@
const _ = require('lodash');
const fs = require('fs');
const https = require('https');
const fs = require('node:fs');
const https = require('node:https');
const tempWrite = require('temp-write');
const moment = require('moment');
const archiver = require('archiver');
@@ -49,7 +49,7 @@ const internalCertificate = {
processExpiringHosts: () => {
if (!internalCertificate.intervalProcessing) {
internalCertificate.intervalProcessing = true;
logger.info('Renewing SSL certs expiring within ' + internalCertificate.renewBeforeExpirationBy[0] + ' ' + internalCertificate.renewBeforeExpirationBy[1] + ' ...');
logger.info(`Renewing SSL certs expiring within ${internalCertificate.renewBeforeExpirationBy[0]} ${internalCertificate.renewBeforeExpirationBy[1]} ...`);
const expirationThreshold = moment().add(internalCertificate.renewBeforeExpirationBy[0], internalCertificate.renewBeforeExpirationBy[1]).format('YYYY-MM-DD HH:mm:ss');
@@ -70,7 +70,7 @@ const internalCertificate = {
*/
let sequence = Promise.resolve();
certificates.forEach(function (certificate) {
certificates.forEach((certificate) => {
sequence = sequence.then(() =>
internalCertificate
.renew(
@@ -202,7 +202,7 @@ const internalCertificate = {
.then(() => {
// At this point, the letsencrypt cert should exist on disk.
// Lets get the expiry date from the file and update the row silently
return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem')
return internalCertificate.getCertificateInfoFromFile(`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`)
.then((cert_info) => {
return certificateModel
.query()
@@ -263,7 +263,7 @@ const internalCertificate = {
.then((row) => {
if (row.id !== data.id) {
// Sanity check that something crazy hasn't happened
throw new error.InternalValidationError('Certificate could not be updated, IDs do not match: ' + row.id + ' !== ' + data.id);
throw new error.InternalValidationError(`Certificate could not be updated, IDs do not match: ${row.id} !== ${data.id}`);
}
return certificateModel
@@ -308,7 +308,7 @@ const internalCertificate = {
return access.can('certificates:get', data.id)
.then((access_data) => {
let query = certificateModel
const query = certificateModel
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
@@ -323,7 +323,7 @@ const internalCertificate = {
}
if (typeof data.expand !== 'undefined' && data.expand !== null) {
query.withGraphFetched('[' + data.expand.join(', ') + ']');
query.withGraphFetched(`[${data.expand.join(', ')}]`);
}
return query.then(utils.omitRow(omissions()));
@@ -354,17 +354,17 @@ const internalCertificate = {
})
.then((certificate) => {
if (certificate.provider === 'letsencrypt') {
const zipDirectory = '/etc/letsencrypt/live/npm-' + data.id;
const zipDirectory = internalCertificate.getLiveCertPath(data.id);
if (!fs.existsSync(zipDirectory)) {
throw new error.ItemNotFoundError('Certificate ' + certificate.nice_name + ' does not exists');
throw new error.ItemNotFoundError(`Certificate ${certificate.nice_name} does not exists`);
}
let certFiles = fs.readdirSync(zipDirectory)
const certFiles = fs.readdirSync(zipDirectory)
.filter((fn) => fn.endsWith('.pem'))
.map((fn) => fs.realpathSync(path.join(zipDirectory, fn)));
const downloadName = 'npm-' + data.id + '-' + `${Date.now()}.zip`;
const opName = '/tmp/' + downloadName;
const downloadName = `npm-${data.id}-${Date.now()}.zip`;
const opName = `/tmp/${downloadName}`;
internalCertificate.zipFiles(certFiles, opName)
.then(() => {
logger.debug('zip completed : ', opName);
@@ -392,7 +392,7 @@ const internalCertificate = {
return new Promise((resolve, reject) => {
source
.map((fl) => {
let fileName = path.basename(fl);
const fileName = path.basename(fl);
logger.debug(fl, 'added to certificate zip');
archive.file(fl, { name: fileName });
});
@@ -462,7 +462,7 @@ const internalCertificate = {
getAll: (access, expand, search_query) => {
return access.can('certificates:list')
.then((access_data) => {
let query = certificateModel
const query = certificateModel
.query()
.where('is_deleted', 0)
.groupBy('id')
@@ -479,12 +479,12 @@ const internalCertificate = {
// Query is used for searching
if (typeof search_query === 'string') {
query.where(function () {
this.where('nice_name', 'like', '%' + search_query + '%');
this.where('nice_name', 'like', `%${search_query}%`);
});
}
if (typeof expand !== 'undefined' && expand !== null) {
query.withGraphFetched('[' + expand.join(', ') + ']');
query.withGraphFetched(`[${expand.join(', ')}]`);
}
return query.then(utils.omitRows(omissions()));
@@ -499,7 +499,7 @@ const internalCertificate = {
* @returns {Promise}
*/
getCount: (user_id, visibility) => {
let query = certificateModel
const query = certificateModel
.query()
.count('id as count')
.where('is_deleted', 0);
@@ -521,7 +521,7 @@ const internalCertificate = {
writeCustomCert: (certificate) => {
logger.info('Writing Custom Certificate:', certificate);
const dir = '/data/custom_ssl/npm-' + certificate.id;
const dir = `/data/custom_ssl/npm-${certificate.id}`;
return new Promise((resolve, reject) => {
if (certificate.provider === 'letsencrypt') {
@@ -531,7 +531,7 @@ const internalCertificate = {
let certData = certificate.meta.certificate;
if (typeof certificate.meta.intermediate_certificate !== 'undefined') {
certData = certData + '\n' + certificate.meta.intermediate_certificate;
certData = `${certData}\n${certificate.meta.intermediate_certificate}`;
}
try {
@@ -543,7 +543,7 @@ const internalCertificate = {
return;
}
fs.writeFile(dir + '/fullchain.pem', certData, function (err) {
fs.writeFile(`${dir}/fullchain.pem`, certData, (err) => {
if (err) {
reject(err);
} else {
@@ -553,7 +553,7 @@ const internalCertificate = {
})
.then(() => {
return new Promise((resolve, reject) => {
fs.writeFile(dir + '/privkey.pem', certificate.meta.certificate_key, function (err) {
fs.writeFile(`${dir}/privkey.pem`, certificate.meta.certificate_key, (err) => {
if (err) {
reject(err);
} else {
@@ -591,7 +591,7 @@ const internalCertificate = {
validate: (data) => {
return new Promise((resolve) => {
// Put file contents into an object
let files = {};
const files = {};
_.map(data.files, (file, name) => {
if (internalCertificate.allowedSslFiles.indexOf(name) !== -1) {
files[name] = file.data.toString();
@@ -603,7 +603,7 @@ const internalCertificate = {
.then((files) => {
// For each file, create a temp file and write the contents to it
// Then test it depending on the file type
let promises = [];
const promises = [];
_.map(files, (content, type) => {
promises.push(new Promise((resolve) => {
if (type === 'certificate_key') {
@@ -688,11 +688,11 @@ const internalCertificate = {
reject(new error.ValidationError('Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.'));
}, 10000);
utils
.exec('openssl pkey -in ' + filepath + ' -check -noout 2>&1 ')
.exec(`openssl pkey -in ${filepath} -check -noout 2>&1 `)
.then((result) => {
clearTimeout(failTimeout);
if (!result.toLowerCase().includes('key is valid')) {
reject(new error.ValidationError('Result Validation Error: ' + result));
reject(new error.ValidationError(`Result Validation Error: ${result}`));
}
fs.unlinkSync(filepath);
resolve(true);
@@ -700,7 +700,7 @@ const internalCertificate = {
.catch((err) => {
clearTimeout(failTimeout);
fs.unlinkSync(filepath);
reject(new error.ValidationError('Certificate Key is not valid (' + err.message + ')', err));
reject(new error.ValidationError(`Certificate Key is not valid (${err.message})`, err));
});
});
});
@@ -735,9 +735,9 @@ const internalCertificate = {
* @param {Boolean} [throw_expired] Throw when the certificate is out of date
*/
getCertificateInfoFromFile: (certificate_file, throw_expired) => {
let certData = {};
const certData = {};
return utils.exec('openssl x509 -in ' + certificate_file + ' -subject -noout')
return utils.execFile('openssl', ['x509', '-in', certificate_file, '-subject', '-noout'])
.then((result) => {
// Examples:
// subject=CN = *.jc21.com
@@ -745,11 +745,11 @@ const internalCertificate = {
const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim;
const match = regex.exec(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');
return utils.execFile('openssl', ['x509', '-in', certificate_file, '-issuer', '-noout']);
})
.then((result) => {
@@ -760,11 +760,11 @@ const internalCertificate = {
const regex = /^(?:issuer=)?(.*)$/gim;
const match = regex.exec(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');
return utils.execFile('openssl', ['x509', '-in', certificate_file, '-dates', '-noout']);
})
.then((result) => {
// notBefore=Jul 14 04:04:29 2018 GMT
@@ -773,7 +773,7 @@ const internalCertificate = {
let validTo = null;
const lines = result.split('\n');
lines.map(function (str) {
lines.map((str) => {
const regex = /^(\S+)=(.*)$/gim;
const match = regex.exec(str.trim());
@@ -789,21 +789,21 @@ const internalCertificate = {
});
if (!validFrom || !validTo) {
throw new error.ValidationError('Could not determine dates from certificate: ' + result);
throw new error.ValidationError(`Could not determine dates from certificate: ${result}`);
}
if (throw_expired && validTo < parseInt(moment().format('X'), 10)) {
throw new error.ValidationError('Certificate has expired');
}
certData['dates'] = {
certData.dates = {
from: validFrom,
to: validTo
};
return certData;
}).catch((err) => {
throw new error.ValidationError('Certificate is not valid (' + err.message + ')', err);
throw new error.ValidationError(`Certificate is not valid (${err.message})`, err);
});
},
@@ -814,7 +814,7 @@ const internalCertificate = {
* @param {Boolean} [remove]
* @returns {Object}
*/
cleanMeta: function (meta, remove) {
cleanMeta: (meta, remove) => {
internalCertificate.allowedSslFiles.map((key) => {
if (typeof meta[key] !== 'undefined' && meta[key]) {
if (remove) {
@@ -834,24 +834,35 @@ const internalCertificate = {
* @returns {Promise}
*/
requestLetsEncryptSsl: (certificate) => {
logger.info('Requesting Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
logger.info(`Requesting LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
const cmd = `${certbotCommand} certonly ` +
`--config '${letsencryptConfig}' ` +
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
`--cert-name "npm-${certificate.id}" ` +
'--agree-tos ' +
'--authenticator webroot ' +
`--email '${certificate.meta.letsencrypt_email}' ` +
'--preferred-challenges "dns,http" ' +
`--domains "${certificate.domain_names.join(',')}" ` +
(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') +
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
const args = [
'certonly',
'--config',
letsencryptConfig,
'--work-dir',
'/tmp/letsencrypt-lib',
'--logs-dir',
'/tmp/letsencrypt-log',
'--cert-name',
`npm-${certificate.id}`,
'--agree-tos',
'--authenticator',
'webroot',
'--email',
certificate.meta.letsencrypt_email,
'--preferred-challenges',
'dns,http',
'--domains',
certificate.domain_names.join(','),
];
logger.info('Command:', cmd);
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id);
args.push(...adds.args);
return utils.exec(cmd)
logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`);
return utils.execFile(certbotCommand, args, adds.opts)
.then((result) => {
logger.success(result);
return result;
@@ -868,50 +879,48 @@ const internalCertificate = {
requestLetsEncryptSslWithDnsChallenge: async (certificate) => {
await certbot.installPlugin(certificate.meta.dns_provider);
const dnsPlugin = dnsPlugins[certificate.meta.dns_provider];
logger.info(`Requesting Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
logger.info(`Requesting LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
const credentialsLocation = `/etc/letsencrypt/credentials/credentials-${certificate.id}`;
fs.mkdirSync('/etc/letsencrypt/credentials', { recursive: true });
fs.writeFileSync(credentialsLocation, certificate.meta.dns_provider_credentials, {mode: 0o600});
// Whether the plugin has a --<name>-credentials argument
const hasConfigArg = certificate.meta.dns_provider !== 'route53';
let mainCmd = certbotCommand + ' certonly ' +
`--config '${letsencryptConfig}' ` +
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
`--cert-name 'npm-${certificate.id}' ` +
'--agree-tos ' +
`--email '${certificate.meta.letsencrypt_email}' ` +
`--domains '${certificate.domain_names.join(',')}' ` +
`--authenticator '${dnsPlugin.full_plugin_name}' ` +
(
hasConfigArg
? `--${dnsPlugin.full_plugin_name}-credentials '${credentialsLocation}' `
: ''
) +
(
certificate.meta.propagation_seconds !== undefined
? `--${dnsPlugin.full_plugin_name}-propagation-seconds '${certificate.meta.propagation_seconds}' `
: ''
) +
(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') +
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
const args = [
'certonly',
'--config',
letsencryptConfig,
'--work-dir',
'/tmp/letsencrypt-lib',
'--logs-dir',
'/tmp/letsencrypt-log',
'--cert-name',
`npm-${certificate.id}`,
'--agree-tos',
'--email',
certificate.meta.letsencrypt_email,
'--domains',
certificate.domain_names.join(','),
'--authenticator',
dnsPlugin.full_plugin_name,
];
// Prepend the path to the credentials file as an environment variable
if (certificate.meta.dns_provider === 'route53') {
mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd;
if (hasConfigArg) {
args.push(`--${dnsPlugin.full_plugin_name}-credentials`, credentialsLocation);
}
if (certificate.meta.propagation_seconds !== undefined) {
args.push(`--${dnsPlugin.full_plugin_name}-propagation-seconds`, certificate.meta.propagation_seconds.toString());
}
if (certificate.meta.dns_provider === 'duckdns') {
mainCmd = mainCmd + ' --dns-duckdns-no-txt-restore';
}
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider);
args.push(...adds.args);
logger.info('Command:', mainCmd);
logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`);
try {
const result = await utils.exec(mainCmd);
const result = await utils.execFile(certbotCommand, args, adds.opts);
logger.info(result);
return result;
} catch (err) {
@@ -939,7 +948,7 @@ const internalCertificate = {
return renewMethod(certificate)
.then(() => {
return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem');
return internalCertificate.getCertificateInfoFromFile(`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`);
})
.then((cert_info) => {
return certificateModel
@@ -971,22 +980,31 @@ const internalCertificate = {
* @returns {Promise}
*/
renewLetsEncryptSsl: (certificate) => {
logger.info('Renewing Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
logger.info(`Renewing LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
const cmd = certbotCommand + ' renew --force-renewal ' +
`--config '${letsencryptConfig}' ` +
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
`--cert-name 'npm-${certificate.id}' ` +
'--preferred-challenges "dns,http" ' +
'--no-random-sleep-on-renew ' +
'--disable-hook-validation ' +
(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') +
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
const args = [
'renew',
'--force-renewal',
'--config',
letsencryptConfig,
'--work-dir',
'/tmp/letsencrypt-lib',
'--logs-dir',
'/tmp/letsencrypt-log',
'--cert-name',
`npm-${certificate.id}`,
'--preferred-challenges',
'dns,http',
'--no-random-sleep-on-renew',
'--disable-hook-validation',
];
logger.info('Command:', cmd);
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider);
args.push(...adds.args);
return utils.exec(cmd)
logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`);
return utils.execFile(certbotCommand, args, adds.opts)
.then((result) => {
logger.info(result);
return result;
@@ -1004,27 +1022,29 @@ const internalCertificate = {
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
}
logger.info(`Renewing Let'sEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
logger.info(`Renewing LetsEncrypt certificates via ${dnsPlugin.name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
let mainCmd = certbotCommand + ' renew --force-renewal ' +
`--config "${letsencryptConfig}" ` +
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
`--cert-name 'npm-${certificate.id}' ` +
'--disable-hook-validation ' +
'--no-random-sleep-on-renew ' +
(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') +
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
const args = [
'renew',
'--force-renewal',
'--config',
letsencryptConfig,
'--work-dir',
'/tmp/letsencrypt-lib',
'--logs-dir',
'/tmp/letsencrypt-log',
'--cert-name',
`npm-${certificate.id}`,
'--disable-hook-validation',
'--no-random-sleep-on-renew',
];
// Prepend the path to the credentials file as an environment variable
if (certificate.meta.dns_provider === 'route53') {
const credentialsLocation = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
mainCmd = 'AWS_CONFIG_FILE=\'' + credentialsLocation + '\' ' + mainCmd;
}
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id, certificate.meta.dns_provider);
args.push(...adds.args);
logger.info('Command:', mainCmd);
logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`);
return utils.exec(mainCmd)
return utils.execFile(certbotCommand, args, adds.opts)
.then(async (result) => {
logger.info(result);
return result;
@@ -1037,25 +1057,29 @@ const internalCertificate = {
* @returns {Promise}
*/
revokeLetsEncryptSsl: (certificate, throw_errors) => {
logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
logger.info(`Revoking LetsEncrypt certificates for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
const mainCmd = certbotCommand + ' revoke ' +
`--config '${letsencryptConfig}' ` +
'--work-dir "/tmp/letsencrypt-lib" ' +
'--logs-dir "/tmp/letsencrypt-log" ' +
`--cert-path '/etc/letsencrypt/live/npm-${certificate.id}/fullchain.pem' ` +
'--delete-after-revoke ' +
(letsencryptServer !== null ? `--server '${letsencryptServer}' ` : '') +
(letsencryptStaging && letsencryptServer === null ? '--staging ' : '');
const args = [
'revoke',
'--config',
letsencryptConfig,
'--work-dir',
'/tmp/letsencrypt-lib',
'--logs-dir',
'/tmp/letsencrypt-log',
'--cert-path',
`${internalCertificate.getLiveCertPath(certificate.id)}/fullchain.pem`,
'--delete-after-revoke',
];
// Don't fail command if file does not exist
const delete_credentialsCmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`;
const adds = internalCertificate.getAdditionalCertbotArgs(certificate.id);
args.push(...adds.args);
logger.info('Command:', mainCmd + '; ' + delete_credentialsCmd);
logger.info(`Command: ${certbotCommand} ${args ? args.join(' ') : ''}`);
return utils.exec(mainCmd)
return utils.execFile(certbotCommand, args, adds.opts)
.then(async (result) => {
await utils.exec(delete_credentialsCmd);
await utils.exec(`rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`);
logger.info(result);
return result;
})
@@ -1073,9 +1097,8 @@ const internalCertificate = {
* @returns {Boolean}
*/
hasLetsEncryptSslCerts: (certificate) => {
const letsencryptPath = '/etc/letsencrypt/live/npm-' + certificate.id;
return fs.existsSync(letsencryptPath + '/fullchain.pem') && fs.existsSync(letsencryptPath + '/privkey.pem');
const letsencryptPath = internalCertificate.getLiveCertPath(certificate.id);
return fs.existsSync(`${letsencryptPath}/fullchain.pem`) && fs.existsSync(`${letsencryptPath}/privkey.pem`);
},
/**
@@ -1087,7 +1110,7 @@ const internalCertificate = {
*/
disableInUseHosts: (in_use_result) => {
if (in_use_result.total_count) {
let promises = [];
const promises = [];
if (in_use_result.proxy_hosts.length) {
promises.push(internalNginx.bulkDeleteConfigs('proxy_host', in_use_result.proxy_hosts));
@@ -1117,7 +1140,7 @@ const internalCertificate = {
*/
enableInUseHosts: (in_use_result) => {
if (in_use_result.total_count) {
let promises = [];
const promises = [];
if (in_use_result.proxy_hosts.length) {
promises.push(internalNginx.bulkGenerateConfigs('proxy_host', in_use_result.proxy_hosts));
@@ -1150,12 +1173,12 @@ const internalCertificate = {
// Create a test challenge file
const testChallengeDir = '/data/letsencrypt-acme-challenge/.well-known/acme-challenge';
const testChallengeFile = testChallengeDir + '/test-challenge';
const testChallengeFile = `${testChallengeDir}/test-challenge`;
fs.mkdirSync(testChallengeDir, {recursive: true});
fs.writeFileSync(testChallengeFile, 'Success', {encoding: 'utf8'});
async function performTestForDomain (domain) {
logger.info('Testing http challenge for ' + domain);
logger.info(`Testing http challenge for ${domain}`);
const url = `http://${domain}/.well-known/acme-challenge/test-challenge`;
const formBody = `method=G&url=${encodeURI(url)}&bodytype=T&requestbody=&headername=User-Agent&headervalue=None&locationid=1&ch=false&cc=false`;
const options = {
@@ -1169,13 +1192,16 @@ const internalCertificate = {
const result = await new Promise((resolve) => {
const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, function (res) {
const req = https.request('https://www.site24x7.com/tools/restapi-tester', options, (res) => {
let responseBody = '';
res.on('data', (chunk) => responseBody = responseBody + chunk);
res.on('end', function () {
res.on('data', (chunk) => {
responseBody = responseBody + chunk;
});
res.on('end', () => {
try {
const parsedBody = JSON.parse(responseBody + '');
const parsedBody = JSON.parse(`${responseBody}`);
if (res.statusCode !== 200) {
logger.warn(`Failed to test HTTP challenge for domain ${domain} because HTTP status code ${res.statusCode} was returned: ${parsedBody.message}`);
resolve(undefined);
@@ -1196,7 +1222,7 @@ const internalCertificate = {
// Make sure to write the request body.
req.write(formBody);
req.end();
req.on('error', function (e) { logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e);
req.on('error', (e) => { logger.warn(`Failed to test HTTP challenge for domain ${domain}`, e);
resolve(undefined); });
});
@@ -1238,6 +1264,34 @@ const internalCertificate = {
fs.unlinkSync(testChallengeFile);
return results;
},
getAdditionalCertbotArgs: (certificate_id, dns_provider) => {
const args = [];
if (letsencryptServer !== null) {
args.push('--server', letsencryptServer);
}
if (letsencryptStaging && letsencryptServer === null) {
args.push('--staging');
}
// For route53, add the credentials file as an environment variable,
// inheriting the process env
const opts = {};
if (certificate_id && dns_provider === 'route53') {
opts.env = process.env;
opts.env.AWS_CONFIG_FILE = `/etc/letsencrypt/credentials/credentials-${certificate_id}`;
}
if (dns_provider === 'duckdns') {
args.push('--dns-duckdns-no-txt-restore');
}
return {args: args, opts: opts};
},
getLiveCertPath: (certificate_id) => {
return `/etc/letsencrypt/live/npm-${certificate_id}`;
}
};

View File

@@ -1,5 +1,5 @@
const _ = require('lodash');
const fs = require('fs');
const fs = require('node:fs');
const logger = require('../logger').nginx;
const config = require('../lib/config');
const utils = require('../lib/utils');
@@ -57,9 +57,9 @@ const internalNginx = {
// It will always look like this:
// nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (6: No such device or address)
let valid_lines = [];
let err_lines = err.message.split('\n');
err_lines.map(function (line) {
const valid_lines = [];
const err_lines = err.message.split('\n');
err_lines.map((line) => {
if (line.indexOf('/var/log/nginx/error.log') === -1) {
valid_lines.push(line);
}
@@ -105,7 +105,7 @@ const internalNginx = {
logger.info('Testing Nginx configuration');
}
return utils.exec('/usr/sbin/nginx -t -g "error_log off;"');
return utils.execFile('/usr/sbin/nginx', ['-t', '-g', 'error_log off;']);
},
/**
@@ -115,7 +115,7 @@ const internalNginx = {
return internalNginx.test()
.then(() => {
logger.info('Reloading Nginx');
return utils.exec('/usr/sbin/nginx -s reload');
return utils.execFile('/usr/sbin/nginx', ['-s', 'reload']);
});
},
@@ -128,7 +128,7 @@ const internalNginx = {
if (host_type === 'default') {
return '/data/nginx/default_host/site.conf';
}
return '/data/nginx/' + internalNginx.getFileFriendlyHostType(host_type) + '/' + host_id + '.conf';
return `/data/nginx/${internalNginx.getFileFriendlyHostType(host_type)}/${host_id}.conf`;
},
/**
@@ -141,7 +141,7 @@ const internalNginx = {
let template;
try {
template = fs.readFileSync(__dirname + '/../templates/_location.conf', {encoding: 'utf8'});
template = fs.readFileSync(`${__dirname}/../templates/_location.conf`, {encoding: 'utf8'});
} catch (err) {
reject(new error.ConfigurationError(err.message));
return;
@@ -152,7 +152,7 @@ const internalNginx = {
const locationRendering = async () => {
for (let i = 0; i < host.locations.length; i++) {
let locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id},
const locationCopy = Object.assign({}, {access_list_id: host.access_list_id}, {certificate_id: host.certificate_id},
{ssl_forced: host.ssl_forced}, {caching_enabled: host.caching_enabled}, {block_exploits: host.block_exploits},
{allow_websocket_upgrade: host.allow_websocket_upgrade}, {http2_support: host.http2_support},
{hsts_enabled: host.hsts_enabled}, {hsts_subdomains: host.hsts_subdomains}, {access_list: host.access_list},
@@ -183,21 +183,21 @@ const internalNginx = {
*/
generateConfig: (host_type, host_row) => {
// Prevent modifying the original object:
let host = JSON.parse(JSON.stringify(host_row));
const host = JSON.parse(JSON.stringify(host_row));
const nice_host_type = internalNginx.getFileFriendlyHostType(host_type);
if (config.debug()) {
logger.info('Generating ' + nice_host_type + ' Config:', JSON.stringify(host, null, 2));
logger.info(`Generating ${nice_host_type} Config:`, JSON.stringify(host, null, 2));
}
const renderEngine = utils.getRenderEngine();
return new Promise((resolve, reject) => {
let template = null;
let filename = internalNginx.getConfigName(nice_host_type, host.id);
let template = null;
const filename = internalNginx.getConfigName(nice_host_type, host.id);
try {
template = fs.readFileSync(__dirname + '/../templates/' + nice_host_type + '.conf', {encoding: 'utf8'});
template = fs.readFileSync(`${__dirname}/../templates/${nice_host_type}.conf`, {encoding: 'utf8'});
} catch (err) {
reject(new error.ConfigurationError(err.message));
return;
@@ -252,7 +252,7 @@ const internalNginx = {
})
.catch((err) => {
if (config.debug()) {
logger.warn('Could not write ' + filename + ':', err.message);
logger.warn(`Could not write ${filename}:`, err.message);
}
reject(new error.ConfigurationError(err.message));
@@ -277,11 +277,11 @@ const internalNginx = {
const renderEngine = utils.getRenderEngine();
return new Promise((resolve, reject) => {
let template = null;
let filename = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf';
let template = null;
const filename = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`;
try {
template = fs.readFileSync(__dirname + '/../templates/letsencrypt-request.conf', {encoding: 'utf8'});
template = fs.readFileSync(`${__dirname}/../templates/letsencrypt-request.conf`, {encoding: 'utf8'});
} catch (err) {
reject(new error.ConfigurationError(err.message));
return;
@@ -302,7 +302,7 @@ const internalNginx = {
})
.catch((err) => {
if (config.debug()) {
logger.warn('Could not write ' + filename + ':', err.message);
logger.warn(`Could not write ${filename}:`, err.message);
}
reject(new error.ConfigurationError(err.message));
@@ -316,7 +316,7 @@ const internalNginx = {
* @param {String} filename
*/
deleteFile: (filename) => {
logger.debug('Deleting file: ' + filename);
logger.debug(`Deleting file: ${filename}`);
try {
fs.unlinkSync(filename);
} catch (err) {
@@ -330,7 +330,7 @@ const internalNginx = {
* @returns String
*/
getFileFriendlyHostType: (host_type) => {
return host_type.replace(new RegExp('-', 'g'), '_');
return host_type.replace(/-/g, '_');
},
/**
@@ -340,7 +340,7 @@ const internalNginx = {
* @returns {Promise}
*/
deleteLetsEncryptRequestConfig: (certificate) => {
const config_file = '/data/nginx/temp/letsencrypt_' + certificate.id + '.conf';
const config_file = `/data/nginx/temp/letsencrypt_${certificate.id}.conf`;
return new Promise((resolve/*, reject*/) => {
internalNginx.deleteFile(config_file);
resolve();
@@ -355,7 +355,7 @@ const internalNginx = {
*/
deleteConfig: (host_type, host, delete_err_file) => {
const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id);
const config_file_err = config_file + '.err';
const config_file_err = `${config_file}.err`;
return new Promise((resolve/*, reject*/) => {
internalNginx.deleteFile(config_file);
@@ -373,7 +373,7 @@ const internalNginx = {
*/
renameConfigAsError: (host_type, host) => {
const config_file = internalNginx.getConfigName(internalNginx.getFileFriendlyHostType(host_type), typeof host === 'undefined' ? 0 : host.id);
const config_file_err = config_file + '.err';
const config_file_err = `${config_file}.err`;
return new Promise((resolve/*, reject*/) => {
fs.unlink(config_file, () => {
@@ -392,8 +392,8 @@ const internalNginx = {
* @returns {Promise}
*/
bulkGenerateConfigs: (host_type, hosts) => {
let promises = [];
hosts.map(function (host) {
const promises = [];
hosts.map((host) => {
promises.push(internalNginx.generateConfig(host_type, host));
});
@@ -406,8 +406,8 @@ const internalNginx = {
* @returns {Promise}
*/
bulkDeleteConfigs: (host_type, hosts) => {
let promises = [];
hosts.map(function (host) {
const promises = [];
hosts.map((host) => {
promises.push(internalNginx.deleteConfig(host_type, host, true));
});
@@ -418,14 +418,12 @@ const internalNginx = {
* @param {string} config
* @returns {boolean}
*/
advancedConfigHasDefaultLocation: function (cfg) {
return !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im);
},
advancedConfigHasDefaultLocation: (cfg) => !!cfg.match(/^(?:.*;)?\s*?location\s*?\/\s*?{/im),
/**
* @returns {boolean}
*/
ipv6Enabled: function () {
ipv6Enabled: () => {
if (typeof process.env.DISABLE_IPV6 !== 'undefined') {
const disabled = process.env.DISABLE_IPV6.toLowerCase();
return !(disabled === 'on' || disabled === 'true' || disabled === '1' || disabled === 'yes');

View File

@@ -11,7 +11,7 @@ const certbot = {
/**
* @param {array} pluginKeys
*/
installPlugins: async function (pluginKeys) {
installPlugins: async (pluginKeys) => {
let hasErrors = false;
return new Promise((resolve, reject) => {
@@ -21,7 +21,7 @@ const certbot = {
}
batchflow(pluginKeys).sequential()
.each((i, pluginKey, next) => {
.each((_i, pluginKey, next) => {
certbot.installPlugin(pluginKey)
.then(() => {
next();
@@ -51,7 +51,7 @@ const certbot = {
* @param {string} pluginKey
* @returns {Object}
*/
installPlugin: async function (pluginKey) {
installPlugin: async (pluginKey) => {
if (typeof dnsPlugins[pluginKey] === 'undefined') {
// throw Error(`Certbot plugin ${pluginKey} not found`);
throw new error.ItemNotFoundError(pluginKey);
@@ -63,8 +63,15 @@ const certbot = {
plugin.version = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT);
plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT);
const cmd = '. /opt/certbot/bin/activate && pip install --no-cache-dir ' + plugin.dependencies + ' ' + plugin.package_name + plugin.version + ' ' + ' && deactivate';
return utils.exec(cmd)
// SETUPTOOLS_USE_DISTUTILS is required for certbot plugins to install correctly
// in new versions of Python
let env = Object.assign({}, process.env, {SETUPTOOLS_USE_DISTUTILS: 'stdlib'});
if (typeof plugin.env === 'object') {
env = Object.assign(env, plugin.env);
}
const cmd = `. /opt/certbot/bin/activate && pip install --no-cache-dir ${plugin.dependencies} ${plugin.package_name}${plugin.version} && deactivate`;
return utils.exec(cmd, {env})
.then((result) => {
logger.complete(`Installed ${pluginKey}`);
return result;

View File

@@ -1,13 +1,13 @@
const _ = require('lodash');
const exec = require('child_process').exec;
const execFile = require('child_process').execFile;
const exec = require('node:child_process').exec;
const execFile = require('node:child_process').execFile;
const { Liquid } = require('liquidjs');
const logger = require('../logger').global;
const error = require('./error');
module.exports = {
exec: async function(cmd, options = {}) {
exec: async (cmd, options = {}) => {
logger.debug('CMD:', cmd);
const { stdout, stderr } = await new Promise((resolve, reject) => {
@@ -29,15 +29,19 @@ module.exports = {
/**
* @param {String} cmd
* @param {Array} args
* @param {Object|undefined} options
* @returns {Promise}
*/
execFile: function (cmd, args) {
// logger.debug('CMD: ' + cmd + ' ' + (args ? args.join(' ') : ''));
execFile: (cmd, args, options) => {
logger.debug(`CMD: ${cmd} ${args ? args.join(' ') : ''}`);
if (typeof options === 'undefined') {
options = {};
}
return new Promise((resolve, reject) => {
execFile(cmd, args, function (err, stdout, /*stderr*/) {
execFile(cmd, args, options, (err, stdout, stderr) => {
if (err && typeof err === 'object') {
reject(err);
reject(new error.CommandError(stderr, 1, err));
} else {
resolve(stdout.trim());
}
@@ -51,7 +55,7 @@ module.exports = {
* @param {Array} omissions
* @returns {Function}
*/
omitRow: function (omissions) {
omitRow: (omissions) => {
/**
* @param {Object} row
* @returns {Object}
@@ -67,7 +71,7 @@ module.exports = {
* @param {Array} omissions
* @returns {Function}
*/
omitRows: function (omissions) {
omitRows: (omissions) => {
/**
* @param {Array} rows
* @returns {Object}
@@ -83,9 +87,9 @@ module.exports = {
/**
* @returns {Object} Liquid render engine
*/
getRenderEngine: function () {
getRenderEngine: () => {
const renderEngine = new Liquid({
root: __dirname + '/../templates/'
root: `${__dirname}/../templates/`
});
/**

View File

@@ -6,7 +6,7 @@ const apiValidator = require('../../lib/validator/api');
const internalCertificate = require('../../internal/certificate');
const schema = require('../../schema');
let router = express.Router({
const router = express.Router({
caseSensitive: true,
strict: true,
mergeParams: true
@@ -231,7 +231,7 @@ router
*/
router
.route('/:certificate_id/download')
.options((req, res) => {
.options((_req, res) => {
res.sendStatus(204);
})
.all(jwtdecode())

View File

@@ -110,6 +110,11 @@
"caching_enabled": {
"description": "Should we cache assets",
"type": "boolean"
},
"email": {
"description": "Email address",
"type": "string",
"pattern": "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
}
}
}

View File

@@ -69,7 +69,7 @@
"type": "object"
},
"letsencrypt_email": {
"type": "string"
"$ref": "../common.json#/properties/email"
},
"propagation_seconds": {
"type": "integer",

View File

@@ -24,7 +24,7 @@ const setupDefaultUser = () => {
const email = process.env.INITIAL_ADMIN_EMAIL || 'admin@example.com';
const password = process.env.INITIAL_ADMIN_PASSWORD || 'changeme';
logger.info('Creating a new user: ' + email + ' with password: ' + password);
logger.info(`Creating a new user: ${email} with password: ${password}`);
const data = {
is_deleted: 0,
@@ -113,20 +113,20 @@ const setupCertbotPlugins = () => {
.andWhere('provider', 'letsencrypt')
.then((certificates) => {
if (certificates && certificates.length) {
let plugins = [];
let promises = [];
const plugins = [];
const promises = [];
certificates.map(function (certificate) {
certificates.map((certificate) => {
if (certificate.meta && certificate.meta.dns_challenge === true) {
if (plugins.indexOf(certificate.meta.dns_provider) === -1) {
plugins.push(certificate.meta.dns_provider);
}
// Make sure credentials file exists
const credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
const credentials_loc = `/etc/letsencrypt/credentials/credentials-${certificate.id}`;
// Escape single quotes and backslashes
const escapedCredentials = certificate.meta.dns_provider_credentials.replaceAll('\'', '\\\'').replaceAll('\\', '\\\\');
const credentials_cmd = '[ -f \'' + credentials_loc + '\' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + escapedCredentials + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'; }';
const credentials_cmd = `[ -f '${credentials_loc}' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo '${escapedCredentials}' > '${credentials_loc}' && chmod 600 '${credentials_loc}'; }`;
promises.push(utils.exec(credentials_cmd));
}
});
@@ -136,7 +136,7 @@ const setupCertbotPlugins = () => {
if (promises.length) {
return Promise.all(promises)
.then(() => {
logger.info('Added Certbot plugins ' + plugins.join(', '));
logger.info(`Added Certbot plugins ${plugins.join(', ')}`);
});
}
});
@@ -165,9 +165,7 @@ const setupLogrotation = () => {
return runLogrotate();
};
module.exports = function () {
return setupDefaultUser()
.then(setupDefaultSettings)
.then(setupCertbotPlugins)
.then(setupLogrotation);
};
module.exports = () => setupDefaultUser()
.then(setupDefaultSettings)
.then(setupCertbotPlugins)
.then(setupLogrotation);

View File

@@ -8,34 +8,53 @@ log_info 'Setting ownership ...'
# root
chown root /tmp/nginx
# npm user and group
chown -R "$PUID:$PGID" /data
chown -R "$PUID:$PGID" /etc/letsencrypt
chown -R "$PUID:$PGID" /run/nginx
chown -R "$PUID:$PGID" /tmp/nginx
chown -R "$PUID:$PGID" /var/cache/nginx
chown -R "$PUID:$PGID" /var/lib/logrotate
chown -R "$PUID:$PGID" /var/lib/nginx
chown -R "$PUID:$PGID" /var/log/nginx
locations=(
"/data"
"/etc/letsencrypt"
"/run/nginx"
"/tmp/nginx"
"/var/cache/nginx"
"/var/lib/logrotate"
"/var/lib/nginx"
"/var/log/nginx"
"/etc/nginx/nginx"
"/etc/nginx/nginx.conf"
"/etc/nginx/conf.d"
)
# Don't chown entire /etc/nginx folder as this causes crashes on some systems
chown -R "$PUID:$PGID" /etc/nginx/nginx
chown -R "$PUID:$PGID" /etc/nginx/nginx.conf
chown -R "$PUID:$PGID" /etc/nginx/conf.d
chownit() {
local dir="$1"
local recursive="${2:-true}"
# Certbot directories - optimized approach
CERT_INIT_FLAG="/opt/certbot/.ownership_initialized"
local have
have="$(stat -c '%u:%g' "$dir")"
echo "- $dir ... "
if [ ! -f "$CERT_INIT_FLAG" ]; then
# Prevents errors when installing python certbot plugins when non-root
chown "$PUID:$PGID" /opt/certbot /opt/certbot/bin
if [ "$have" != "$PUID:$PGID" ]; then
if [ "$recursive" = 'true' ] && [ -d "$dir" ]; then
chown -R "$PUID:$PGID" "$dir"
else
chown "$PUID:$PGID" "$dir"
fi
echo " DONE"
else
echo " SKIPPED"
fi
}
for loc in "${locations[@]}"; do
chownit "$loc"
done
if [ "$(is_true "${SKIP_CERTBOT_OWNERSHIP:-}")" = '1' ]; then
log_info 'Skipping ownership change of certbot directories'
else
log_info 'Changing ownership of certbot directories, this may take some time ...'
chownit "/opt/certbot" false
chownit "/opt/certbot/bin" false
# Handle all site-packages directories efficiently
find /opt/certbot/lib -type d -name "site-packages" | while read -r SITE_PACKAGES_DIR; do
chown -R "$PUID:$PGID" "$SITE_PACKAGES_DIR"
chownit "$SITE_PACKAGES_DIR"
done
# Create a flag file to skip this step on subsequent runs
touch "$CERT_INIT_FLAG"
chown "$PUID:$PGID" "$CERT_INIT_FLAG"
fi

View File

@@ -5,12 +5,9 @@ set -e
log_info 'Dynamic resolvers ...'
DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]')
# Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]`
# thanks @tfmm
if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ];
then
if [ "$(is_true "$DISABLE_IPV6")" = '1' ]; then
echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) ipv6=off valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf
else
echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf

View File

@@ -8,14 +8,11 @@ set -e
log_info 'IPv6 ...'
# Lowercase
DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]')
process_folder () {
FILES=$(find "$1" -type f -name "*.conf")
SED_REGEX=
if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ]; then
if [ "$(is_true "$DISABLE_IPV6")" = '1' ]; then
# IPV6 is disabled
echo "Disabling IPV6 in hosts in: $1"
SED_REGEX='s/^([^#]*)listen \[::\]/\1#listen [::]/g'

View File

@@ -56,3 +56,13 @@ get_group_id () {
getent group "$1" | cut -d: -f3
fi
}
# param $1: value
is_true () {
VAL=$(echo "${1:-}" | tr '[:upper:]' '[:lower:]')
if [ "$VAL" == 'true' ] || [ "$VAL" == 'on' ] || [ "$VAL" == '1' ] || [ "$VAL" == 'yes' ]; then
echo '1'
else
echo '0'
fi
}

View File

@@ -8,7 +8,7 @@ BLUE='\E[1;34m'
GREEN='\E[1;32m'
RESET='\E[0m'
S6_OVERLAY_VERSION=3.2.0.2
S6_OVERLAY_VERSION=3.2.1.0
TARGETPLATFORM=${1:-linux/amd64}
# Determine the correct binary file for the architecture given

View File

@@ -1982,12 +1982,12 @@ ci-info@^2.0.0:
integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==
version "1.0.6"
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.6.tgz#8fe672437d01cd6c4561af5334e0cc50ff1955f7"
integrity sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==
dependencies:
inherits "^2.0.1"
safe-buffer "^5.0.1"
inherits "^2.0.4"
safe-buffer "^5.2.1"
class-utils@^0.3.5:
version "0.3.6"
@@ -6253,12 +6253,13 @@ setimmediate@^1.0.4:
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8:
version "2.4.11"
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==
version "2.4.12"
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.12.tgz#eb8b568bf383dfd1867a32c3f2b74eb52bdbf23f"
integrity sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==
dependencies:
inherits "^2.0.1"
safe-buffer "^5.0.1"
inherits "^2.0.4"
safe-buffer "^5.2.1"
to-buffer "^1.2.0"
shebang-command@^1.2.0:
version "1.2.0"

View File

@@ -56,18 +56,18 @@
"full_plugin_name": "dns-bunny"
},
"cdmon": {
"name": "cdmon",
"package_name": "certbot-dns-cdmon",
"version": "~=0.4.1",
"dependencies": "",
"credentials": "dns_cdmon_api_key=your-cdmon-api-token\ndns_cdmon_domain=your_domain_is_optional",
"full_plugin_name": "dns-cdmon"
},
"name": "cdmon",
"package_name": "certbot-dns-cdmon",
"version": "~=0.4.1",
"dependencies": "",
"credentials": "dns_cdmon_api_key=your-cdmon-api-token\ndns_cdmon_domain=your_domain_is_optional",
"full_plugin_name": "dns-cdmon"
},
"cloudflare": {
"name": "Cloudflare",
"package_name": "certbot-dns-cloudflare",
"version": "=={{certbot-version}}",
"dependencies": "cloudflare==4.0.* acme=={{certbot-version}}",
"dependencies": "acme=={{certbot-version}}",
"credentials": "# Cloudflare API token\ndns_cloudflare_api_token=0123456789abcdef0123456789abcdef01234567",
"full_plugin_name": "dns-cloudflare"
},

View File

@@ -10,7 +10,7 @@ describe('Certificates endpoints', () => {
});
});
it('Validate custom certificate', function() {
it('Validate custom certificate', () => {
cy.task('backendApiPostFiles', {
token: token,
path: '/api/nginx/certificates/validate',
@@ -25,7 +25,7 @@ describe('Certificates endpoints', () => {
});
});
it('Custom certificate lifecycle', function() {
it('Custom certificate lifecycle', () => {
// Create custom cert
cy.task('backendApiPost', {
token: token,
@@ -73,7 +73,7 @@ describe('Certificates endpoints', () => {
});
});
it('Request Certificate - CVE-2024-46256/CVE-2024-46257', function() {
it('Request Certificate - CVE-2024-46256/CVE-2024-46257', () => {
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/certificates',
@@ -96,4 +96,28 @@ describe('Certificates endpoints', () => {
expect(data.error.message).to.contain('data/domain_names/0 must match pattern');
});
});
it('Request Certificate - LE Email Escaped', () => {
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/certificates',
data: {
domain_names: ['test.com"||echo hello-world||\\\\n test.com"'],
meta: {
dns_challenge: false,
letsencrypt_agree: true,
letsencrypt_email: "admin@example.com' --version;echo hello-world",
},
provider: 'letsencrypt',
},
returnOnError: true,
}).then((data) => {
cy.validateSwaggerSchema('post', 400, '/nginx/certificates', data);
expect(data).to.have.property('error');
expect(data.error).to.have.property('message');
expect(data.error).to.have.property('code');
expect(data.error.code).to.equal(400);
expect(data.error.message).to.contain('data/meta/letsencrypt_email must match pattern');
});
});
});

View File

@@ -9,7 +9,7 @@ describe('Dashboard endpoints', () => {
});
});
it('Should be able to get host counts', function() {
it('Should be able to get host counts', () => {
cy.task('backendApiGet', {
token: token,
path: '/api/reports/hosts'

View File

@@ -9,7 +9,7 @@ describe('Full Certificate Provisions', () => {
});
});
it('Should be able to create new http certificate', function() {
it('Should be able to create new http certificate', () => {
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/certificates',
@@ -32,7 +32,7 @@ describe('Full Certificate Provisions', () => {
});
});
it('Should be able to create new DNS certificate with Powerdns', function() {
it('Should be able to create new DNS certificate with Powerdns', () => {
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/certificates',

View File

@@ -1,7 +1,7 @@
/// <reference types="cypress" />
describe('Basic API checks', () => {
it('Should return a valid health payload', function () {
it('Should return a valid health payload', () => {
cy.task('backendApiGet', {
path: '/api/',
}).then((data) => {
@@ -10,9 +10,9 @@ describe('Basic API checks', () => {
});
});
it('Should return a valid schema payload', function () {
it('Should return a valid schema payload', () => {
cy.task('backendApiGet', {
path: '/api/schema?ts=' + Date.now(),
path: `/api/schema?ts=${Date.now()}`,
}).then((data) => {
expect(data.openapi).to.be.equal('3.1.0');
});

View File

@@ -1,12 +1,12 @@
/// <reference types="cypress" />
describe('LDAP with Authentik', () => {
let token;
let _token;
if (Cypress.env('skipStackCheck') === 'true' || Cypress.env('stack') === 'postgres') {
before(() => {
cy.getToken().then((tok) => {
token = tok;
_token = tok;
// cy.task('backendApiPut', {
// token: token,
@@ -45,7 +45,7 @@ describe('LDAP with Authentik', () => {
});
});
it.skip('Should log in with LDAP', function() {
it.skip('Should log in with LDAP', () => {
// cy.task('backendApiPost', {
// token: token,
// path: '/api/auth',

View File

@@ -1,12 +1,12 @@
/// <reference types="cypress" />
describe('OAuth with Authentik', () => {
let token;
let _token;
if (Cypress.env('skipStackCheck') === 'true' || Cypress.env('stack') === 'postgres') {
before(() => {
cy.getToken().then((tok) => {
token = tok;
_token = tok;
// cy.task('backendApiPut', {
// token: token,
@@ -47,7 +47,7 @@ describe('OAuth with Authentik', () => {
});
});
it.skip('Should log in with OAuth', function() {
it.skip('Should log in with OAuth', () => {
// cy.task('backendApiGet', {
// path: '/oauth/login?redirect_base=' + encodeURI(Cypress.config('baseUrl')),
// }).then((data) => {

View File

@@ -9,7 +9,7 @@ describe('Proxy Hosts endpoints', () => {
});
});
it('Should be able to create a http host', function() {
it('Should be able to create a http host', () => {
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/proxy-hosts',

View File

@@ -9,7 +9,7 @@ describe('Settings endpoints', () => {
});
});
it('Get all settings', function() {
it('Get all settings', () => {
cy.task('backendApiGet', {
token: token,
path: '/api/settings',
@@ -19,7 +19,7 @@ describe('Settings endpoints', () => {
});
});
it('Get default-site setting', function() {
it('Get default-site setting', () => {
cy.task('backendApiGet', {
token: token,
path: '/api/settings/default-site',
@@ -30,7 +30,7 @@ describe('Settings endpoints', () => {
});
});
it('Default Site congratulations', function() {
it('Default Site congratulations', () => {
cy.task('backendApiPut', {
token: token,
path: '/api/settings/default-site',
@@ -46,7 +46,7 @@ describe('Settings endpoints', () => {
});
});
it('Default Site 404', function() {
it('Default Site 404', () => {
cy.task('backendApiPut', {
token: token,
path: '/api/settings/default-site',
@@ -62,7 +62,7 @@ describe('Settings endpoints', () => {
});
});
it('Default Site 444', function() {
it('Default Site 444', () => {
cy.task('backendApiPut', {
token: token,
path: '/api/settings/default-site',
@@ -78,7 +78,7 @@ describe('Settings endpoints', () => {
});
});
it('Default Site redirect', function() {
it('Default Site redirect', () => {
cy.task('backendApiPut', {
token: token,
path: '/api/settings/default-site',
@@ -100,7 +100,7 @@ describe('Settings endpoints', () => {
});
});
it('Default Site html', function() {
it('Default Site html', () => {
cy.task('backendApiPut', {
token: token,
path: '/api/settings/default-site',

View File

@@ -33,7 +33,7 @@ describe('Streams', () => {
cy.exec('rm -f /test/results/testssl.json');
});
it('Should be able to create TCP Stream', function() {
it('Should be able to create TCP Stream', () => {
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/streams',
@@ -65,7 +65,7 @@ describe('Streams', () => {
});
});
it('Should be able to create UDP Stream', function() {
it('Should be able to create UDP Stream', () => {
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/streams',
@@ -92,7 +92,7 @@ describe('Streams', () => {
});
});
it('Should be able to create TCP/UDP Stream', function() {
it('Should be able to create TCP/UDP Stream', () => {
cy.task('backendApiPost', {
token: token,
path: '/api/nginx/streams',
@@ -124,7 +124,7 @@ describe('Streams', () => {
});
});
it('Should be able to create SSL TCP Stream', function() {
it('Should be able to create SSL TCP Stream', () => {
let certID = 0;
// Create custom cert
@@ -184,7 +184,7 @@ describe('Streams', () => {
cy.exec('/testssl/testssl.sh --quiet --add-ca="$(/bin/mkcert -CAROOT)/rootCA.pem" --jsonfile=/test/results/testssl.json website1.example.com:1503', {
timeout: 120000, // 2 minutes
}).then((result) => {
cy.task('log', '[testssl.sh] ' + result.stdout);
cy.task('log', `[testssl.sh] ${result.stdout}`);
const allowedSeverities = ["INFO", "OK", "LOW", "MEDIUM"];
const ignoredIDs = [
@@ -210,7 +210,7 @@ describe('Streams', () => {
});
});
it('Should be able to List Streams', function() {
it('Should be able to List Streams', () => {
cy.task('backendApiGet', {
token: token,
path: '/api/nginx/streams?expand=owner,certificate',

View File

@@ -9,7 +9,7 @@ describe('Users endpoints', () => {
});
});
it('Should be able to get yourself', function() {
it('Should be able to get yourself', () => {
cy.task('backendApiGet', {
token: token,
path: '/api/users/me'
@@ -20,7 +20,7 @@ describe('Users endpoints', () => {
});
});
it('Should be able to get all users', function() {
it('Should be able to get all users', () => {
cy.task('backendApiGet', {
token: token,
path: '/api/users'
@@ -30,7 +30,7 @@ describe('Users endpoints', () => {
});
});
it('Should be able to update yourself', function() {
it('Should be able to update yourself', () => {
cy.task('backendApiPut', {
token: token,
path: '/api/users/me',

View File

@@ -108,10 +108,10 @@
dependencies:
"@types/json-schema" "^7.0.15"
"@eslint/core@^0.15.0":
version "0.15.0"
resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.0.tgz#8fc04709a7b9a179d9f7d93068fc000cb8c5603d"
integrity sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==
"@eslint/core@^0.15.2":
version "0.15.2"
resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.2.tgz#59386327d7862cc3603ebc7c78159d2dcc4a868f"
integrity sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==
dependencies:
"@types/json-schema" "^7.0.15"
@@ -141,11 +141,11 @@
integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==
"@eslint/plugin-kit@^0.3.1":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz#0cad96b134d23a653348e3342f485636b5ef4732"
integrity sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==
version "0.3.5"
resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz#fd8764f0ee79c8ddab4da65460c641cefee017c5"
integrity sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==
dependencies:
"@eslint/core" "^0.15.0"
"@eslint/core" "^0.15.2"
levn "^0.4.1"
"@humanfs/core@^0.19.1":
@@ -2220,9 +2220,9 @@ tldts@^6.1.32:
tldts-core "^6.1.86"
tmp@~0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae"
integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==
version "0.2.4"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.4.tgz#c6db987a2ccc97f812f17137b36af2b6521b0d13"
integrity sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==
tough-cookie@^5.0.0:
version "5.1.2"