Multiple SSL Certificates and HTTP/2 with Express.js
Asked Answered
S

3

8

Scenario:

I have an express.js server which serves variations of the same static landing page based on where req.headers.host says the user is coming from - think sort of like A/B testing.

GET tulip.flower.com serves pages/flower.com/tulip.html

GET rose.flower.com serves pages/flower.com/rose.html

At the same time, this one IP is also responsible for:

GET potato.vegetable.com serving pages/vegetable.com/potato.html

It's important that these pages are served FAST, so they are precompiled and optimized in all sorts of ways.

The server now needs to:

  1. Provide separate certificates for *.vegetables.com, *.fruits.com, *.rocks.net
  2. Optionally provide no certificate for *.flowers.com
  3. Offer HTTP2

The problem is that HTTP2 mandates a certificate, and there's now multiple certificates in play.

It appears that it's possible to use multiple certificates on one Node.js (and presumably by extension Express.js) server, but is it possible to combine it with a module like spdy, and if so, how?

Instead of hacking node, would it be smarter to pawn the task of sorting out http2 and SSL to nginx? Should the caching network like Imperva or Akamai handle this?

Shawnee answered 1/3, 2017 at 3:46 Comment(0)
P
0

Nginx can handle SSL termination nicely, and this will offload ssl processing power from your application servers.

If you have a secure private network between your nginx and application servers I recommend offloading ssl via nginx reverse proxy. In this practice nginx will listen on ssl, (certificates will be managed on nginx servers) then it will reverse proxy requests to application server on non ssl (so application servers dont require to have certificates on them, no ssl config and no ssl process burden).

If you don't have a secure private network between your nginx and application servers you can still use nginx as reverse proxy via configuring upstreams as ssl, but you will lose offloading benefits.

CDNs can do this too. They are basically reverse proxy + caching so I dont see a problem there.

Good read.

Pastel answered 1/3, 2017 at 5:43 Comment(2)
So you're saying I should remove http2 from node.js server entirely and have others (nginx, cdn) deal with these things?Shawnee
That is possible, You can also leave http2 on on node servers, but remove ssl there, and terminate ssl on nginx servers. Updated answer with a good read.Pastel
O
5

You can use also tls.createSecureContext, Nginx is not necassary.

MY example here:

const https = require("https");
const tls = require("tls");

const certs = {
    "localhost": {
        key: "./certs/localhost.key",
        cert: "./certs/localhost.crt",
    },
    "example.com": {
        key: "./certs/example.key",
        cert: "./certs/example.cert",
        ca: "./certs/example.ca",
    },
} 

function getSecureContexts(certs) {

    if (!certs || Object.keys(certs).length === 0) {
      throw new Error("Any certificate wasn't found.");
    }

    const certsToReturn = {};

    for (const serverName of Object.keys(certs)) {
      const appCert = certs[serverName];

      certsToReturn[serverName] = tls.createSecureContext({
        key: fs.readFileSync(appCert.key),
        cert: fs.readFileSync(appCert.cert),
        // If the 'ca' option is not given, then node.js will use the default
        ca: appCert.ca ? sslCADecode(
          fs.readFileSync(appCert.ca, "utf8"),
        ) : null,
      });
    }

    return certsToReturn;
}

// if CA contains more certificates it will be parsed to array
function sslCADecode(source) {

    if (!source || typeof (source) !== "string") {
        return [];
    }

    return source.split(/-----END CERTIFICATE-----[\s\n]+-----BEGIN CERTIFICATE-----/)
        .map((value, index: number, array) => {
        if (index) {
            value = "-----BEGIN CERTIFICATE-----" + value;
        }
        if (index !== array.length - 1) {
            value = value + "-----END CERTIFICATE-----";
        }
        value = value.replace(/^\n+/, "").replace(/\n+$/, "");
        return value;
    });
}

const secureContexts = getSecureContexts(certs)

const options = {
    // A function that will be called if the client supports SNI TLS extension.
    SNICallback: (servername, cb) => {

        const ctx = secureContexts[servername];

        if (!ctx) {
            log.debug(`Not found SSL certificate for host: ${servername}`);
        } else {
            log.debug(`SSL certificate has been found and assigned to ${servername}`);
        }

        if (cb) {
            cb(null, ctx);
        } else {
            return ctx;
        }
    },
};


var https = require('https');
var httpsServer = https.createServer(options, (req, res) => { console.log(res, req)});
httpsServer.listen(443, function () {
   console.log("Listening https on port: 443")
});

If you want test it:

  1. edit /etc/hosts and add record 127.0.0.1 example.com

  2. open browser with url https://example.com:443

Offering answered 18/1, 2019 at 13:20 Comment(1)
I understand that Nginx is the preferred way to do this, but I like this answer as it actually answers the question asked. Also, this answer just saved me for a hotfix that needed to be done today, and where I didn't have time to reconfigure my whole site. Thanks!Virgate
P
0

Nginx can handle SSL termination nicely, and this will offload ssl processing power from your application servers.

If you have a secure private network between your nginx and application servers I recommend offloading ssl via nginx reverse proxy. In this practice nginx will listen on ssl, (certificates will be managed on nginx servers) then it will reverse proxy requests to application server on non ssl (so application servers dont require to have certificates on them, no ssl config and no ssl process burden).

If you don't have a secure private network between your nginx and application servers you can still use nginx as reverse proxy via configuring upstreams as ssl, but you will lose offloading benefits.

CDNs can do this too. They are basically reverse proxy + caching so I dont see a problem there.

Good read.

Pastel answered 1/3, 2017 at 5:43 Comment(2)
So you're saying I should remove http2 from node.js server entirely and have others (nginx, cdn) deal with these things?Shawnee
That is possible, You can also leave http2 on on node servers, but remove ssl there, and terminate ssl on nginx servers. Updated answer with a good read.Pastel
A
0

Let's Encrypt w/ Greenlock Express v3

I'm the author if Greenlock Express, which is Let's Encrypt for Node.js, Express, etc, and this use case is exactly what I made it for.

The basic setup looks like this:

require("greenlock-express")
    .init(function getConfig() {
        return {
          package: require("./package.json")
          manager: 'greenlock-manager-fs',
          cluster: false,
          configFile: '~/.config/greenlock/manager.json'
        };
    })
    .serve(httpsWorker);

function httpsWorker(server) {
    // Works with any Node app (Express, etc)
    var app = require("./my-express-app.js");

    // See, all normal stuff here
    app.get("/hello", function(req, res) {
        res.end("Hello, Encrypted World!");
    });

    // Serves on 80 and 443
    // Get's SSL certificates magically!
    server.serveApp(app);
}

It also works with node cluster so that you can take advantage of multiple cores.

It uses SNICallback to dynamically add certificates on the fly.

Site Management

The default manager plugin uses files on the file system, but there's great documentation on how to build your own.

Just to get started, the file-based plugin uses a config file that looks like this:

~/.config/greenlock/manager.json:

{
    "subscriberEmail": "[email protected]",
    "agreeToTerms": true,
    "sites": [
        {
            "subject": "example.com",
            "altnames": ["example.com", "www.example.com"]
        }
    ]
}

Very Extensible

I can't post all the possible options here, but it's very small and simple to start with, and very easy to scale out with advanced options as you need them.

Almazan answered 31/10, 2019 at 7:16 Comment(1)
It looks great, but ... lacks documentation. Not to mention not feeling safe with code on self hosted git instead of e.g. Github, so it can disappear :/ I cannot find any docs about creating custom manager for v4. Could you help with that?Pede

© 2022 - 2024 — McMap. All rights reserved.