Add ability to add extra CA, and pin certs(fingerprint) for comms to each haproxy server

develop
Thomas Lynch 1 year ago
parent ccb4bcae60
commit 703b6d5ccd
  1. 2
      .env.example
  2. 1
      .gitignore
  3. 1
      Dockerfile
  4. 30
      controllers/certs.js
  5. 1
      package-lock.json
  6. 1
      package.json
  7. 26
      router.js

@ -15,3 +15,5 @@ DEFAULT_CLUSTER="http://admin:admin@localhost:2001/"
NAMESERVERS="what.ever.your.nameserver"
ALL_IP_DOMAIN="whatever.whatever.com"
ALLOW_SELF_SIGNED_SSL=
PINNED_FP=
CUSTOM_CA_PATH=

1
.gitignore vendored

@ -2,3 +2,4 @@ node_modules
.next/
out/
.env
cas/

@ -9,6 +9,7 @@ RUN npm install --omit=dev
COPY .env /opt/.env
COPY . /opt
COPY cas/ca.pem /opt/ca.pem
RUN npm run build

@ -108,13 +108,18 @@ exports.addCert = async (req, res, next) => {
try {
console.log('Add cert request:', subject, altnames);
const { csr, key, cert, haproxyCert, date } = await acme.generate(subject, altnames);
const fd = new FormData();
fd.append('file_upload', new Blob([haproxyCert], { type: 'text/plain' }), `${subject}.pem`);
const { message, description, file, storage_name: storageName } = await res.locals.fetchAll('/v2/services/haproxy/storage/ssl_certificates?force_reload=true', {
const { message, description, file, storage_name: storageName } = await res.locals.postFileAll(
'/v2/services/haproxy/storage/ssl_certificates?force_reload=true',
{
method: 'POST',
headers: { 'authorization': res.locals.dataPlane.defaults.headers.authorization },
body: fd,
});
},
haproxyCert,
{
filename: `${subject}.pem`,
contentType: 'text/plain',
}
);
if (message) {
return dynamicResponse(req, res, 400, { error: message });
}
@ -166,13 +171,18 @@ exports.uploadCert = async (req, res, next) => {
try {
console.log('Upload cert:', existingCert.subject, existingCert.altnames);
const fd = new FormData();
fd.append('file_upload', new Blob([existingCert.haproxyCert], { type: 'text/plain' }), `${existingCert.subject}.pem`);
const { message, description, file, storage_name: storageName } = await res.locals.fetchAll('/v2/services/haproxy/storage/ssl_certificates?force_reload=true', {
const { message, description, file, storage_name: storageName } = await res.locals.postFileAll(
'/v2/services/haproxy/storage/ssl_certificates?force_reload=true',
{
method: 'POST',
headers: { 'authorization': res.locals.dataPlane.defaults.headers.authorization },
body: fd,
});
},
existingCert.haproxyCert,
{
filename: `${existingCert.subject}.pem`,
contentType: 'text/plain',
}
);
if (message) {
return dynamicResponse(req, res, 400, { error: message });
}

1
package-lock.json generated

@ -24,6 +24,7 @@
"gulp": "^4.0.2",
"ip6addr": "^0.2.5",
"next": "^12.3.4",
"node-fetch": "^2.6.9",
"nprogress": "^0.2.0",
"openapi-client-axios": "^7.1.1",
"react": "^18.2.0",

@ -29,6 +29,7 @@
"gulp": "^4.0.2",
"ip6addr": "^0.2.5",
"next": "^12.3.4",
"node-fetch": "^2.6.9",
"nprogress": "^0.2.0",
"openapi-client-axios": "^7.1.1",
"react": "^18.2.0",

@ -8,7 +8,25 @@ const express = require('express')
, { dynamicResponse } = require('./util.js')
, definition = require('./openapi-definition.js')
, https = require('https')
, agent = new https.Agent({ rejectUnauthorized: !process.env.ALLOW_SELF_SIGNED_SSL });
, fetch = require('node-fetch')
, FormData = require('form-data');
const agentOptions = {
rejectUnauthorized: !process.env.ALLOW_SELF_SIGNED_SSL
};
if (process.env.PINNED_FP && process.env.CUSTOM_CA_PATH) {
// console.log('Pinned fingerprint:', process.env.PINNED_FP);
// console.log('Private CA file path:', process.env.CUSTOM_CA_PATH);
agentOptions.ca = require('fs').readFileSync(process.env.CUSTOM_CA_PATH);
agentOptions.checkServerIdentity = (host, cert) => {
//TODO: host verification? e.g. tls.checkServerIdentity(host, cert);
// console.log('Checking:', cert.fingerprint256);
if (process.env.PINNED_FP !== cert.fingerprint256) {
return new Error('Certificate not pinned');
}
}
}
const agent = new https.Agent(agentOptions);
const testRouter = (server, app) => {
@ -110,10 +128,12 @@ const testRouter = (server, app) => {
}));
return all ? promiseResults.map(p => p.data) : promiseResults[0]; //TODO: better desync handling
}
res.locals.fetchAll = async (path, options) => {
res.locals.postFileAll = async (path, options, file, fdOptions) => {
//used for stuff that dataplaneapi with axios seems to struggle with e.g. multipart body
const promiseResults = await Promise.all(clusterUrls.map(clusterUrl => {
return fetch(`${clusterUrl.origin}${path}`, { ...options, agent }).then(resp => resp.json());
const fd = new FormData(); //must resonctruct each time, or get a socket hang up
fd.append('file_upload', file, fdOptions);
return fetch(`${clusterUrl.origin}${path}`, { ...options, body: fd, agent }).then(resp => resp.json());
}));
return promiseResults[0]; //TODO: better desync handling
}

Loading…
Cancel
Save