OpenVPN 安全加固 的重点不在换更强的加密套件,而在把「谁能进、进来能到哪、证书丢了怎么办」这三件事收敛住。默认 server.conf 只保证隧道加密,客户端证书一旦从群文件或邮件里外流,攻击者拿到的就是一张内网通行证。本文按证书体系、控制通道、路由下发、防火墙白名单四层给出可直接复制的配置,命令在 Debian 12 + OpenVPN 2.6.12 环境实测通过。
适用场景
- 自建 OpenVPN 供运维与外包远程接入,客户端证书曾在聊天群、网盘或邮件中流转
- 服务端只做了 CA 签发,没有启用 tls-crypt,UDP 端口可被主动探测识别为 OpenVPN
- 所有账号共用同一张证书或同一网段,一个人失陷等于整个内网失守
- 离职人员账号未及时吊销,或怀疑私钥已泄露需要立即止损
- 需要由服务端强制分流,只让客户端访问指定业务网段
前置条件
- Debian 12 或 Ubuntu 22.04,OpenVPN 2.6.x,具备 root 或 sudo 权限
- 云主机安全组已放通所用 UDP 端口(示例为 1194,建议改为高位端口)
- 已安装 easy-rsa 3.x:
apt install easy-rsa openvpn iptables-persistent - 服务端与客户端时间同步(chrony 或 systemd-timesyncd),证书有效期校验依赖系统时间
- 防火墙使用 iptables 或 nftables 之一,本文示例为 iptables
原理说明
OpenVPN 的认证是双向的:服务端向客户端出示证书,客户端也要出示由同一 CA 签发的证书,CA 是唯一的信任锚点。只要配置里出现 verify-client-cert none,客户端仅凭 CA 证书就能不提交客户端证书接入,这是自建环境最常见的失配。
控制通道保护分三档。tls-auth 用一份静态 HMAC 密钥给控制包签名,能挡住端口扫描和简单洪泛,但握手内容仍是明文可解析;tls-crypt 对整个控制通道加密,隐藏证书与 TLS 指纹,让服务端在扫描器眼里不像 OpenVPN;tls-crypt-v2 为每个客户端签发独立密钥,单个客户端泄露不会牵连全员,适合人员规模较大、需要按人吊销的场景。
权限收敛靠两层。服务端用 client-config-dir 给每个客户端下发专属路由,实现「谁只能到 10.10.20.0/24」;再叠加 FORWARD 链的显式放行、默认策略为 DROP,隧道内也遵循最小权限,而不是连上就能访问全部内网。
私钥保护同样有两道闸。客户端私钥用 passphrase 二次加密,防止配置文件被拷走后直接可用;服务端启用 crl-verify 加载吊销列表,解决离职、设备丢失后的即时失效。两者缺一,证书体系就有缺口。
还有一个常被忽视的泄露点是 DNS 与默认网关。若服务端没有下发 dhcp-option DNS,客户端连上隧道后仍用本地 DNS 解析内网域名,解析请求直接走公网,内网拓扑与业务域名会被运营商或旁路设备看得一清二楚。同理,redirect-gateway def1 决定是否把全部流量压进隧道,安全优先的场景应全量引流,只给必要业务做分流例外。
操作步骤
第一步:收敛服务端基础配置
先备份原文件,再整体替换为下面的配置。关键项:tls-crypt 强制启用、verify-client-cert require、crl-verify 生效、user/group nobody 降权、tls-version-min 1.2。
cp /etc/openvpn/server/server.conf /etc/openvpn/server/server.conf.bak
cat > /etc/openvpn/server/server.conf <<'EOF'
port 1194
proto udp
dev tun
ca /etc/openvpn/server/ca.crt
cert /etc/openvpn/server/server.crt
key /etc/openvpn/server/server.key
dh /etc/openvpn/server/dh.pem
tls-crypt /etc/openvpn/server/ta.key
crl-verify /etc/openvpn/server/crl.pem
verify-client-cert require
remote-cert-tls client
tls-version-min 1.2
data-ciphers AES-256-GCM:AES-128-GCM
auth SHA256
server 10.8.0.0 255.255.255.0
client-config-dir /etc/openvpn/server/ccd
topology subnet
keepalive 10 60
persist-key
persist-tun
user nobody
group nogroup
explicit-exit-notify 1
status /var/log/openvpn-status.log 30
verb 3
EOF
其中 duplicate-cn 是否保留取决于业务:一人多设备需要它,但会削弱「一证一人」的追责能力,安全优先的团队应关闭它,改为按设备签发证书。
第二步:用 easy-rsa 重建 PKI 并按人签发
make-cadir /etc/openvpn/easy-rsa
cd /etc/openvpn/easy-rsa
./easyrsa init-pki
./easyrsa build-ca nopass
./easyrsa gen-req server nopass
./easyrsa sign-req server server
./easyrsa gen-dh
./easyrsa gen-crl
openvpn --genkey secret ta.key
# 每个使用者一张证书,CN 即身份标识
./easyrsa build-client-full ops_zhang nopass
./easyrsa build-client-full ops_li nopass
# 交付给客户端的文件只有这四份
mkdir -p /tmp/ops_zhang
cp pki/ca.crt pki/issued/ops_zhang.crt pki/private/ops_zhang.key ta.key /tmp/ops_zhang/
若客户端设备可能丢失,把 build-client-full 的 nopass 去掉,改为签发带 passphrase 的私钥;也可在签发后用 openssl rsa -in key -aes256 -out key.enc 补加密。私钥一旦离开服务端,只通过加密渠道分发,不要走聊天工具。
第三步:吊销与 CRL 热更新
cd /etc/openvpn/easy-rsa
./easyrsa revoke ops_li
./easyrsa gen-crl
cp pki/crl.pem /etc/openvpn/server/crl.pem
chmod 644 /etc/openvpn/server/crl.pem
systemctl restart openvpn-server@server
CRL 不是实时生效的:OpenVPN 在启动时加载 crl.pem,吊销后必须重新生成并重载服务,否则被吊销的证书仍能连上。建议把这几行做成脚本,挂到人员离职流程里执行,而不是靠人记住。
第四步:按人分流,限制可达网段
mkdir -p /etc/openvpn/server/ccd
cat > /etc/openvpn/server/ccd/ops_zhang <<'EOF'
ifconfig-push 10.8.0.11 255.255.255.0
push "route 10.10.20.0 255.255.255.0"
iroute 10.10.20.0 255.255.255.0
EOF
chown -R nobody:nogroup /etc/openvpn/server/ccd
chmod 750 /etc/openvpn/server/ccd
CCD 文件名必须与客户端证书 CN 完全一致,且目录需被降权后的 nobody 用户可读,否则服务端会静默跳过该配置。若启用了 client-to-client,请确认业务不需要跨客户端互访,否则一台失陷主机可以直接横向扫其它客户端。
第五步:防火墙只放行必要流量
iptables -A INPUT -p udp --dport 1194 -m state --state NEW -j ACCEPT
iptables -A FORWARD -i tun0 -o eth0 -s 10.8.0.0/24 -d 10.10.20.0/24 -j ACCEPT
iptables -A FORWARD -i tun0 -j DROP
iptables -P FORWARD DROP
netfilter-persistent save
FORWARD 链的顺序决定结果:先放行目标网段,再显式 DROP tun0 的其它流量,最后把默认策略设为 DROP。不要在 INPUT 链上对 1194 做全量限速,UDP 重传会让正常连接一起抖动,限速应放在 conntrack 或应用层处理。
配置验证
# 1. 配置语法与服务状态
systemctl status openvpn-server@server --no-pager
journalctl -u openvpn-server@server -n 30 --no-pager
# 2. 确认关键指令已加载
grep -E "tls-crypt|verify-client-cert|crl-verify" /etc/openvpn/server/server.conf
# 3. 确认 CRL 中的吊销条目数
openssl crl -in /etc/openvpn/server/crl.pem -noout -text | grep -c "Serial Number"
# 4. 客户端连上后核对路由与地址
ip route | grep tun0
ip -4 addr show tun0
验证的核心是三条负面用例:不带客户端证书时握手必须失败;使用已吊销证书必须被 CRL 拒绝;访问非授权网段必须被 FORWARD 的 DROP 命中。日志里出现 VERIFY ERROR 与 certificate revoked 才算真正生效。
常见问题
Q1:启用 tls-crypt 后老客户端全部连不上怎么办?
tls-crypt 要求客户端与服务端使用同一份 ta.key,老配置往往是 tls-auth 且方向参数不同,直接改生产配置会导致全员掉线。稳妥做法是并行过渡:先保留旧实例与旧端口,把 ta.key 分发下去,客户端配置改为 tls-crypt ta.key,全部切换完成后再关旧实例。
Q2:客户端显示 connected,但访问不了内网业务?
先看服务端是否真的下发了路由:客户端 ip route 里应出现 10.10.20.0/24 via tun0。若没有,检查 CCD 文件名与证书 CN 是否一致、目录权限是否允许 nobody 读取。若路由存在但仍不通,问题通常在 FORWARD 链被默认 DROP 拦住,用 iptables -vnL FORWARD 看计数器是否增长,并确认内网侧存在回程路由。
Q3:如何确认某个客户端证书已经彻底失效?
三步确认:openssl crl -text 中能找到该序列号;重启服务后该客户端连接被拒并记录 certificate revoked;用旧证书在测试机实测握手失败。只做 revoke 而不重新生成 CRL 并重载服务,证书依旧可用,这是最容易漏的一步。
总结
OpenVPN 的加固可以概括成四句话:控制通道用 tls-crypt 隐藏指纹,客户端证书强制双向校验,按人分流把可达范围压到最小,吊销流程接进人员变动。四层里任何一层缺失,其余三层的强度都会被拉低。配置完成后建议每季度跑一次完整验证,尤其要演练一次「证书吊销 + 服务重载」的全链路,别等到真出事才发现 CRL 是三个月前的文件。