fatedier / frp

A fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet.
Apache License 2.0
80.97k stars 12.78k forks source link

Https 代理模式对证书的处理可能有问题 #520

Closed sxul closed 6 years ago

sxul commented 6 years ago

Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly. (为了节约时间,提高处理问题的效率,不按照格式填写的 issue 将会直接关闭。)

Use the commands below to provide key information from your environment: You do NOT have to include this information if this is a FEATURE REQUEST

What version of frp are you using (./frpc -v or ./frps -v)? 0.13.0

What operating system and processor architecture are you using (go env)? CentOS 7 Server + Nginx 1.13.5 (built with OpenSSL 1.0.2l) / Debian 9 Client

Configures you used:

/frpc.ini

[web01]
type = tcp
local_ip = 127.0.0.1
local_port = 443
use_encryption = false
use_compression = false
remote_port = 7443
subdomain = web

[web02]
type = https
local_ip = 127.0.0.1
local_port = 443
use_encryption = false
use_compression = false 
subdomain = web

/frps.ini

[common]
bind_port = 7000
kcp_bind_port = 7000

vhost_http_port = 8080
vhost_https_port = 8443

privilege_token = xxxxx

subdomain_host = xxx.com

nginx vhost config

server {
    listen       80;
    listen       443 ssl http2;
    server_name  web.xxx.com;

    ssl_certificate  /root/keys/xxx.crt;
    ssl_certificate_key  /root/keys/xxx.key;

    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;

    ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !kECDH !DSS !MD5 !EXP !PSK !SRP !CAMELLIA !SEED';
    ssl_prefer_server_ciphers  on;

    if ( $ssl_protocol = "" ) {
        rewrite ^ https://$host$request_uri?;
    }
    location / {
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto  $scheme;
        proxy_pass https://127.0.0.1:8443;
    }
}


Steps to reproduce the issue:

  1. 使用以上配置 用 https://web.xxx.com 访问服务器端 Nginx会报 502 Bad Gateway
  2. 查阅 Nginx 的错误日志找到了以下内容: 2017/11/13 07:51:16 [error] 32703#32703: *6 peer closed connection in SSL handshake while SSL handshaking to upstream,
  3. 在服务器运行 openssl s_client -connect 127.0.0.1:8443 -tls1 的结果显示的证书信息是空的

snipaste_2017-11-13-01

PS: 浏览器直接访问 https://web.xxx.com:7443https://web.xxx.com:8443 都是可以的 但是似乎使用https模式的 8443 端口 页面载入会稍慢一些

Describe the results you received:proxy_pass 修改为 https://127.0.0.1:7443 就可以正常访问 https://web.xxx.com

openssl s_client -connect 127.0.0.1:7443 -tls1 也可以返回正确信息 snipaste_2017-11-13_02 snipaste_2017-11-13_03

Describe the results you expected: https 模式无法证书的话就只能使用 tcp 模式进行反代,这样的话 8443 端口无法复用,每次新增站点也要手动配置 nginx 很麻烦。

Additional information you deem important (e.g. issue happens only occasionally):

Can you point out what caused this issue (optional)

fatedier commented 6 years ago

frp 的 https 类型的代理是接收浏览器发送过来的 https 连接,然后探测到其中的域名,之后根据域名将这个连接路由到后端的 https 服务。

你通过 nginx 接收 https 请求,进行了加解密,再转发给 frp,这时候这个请求已经不是标准的 https 请求了,frp 当然无法处理。

frp 本身不关心证书,也不会进行相关的处理,只是解析 https 协议其中的域名部分。

sxul commented 6 years ago

也就是说如果没有带正确的域名信息的话就不会转发到后端的https服务,导致证书信息会是空的 是这样吗?

fatedier commented 6 years ago

通常新的浏览器都会带上的,比如 Chrome 最新版。你的问题是中间放了一个 nginx,你要确定经过 nginx 后这个 https 请求没有被修改?还和直接通过浏览器发送的一致?

sxul commented 6 years ago

nginx应该不是直接转发的https 他只是相当于作为客户端去访问https服务 再把获得的数据转发出去 所以在后端(frp)是拿不到浏览器的请求的。现在问题是经过frp转发的https,openssl认不出证书信息了,导致nginx还没去访问直接就报错了

fatedier commented 6 years ago

你把 nginx 去掉,直接浏览器请求 frp 的 https 端口,如果仍然有问题,再反馈一下相关信息。

sxul commented 6 years ago

是这样的,我现在的情况是 用 浏览器直接请求frp的https和frp的tcp都是没问题的,但是经过nginx转发的话只有tcp模式可以工作,https模式会因为nginx读不到https证书信息而直接终止请求然后报502错误。现在的解决方式是一个https站点用一个端口 再让frp一个一个的tcp转出去,但是这样就不能让frp处理域名了,比较麻烦,所以想请作者考虑一下能不能对这方面的做一下优化0.0

fatedier commented 6 years ago

明确一个问题,你是通过 frp 转发给 nginx,还是 nginx 接收浏览器的请求转发给 frp。 如果是后者,那么问题出在 nginx 的使用上,如果是前者,我们再继续讨论。

sxul commented 6 years ago

前者。通过frp转发给nginx,在这个过程中丢失了可以被openssl读取的证书信息,导致nginx报错了( 具体的我不是很清楚https的工作顺序,猜测大概是frp转发https的时候漏处理了一些请求

fatedier commented 6 years ago

前一种的话 nginx 里为什么要设置 proxy_pass 到 frp 的端口?

sxul commented 6 years ago

比如我有 Server1 和 Server2 两台机器,以下简称 S1 和 S2, S1是长时间开机并且暴露在公网的正常服务器,S2可能是一台在内网的机器,我现在想要在 Server1 部署一个https 服务,地址是 https://xxx.com/ ,同时我又想让 https://xxx.com/s2/ 能访问到 S2 上的服务(也有可能是 aaa.xxx.com)。

我现在采用的方案就是在 S1 运行 nginx 和 frps,在 S2 运行一个 https 服务和 frpc。然后在 S1 的 nginx 处理 443 端口的请求,把对应的请求转发给 S2 的 https 服务。 就是这样

sxul commented 6 years ago

我遇到的问题就是在 S1 的 nginx 在直接转发请求到 frp 用 https 方法转发上来的端口的时候 会报错,虽然把 frp 的转发方式改成 tcp, 或者把 frpc 上的服务降级成 http 都是可以正常使用的 😭

fatedier commented 6 years ago

明确一个问题,你是通过 frp 转发给 nginx,还是 nginx 接收浏览器的请求转发给 frp。 如果是后者,那么问题出在 nginx 的使用上,如果是前者,我们再继续讨论。

所以你用 nginx 接收浏览器发送过来的请求,转发给 frp,那么属于后者,问题出在 nginx 的使用上,需要你自行查找相关的解决方案。

sxul commented 6 years ago

你是不是误解了什么…请求是发给frp的 但是在发送的时候他会读取证书信息 而经过frp转发的https端口没有这个数据 导致nginx报错,这种情况你告诉我不应该在frp上完善对https请求的处理,而是想办法让nginx无视这个错误吗?

fatedier commented 6 years ago

你需要自己理清整个流程,从你的回复以及你贴的 frp 和 nginx 配置来看 是这样的,我现在的情况是 用 浏览器直接请求frp的https和frp的tcp都是没问题的,但是经过nginx转发的话只有tcp模式可以工作 我遇到的问题就是在 S1 的 nginx 在直接转发请求到 frp 用 https 方法转发上来的端口的时候 会报错

frp 反代 https 没有问题(我本地测试通过 frp 转发请求给 nginx,通过浏览器请求 frp 的 https 端口,没有问题),通过 nginx 在前端转发后有问题,请自行搜索 nginx 的相关解决方案。除非有明确指出存在的问题,这个 issue 不再回复了。

sxul commented 6 years ago

你还是没懂吗…frp的https模式转发上来的请求是用浏览器访问没问题,但是用nginx转发会报错,并且我用openssl进行对https模式的frp端口测试的时候返回的证书信息是空的… 使用tcp模式就能读到证书信息…所以是frp转发https请求的时候有问题难道不对吗???

sxul commented 6 years ago

https模式会因为nginx读不到https证书信息而直接终止请求然后报502错误

你是看到前面的一句说直接访问frp没问题就把这句话无视了吗???

jasonhu commented 6 years ago

可能是,Nginx作为前端向后端frps按照HTTPS协议发送握手请求的时候,还是在用老的标准,导致frps无法解析出域名?

jasonhu commented 6 years ago

Nginx配置修改为proxy_pass https://web.xxx.com:8443; 看看

icksky commented 5 years ago

我现在也是这个想法, 流程大概这样: 浏览器 -> S1.nginx -> S1.frps -> S2.nginx. 然后https 就502了

SeaHOH commented 5 years ago

关键是加密,tcp 模式才能转发加密后的数据。 除非浏览器 -> S1.nginx这个过程不进行 https 握手,才是未加密连接。

nickfan commented 5 years ago

@sxul 我用ssh的穿透临时解决的问题:

/usr/bin/ssh -gNR 5743:127.0.0.1:443 user@公网机器ip -p22

由于我不是题主无法reopen这个issue,我就另开了一个 #1035

具体的配置过程可以参考

但问题还请 @fatedier 大神能帮忙解答。