写点什么

Pipy 同一 IP 多个 SSL 域名

作者:Flomesh
  • 2022 年 9 月 22 日
    北京
  • 本文字数:2212 字

    阅读完需:约 7 分钟

Pipy 同一 IP 多个 SSL 域名

之前在 Pipy 教程系列之(十八)TLS 中曾介绍过如何为代理增加 TLS 卸载的功能,来提供 HTTPS 代理服务。在教程里的代码使用单一证书,也就是说只能支持单个 HTTPS 站点。正好,昨天有社区的同学询问,能否像 Nginx 一样配置多个证书来支持多个域名。

答案当然是可以,实际上 Flomesh 的多个站点都是运行在 Pipy 上,且都是 HTTPS 的。这篇文章就为大家介绍如何通过 编程 的方式支持多证书。

由于篇幅的原因,文中只贴出部分 PipyJS 代码,有兴趣的同学可以从 GitHub 上 下载整套源码 (在 pipy-multi-tls 目录中)。

原理

一句话概括就是通过 TLS 的 SNI 扩展来实现的,对此已经很熟的同学可以直接跳过本节。

SNI 是服务器名称标识(Server Name Indication)的缩写,它是 TLS 的一个扩展协议。在该协议下,客户端在握手过程中会告知服务器它正在连接的主机名称。这个主机名称,就是 SNI。通过这个协议,服务器可以在相同的 IP 地址和 TCP 端口上呈现多个证书,也就是说同一个 IP 地址和端口上可以提供多个 HTTP 站点,或者其他 TLS 服务,每个站点可以使用独立的证书。

下面是互联网上找到的 TLS 握手的流程图,SNI 的信息就在第一步的 “client helo” 报文中。



这里我抓取了 TLS 握手的网络包。从图中可以看到客户端发起的 “client hello” 报文中,除了有 TLS 协议本身要求的信息外,还有扩展协议信息,其中就是 SNI 协议。从协议的内容,服务器可以确认客户端要访问 hello.com 站点。服务器可以在接受到 “client hello” 报文后,找到 SNI 所对应的证书信息通过 “server hello” 返回给客户端。



Pipy 的实现

Pipy 提供了 acceptTLS 过滤器,这个过滤器会完成如下工作:

  1. 从过滤器的输入中获取到客户端发送的握手信息,来完成 TLS 握手(比如返回证书等)

  2. 在完成握手之后,acceptTLS 会持续从输入中读取数据并进行 解密,解密之后的数据(TLS Offloaded)会交给 子管道 处理,与普通的 HTTP 信息处理一样。

  3. 子管道返回的数据回到 acceptTLS 过滤器,在发回客户端之前会先 加密

从上面的描述不难看出,相比处理 HTTP 请求,多了一个 acceptTLS 过滤器。

接下来,我们通过实际的代码来进行说明。

Demo

假设 Pipy 代理了两个 HTTPS 站点:https://hello.com 和 https://hello.io,体现在配置中就有证书配置,以及路由和负载均衡配置(这里为了文章效果,整合到一个配置文件中)。

有些证书中可以会有多个 CN(Common Name),这里使用正则进行匹配。实际场景,则根据需求进行设置。

{  "certificates": {    "((.+)|(.+\\.){0,1})hello\\.com": {      "cert": "./secret/com-cert.pem",      "key": "./secret/com-key.pem"    },    "((.+)|(.+\\.){0,1})hello\\.io": {      "cert": "./secret/io-cert.pem",      "key": "./secret/io-key.pem"    }  },  "routes": {    "hello.com/*": "hello.com",    "*.hello.com/*": "hello.com",    "hello.io/*": "hello.io",    "*.hello.io/*": "hello.io"  },  "services": {    "hello.com": [      "localhost:8081"    ],    "hello.io": [      "localhost:8082"    ]  }    }
复制代码

配置中使用的证书,可以通过下面的命令进行签发,并保存在脚本的 secret 目录中。

openssl genrsa 2048 > ca-key.pem
openssl req -new -x509 -nodes -days 365000 \ -key ca-key.pem \ -out ca-cert.pem \ -subj '/CN=flomesh.io'
openssl genrsa -out com-key.pem 2048openssl req -new -key com-key.pem -out com.csr -subj '/CN=hello.com'openssl x509 -req -in com.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out com-cert.pem -days 365
openssl genrsa -out io-key.pem 2048openssl req -new -key io-key.pem -out io.csr -subj '/CN=io.com'openssl x509 -req -in io.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out io-cert.pem -days 365
复制代码

下面是基于 SNI 选择证书进行握手的 PipyJS 代码片段。

acceptTLS 过滤器的参数(options)中有 4 个字段,分别是:certificatetrustedalpnhandshake。这里我们只用到 certificate,其他几个参数的说明可以参考 acceptTLS 的文档

certificate 参数可以是一个包含 cert 和 key 字段的对象,也可以是返回同样对象的函数,该函数的参数就是握手信息中的 sni

在代码中,我们通过 SNI 的内容从配置中选择证书进行握手处理。

pipy({  _certificates: Object.fromEntries(    Object.entries(config.certificates).map(      ([k, v]) => [        k, {          cert: new crypto.CertificateChain(os.readFile(v.cert)),          key: new crypto.PrivateKey(os.readFile(v.key)),        }      ]    )  ),}).listen(config.listenTLS, { maxConnections: config.maxConnections })  .acceptTLS({    certificate: (sni, cert) => (      sni && Object.entries(_certificates).find(([k, v]) => new RegExp(k).test(sni))?.[1]    )  }).to('tls-offloaded')
.pipeline('tls-offloaded') .demuxHTTP().to( $ => $.chain(config.plugins) )
复制代码

总结

这篇文章介绍了如何在代理中支持多个 HTTPS 站点,相信从代码片段上大家都能体会到 编程 的灵活、强大,还有 乐趣

就是那种可以通过编码的方式解决“任何”问题的乐趣,相信通过 Pipy 你也可以感受到。

发布于: 刚刚阅读数: 3
用户头像

Flomesh

关注

微信订阅号:flomesh 2022.04.07 加入

一站式云原生应用流量管理供应商 官网:https://flomesh.io

评论

发布
暂无评论
Pipy 同一 IP 多个 SSL 域名_Service Mesh 服务网格_Flomesh_InfoQ写作社区