前言
本来这个文章应该今年六月份就发布的,但是种种原因,拖到了现在,期间openvpn已经重新部署、换IP等事件多次🤣。这个文章主要用于我自己记录,本人不是网络专业,对计算机网络只有基本了解(不过本人正在努力深入)。话不多说,开撸~。
问题
客户遇到服务器被对立组织攻击(主要是Ddos),导致服务器崩溃,也没有太多资金支持进行负载均衡或者其他处理,同时服务器也只需要一部分人内部使用。
故我给出两个解决方案:
A: 停止使用域名
原因有二:一是域名解析能直接获取到ip然后进行攻击,就算换了ip,还是会被攻击;二是国内域名国内使用都需备案,有心人可通过备案查询到域名,再查询到ip。(其实第二条不需要补充,但是这次就是因为在公开场合漏出了备案号导致域名和ip全泄露)。
B: 使用内网机器
原因:局域网,不必多说😁
C: vpn
原因:同时拥有外网访问和内网访问,只需一台vpn主机,这样域名解析可以一直用。解析出的效果是内网ip,其他人没办法进行访问(攻击)。
最终使用方案C
解决思路和步骤
假设机器A是需要其他客户端都访问的,机器B就代表其他所有客户端,机器0代表vpn主机。
由于openvpn的开源性,所以直接选择了,主要是成本问题💩。
失败思路
查看openvpn的文档,不难发现,openvpn本身支持多客户端多ip固定分配的配置通过 client-config-dir (CCD) 和 ifconfig-push 实现
,但前提是,你要为每个客户端分配不同的CN,这里的CN不是中国的意思,我不再多赘述,由于我不可能每个人都分配一个CN,所以淘汰这个思路。
因为本人英语也不好,基本都靠机器直译,所以到底是不是需要多CN,我也不清楚,但是我测试了基于这两种配置的多个办法,都不能实现多客户端分配固定ip和子网。
成功思路
采用openvpn更古老(也更自由)的方式分配子网,首先生成两个不同的客户端证书,采用openvpn的脚本连接模式,如果客户端连接,会先通过证书号选择是否采用固定子网ip,之后成功分配。
失败思路不代表不正确,也不代表openvpn的配置是失败的,极大可能是本人技术问题没有成功而已。
步骤
思路很简单,当时想到这个思路耗费了我一天,别问为什么😅
部署openvpn
在这里我采用docker部署,因为我觉得在本机部署如果卸载太麻烦,我个人比较喜欢干净的机器。不过事实也是docker部署更轻松便捷。
拉取docker镜像
openvpn/dockerGithub链接在这,使用docker拉取镜像,不知道为什么采用阿里云拉取会出现启动失败的问题,我建议使用docker官方镜像库,没条件的自求多福。配置docker数据卷
# 设置数据卷名称(可自定义后缀) OVPN_DATA="ovpn-data-example" docker volume create --name $OVPN_DATA
生成初始配置
# 替换VPN.SERVERNAME.COM为您的公网IP或域名 docker run -v $OVPN_DATA:/etc/openvpn --rm kylemanna/openvpn \ ovpn_genconfig -u udp://VPN.SERVERNAME.COM
初始化PKI证书体系
docker run -v $OVPN_DATA:/etc/openvpn --rm -it kylemanna/openvpn ovpn_initpki # 会提示输入CA私钥的保护密码(请牢记) # 需要等待生成Diffie-Hellman参数(可能需要1-2分钟)
启动OpenVPN容器
docker run -v $OVPN_DATA:/etc/openvpn -d -p 1194:1194/udp --cap-add=NET_ADMIN --name openvpn kylemanna/openvpn
生成客户端证书
这里生成两个,为机器A和B各生成一个,然后导出,我这里假设client1
是机器B
的证书,client2
是机器A
的证书。# 生成无密码客户端证书(client1为自定义名称) docker run -v $OVPN_DATA:/etc/openvpn --rm -it kylemanna/openvpn easyrsa build-client-full client1 nopass # 生成的客户端证书可能会丢失一个配置 ,自己手动添加上: comp-lzo no
导出客户端配置
docker run -v $OVPN_DATA:/etc/openvpn --rm kylemanna/openvpn ovpn_getclient client1 > client1.ovpn # 生成的客户端证书可能会丢失一个配置 ,自己手动添加上: comp-lzo no
找出数据卷位置,替换
openvpn.conf
文件内容# 文件内容 # --- 服务器基本配置 --- mode server tls-server # 表明是TLS服务器模式 (通常由 server 指令隐式包含) dev tun0 proto udp port 1194 key /etc/openvpn/pki/private/160.202.x.x.key ca /etc/openvpn/pki/ca.crt cert /etc/openvpn/pki/issued/160.202.x.x.crt dh /etc/openvpn/pki/dh.pem tls-auth /etc/openvpn/pki/ta.key key-direction 0 # --- 服务器VPN接口和拓扑 --- # 手动配置服务器在tun0上的IP地址和主VPN子网的掩码 ifconfig 10.50.0.1 255.255.252.0 topology subnet # 必须明确指定,因为不再使用 server 指令 # --- 普通客户端动态IP池定义 --- # 手动定义动态IP地址池的范围 (起始IP, 结束IP) # 这个范围必须在 ifconfig 定义的子网内 (10.50.0.0/22) # 可用范围是 10.50.0.2 到 10.50.3.254 ifconfig-pool 10.50.0.2 10.50.3.254 # 注意: 一些旧版OpenVPN的ifconfig-pool可能还需要第三个参数 netmask, # 但在 topology subnet 模式下,通常它会从服务器的 ifconfig 推断。 # 如果遇到问题,可以尝试 ifconfig-pool 10.50.0.2 10.50.3.254 255.255.252.0 # (可选) IP持久化,对于大量用户且不需要IP持久性,建议注释掉 # ifconfig-pool-persist /etc/openvpn/ipp.txt # --- 推送给所有客户端的配置 (手动补充 server 指令会自动处理的部分) --- # 1. 推送主VPN子网的路由,以便客户端知道如何访问这个子网内的其他(如果允许的话)对等体 push "route 10.50.0.0 255.255.252.0" # 2. 推送VPN网关 (即服务器的tun0 IP) push "route-gateway 10.50.0.1" # 3. 推送拓扑结构 push "topology subnet" # 4. 推送ping参数 (与keepalive对应) push "ping 10" push "ping-restart 60" # 客户端的ping-restart,通常是服务器keepalive的第二个参数 # 5. 推送到达目标客户端 client2 (10.10.10.2) 的路由 push "route 10.10.10.2 255.255.255.255" # 6. 推送压缩配置 push "comp-lzo no" # --- 服务器内核路由 --- # 告诉服务器内核如何到达目标客户端所在的"特殊"网络 route 10.10.10.0 255.255.255.0 10.50.0.1 # 服务器内核路由,下一跳默认是dev tun0 # 如果OpenVPN添加的路由仍然是 via <某个客户端IP> # 可以尝试 route 10.10.10.0 255.255.255.0 10.50.0.1 # --- 脚本和安全设置 --- script-security 2 client-connect /etc/openvpn/scripts/assign_target_ip.sh duplicate-cn # client-to-client # 确保已移除 # --- 其他服务器配置 --- keepalive 10 60 # 服务器端的ping参数,会影响上面 push "ping-restart" 的服务器端行为 persist-key persist-tun status /etc/openvpn/openvpn-status.log # 路径要确保OpenVPN用户可写 log /etc/openvpn/openvpn.log # 路径要确保OpenVPN用户可写 verb 4 # 调试级别 user nobody group nogroup comp-lzo no # 服务器端的压缩设置 # (可选) 如果你还用了其他 server 指令会自动处理的选项,需要手动添加 # 例如,如果之前依赖 server 指令来设置 tls-server,现在需要明确写上
创建
assign_target_ip.sh
文件
注意路径和配置一样,注意这里的路径是docker路径,也就是对应数据卷的主路径可能会不一样。
要注意其中的证书号要填正确,可以借助查询工具:在线解析证书,将机器A客户端证书client2
中的cert
内容放入解析出证书号#!/bin/bash # OpenVPN client-connect script to assign a static IP to a target client # and let other clients get dynamic IPs from the main pool. CONFIG_FILE="$1" DEBUG_LOG_FILE="/etc/openvpn/scripts/openvpn_client_connect_debug.log" # --- Script Configuration --- TARGET_CLIENT_SERIAL="xxxx" # 务必用真实的十六进制序列号 TARGET_CLIENT_VPN_IP="10.10.10.2" TARGET_CLIENT_VPN_NETMASK="255.255.255.0" # 为 client2 定义一个它这条连接看到的 "服务器端" VPN IP。 # 这个IP应该是 client2 认为的网关。 # 它可以是 10.10.10.0/24 子网中的一个未使用地址,例如 10.10.10.1。 # OpenVPN 服务器实际上不需要在 tun0 上配置这个 10.10.10.1, # 而是通过内部机制响应 client2 发往此地址的流量。 TARGET_CLIENT_ROUTE_GATEWAY="10.10.10.1" # --- End Script Configuration --- log_message() { echo "$(date '+%Y-%m-%d %H:%M:%S') - CN: '${common_name}', Serial: '${tls_serial_0:-N/A}', IP: '${trusted_ip:-N/A}' - $1" >> "${DEBUG_LOG_FILE}" } log_message "Script started. Config file: '${CONFIG_FILE}'" CURRENT_CLIENT_SERIAL="${tls_serial_0}" # 假设格式已正确 if [ "${CURRENT_CLIENT_SERIAL}" == "${TARGET_CLIENT_SERIAL}" ]; then log_message "目标客户端识别: ${common_name} (${CURRENT_CLIENT_SERIAL})。分配静态IP ${TARGET_CLIENT_VPN_IP}/${TARGET_CLIENT_VPN_NETMASK} 并设置特定网关 ${TARGET_CLIENT_ROUTE_GATEWAY}." echo "ifconfig-push ${TARGET_CLIENT_VPN_IP} ${TARGET_CLIENT_VPN_NETMASK}" > "${CONFIG_FILE}" echo "iroute ${TARGET_CLIENT_VPN_IP} 255.255.255.255" >> "${CONFIG_FILE}" # 为 client2 推送特定的路由网关,覆盖全局推送的 route-gateway echo "push \"route-gateway ${TARGET_CLIENT_ROUTE_GATEWAY}\"" >> "${CONFIG_FILE}" # (可选) 如果 client2 需要访问普通客户端所在的 10.50.0.0/22 网络 # 它需要一条路由指向那个网络,网关是它看到的服务器IP (TARGET_CLIENT_ROUTE_GATEWAY) echo "push \"route 10.50.0.0 255.255.252.0 ${TARGET_CLIENT_ROUTE_GATEWAY}\"" >> "${CONFIG_FILE}" else log_message "非目标客户端: ${common_name} (${CURRENT_CLIENT_SERIAL})。将从主IP池分配并使用全局网关。" # 对于普通客户端,它们会使用 server.conf 中全局 push 的 "route-gateway 10.50.0.1" > "${CONFIG_FILE}" fi log_message "--- Start of generated config for ${CONFIG_FILE} ---" if [ -s "${CONFIG_FILE}" ]; then sed 's/^/ /' "${CONFIG_FILE}" >> "${DEBUG_LOG_FILE}" else log_message " (Temporary config file is empty or not created)" fi log_message "--- End of generated config for ${CONFIG_FILE} ---" exit 0
至此,你只需要重启该docker容器即可完成openvpn主机配置
客户端需要安装openvpn
OpenVPN/openvpn: OpenVPN is an open source VPN daemon机器A开启
安装完成后,导入配置到
/etc/openvpn/client
使用命令
systemctl start openvpn-client@client2
开启服务使用
systemctl status openvpn-client@client2
查看状态机器A开启完毕
机器B开启只需要修改证书为client1即可。
至此,配置结束,可以实现机器B访问到机器A。
总结
这个配置看似简单,实则我丢失了将近2天的时间,来回调试,一开始忘记了配置duplicate-cn选项,然后用户那边反馈连不上,其实是被顶掉线了,我重新配置了三四次,才发现丢了个选项....。
一开始想用ccd实现分配ip,结果试了多次无果,以为是我的问题,换机器,测试,重装系统,测试,最后结合AI给的另一个思路,也就是现在实现的思路才结束战斗,其实这个思路的出现是巧合,因为AI一开始给我的思路和我的思路一样,也是ccd,后面我一直在反驳他, 最后使用"最自由"的配置方式。
最后给自己一句话,遇到问题多问AI(提醒大家,不要全信),多看文档,多提升自己的英文水平!