使用 Strongswan 构建基于帐号密码及用户组认证的 IKEv2 VPN

Posted by

on

前言

iEdon-Net: 2024 年 1 月架构图

前两篇文章中陆续完善了内网,现在想要构建一个 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

参考

  1. https://world.203.jp/blog/post/418, OWNER203, strongSwanでLet’s Encrypt証明書を使ったIKEv2 VPNサーバーを作る
  2. 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 値

6 responses to “使用 Strongswan 构建基于帐号密码及用户组认证的 IKEv2 VPN”

  1. 彻底关闭大陆区域的访问 一下子就冷清了

    1. 是这样子的!🤣

      1. 邮件通知都下了吗😑

        1. 是这样子的!😤

          1. 那可太不方便了😒

Leave a Reply

Your email address will not be published. Required fields are marked *