Next.js+React web interface for controlling HAProxy clusters (groups of servers), in conjunction with with https://gitgud.io/fatchan/haproxy-protection.
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
4.1 KiB
159 lines
4.1 KiB
'use strict';
|
|
|
|
const { generateKeyPairSync } = require('crypto')
|
|
, fs = require('fs')
|
|
, forge = require('node-forge')
|
|
, pki = forge.pki
|
|
, CAAttrs = [
|
|
// {
|
|
// name: "commonName",
|
|
// value: "cp.basedflare.com",
|
|
// },
|
|
{
|
|
name: "countryName",
|
|
value: "XX",
|
|
},
|
|
{
|
|
shortName: "ST",
|
|
value: "BASEDFLARE",
|
|
},
|
|
{
|
|
name: "localityName",
|
|
value: "BASEDFLARE",
|
|
},
|
|
{
|
|
name: "organizationName",
|
|
value: "BASEDFLARE",
|
|
},
|
|
{
|
|
shortName: "OU",
|
|
value: "BASEDFLARE",
|
|
},
|
|
];
|
|
|
|
let RootCAPrivateKey = null
|
|
, RootCAPublicKey = null
|
|
, RootCACertificate = null;
|
|
|
|
function generateCAKeyPair() {
|
|
return generateKeyPairSync('rsa', {
|
|
modulusLength: 4096,
|
|
publicKeyEncoding: {
|
|
type: 'spki',
|
|
format: 'pem'
|
|
},
|
|
privateKeyEncoding: {
|
|
type: 'pkcs8',
|
|
format: 'pem',
|
|
// cipher: 'aes-256-cbc',
|
|
// passphrase: 'changeme'
|
|
}
|
|
});
|
|
}
|
|
|
|
function generateCertificate(privateKey, publicKey) {
|
|
const prKey = pki.privateKeyFromPem(privateKey);
|
|
const pubKey = pki.publicKeyFromPem(publicKey);
|
|
const cert = pki.createCertificate();
|
|
cert.publicKey = pubKey;
|
|
cert.serialNumber = '01';
|
|
//TODO: shorter/customisable
|
|
cert.validity.notBefore = new Date();
|
|
cert.validity.notAfter = new Date();
|
|
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10);
|
|
cert.setSubject(CAAttrs);
|
|
cert.setIssuer(CAAttrs);
|
|
cert.setExtensions([
|
|
{
|
|
name: "basicConstraints",
|
|
cA: true,
|
|
},
|
|
{
|
|
name: "keyUsage",
|
|
keyCertSign: true,
|
|
digitalSignature: true,
|
|
nonRepudiation: true,
|
|
keyEncipherment: true,
|
|
dataEncipherment: true,
|
|
},
|
|
]);
|
|
cert.sign(prKey, forge.md.sha256.create());
|
|
return pki.certificateToPem(cert);
|
|
}
|
|
|
|
function verifyCSR(csrPem, allowedDomains) {
|
|
const csr = pki.certificationRequestFromPem(csrPem);
|
|
const subject = csr.subject.getField('CN').value;
|
|
if (!allowedDomains.includes(subject)) {
|
|
throw new Error('No permission for subject');
|
|
}
|
|
const exts = csr.getAttribute({name: 'extensionRequest'});
|
|
if (exts && exts.extensions) {
|
|
const altNamesExt = exts.extensions.find(ext => ext.name === 'subjectAltName');
|
|
if (altNamesExt) {
|
|
const badAltNames = altNamesExt.altNames.some(altName => {
|
|
return !allowedDomains.includes(altName.value);
|
|
});
|
|
if (badAltNames) {
|
|
throw new Error('No permission for altnames');
|
|
}
|
|
}
|
|
}
|
|
const caCert = RootCACertificate;
|
|
const caKey = RootCAPrivateKey;
|
|
if (!csr.verify()) {
|
|
throw new Error('Signature not verified.');
|
|
}
|
|
const cert = pki.createCertificate();
|
|
cert.serialNumber = '01';
|
|
//TODO: shorter/customisable
|
|
cert.validity.notBefore = new Date();
|
|
cert.validity.notAfter = new Date();
|
|
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10);
|
|
cert.setSubject(csr.subject.attributes); //CSR subject (user sets IP)
|
|
cert.setIssuer(caCert.subject.attributes); //CA issuer
|
|
cert.setExtensions([
|
|
{
|
|
name: "basicConstraints",
|
|
cA: true,
|
|
},
|
|
{
|
|
name: "keyUsage",
|
|
keyCertSign: true,
|
|
digitalSignature: true,
|
|
nonRepudiation: true,
|
|
keyEncipherment: true,
|
|
dataEncipherment: true,
|
|
},
|
|
]);
|
|
cert.publicKey = csr.publicKey;
|
|
cert.sign(caKey, forge.md.sha256.create());
|
|
return pki.certificateToPem(cert);
|
|
}
|
|
|
|
try {
|
|
RootCAPrivateKey = pki.privateKeyFromPem(fs.readFileSync('./ca/ca-private-key.pem'));
|
|
RootCAPublicKey = pki.publicKeyFromPem(fs.readFileSync('./ca/ca-public-key.pem'));
|
|
RootCACertificate = pki.certificateFromPem(fs.readFileSync('./ca/ca-cert.pem'));
|
|
} catch (e) {
|
|
console.warn('CA cert not loaded:', e);
|
|
}
|
|
|
|
if (!RootCAPrivateKey || !RootCAPublicKey || !RootCACertificate) {
|
|
console.log('Generating root CA Keys');
|
|
const Keys = generateCAKeyPair();
|
|
RootCAPrivateKey = Keys.privateKey;
|
|
RootCAPublicKey = Keys.publicKey;
|
|
fs.writeFileSync('./ca/ca-private-key.pem', RootCAPrivateKey, { encoding: 'utf-8' });
|
|
fs.writeFileSync('./ca/ca-public-key.pem', RootCAPublicKey, { encoding: 'utf-8' });
|
|
console.log('Generating root CA Cert');
|
|
const CACert = generateCertificate(RootCAPrivateKey, RootCAPublicKey);
|
|
RootCACertificate = pki.certificateFromPem(CACert);
|
|
fs.writeFileSync('./ca/ca-cert.pem', CACert, { encoding: 'utf-8' });
|
|
}
|
|
|
|
module.exports = {
|
|
// generateCAKeyPair,
|
|
// generateCertificate,
|
|
verifyCSR,
|
|
};
|
|
|