【问题标题】:Multiple SSL Certificates and HTTP/2 with Express.js使用 Express.js 的多个 SSL 证书和 HTTP/2
【发布时间】:2017-07-20 06:05:13
【问题描述】:

场景:

我有一个 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

提供这些页面FAST很重要,因此它们经过预编译和优化in all sorts of ways

服务器现在需要:

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

问题在于 HTTP2 需要一个证书,而现在有多个证书在起作用。

it's possible 似乎在一个 Node.js(并且可能通过扩展名 Express.js)服务器上使用多个证书,但是否可以将其与 spdy 之类的模块结合使用,如果可以,如何?

与其破解节点,不如将整理http2和SSL的任务典当给nginx会不会更聪明?像 Imperva 或 Akamai 这样的缓存网络应该处理这个问题吗?

【问题讨论】:

    标签: node.js express ssl nginx http2


    【解决方案1】:

    你也可以使用 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")
    });
    

    如果你想测试它:

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

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

    【讨论】:

    • 我知道 Nginx 是执行此操作的首选方式,但我喜欢这个答案,因为它实际上回答了所提出的问题。此外,这个答案只是为我节省了今天需要完成的修补程序,并且我没有时间重新配置我的整个站点。谢谢!
    【解决方案2】:

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

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

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

    CDN 也可以做到这一点。它们基本上是反向代理 + 缓存,所以我看不出有问题。

    Good read.

    【讨论】:

    • 所以你是说我应该从 node.js 服务器中完全删除 http2 并让其他人(nginx、cdn)处理这些事情?
    • 这是可能的,您也可以在节点服务器上保留 http2,但在此处删除 ssl,并在 nginx 服务器上终止 ssl。更新了答案并阅读。
    【解决方案3】:

    让我们用Greenlock Express v3 加密

    我是 Greenlock Express 的作者,它是用于 Node.js、Express 等的 Let's Encrypt,而这个用例正是我所做的。

    基本设置如下所示:

    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);
    }
    

    它也适用于节点集群,因此您可以利用多个核心。

    它使用SNICallback 动态添加证书。

    网站管理

    默认管理器插件使用文件系统上的文件,但有great documentation 说明如何构建自己的。

    刚开始,基于文件的插件使用如下配置文件:

    ~/.config/greenlock/manager.json:

    {
        "subscriberEmail": "letsencrypt-test@therootcompany.com",
        "agreeToTerms": true,
        "sites": [
            {
                "subject": "example.com",
                "altnames": ["example.com", "www.example.com"]
            }
        ]
    }
    

    非常可扩展

    我无法在此处发布所有可能的选项,但它非常小且易于开始,并且很容易根据需要使用高级选项进行扩展。

    【讨论】:

    • 看起来不错,但是……缺少文档。更不用说使用自托管 git 上的代码而不是例如使用代码感到不安全。 Github,所以它可以消失:/我找不到任何关于为 v4 创建自定义管理器的文档。你能帮忙吗?
    猜你喜欢
    • 1970-01-01
    • 2017-07-04
    • 2019-05-26
    • 2012-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-01
    相关资源
    最近更新 更多