redbird
Version:
A reverse proxy with support for dynamic tables
127 lines • 4.98 kB
JavaScript
/**
* Letsecript module for Redbird (c) Optimalbits 2016-2024
*
*
*
*/
import { createServer } from 'http';
import path from 'path';
import url from 'url';
import fs from 'fs';
import LeChallengeFs from './third-party/le-challenge-fs.js';
/**
* LetsEncrypt certificates are stored like the following:
*
* /example.com
* /
*
*
*
*/
let leStoreConfig = {};
const webrootPath = ':configDir/:hostname/.well-known/acme-challenge';
function init(certPath, port, logger) {
logger === null || logger === void 0 ? void 0 : logger.info('Initializing letsencrypt, path %s, port: %s', certPath, port);
leStoreConfig = {
configDir: certPath,
privkeyPath: ':configDir/:hostname/privkey.pem',
fullchainPath: ':configDir/:hostname/fullchain.pem',
certPath: ':configDir/:hostname/cert.pem',
chainPath: ':configDir/:hostname/chain.pem',
workDir: ':configDir/letsencrypt/var/lib',
logsDir: ':configDir/letsencrypt/var/log',
webrootPath,
debug: false,
};
// we need to proxy for example: 'example.com/.well-known/acme-challenge' -> 'localhost:port/example.com/'
return createServer(function (req, res) {
if (req.method !== 'GET') {
res.statusCode = 405; // Method Not Allowed
res.end();
return;
}
const reqPath = url.parse(req.url).pathname;
const basePath = path.resolve(certPath);
const safePath = path.normalize(reqPath).replace(/^(\.\.[\/\\])+/, ''); // Prevent directory traversal
const fullPath = path.join(basePath, safePath);
if (!fullPath.startsWith(basePath)) {
logger === null || logger === void 0 ? void 0 : logger.info(`Attempted directory traversal attack: ${req.url}`);
res.statusCode = 403; // Forbidden
res.end('Access denied');
return;
}
logger === null || logger === void 0 ? void 0 : logger.info('LetsEncrypt CA trying to validate challenge %s', fullPath);
fs.stat(fullPath, function (err, stats) {
if (err || !stats.isFile()) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.write('404 Not Found\n');
res.end();
return;
}
res.writeHead(200);
fs.createReadStream(fullPath, 'binary').pipe(res);
});
}).listen(port);
}
/**
* Gets the certificates for the given domain.
* Handles all the LetsEncrypt protocol. Uses
* existing certificates if any, or negotiates a new one.
* Returns a promise that resolves to an object with the certificates.
* TODO: We should use something like https://github.com/PaquitoSoft/memored/blob/master/index.js
* to avoid
*/
async function getCertificates(domain, email, loopbackPort, production, renew, logger) {
const LE = (await import('greenlock')).default;
// Storage Backend
const leStore = (await import('le-store-certbot')).create(leStoreConfig);
// ACME Challenge Handlers
const leChallenge = LeChallengeFs.create({
loopbackPort: loopbackPort,
webrootPath,
debug: false,
});
const le = LE.create({
server: production
? 'https://acme-v02.api.letsencrypt.org/directory'
: 'https://acme-staging-v02.api.letsencrypt.org/directory',
store: leStore, // handles saving of config, accounts, and certificates
challenges: { 'http-01': leChallenge }, // handles /.well-known/acme-challege keys and tokens
challengeType: 'http-01', // default to this challenge type
debug: false,
log: function () {
logger === null || logger === void 0 ? void 0 : logger.info(arguments, 'Lets encrypt debugger');
},
});
// Check in-memory cache of certificates for the named domain
const cert = await le.check({ domains: [domain] });
const opts = {
domains: [domain],
email: email,
agreeTos: true,
rsaKeySize: 2048, // 2048 or higher
challengeType: 'http-01',
};
if (cert) {
if (renew) {
logger && logger.info('renewing cert for ' + domain);
opts.duplicate = true;
return le.renew(opts, cert).catch(function (err) {
logger && logger.error(err, 'Error renewing certificates for ', domain);
});
}
else {
logger && logger.info('Using cached cert for ' + domain);
return cert;
}
}
else {
// Register Certificate manually
logger === null || logger === void 0 ? void 0 : logger.info('Manually registering certificate for %s', domain);
return le.register(opts).catch(function (err) {
logger === null || logger === void 0 ? void 0 : logger.error(err, 'Error registering LetsEncrypt certificates');
});
}
}
export { init, getCertificates };
//# sourceMappingURL=letsencrypt.js.map