[问题]在NAT背后使用Caddy提供HTTPS服务

前段时间,我尝试了 在内网中使用 HTTPS 部署,但立刻就在随后的实践中发现了一个问题。

我的领导告诉我,客户并不想运行我的证书安装程序,客户只是想打开浏览器输入地址就可以访问系统(因为这样比较方便)。

于是我就使用服务器的内网地址签发了一张证书,这样在内网通过 https://ip 的方式就可以访问系统了,虽然浏览器会提示不安全,但是所有功能也都可以正常使用,问题就算解决了。

随后,我想把这个服务通过一台路由设备代理到外网,也通过 https 访问,这样我可以方便的进行一些运维测试之类的工作。我就这样掉进了坑里。

环境

涉及到的设备包括:

  • 公网客户端(有公网地址的普通电脑一台)
  • 路由器(用来做 NAT,只有基本的 NAT 能力)
  • 服务器(在内网提供服务)

涉及到的网络地址包括:

  • 路由器公网地址:36.33.xx.xx
  • 路由器内网地址:192.168.34.1
  • 服务器内网地址:192.168.34.197

当前已经可以在内网使用 https://192.168.34.197 访问服务,目标是通过配置路由器 NAT 和服务器上的 caddy,使公网客户端可以使用 https://36.33.xx.xx 访问服务。

公网地址的 443 端口确定可用,之前测试过

失败的尝试

公网地址 443 端口代理到服务器 443

最开始的思路比较简单,直接配置路由器将公网 443 端口映射到服务器 443 端口。

Caddyfile的配置也很简单:

https://192.168.34.197 {
	encode zstd gzip
	tls /home/caddy/herong/tls/19216834197-cert.pem /home/caddy/herong/tls/19216834197-key.pem
        handle {
                root * /home/caddy/herong/web/dist
                file_server
        }
}

这种情况下,浏览器在报安全验证之后就可以进入网页,但请求不返回任何内容。观察 response 发现:

  • content-length: 0
  • server: Caddy

服务到达了 caddy,但是没有提供网站内容,接下来查看 caddy 的日志发现:

2024/10/30 07:05:56.420	DEBUG	http.stdlib	http: TLS handshake error from 36.33.xx.xx:65222: remote error: tls: unknown certificate
2024/10/30 07:05:56.422	DEBUG	events	event	{"name": "tls_get_certificate", "id": "9a9f30ec-e627-48e4-b7ee-52beb6bad236", "origin": "tls", "data": {"client_hello":{"CipherSuites":[10794,4865,4866,4867,49195,49199,49196,49200,52393,52392,49171,49172,156,157,47,53],"ServerName":"","SupportedCurves":[64250,25497,29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[1027,2052,1025,1283,2053,1281,2054,1537],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[60138,772,771],"RemoteAddr":{"IP":"36.33.xx.xx","Port":65223,"Zone":""},"LocalAddr":{"IP":"192.168.34.197","Port":443,"Zone":""}}}}
2024/10/30 07:05:56.422	DEBUG	tls.handshake	choosing certificate	{"identifier": "192.168.34.197", "num_choices": 1}
2024/10/30 07:05:56.422	DEBUG	tls.handshake	default certificate selection results	{"identifier": "192.168.34.197", "subjects": ["192.168.34.197", "*.192.168.34.197"], "managed": false, "issuer_key": "", "hash": "00c8e2e97b167d3f278fe71d0a962cd29174fec485fe2c00f25bb718e345f961"}
2024/10/30 07:05:56.422	DEBUG	tls.handshake	matched certificate in cache	{"remote_ip": "36.33.xx.xx", "remote_port": "65223", "subjects": ["192.168.34.197", "*.192.168.34.197"], "managed": false, "expiration": "2094/10/31 03:05:40.000", "hash": "00c8e2e97b167d3f278fe71d0a962cd29174fec485fe2c00f25bb718e345f961"}

从日志里可以看到 remote error: tls: unknown certificate,我猜想是客户端不认证书,然后看到证书的 subjects 是 192.168.34.197,我就觉得是因为客户端输入的地址和证书提供的 CN 或者 SAN 不匹配导致的问题。

然后我就觉得,如果服务器能够提供 36.33.xx.xx 的证书就好了。

使用公网地址的证书

然后就按照公网地址制作了证书,修改 Caddyfile:

https://192.168.34.197 {
	encode zstd gzip
	tls /home/caddy/herong/tls/3633xxxx-cert.pem /home/caddy/herong/tls/3633xxxx-key.pem
        handle {
                root * /home/caddy/herong/web/dist
                file_server
        }
}

这下连 443 端口都没起来,查看日志是这样的:

2024/10/30 07:16:40.600	WARN	tls	stapling OCSP	{"error": "no OCSP stapling for [36.33.xx.xx *.36.33.xx.xx]: no OCSP server specified in certificate"}
2024/10/30 07:16:40.600	DEBUG	events	event	{"name": "cached_unmanaged_cert", "id": "ca1f1002-a94b-4af4-a7fe-a88960129791", "origin": "tls", "data": {"sans":["36.33.xx.xx","*.36.33.xx.xx"]}}
2024/10/30 07:16:40.600	DEBUG	tls.cache	added certificate to cache	{"subjects": ["36.33.xx.xx", "*.36.33.xx.xx"], "expiration": "2094/10/31 03:01:19.000", "managed": false, "issuer_key": "", "hash": "15f7c42ab20c2daa817947d6d066a353fea00e6e69e2c873c33d3ebd2542257a", "cache_size": 1, "cache_capacity": 10000}
2024/10/30 07:16:40.600	INFO	http.auto_https	enabling automatic HTTP->HTTPS redirects	{"server_name": "srv0"}
2024/10/30 07:16:40.600	DEBUG	http.auto_https	adjusted config	{"tls": {"automation":{"policies":[{"subjects":["192.168.34.197"]},{}]}}, "http": {"servers":{"remaining_auto_https_redirects":{"listen":[":80"],"routes":[{},{}]},"srv0":{"listen":[":443"],"routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"encodings":{"gzip":{},"zstd":{}},"handler":"encode","prefer":["zstd","gzip"]},{"handler":"subroute","routes":[{"handle":[{"handler":"vars","root":"/home/caddy/herong/web/dist"},{"handler":"file_server","hide":["./Caddyfile"]}]}]}]}]}],"terminal":true}],"tls_connection_policies":[{"match":{"sni":["192.168.34.197"]},"certificate_selection":{"any_tag":["cert0"]}},{}],"automatic_https":{}}}}}

怀疑是证书中的地址和 caddy 配置中的地址对不上。

于是就再签了一张 SAN 同时包括两个地址的证书。

同时包含公网和私网的证书

制作了 CN 为 36.33.xx.xx,SAN 同时包括 36.33.xx.xx192.168.34.197 的证书,使用后 443 端口算是能够起来了,caddy 配置文件也只改了证书:

https://192.168.34.197 {
	encode zstd gzip
	tls /home/caddy/herong/tls/3633xxxxw197-cert.pem /home/caddy/herong/tls/3633xxxxw197-key.pem
        handle {
                root * /home/caddy/herong/web/dist
                file_server
        }
}

但是!客户端访问的问题依然存在,问题现象也和之前一模一样。只不过这次从客户端看到的证书的 CN 和 SAN 都是 36.33.xx.xx,这一点倒是符合预期。

查看 caddy 的日志,发现连报错都一样:

2024/10/30 07:25:45.013	DEBUG	http.stdlib	http: TLS handshake error from 36.33.xx.xx:49913: remote error: tls: unknown certificate
2024/10/30 07:25:45.015	DEBUG	events	event	{"name": "tls_get_certificate", "id": "aa9a98f8-1058-4331-a1d9-e642f0645cf4", "origin": "tls", "data": {"client_hello":{"CipherSuites":[19018,4865,4866,4867,49195,49199,49196,49200,52393,52392,49171,49172,156,157,47,53],"ServerName":"","SupportedCurves":[60138,25497,29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[1027,2052,1025,1283,2053,1281,2054,1537],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[51914,772,771],"RemoteAddr":{"IP":"36.33.xx.xx","Port":49914,"Zone":""},"LocalAddr":{"IP":"192.168.34.197","Port":443,"Zone":""}}}}
2024/10/30 07:25:45.015	DEBUG	tls.handshake	choosing certificate	{"identifier": "192.168.34.197", "num_choices": 1}
2024/10/30 07:25:45.015	DEBUG	tls.handshake	default certificate selection results	{"identifier": "192.168.34.197", "subjects": ["36.33.xx.xx", "192.168.34.197"], "managed": false, "issuer_key": "", "hash": "109ba9582f4bde945134e6794168af4c947a3cdf6d0391388cb09310fb811def"}
2024/10/30 07:25:45.015	DEBUG	tls.handshake	matched certificate in cache	{"remote_ip": "36.33.xx.xx", "remote_port": "49914", "subjects": ["36.33.xx.xx", "192.168.34.197"], "managed": false, "expiration": "2094/10/31 07:24:23.000", "hash": "109ba9582f4bde945134e6794168af4c947a3cdf6d0391388cb09310fb811def"}

其他的失败尝试

例如另起一个服务端口,然后使用反向代理提供服务:

https://192.168.34.197:10443 {
        encode zstd gzip
        tls /home/caddy/herong/tls/3633xxxx-cert.pem /home/caddy/herong/tls/3633xxxx-key.pem
        reverse_proxy https://192.168.34.197 {
                header_up Host {upstream_hostport}
        }
}
https://192.168.34.197 {
	encode zstd gzip
	tls /home/caddy/herong/tls/19216834197-cert.pem /home/caddy/herong/tls/19216834197-key.pem
        handle {
                root * /home/caddy/herong/web/dist
                file_server
        }
}

我期望着第一个服务能够提供公网地址的证书,但是不行。

甚至用 HTTP 都不行:

http://192.168.34.197:10443 {
        encode zstd gzip
        reverse_proxy https://192.168.34.197 {
                header_up Host {upstream_hostport}
        }
}
https://192.168.34.197 {
        encode zstd gzip
        tls /home/caddy/herong/tls/19216834197-cert.pem /home/caddy/herong/tls/19216834197-key.pem
        handle {
                root * /home/caddy/herong/web/dist
                file_server
        }
}

思考

  1. 是不是提供 HTTPS 服务的设备必须同时拥有地址和证书?
  2. 大型企业是否会遇到这样的问题?他们是怎么解决的?是不是有相关解决方案的供应商?
  3. 如果在更低层的网络上进行编程,能否解决这个问题?例如在服务器的 TCP 层进行编程?是否已经有相关的解决方案?
  4. 好像可以用 frp 解决??!!

[问题]在NAT背后使用Caddy提供HTTPS服务
https://vitsumoc.github.io/[问题]在NAT背后使用Caddy提供HTTPS服务.html
作者
vc
发布于
2024年10月28日
许可协议