使用EdgeOne安全开放本地WEB服务

提要

在有动态公网 IPV6 的情况下,使用 DDNS 和自定义端口对外提供 Web 服务并不难,但面临两个重要问题:

  • 如果客户端不支持 IPV6,怎么办?
  • 需要打开防火墙,暴露端口,是否不太安全?

提供 Web 服务的方案有很多,比如使用端口映射方案。我曾使用过 fatedier/frp 这样的开源项目,但它需要另外维护一台 VPS,其带宽和成本都是问题。此外,端口映射的方案实际上也并不安全。

因此,我选择了另一种更加适合我目前场景的方案:

腾讯云 Edge One

EdgeOne 的具体介绍可以查看腾讯云的产品介绍,这里不再赘述。可以简单将它看作一个更加安全的 CDN。EdgeOne 的几个特性很好地解决了我的需求:

  • 支持 IPV6 的自定义端口源站:EO 可以直接回源 IPV6 地址的指定端口,而客户端访问时可以直接使用域名,并支持 V4 和 V6 双栈。
  • 多个子域名可共用源站组:可以配置一个源站组,然后在各个子域中引入这个源站组。这样,在动态 IP 地址变更时,只需更新源站组的 IP,而无需操作多个源站。
  • 支持 API 更新源站组:可以在服务器上使用自动化脚本来监测 IPV6 地址变更情况,并在地址变更时自动更新源站组,保障服务可用性。
  • 提供 EO 节点 IP 地址:防火墙仅需对 EdgeOne 节点放通入站请求,规避一些安全问题。

其实 Cloudflare 也提供了类似服务,甚至免费,但在中国大陆地区的服务质量不是特别稳定,我所在的地区测试延迟很高,无法使用。

部署小记

1. 本地服务器上线 Web 服务

我的本地服务器环境为 Windows Server 2022,已使用 Docker 部署了我的博客,使用宿主机的 60023 端口。

image

2. 为 EdgeOne 节点开放防火墙

腾讯云通过地址 https://api.edgeone.ai/ips 提供 EdgeOne 的节点 IP,并接收参数 versionarea 来指定地区和 IPV4/IPV6。

为了能够动态地更新防火墙规则,我将其编写为一个 Python 脚本,放在计划任务中定期执行:

import requests  
import subprocess  

def get_ips(url):  
    try:  
        response = requests.get(url)  
        response.raise_for_status()  
        ip_list = response.text.strip().split('\n')  
        return sorted(ip_list)  # 对获取的 IP 列表进行排序  
    except requests.RequestException:  
        return None  

def read_previous_ips(filename):  
    try:  
        with open(filename, 'r') as file:  
            ip_list = file.read().strip().split('\n')  
            return sorted(ip_list)  
    except FileNotFoundError:  
        return None  

def write_ips(filename, ips):  
    with open(filename, 'w') as file:  
        file.write('\n'.join(ips))  

def delete_firewall_rules(group="@用户定义_EdgeOne"):  
    cmd = f"PowerShell -Command \"Remove-NetFirewallRule -Group '{group}'\""  
    subprocess.run(cmd, shell=True)  

def add_firewall_rule(name, ips, protocol="TCP", direction="Inbound", action="ALLOW", group="@用户定义_EdgeOne"):  
    ips_str = '","'.join(ips)  
    cmd = f"PowerShell -Command \"New-NetFirewallRule -DisplayName '{name}' -Direction {direction} -Action {action.capitalize()} -Protocol {protocol} -RemoteAddress \"{ips_str}\" -Group '{group}'\""  
    subprocess.run(cmd, shell=True)  

def main():  
    ip_versions = ['v4', 'v6']  
    areas = ['mainland-china', 'overseas']  
    changes_made = False  

    set_list = {}  

    for version in ip_versions:  
        for area in areas:  
            url = f"https://api.edgeone.ai/ips?version={version}&area={area}"  
            ips = get_ips(url)  
            if ips:  
                base_rule_name = f"{version}_{area}"  
                set_list[base_rule_name] = ips  

                filename = f"{base_rule_name}_ips.txt"  
                previous_ips = read_previous_ips(filename)  
                if ips != previous_ips:  
                    changes_made = True  
    if changes_made:  
        delete_firewall_rules()  
        for base_rule_name, ips in set_list.items():  
            for i in range(0, len(ips), 200):  
                ip_group = ips[i:i + 200]  
                rule_name = f"EdgeOne_{base_rule_name}_Part_{i // 200 + 1}"  
                add_firewall_rule(rule_name, ip_group)  
            write_ips(f"{base_rule_name}_ips.txt", ips)  

    return changes_made  

if __name__ == "__main__":  
    main()

该脚本从地址中读取节点地址,并分别添加一系列的规则,并且可以通过组进行筛选:

image

正确配置防火墙可以保证只有 EdgeOne 能访问到源站,以免被透过 EdgeOne 直接攻击源站服务器。

3. 创建 EdgeOne 站点

登录腾讯云控制台,进入到 站点列表 - EdgeOne - 控制台 页面,新增站点,并记录下站点 ID。

4. 配置计划任务动态更新源站组

脚本源码可以查看 sqkkyzx/EdgeOneDynamicOrigin

下载源码后,重命名 config.example.yamlconfig.yaml ,并添加必要的配置。然后使用任务计划程序或 crontab 来执行脚本。

第一次运行时,脚本日志会输出查询到的公网 IPV6 地址,并新增一个本机主机名的源站组:

2024-12-11 19:00:03,649 - root - INFO - 地址 <240e:1****> Ping 测试通过,是公网地址。
2024-12-11 19:00:05,704 - root - INFO - 地址 <240e:2****> Ping 测试通过,是公网地址。
2024-12-11 19:00:07,861 - root - INFO - 地址 <2001:3****> Ping 测试失败。
2024-12-11 19:00:09,946 - root - INFO - 地址 <fd7a:4****> Ping 测试失败。
2024-12-11 19:00:09,947 - root - INFO - 本机公网 IPV6 地址为: ['240e:1****', '240e:2****']
2024-12-11 19:00:10,410 - root - INFO - 公网 IPV6 地址未发生变更,站点 zone-1***** 的源站组 **** 无需更新。
2024-12-11 19:00:10,907 - root - INFO - 公网 IPV6 地址未发生变更,站点 zone-2***** 的源站组 ***** 无需更新。
2024-12-11 19:00:11,545 - root - INFO - 解析记录 ***** 存在已过期的值 ***** , 正在删除。

如果配置了 DNS 相关配置,也会处理 DNS 解析。如果配置了钉钉机器人,则会发送通知到钉钉。

5. 配置域名

登录腾讯云控制台,进入到 站点列表 - EdgeOne - 控制台 页面,为站点添加子域名,回源到服务器主机名的源站组和对应服务的端口即可。

结语

该方案与内网穿透相比,有一定局限性,比较依赖运营商是否开放 IPV6,其流量也很少,但它并不局限于内网穿透的 VPS 带宽。此外,仅用于 Web 服务时,价格相对可接受,并提供了一些基础防护和 CDN。总的来说,我的使用体验还不错。

Error parsing Mermaid diagram!

Cannot read properties of null (reading 'getBBox')