前言
在前两篇文章中陆续完善了内网,现在想要构建一个 VPN,利用既有的 FreeRADIUS 作为认证服务器。
目的是在外面接入上图某一个节点,就可以通达内网全网。
在自由净土,VPN 可以随便搭建。本次选用了时下在 macOS, iOS, Windows, (Android: 需要支持 EAP) 下面无需任何客户端,系统内建支援的 IKEv2 协议。
为什么选用 IKEv2 & 需求?
- 个人私用及小范围朋友共享的情景下,分发证书和管理较为麻烦
- 想要使用简单的帐号密码进行认证,但仍然需要一定的安全性(客户端认证服务端,服务端依靠帐号密码认证客户端)
- 想要和利用几篇文章中部署的 FreeRADIUS 和 OpenLDAP 服务器进行集成,实行统一认证
- 想要对用户进行分组,例如:不在 “vpnuser” 组的用户没有 VPN 拨入权限
- 服务地不在某个不可言及之国,因而目的不是代理上网,而是内网接入,故不需要各种奇怪的混淆、伪装,发包手段
- 如前言所及,大部分主流系统内置了支持,不需要安装客户端,也无需分发客户端证书
部署
软件包的安装
# 安装 Strongswan 套件包和附加组件 # eap-radius 模块包含在 "libcharon-extra-plugins" 中, # "chapoly(chacha poly)" 等模块包含在 "libstrongswan-extra-plugins" 中 apt update && apt upgrade apt install strongswan libcharon-extra-plugins libstrongswan-extra-plugins
内核转发的配置
# 打开内核转发及做一些安全设置 echo "net.ipv4.ip_forward = 1" | tee -a /etc/sysctl.conf echo "net.ipv4.conf.all.accept_redirects = 0" | tee -a /etc/sysctl.conf echo "net.ipv4.conf.all.send_redirects = 0" | tee -a /etc/sysctl.conf echo "net.ipv4.conf.default.rp_filter = 0" | tee -a /etc/sysctl.conf echo "net.ipv6.conf.all.forwarding = 1" | tee -a /etc/sysctl.conf echo "net.ipv4.conf.default.accept_source_route = 0" | tee -a /etc/sysctl.conf echo "net.ipv4.conf.default.send_redirects = 0" | tee -a /etc/sysctl.conf echo "net.ipv4.icmp_ignore_bogus_error_responses = 1" | tee -a /etc/sysctl.conf sysctl -p
IPSec 的设定
然后我们对 “/etc/strongswan.d/charon/eap-radius.conf” 进行配置。
eap-radius { #... accounting = no nas_identifier = JapanVPN port = 1812 server = 0.0.0.0 secret = _________ #... }
关闭 “/etc/strongswan.d/charon” 目录中所有 “eap-*.conf” 里的 load 开关(load = no)。
接下来,配置 “/etc/ipsec.conf”。
config setup strictcrlpolicy=yes uniqueids=no conn ikev2_vpn auto=add compress=yes type=tunnel keyexchange=ikev2 fragmentation=yes forceencaps=no # 不论 yes 还是 no,此处在 IPv6 NAT 情况下有坑,参考本文后面小节 ike=chacha20poly1305-sha512-curve25519-prfsha512,chacha20poly1305-prfsha512-curve25519,aes256-sha256-ecp384,aes256-sha256-modp4096,aes256-sha256-modp3072 esp=chacha20poly1305-sha512-curve25519-prfsha512,chacha20poly1305-prfsha512-curve25519,chacha20poly1305-curve25519,chacha20poly1305-sha512,aes256gcm16-ecp384,aes256gcm16-sha256 dpdaction=clear dpddelay=120s rekey=yes [email protected] left=%any leftcert=fullchain.pem leftsendcert=always leftsubnet=0.0.0.0/0,::/0 right=%any rightid=%any rightauth=eap-radius eap_identity=%any rightdns=1.1.1.1,fd00::1 rightsourceip=100.64.0.0/24,fd00:1::/80 rightsendcert=never
配置 “/etc/ipsec.secret”。在该文件中,指定 RSA 证书私钥文件。
# This file holds shared secrets or RSA private keys for authentication. # RSA private key for this host, authenticating it to any other host # which knows the public part. : RSA "privkey.pem"
证书的放置与 IPSec 重启
配合 Let’s Encrypt 等平台,实现证书的下发与自动重新加载。
- 当前情况下,除了 strongSwan 客户端经测试可以使用 ECC 证书, iOS 未通过测试
- Wildcard 证书未通过测试
- 需要服务器域名在 “CN” 字段里
- 建议依然使用 RSA 保证兼容性
使用 Let’s Encrypt 的情况下,需要额外补充一个交叉签名的根证书:
wget https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem -O /etc/ipsec.d/cacerts/lets-encrypt-x3-cross-signed.pem
按照自己证书分发方案/ certbot / acme.sh 等环境,配置 reload 脚本用来将新的证书安放到 ipsec 的目录中:
#!/bin/bash # reload.sh cp -f /data/microservices/certs-sync/certs/fullchain.pem /etc/ipsec.d/certs/fullchain.pem cp -f /data/microservices/certs-sync/certs/ca.crt /etc/ipsec.d/cacerts/idn_ca.pem cp -f /data/microservices/certs-sync/certs/privkey.pem /etc/ipsec.d/private/privkey.pem ipsec restart exit 0
为什么使用 “ipsec restart”
目前经过测试,”ipsec reload”, “ipsec rereadall”均不能重新加载包括根证书或新证书在内的需求。
虽然 restart 会使客户端可能性地断开连接,但 3 个月(证书签发周期)一次的频率尚属可以接受。此部分值得后续展开深入研究。
配置 NAT 与 OSPF
iptables 规则
喜闻乐见的 iptables 配置部分。需要注意的是内网访问按需 NAT。
例如在本案例,服务端分发了内网的 IPv6,因此访问内网 IPv6 的情况下,不需要进行 IPv6 的 NAT。
#!/bin/bash # /data/iptables/nat.sh ########################## COMMON ########################## PUB_NET_IF="eth0" PRIV_NET_CIDRS=("172.23.91.0/25" "172.23.91.128/26" "10.127.21.0/24" "10.127.25.0/24") PRIV_NET6_CIDRS=("fd42:4242:2189::/48") LOCAL_IP_BLOCKS=("172.16.0.0/12" "10.0.0.0/8" "192.168.0.0/16" "100.64.0.0/10") LOCAL_IP6_BLOCKS=("fd00::/8" "fe80::/10") ############################################################ ### Private network NAT to public, and MTU Clamping # IPv4 iptables -t nat -A POSTROUTING -s 100.64.0.0/24 -o $PUB_NET_IF -j MASQUERADE iptables -A FORWARD -s 100.64.0.0/24 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu iptables -A FORWARD -d 100.64.0.0/24 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu # IPv6 for net6 in ${PRIV_NET6_CIDRS[@]}; do # 访问内网 IPv6 不进行 NAT for local6 in ${LOCAL_IP6_BLOCKS[@]}; do ip6tables -t nat -A POSTROUTING -s $net6 -d $local6 -o $PUB_NET_IF -j RETURN done # 其余 IPv6 访问进行 NAT ip6tables -t nat -A POSTROUTING -s $net6 -o $PUB_NET_IF -j MASQUERADE ip6tables -A FORWARD -s $net6 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu ip6tables -A FORWARD -d $net6 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu done
配置 NAT 脚本的开机自启(systemd service):
[Unit] Description=Firewall NAT Runonce After=local-fs.target After=network.target [Service] ExecStart=/data/iptables/nat.sh RemainAfterExit=true Type=oneshot [Install] WantedBy=multi-user.target
OSPF 用 Dummy 接口
#!/bin/bash ip link del dev dummy-vpn ip link add dev dummy-vpn type dummy ip addr add fd00:1::/80 dev dummy-vpn ip link set dummy-vpn up exit 0
在 OSPF Daemon / OSPF 配置文件中,对该接口的前缀进行 redistribute 就能让上游设备知道 VPN 网段的路由指向信息了。
同样,开机自启的 systemd service 文件参考上一小节。
FreeRADIUS 的配置
在前几篇博文中,我们仅简单的利用 “users”(/etc/freeradius/3.0/users) 进行了默认用户组的鉴定,这不是一个很好的实践。现在我们将 “users” 中的更改全部撤销。
更新 “/etc/freeradius/3.0/mods-enabled/ldap”:
# ... ldap { # ... user_dn = "LDAP-UserDn" # ... group { # ... membership_filter = "(|(member=%{control:${..user_dn}})(memberUid=%{%{Stripped-User-Name}:-%{User-Name}}))" #membership_attribute = 'memberOf' # 注: 当前我们的 LDAP 服务器尚未配置 memberOf 支持,当然配置后可以在组查询的时候获得更好的性能,故该行暂且注释掉 # ... } # ... } # ...
更新 “/etc/freeradius/3.0/mods-enabled/eap”:
# ... peap { # ... # 这一行至关重要,如果不开启的话,下文的配置中, "NAS-Port-Type" 将无法正确取值。因为此时的认证已经套了一层娃,必须要将外部的请求信息拷贝到内部。 copy_request_to_tunnel = yes # ... } #...
更新 “/etc/freeradius/3.0/sites-enabled/inner-tunnel”:
# ... post-auth { # ... ldap if (LDAP-Group == "wifiuser" && NAS-Port-Type == "Wireless-802.11") { # Return Default VLAN Info if not set in LDAP # 前文的默认 VLAN 信息,当 LDAP 服务器没有给出明确的用户 VLAN 信息时,自动填充下面的 VLAN 信息。 update reply { Tunnel-Type = "VLAN" Tunnel-Medium-Type = "IEEE-802" Tunnel-Private-Group-Id = "1" } } elsif (LDAP-Group == "vpnuser" && NAS-Port-Type == "Virtual") { noop } else { update reply { Reply-Message := "Sorry, your role is not allowed to connect." } reject } # ... } # ...
这段代码的作用是判断用户属于那个组。”wifiuser” 用户的连接方式必须是 Wi-Fi(“Wireless-802.11”),同理 “vpnuser” 的连接方式必须是虚拟隧道(”Virtual”)。
当用户不属于任何一个组时,拒绝用户拨入。
至此,我们的服务端配置就已经完成了。
IPv6 NAT 情况下的坑
在测试过程中,发现了一个问题:使用 macOS, iOS, Android + stongSwan 客户端可以正常连接并访问网络,但是 Android 原生 IKEv2 客户端与 Windows 在连接成功后,无法访问网络。具体表现为有数据包发出,但无数据包接收。
抓包后,发现 Android 原生客户端与 Windows 在连接后,无视服务端的 “forceencaps=yes” 的指令,依然发送 ESP 包,而不是在 UDP 4500 里面套娃。
可能是 Windows 和 Android 自带的客户端认为,既然服务端地址是一个 IPv6 公网 native 地址,怎么可能还 NAT?于是忽视了服务端强制指定的 UDP 4500 套娃,强行发 ESP 包,然后 ESP 包我没做 NAT,导致能连接但无法传输流量。
为什么一开始没有想到对 ESP 包做 DNAT
因为 OpenWrt 的菜单(“Port Forwards”)里 IP 上层协议只有 TCP, UDP 和 ICMP,没有 ESP 的选项。最后直接去 CLI 改配置文件,居然成了,前台也识别出来了。
为什么使用 IPv6 NAT
- 现在 IPv4 公网环境恶劣,每天有无数脚本小子扫描全网,直接暴露服务在外有一些不安(比如服务器证书告诉对面我的域名是什么)
- VPN 跑在别的 LXC 里面,并没有直接跑在 OpenWrt LXC 里。参考前几篇文章中我所说的,想要各个网络组件各司其职,不想一股脑集中在一个系统里
- 跑服务器用的 LXC 网段没有下发 IPv6 Native 地址,原因是 NTT 在办网的时候没有签约固定电话(光電話)时,不给 PD 下发内网再分发用的 IPv6 前缀
- 现有内网因为 NTT/ISP 的小气,只能使用 ND 中继,然而在我多内网的情况下,多个 ND 中继没有成功实现,只能给 LAN 内网提供中继,其余网络区域只能使用 IPv6 NAT
参考
- https://world.203.jp/blog/post/418, OWNER203, strongSwanでLet’s Encrypt証明書を使ったIKEv2 VPNサーバーを作る
- https://www.juniper.net/documentation/jp/ja/software/junos/subscriber-mgmt-sessions/topics/topic-map/radius-nas-port-id-attributes-options.html#id-manual-configuration-of-the-nas-port-type-radius-attribute__d278e59, Juniper, 表 1:RADIUS NAS-Port-Type 値
Leave a Reply