nginx的多证书应用

雲文 雲文

运行多证书反向代理,充当NOSTR中继转发器快四个月了,今天准备下线这个服务。随便把配制思路分享一下。

NOSTR因为众所周知的原因,国内是无法访问的,而NOSTR需要中继才能存活,本来NOSTR的设计思路就是天然的

去中心社交模式。但是现在因为没有使用分布式方式发布和订阅中继,反而让NOSTR的分布式社交变得有名无实 了。

国内访问那些著名的中继器比如说wss://relay.damus.io, wss://offchain.pub等等。

那么如果有VPS且有域名和证书的情况下,使用Nginx是很容易架设一个反向代理服务器,充当中继转发(Forwarder of Relay)。

这样子,中继还是那些中继。但是因为转发器的存在,让中继器拥有了无数个“分身”。

  1. 域名的申请,跳过,这个非常容易几美金就能完成的事情。
  2. 证书的申请,本文跳过,最简单的是使用certbot来完成,但是官方的certbot手册真的很一般,很容易出错。而且它的基于域名认证的扩展也不够及时,还需要用户自己经验足够丰富。还不如使用稍为麻烦但是可靠的http文件认证模式。但是范域名证书不支持http文件认证,必须用DNS认证。好在DNS认证可以通过手

这里,需要证书,是因为wss协议的要求。其实建立连接之后,Nginx还需要offload原wss请求中的Host,然后重新封装新的wss请求,然后将新的wss请求转发到中继器去。

同时,因为Nginx默认情况下,一般还会提供正常的https访问,所以这就要求在ssl的443端口,提供https以及中继转发的wss多个服务。为了区别这些服务,必须Nginx开启ngx_stream_ssl_preread_module模块,这个模块提供SNIALPN功能,在本文的应用场景中,只需要SNI支持,它允许客户端连接的时候,通知Ningx连接的是那个服务,是https的服务?还是wss的中继转发服务。这样Nginx就会根据客户端连接的通知,选择不同的处理逻辑:

  1. 如果是wss中继转发服务,则进行wss的offload,重新组装wss请求转发到对应的中继器。这个offload主要是替换wss请求中的Host头。
  2. 如果是普通的https服务,则按照正常的https流程,交给本地的https应用来处理。
  3. 如果还有其它的服务,比如说Trojan服务那就一样的专交给Trojan服务。

在这里,可以看到,其实Nginx的的处理层不是在http层来进行的,而是在strea层进行的。

换句话说nginx需要先配制stream块然后配制http块


stream { log_format stream_log '$remote_addr $ssl_preread_server_name [$time_local] ' '$protocol $status $bytes_sent $bytes_received ' '$session_time "$upstream" "$upstream_addr" ' '"$upstream_bytes_sent" "$upstream_bytes_received" "$upstream_connect_time"'; access_log logs/stream.log stream_log; #access_log off; map $ssl_preread_server_name $upstream{ ~t\. trojan; #begin with t.xxx.yyy.. means trojan traffic ~\.relay\.center relay; #nostr relay default web; } upstream trojan{ server 127.0.0.1:8443; } upstream web{ server 127.0.0.1:1443; } upstream relay{ server 127.0.0.1:1443; } server { listen 443 reuseport; ssl_preread on; proxy_pass $upstream; #proxy_ssl on; } }

在这里Nginx所使用到的变量$ssl_preread_server_name,就是ngx_stream_ssl_preread_module模块提供的。它通过在建立ssl连接的时候,提前读取Client Hello中的server_name来给$ssl_preread_server_name赋值,这个时候ssl的握手建立还没有完成,只是收到了客户端的hello数据包,服务器没有返回任何ssl数据包,需要根据配制逻辑,选择不同的服务器证书来完成ssl握手,然后才能进入到https层面的工作。

在这里根据$ssl_preread_server_name定义了不同的正则表达式,然后使用proxy_pass选择不同的后端。

    map $ssl_preread_server_name $upstream{
        ~t\.            trojan; #begin with t.xxx.yyy.. means trojan traffic
        ~\.relay\.center    relay;  #nostr relay
        default         web;
    }

然后后端的配制,就使用普通的https既可,

中继转发的时候,需要动态的把请求代理出去既可。

image-20230723100309287

需要注意的三个地方:

  1. 客户端证书需要自己生成,很多地方都可以,openssl也应该可以
  2. proxy_ssl_protocals的协议版本,最好把最新的1.3版本加上支持,否则有的服务器可能会拒绝连接,比如说damus.io
  3. 最后需要把普通的https升级为wss,这个用proxy_set_header指令既可完成。

至于如何混用trojan流量,等有机会再说。这样子Nginx就一肩挑三担:

  1. 本地的https服务
  2. 远程基于WSS的NOSTR中继服务器
  3. Trojan流量