文章

我们每个人都是这些巨大转变的见证人,都是迫不得已变成了见证人。对我们这代人来说,我们不存在任何逃避,不能像我们先辈那样置身事外;由于时间同步的新机制,我们始终和时代戚戚相关。 - 昨日世界

引言

由于住所不太固定,需要考虑移动以及网络环境不能完全掌握诸如此类的问题,之前搞的服务于自己的服务大多是半截子,除了基于smb的磁盘共享, jellyfin 以及旁路由外,大都没有用起来,一方面是不方便,另一方面则是局域网内使用的服务大多没有太多需要。

而这一现状因为前一段时间正儿八经的买了一个小主机,重新点燃了我折腾 All in Boom Home Server 的热情之下,发生了一些改变。

而作为其第一步的是一个自托管的 bitwarden 它硬性要求访问必须是 https,考虑应用性质也是正常的。
这篇文章记录了,如何安全的把这个服务暴露在公网(主要是为了自己用)的思考及过程。

思考

首先是环境,我选择了在 windwos 里使用 VMware 里搭 Debian,也没什么特别的原因 centerOS 依稀记得不维护了,Unbutu 太重,Debian 则相对精简。不使用 Wsl 的原因是,之前用的时候坑过多,最直接的就是网络不好独立配置;不用 Hyper 则是,避免可能虚拟机卡死,宿主机直接挂了的风险(WSL之后对windows下的这些技术有些不太感冒也占一些原因);以及最主要的现在 VMware 个人使用是免费的。

然后是网络,在这之前这方面我主要使用 Cloudfalred Tunnel 或者 frp 到能在公网暴露的主机上,前者不怎么需要配置就能 Https,而 frp 如果想用 https 协议的配置需要对 frp 的服务端进行各种配置只能暂时作罢。于是我首先在 Debian 里新配了一个基于 Cloudfalred Tunnel 的转发。然后虽然偶尔要多试几次,但基本勉强能用让我坚持了一段时间;直到到了传统节点,发现可访问性只有 25% ,也不知是因为版本问题,还是虚拟机的环境缺什么东西,依或是我以前折腾的一些优化生效了,新的通道的可用性永远只有老的通道的一半。

至此我只能在 frp 这条路上想想法子,条件如下:

  1. 非标准端口,不能改服务端配置。
  2. 需要满足 https,证书可信任,公网访问。

过程

搜索或者去问 bing copilot:"frp 如何使用 https?"
得到的内容大致如下: "frp 有专门的 https 协议相关的配置"
然而,仔细看配置需要的内容,我理解这更类似是在 frp 的服务端上起一个支持 https 协议的 web 服务器向外暴露,或者从协议层面支持客户端向服务端发起基于 https 协议的连接,这不符合我的需求。

省略大量思考及试错过程,只说结论:理论来说 frp tcp 转发是一个透明代理,和直接访问应该没什么区别,那么通过 frp 直接转发配置 https 的 nginx 应该是可以行的通的。

第一步:
配置一个 TCP 反向代理转发。


serverAddr = "xxxx"
serverPort = 7000
auth.token = "xxxx"

[[proxies]]
name = "deban"
type = "tcp"
localIp = "127.0.0.1"
localPort = 8080
remotePort = 8080

因为服务端使用的是 0.52.3 所以我也只能用这个,这是个过渡版本,配置文件格式也有一些改变,但总体来说平淡无奇。

第二步:
安装 Nginx 并配置反响代理。
安装过程及修改地址直接问 bing copilot ,配置大概长这样。


server {
    listen 8080;
    server_name localhost;

    location / {
        proxy_pass http://XXXX;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

至此,一条通过 frp 反向代理Nginx, 再由Nginx 反向代理 目标服务的链路就通了。
当然也可以选择直接通过frp反向代理目标服务,但这种情况,就不好控制 https 了。

第三步
申请一个合法可用的 https 证书。
一般frp服务是固定 ip 或者提供一个域名,如果是前者可以直接解析一个域名过去,如果是后者,则需要用 CNAME 把自己的域名指向 frp 服务的域名,这里使用 example.com 指代。

有域名以后下一步是证书,一般免费证书可以去 Let's Encrypt 或者 ZeroSSL,原理都差不多,验证域名属于你后就可以申请一个三个月长度的证书,这个验证,一般是通过在根目录放文件或者改DNS解析记录来通过,这里我选择使用 acme 通过 DNS 解析记录验证,泛用性更高。

# 安装
    curl https://get.acme.sh | sh

# 通过Token登陆服务商改 DNS 解析使用的环境变量,不同服务商不一样,我这里是 CloudFlare 的
export CF_Token=

export CF_Zone_ID=
export CF_Account_ID=

# 注册(默认使用 ZeroSSL) 证书中的邮箱
 ~/.acme.sh/acme.sh  --register-account -m [email protected]
 
# 可以通过设置更改 letsencrypt  不过我用的 ZeroSSL
 ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt

# 基于 CloudFlare dns 验证方式的证书注册
~/.acme.sh/acme.sh --issue --debug --dns dns_cf  -d 'example.com'

这一步图省事也可以使用自签证书,实际上,我也是先通过自签证书进行验证的。

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt

第四步
配置 https

server {
    listen 8080 ssl;
    server_name example.com;

    ssl_certificate /etc/nginx/ssl/nginx.crt;
    ssl_certificate_key /etc/nginx/ssl/nginx.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        proxy_pass http://XXXX;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

完工 https://example.com:8080 已经可以访问到服务了,至此问题解决,意外的简单。

花絮

由于这里使用了 frp 反向代理了 nginx,于是 nginx 拿不到真实请求的用户地址。使用上不是什么大问题,但是..
一来我在这里摆了一个开源的 waf(拿不到真实请求地址就意义会少一些)
二来纯粹的探索

正好文档里有这么一个描述:
获取用户真实 IP | frp (gofrp.org)

简单来说就是这么配置

[[proxies]]
name = "deban"
type = "tcp"
localIp = "127.0.0.1"
localPort = 8080
remotePort = 8080
transport.proxyProtocolVersion = "v2"

会开启基于 Proxy Protocol 协议的真实 ip 转发。

Nginx 对应的修改
文档:Accepting the PROXY Protocol | NGINX Documentation

server {
    listen 8080 ssl proxy_protocol;
    server_name example.com;

    ssl_certificate /etc/nginx/ssl/nginx.crt;
    ssl_certificate_key /etc/nginx/ssl/nginx.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        proxy_pass http://XXXX;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_protocol_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

至此 X-Forwarded-For 就可以拿到真实访问的 ip 地址了。
代价则是,直接访问会失败,必须由支持 proxy_protocol 协议的代理进行中转,失败可以在 nginx 的 error 日志中看到。

结语

整个过程借助 bing copilot 用了大约 3 个小时左右,包括验证类似灵光一闪但实际没什么实际意义的中间过程,以及让 bing 解释具体的选项是做什么的。感想来说,现在直接搜索效率实在低的令人发指,中文尤甚,互联网真的越来越不互联了。但指定 bing 去翻文档或者限定范围去学习新的完全不了解的概念,以及验证想法的成本也是前所未有的低。联想到现在知乎做的几乎丧心病狂的反爬以及站内问答工具,未来大模型作为“接口”替代搜索引擎作为互联网门户的场景,可以说是前所未有的清晰。

评论

This is just a placeholder img.