Fix main process detect, refine lock, remove axios
This commit is contained in:
parent
9f40085899
commit
07de2fab78
8 changed files with 87 additions and 190 deletions
|
|
@ -1,24 +1,24 @@
|
|||
import crypto from "crypto";
|
||||
import fs from "fs";
|
||||
const ACCOUNT_KEY_ALGORITHM = 'ES256';
|
||||
const CERTIFICATE_KEY_ALGORITHM = 'RS256';
|
||||
export const ACCOUNT_KEY_ALGORITHM = 'ES256';
|
||||
export const CERTIFICATE_KEY_ALGORITHM = 'RS256';
|
||||
const env = (process.env.NODE_ENV || '').trim().toLowerCase();
|
||||
const DIRECTORY_URL = ['development', 'test'].includes(env)
|
||||
export const DIRECTORY_URL = ['development', 'test'].includes(env)
|
||||
? 'https://acme-staging-v02.api.letsencrypt.org/directory'
|
||||
: 'https://acme-v02.api.letsencrypt.org/directory';
|
||||
const PRIVATE_KEY_CIPHER = 'aes-256-cbc';
|
||||
const PRIVATE_KEY_FORMAT = 'pem';
|
||||
const PRIVATE_KEY_PERMISSIONS = 0o600;
|
||||
const PRIVATE_KEY_TYPE = 'pkcs8';
|
||||
const PUBLIC_KEY_FORMAT = 'pem';
|
||||
const PUBLIC_KEY_PERMISSIONS = 0o666;
|
||||
const PUBLIC_KEY_TYPE = 'spki';
|
||||
export const PRIVATE_KEY_CIPHER = 'aes-256-cbc';
|
||||
export const PRIVATE_KEY_FORMAT = 'pem';
|
||||
export const PRIVATE_KEY_PERMISSIONS = 0o600;
|
||||
export const PRIVATE_KEY_TYPE = 'pkcs8';
|
||||
export const PUBLIC_KEY_FORMAT = 'pem';
|
||||
export const PUBLIC_KEY_PERMISSIONS = 0o666;
|
||||
export const PUBLIC_KEY_TYPE = 'spki';
|
||||
/**
|
||||
* @param {crypto.KeyObject} privateKey
|
||||
* @param {String} [passphrase]
|
||||
*
|
||||
*/
|
||||
const exportPrivateKey = (privateKey, passphrase) => {
|
||||
export const exportPrivateKey = (privateKey, passphrase) => {
|
||||
/** @type {crypto.KeyExportOptions<'pem'>} */
|
||||
const privateKeyOpts = {
|
||||
type: PRIVATE_KEY_TYPE,
|
||||
|
|
@ -33,7 +33,7 @@ const exportPrivateKey = (privateKey, passphrase) => {
|
|||
/**
|
||||
* @param {crypto.KeyObject} publicKey
|
||||
*/
|
||||
const exportPublicKey = publicKey => {
|
||||
export const exportPublicKey = publicKey => {
|
||||
/** @type {crypto.KeyExportOptions<'pem'>} */
|
||||
return publicKey.export({
|
||||
type: PUBLIC_KEY_TYPE,
|
||||
|
|
@ -46,7 +46,7 @@ const exportPublicKey = publicKey => {
|
|||
*
|
||||
* @return {crypto.KeyObject}
|
||||
*/
|
||||
const importPrivateKey = (privateKeyData, passphrase) => {
|
||||
export const importPrivateKey = (privateKeyData, passphrase) => {
|
||||
/** @type {crypto.PrivateKeyInput} */
|
||||
const privateKeyOpts = {
|
||||
key: privateKeyData,
|
||||
|
|
@ -68,7 +68,7 @@ const importPrivateKey = (privateKeyData, passphrase) => {
|
|||
*
|
||||
* @return {crypto.KeyObject}
|
||||
*/
|
||||
const importPublicKey = publicKeyData => {
|
||||
export const importPublicKey = publicKeyData => {
|
||||
try {
|
||||
return crypto.createPublicKey({
|
||||
key: publicKeyData,
|
||||
|
|
@ -87,7 +87,7 @@ const importPublicKey = publicKeyData => {
|
|||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
const writeKeyToFile = async (filename, key, passphrase) => {
|
||||
export const writeKeyToFile = async (filename, key, passphrase) => {
|
||||
if (typeof key === 'string') {
|
||||
key = key.includes('PRIVATE KEY')
|
||||
? importPrivateKey(key, passphrase)
|
||||
|
|
@ -103,4 +103,4 @@ const writeKeyToFile = async (filename, key, passphrase) => {
|
|||
const mode = isPrivateKey ? PRIVATE_KEY_PERMISSIONS : PUBLIC_KEY_PERMISSIONS;
|
||||
await fs.promises.writeFile(filename, keyData, { mode });
|
||||
};
|
||||
export { ACCOUNT_KEY_ALGORITHM, CERTIFICATE_KEY_ALGORITHM, DIRECTORY_URL, PRIVATE_KEY_CIPHER, PRIVATE_KEY_FORMAT, PRIVATE_KEY_PERMISSIONS, PRIVATE_KEY_TYPE, PUBLIC_KEY_FORMAT, PUBLIC_KEY_PERMISSIONS, PUBLIC_KEY_TYPE, env, exportPrivateKey, exportPublicKey, importPrivateKey, importPublicKey, writeKeyToFile };
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
import https from "https";
|
||||
const request = (/** @type {string | import("url").URL} */ url, /** @type {https.RequestOptions&{data?: string}} */ { data = '', ...options } = {}, /** @type {() => any} */ cb) => {
|
||||
/**
|
||||
* @param {string | URL} url
|
||||
* @param {import('https').RequestOptions & {data?: string}} [options]
|
||||
* @param {() => void} [cb]
|
||||
* @return {Promise<{data: any, headers: import('http').IncomingHttpHeaders, statusCode: number}>}
|
||||
*/
|
||||
const request = (url, { data = '', ...options } = {}, cb) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
url = new URL(url);
|
||||
|
|
@ -24,7 +30,7 @@ const request = (/** @type {string | import("url").URL} */ url, /** @type {https
|
|||
return;
|
||||
}
|
||||
}
|
||||
resolve({ data, headers, statusCode });
|
||||
resolve({ data, headers, statusCode: statusCode || 0 });
|
||||
})
|
||||
.once('error', reject);
|
||||
})
|
||||
|
|
|
|||
42
src/sni.js
42
src/sni.js
|
|
@ -56,41 +56,33 @@ async function buildCache(host) {
|
|||
};
|
||||
}
|
||||
catch (error) {
|
||||
const { certificate, privateKeyData } = await client.generateCertificate(host);
|
||||
await fs.promises.writeFile(certP, certificate);
|
||||
await writeKeyToFile(keyP, privateKeyData, '');
|
||||
const expire = (Date.now() + 45 * 86400 * 1000);
|
||||
await fs.promises.writeFile(extP, expire.toString());
|
||||
return {
|
||||
cert: certificate,
|
||||
key: privateKeyData,
|
||||
expire
|
||||
};
|
||||
// only one process can generate certificate at a time
|
||||
return await lock.acquire('cert', async () => {
|
||||
const { certificate, privateKeyData } = await client.generateCertificate(host);
|
||||
await fs.promises.writeFile(certP, certificate);
|
||||
await writeKeyToFile(keyP, privateKeyData, '');
|
||||
const expire = (Date.now() + 45 * 86400 * 1000);
|
||||
await fs.promises.writeFile(extP, expire.toString());
|
||||
return {
|
||||
cert: certificate,
|
||||
key: privateKeyData,
|
||||
expire
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} servername
|
||||
* @param {AsyncLock} lock
|
||||
*/
|
||||
async function getKeyCert(servername, lock) {
|
||||
async function getKeyCert(servername) {
|
||||
// Had to use lock because the best authenticator
|
||||
// library seems don't yet fully stateless.
|
||||
servername = servername.toLowerCase();
|
||||
let cache = resolveCache[servername];
|
||||
await ensureDir(certsDir);
|
||||
if (!cache || (Date.now() > cache.expire)) {
|
||||
cache = await lock.acquire(servername, (cb) => {
|
||||
if (!resolveCache[servername] || (Date.now() > resolveCache[servername].expire)) {
|
||||
buildCache(servername).then((cache) => {
|
||||
resolveCache[servername] = cache;
|
||||
cb(null, cache);
|
||||
}).catch((error) => {
|
||||
cb(error, undefined);
|
||||
});
|
||||
} else {
|
||||
cb(null, resolveCache[servername]);
|
||||
}
|
||||
});
|
||||
cache = await buildCache(servername);
|
||||
resolveCache[servername] = cache;
|
||||
}
|
||||
return {
|
||||
key: cache.key,
|
||||
|
|
@ -104,7 +96,7 @@ async function getKeyCert(servername, lock) {
|
|||
async function SniListener(servername, ctx) {
|
||||
// Generate fresh account keys for Let's Encrypt
|
||||
try {
|
||||
ctx(null, tls.createSecureContext(await getKeyCert(servername, lock)));
|
||||
ctx(null, tls.createSecureContext(await getKeyCert(servername)));
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
|
|
|
|||
16
src/util.js
16
src/util.js
|
|
@ -1,6 +1,7 @@
|
|||
import { default as axios } from "axios";
|
||||
import request from "./certnode/lib/request.js";
|
||||
import crypto from "crypto";
|
||||
import fs from "fs";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const recordParamDestUrl = 'forward-domain';
|
||||
const recordParamHttpStatus = 'http-status';
|
||||
|
|
@ -59,7 +60,7 @@ const parseTxtRecordData = (value) => {
|
|||
* @return {Promise<{url: string, httpStatus?: string} | null>}
|
||||
*/
|
||||
export async function findTxtRecord(host) {
|
||||
const resolve = await axios(`https://dns.google/resolve?name=_.${encodeURIComponent(host)}&type=TXT`);
|
||||
const resolve = await request(`https://dns.google/resolve?name=_.${encodeURIComponent(host)}&type=TXT`);
|
||||
if (resolve.data.Answer) {
|
||||
for (const head of resolve.data.Answer) {
|
||||
const txtData = parseTxtRecordData(head.data);
|
||||
|
|
@ -73,8 +74,19 @@ export async function findTxtRecord(host) {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} baseURL
|
||||
* @param {string} relativeURL
|
||||
*/
|
||||
export function combineURLs(baseURL, relativeURL) {
|
||||
return relativeURL
|
||||
? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
|
||||
: baseURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} metaURL
|
||||
*/
|
||||
export function isMainProcess(metaURL) {
|
||||
return [process.argv[1], process.env.pm_exec_path].includes(fileURLToPath(metaURL));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
editor.link_modal.header
Reference in a new issue