多个 SSL 证书和 HTTP/2 与 Express.js

dsp*_*099 6 ssl nginx node.js express http2

设想:

我有一个 express.js 服务器,它根据req.headers.host用户来自哪里来提供相同静态登录页面的变体- 有点像 A/B 测试。

GET tulip.flower.com 供应 pages/flower.com/tulip.html

GET rose.flower.com 供应 pages/flower.com/rose.html

同时,这个IP还负责:

GET potato.vegetable.com 服务 pages/vegetable.com/potato.html

重要的是这些页面的服务速度,因此它们以各种方式进行预编译和优化。

服务器现在需要:

  1. 为、、提供单独的证书*.vegetables.com*.fruits.com*.rocks.net
  2. 可选择不提供证书 *.flowers.com
  3. 提供 HTTP2

问题是 HTTP2 强制要求一个证书,现在有多个证书在起作用。

似乎可以在一个 Node.js(并且可能通过扩展 Express.js)服务器上使用多个证书,但是是否可以将它与像spdy这样的模块结合使用这样,如果是这样,如何?

而不是hacking node,把整理http2和SSL的任务交给nginx会不会更聪明?像 Imperva 或 Akamai 这样的缓存网络应该处理这个问题吗?

小智 5

您也可以使用 tls.createSecureContext,Nginx 不是必需的。

我的例子在这里:

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")
});
Run Code Online (Sandbox Code Playgroud)

如果你想测试一下:

  1. 编辑/etc/hosts并添加记录127.0.0.1 example.com

  2. 打开浏览器输入网址https://example.com:443


Far*_*ahi 0

Nginx 可以很好地处理 SSL 终止,这将减轻应用程序服务器的 ssl 处理能力。

如果您的 nginx 和应用程序服务器之间有一个安全的专用网络,我建议您通过 nginx 反向代理卸载 ssl。在这种实践中,nginx 将侦听 ssl(证书将在 nginx 服务器上管理),然后它将在非 ssl 上将代理请求反向发送到应用程序服务器(因此应用程序服务器不需要拥有证书,没有 ssl 配置,也没有 ssl 进程负担)。

如果您的 nginx 和应用程序服务器之间没有安全的专用网络,您仍然可以通过将上游配置为 ssl 来使用 nginx 作为反向代理,但您将失去卸载优势。

CDN 也可以做到这一点。它们基本上是反向代理+缓存,所以我认为那里没有问题。

好读