Fixes for upgraded debian trixie
- Adds request logging in debug mode for some endpoints - Moves certbot version determination to the startup scripts and removes bash script encapsulation when installing plugins - Revert loose domain validation, which was there for a specific reason addressing CVE's - Fix Cypress suite for cert generation - Adds Cypress test that iterates over the entire certbot plugins list and installs each one, ensuring at the very least that the install works - Fixed some plugins based on this - (!) Still some work to do on this, hostinger is still broken at least - Improved cypress tests for custom certs; they will generate on each run instead of being baked in. The baked ones were due to expire soon
This commit is contained in:
@@ -341,8 +341,8 @@
|
||||
"full_plugin_name": "dns-hostinger",
|
||||
"name": "Hostinger.com",
|
||||
"package_name": "certbot-dns-hostinger",
|
||||
"version": "~=0.1.5"
|
||||
},
|
||||
"version": "~=0.1.3"
|
||||
},
|
||||
"hostingnl": {
|
||||
"credentials": "dns_hostingnl_api_key = 0123456789abcdef0123456789abcdef",
|
||||
"dependencies": "",
|
||||
@@ -553,7 +553,7 @@
|
||||
},
|
||||
"powerdns": {
|
||||
"credentials": "dns_powerdns_api_url = https://api.mypowerdns.example.org\ndns_powerdns_api_key = AbCbASsd!@34",
|
||||
"dependencies": "PyYAML==5.3.1",
|
||||
"dependencies": "PyYAML>=6.0.1",
|
||||
"full_plugin_name": "dns-powerdns",
|
||||
"name": "PowerDNS",
|
||||
"package_name": "certbot-dns-powerdns",
|
||||
|
||||
+16
-13
@@ -4,8 +4,6 @@ import { certbot as logger } from "../logger.js";
|
||||
import errs from "./error.js";
|
||||
import utils from "./utils.js";
|
||||
|
||||
const CERTBOT_VERSION_REPLACEMENT = "$(certbot --version | grep -Eo '[0-9](\\.[0-9]+)+')";
|
||||
|
||||
/**
|
||||
* Installs a cerbot plugin given the key for the object from
|
||||
* ../certbot/dns-plugins.json
|
||||
@@ -15,24 +13,31 @@ const CERTBOT_VERSION_REPLACEMENT = "$(certbot --version | grep -Eo '[0-9](\\.[0
|
||||
*/
|
||||
const installPlugin = async (pluginKey) => {
|
||||
if (typeof dnsPlugins[pluginKey] === "undefined") {
|
||||
// throw Error(`Certbot plugin ${pluginKey} not found`);
|
||||
throw new errs.ItemNotFoundError(pluginKey);
|
||||
}
|
||||
|
||||
const plugin = dnsPlugins[pluginKey];
|
||||
logger.start(`Installing ${pluginKey}...`);
|
||||
|
||||
plugin.version = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT);
|
||||
plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT);
|
||||
plugin.version = plugin.version.replace(/{{certbot-version}}/g, process.env.CERTBOT_VERSION);
|
||||
plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, process.env.CERTBOT_VERSION);
|
||||
|
||||
// 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" });
|
||||
// SETUPTOOLS_USE_DISTUTILS=local uses setuptools' own bundled distutils.
|
||||
// "stdlib" breaks Python 3.13+ where distutils was removed from the standard library.
|
||||
let env = Object.assign({}, process.env, { SETUPTOOLS_USE_DISTUTILS: "local" });
|
||||
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`;
|
||||
const quotedDeps = plugin.dependencies.trim()
|
||||
? plugin.dependencies
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.map((d) => `'${d}'`)
|
||||
.join(" ")
|
||||
: "";
|
||||
const cmd = `. /opt/certbot/bin/activate && pip install --no-cache-dir ${quotedDeps} '${plugin.package_name}${plugin.version}' && deactivate`;
|
||||
return utils
|
||||
.exec(cmd, { env })
|
||||
.then((result) => {
|
||||
@@ -73,9 +78,7 @@ const installPlugins = async (pluginKeys) => {
|
||||
})
|
||||
.end(() => {
|
||||
if (hasErrors) {
|
||||
reject(
|
||||
new errs.CommandError("Some plugins failed to install. Please check the logs above", 1),
|
||||
);
|
||||
reject(new errs.CommandError("Some plugins failed to install. Please check the logs above", 1));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
@@ -83,4 +86,4 @@ const installPlugins = async (pluginKeys) => {
|
||||
});
|
||||
};
|
||||
|
||||
export { installPlugins, installPlugin };
|
||||
export { installPlugin, installPlugins };
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import chalk from "chalk";
|
||||
import { debug, express as logger } from "../../logger.js";
|
||||
|
||||
export default (req, _res, next) => {
|
||||
debug(logger, `[${chalk.yellow(req.method.toUpperCase())}] ${chalk.green(req.path)}`);
|
||||
next();
|
||||
};
|
||||
@@ -3,14 +3,12 @@ import { dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { Liquid } from "liquidjs";
|
||||
import _ from "lodash";
|
||||
import { debug, global as logger } from "../logger.js";
|
||||
import errs from "./error.js";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const exec = async (cmd, options = {}) => {
|
||||
debug(logger, "CMD:", cmd);
|
||||
const { stdout, stderr } = await new Promise((resolve, reject) => {
|
||||
const child = nodeExec(cmd, options, (isError, stdout, stderr) => {
|
||||
if (isError) {
|
||||
@@ -34,7 +32,6 @@ const exec = async (cmd, options = {}) => {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
const execFile = (cmd, args, options) => {
|
||||
debug(logger, `CMD: ${cmd} ${args ? args.join(" ") : ""}`);
|
||||
const opts = options || {};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"bcrypt": "^6.0.0",
|
||||
"better-sqlite3": "^12.10.0",
|
||||
"body-parser": "^2.2.2",
|
||||
"chalk": "5.6.2",
|
||||
"compression": "^1.8.1",
|
||||
"express": "^5.2.1",
|
||||
"express-fileupload": "^1.5.2",
|
||||
@@ -43,7 +44,6 @@
|
||||
"devDependencies": {
|
||||
"@apidevtools/swagger-parser": "^12.1.0",
|
||||
"@biomejs/biome": "^2.4.15",
|
||||
"chalk": "5.6.2",
|
||||
"nodemon": "^3.1.14"
|
||||
},
|
||||
"signale": {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import express from "express";
|
||||
import dnsPlugins from "../certbot/dns-plugins.json" with { type: "json" };
|
||||
import { installPlugin } from "../lib/certbot.js";
|
||||
import { debug, express as logger } from "../logger.js";
|
||||
|
||||
const router = express.Router({
|
||||
caseSensitive: true,
|
||||
strict: true,
|
||||
mergeParams: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* ONLY AVAILABLE IN CI ENVIRONMENT!
|
||||
*/
|
||||
|
||||
/**
|
||||
* /api/ci/certbot-plugins
|
||||
*/
|
||||
router
|
||||
.route("/certbot-plugins")
|
||||
.options((_, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
|
||||
// Return all certbot plugins
|
||||
.get(async (_req, res, _next) => {
|
||||
res.status(200).send(dnsPlugins);
|
||||
});
|
||||
|
||||
/**
|
||||
* /api/ci/certbot-plugins/{plugin}
|
||||
*/
|
||||
router
|
||||
.route("/certbot-plugins/:plugin")
|
||||
.options((_, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
|
||||
// Install a certbot plugin
|
||||
.post(async (req, res, next) => {
|
||||
try {
|
||||
const pluginName = req.params.plugin;
|
||||
// check if plugin exists
|
||||
if (!dnsPlugins[pluginName]) {
|
||||
return res.status(404).send({
|
||||
error: "Plugin not found",
|
||||
});
|
||||
}
|
||||
|
||||
await installPlugin(pluginName);
|
||||
res.status(200).send(true);
|
||||
} catch (err) {
|
||||
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
|
||||
next(err);
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,8 +1,11 @@
|
||||
import express from "express";
|
||||
import { isCI } from "../lib/config.js";
|
||||
import errs from "../lib/error.js";
|
||||
import logRequest from "../lib/express/log-request.js";
|
||||
import pjson from "../package.json" with { type: "json" };
|
||||
import { isSetup } from "../setup.js";
|
||||
import auditLogRoutes from "./audit-log.js";
|
||||
import ciRoutes from "./ci.js";
|
||||
import accessListsRoutes from "./nginx/access_lists.js";
|
||||
import certificatesHostsRoutes from "./nginx/certificates.js";
|
||||
import deadHostsRoutes from "./nginx/dead_hosts.js";
|
||||
@@ -22,6 +25,8 @@ const router = express.Router({
|
||||
mergeParams: true,
|
||||
});
|
||||
|
||||
router.use(logRequest);
|
||||
|
||||
/**
|
||||
* Health Check
|
||||
* GET /api
|
||||
@@ -55,6 +60,11 @@ router.use("/nginx/streams", streamsRoutes);
|
||||
router.use("/nginx/access-lists", accessListsRoutes);
|
||||
router.use("/nginx/certificates", certificatesHostsRoutes);
|
||||
|
||||
// Only include CI routes if we're in a CI environment
|
||||
if (isCI()) {
|
||||
router.use("/ci", ciRoutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* API 404 for all other routes
|
||||
*
|
||||
|
||||
@@ -77,12 +77,14 @@
|
||||
"example": 3
|
||||
},
|
||||
"domain_names": {
|
||||
"description": "Domain Names array",
|
||||
"description": "Domain Names separated by a comma",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 100,
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
"pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"]+$"
|
||||
},
|
||||
"example": ["example.com", "www.example.com"]
|
||||
},
|
||||
|
||||
@@ -25,7 +25,15 @@
|
||||
"example": "My Custom Cert"
|
||||
},
|
||||
"domain_names": {
|
||||
"$ref": "../common.json#/properties/domain_names"
|
||||
"description": "Domain Names separated by a comma",
|
||||
"type": "array",
|
||||
"maxItems": 100,
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[^&| @!#%^();:/\\\\}{=+?<>,~`'\"]+$"
|
||||
},
|
||||
"example": ["example.com", "www.example.com"]
|
||||
},
|
||||
"expires_on": {
|
||||
"description": "Date and time of expiration",
|
||||
|
||||
Reference in New Issue
Block a user