2 minute read

背景

在教育网内有一台服务器提供 WEB 服务(假设其教育网 IP 是 202.38.64.246),绑定域名 example.com。教育网 IP 的 443 端口在公网无法访问,但有一台公网服务器(假设 IP 是 112.80.248.75)。

现希望在教育网内访问 https://example.com 网站时能直达教育网服务器,在公网访问 https://example.com 能反向代理到教育网服务器:即用户可以不关心自己的网络情况透明地访问 https://example.com

为了让问题一般化,假设公网服务器上原先还在 443 端口跑着 https://example.org 网站,我们希望反向代理 https://example.com 不影响 https://example.org 的正常运行。

方案

基本思路

借助 DNS 解析服务商的“智能解析”,在教育网线路解析到教育网真实 IP,在公网线路解析到公网服务器 IP,同时这个公网服务器运行 frp server 端,教育网服务器运行 frp client 端,公网服务器借助 Nginx 和 frp 将请求反向代理至教育网服务器(实际上这里的 Nginx 和 frp 各自运行了一个独立的反向代理服务,即串联了两层反向代理)。

具体过程

教育网服务器

frp

frpc.ini

使公网服务器 112.80.248.75 的 10443 端口为本地 443 端口提供反向代理。

[common]
server_addr = 112.80.248.75
server_port = 7000
authentication_method = token
authenticate_new_work_conns = true
token = here_is_the_token

[tcp_port]
type = tcp
local_ip = 127.0.0.1
local_port = 443
remote_port = 10443

frpc.service(用于开机自启)

[Unit]
Description=Frp Client Service
After=network.target

[Service]
Type=simple
User=nobody
Restart=on-failure
RestartSec=5s
ExecStart=/usr/bin/frpc -c /etc/frp/frpc.ini
ExecReload=/usr/bin/frpc reload -c /etc/frp/frpc.ini
LimitNOFILE=1048576

[Install]
WantedBy=multi-user.target
Nginx

保持原始正常配置即可,如:

server {
    listen 80 default_server;
    server_name _;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;
    root ...;
    index index.html;
    ssl_certificate ...;
    ssl_certificate_key ...;
    location / {
        try_files $uri $uri/ =404;
    }
}

公网服务器

防火墙放行

在服务器控制面板放行 frp 的 7000 端口。

frp

frps.ini

[common]
bind_port = 7000
authentication_method = token
authenticate_new_work_conns = true
token = here_is_the_token

frps.service(用于开机自启)

[Unit]
Description=Frp Server Service
After=network.target

[Service]
Type=simple
User=nobody
Restart=on-failure
RestartSec=5s
ExecStart=/usr/bin/frps -c /etc/frp/frps.ini
LimitNOFILE=1048576

[Install]
WantedBy=multi-user.target
Nginx

它的作用是将访问 https://example.com 的请求反向代理至本地 10443 端口。这里提供两种思路:使用 Nginx stream 块实现的 SSL Passthrough 和使用 Nginx http 块实现的 SSL Bridging。

它们的简单对比如下表:

  SSL Passthrough SSL Bridging
速度 Good(反向代理服务器不解密流量) Bad(反向代理服务器解密、再加密)
对原网站 example.org 配置的影响 Bad(需要改原网站的监听端口) Good(不用改原网站的配置)
反向代理服务器证书配置 Good(反向代理服务器上不需要提供 SSL 证书) Bad(反向代理服务器上也需要提供 SSL 证书)
反向代理服务器端流量控制能力 Bad(流量未解密,不能为所欲为) Good(流量解密后可以为所欲为)
安全性 Good Good
SSL Passthrough

下图偷自 https://parksehun.medium.com/ssl-passthrough-vs-ssl-termination-vs-ssl-bridging-f66b24d4d0aa

http {
    server {
        listen 80 default_server;
        server_name _ ;
        return 301 https://$host$request_uri;
    }

    server {
        listen 9443 ssl; # 注意这里需要把 443 改成别的端口,因为 stream 块中已经在监听 443 端口了
        server_name example.org;
        ...
    }
}

stream {
    map $ssl_preread_server_name $name {
        example.com edu_backend; # 代理流量导向本地 frps serve 的 10443 端口
        default local_backend; # 其他流量导向本地 9443 端口
    }

    upstream edu_backend {
        server 127.0.0.1:10443;
    }

    upstream local_backend {
        server 127.0.0.1:9443;
    }

    server {
        listen 443;
        proxy_pass $name;
        ssl_preread on;
    }
}

SSL Bridging

下图图源和上图相同。

http {
    server {
        listen 80 default_server;
        server_name _ ;
        return 301 https://$host$request_uri;
    }

    # example.org 的配置保持不变
    server {
        listen 443 ssl;
        server_name example.org;
        ...
    }

    server {
        listen 443 ssl;
        server_name example.com;
        # 这里仍需要 example.com 的证书
        ssl_certificate ...;
        ssl_certificate_key ...;
        location / {
            proxy_pass https://127.0.0.1:10443;
            proxy_set_header    Host            $host;
            proxy_set_header    X-Real-IP       $remote_addr;
            proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

stream {
    # 不需要这个块
}

域名解析

借助 DNS 解析服务商的“智能解析”,在教育网线路解析到教育网真实 IP,在公网线路解析到公网服务器 IP。

Comments