有关RADIUS如何部署的探究

起因

家中的WiFi很早前已被笔者分成了多个网络进行隔离,包括主网络供自己用,设备网络供智能设备用,访客网络供朋友用,测试网络供测试一些服务时使用。其中访客网路同样也是采用WAP2-PSK方式,但是一旦要换密码,应用时就会造成同AP上的其他网络中断,且也不方便利用程序定时更换。最近在考虑了一段时间后,决定采用企业级使用的RADIUS认证方式以实现需求。

分析

既然要使用RADIUS,那么必然离不开大名鼎鼎的软件FreeRADIUS,恰好笔者的主路由器是OpenWrt也支持其部署,那就没有问题只待安装配置了。接着考虑安全性和兼容性,在参考了一些文章后,最终决定采用PEAP-MSCHAPv2认证方式进行部署。至于用户名和密码,则采用最基础的文件方式进行配置,这样只要定时用代码生成对应格式的文件再重启FreeRADIUS即可达到密码轮换的目的。

处理

在AP上配置非常简单,笔者用的是dd-wrt,安全模式选择WAP2-EAP,WPA算法选择CCMP-128(AES),接着配置RADIUS服务器地址、端口、共享密钥就行。

在FreeRADIUS上配置则略显繁琐。首先在OpenWrt上安装freeradius3-defaultfreeradius3-utils两个包。接着进行配置,大部分采用默认值即可,相关涉及的配置如下:

/etc/freeradius3/radiusd.conf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
prefix = /usr
exec_prefix = /usr
sysconfdir = /etc
localstatedir = /var
sbindir = /usr/sbin
logdir = /var/log
raddbdir = /etc/freeradius3
radacctdir = /var/db/radacct
name = radiusd
confdir = ${raddbdir}
modconfdir = ${confdir}/mods-config
certdir = ${confdir}/certs
cadir = ${confdir}/certs
run_dir = ${localstatedir}/run/${name}
db_dir = ${raddbdir}
libdir = /usr/lib/freeradius3
pidfile = ${run_dir}/${name}.pid
correct_escapes = true
max_request_time = 30
cleanup_delay = 5
max_requests = 16384
hostname_lookups = no

log {
destination = files
colourise = yes
file = ${logdir}/radius.log
syslog_facility = daemon
stripped_names = no
auth = no
auth_badpass = no
auth_goodpass = no
msg_denied = "You are already logged in - access denied"
}

checkrad = ${sbindir}/checkrad

ENV {
}

security {
allow_core_dumps = no
max_attributes = 200
reject_delay = 1
status_server = yes
}

proxy_requests = no

$INCLUDE clients.conf

thread pool {
start_servers = 1
max_servers = 3
min_spare_servers = 1
max_spare_servers = 3
max_requests_per_server = 0
auto_limit_acct = no
}

modules {
$INCLUDE mods-enabled/
}

instantiate {

}

policy {
$INCLUDE policy.d/
}

$INCLUDE sites-enabled/

/etc/freeradius3/sites-enabled/default:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
server default {
listen {
type = auth
ipaddr = 172.16.xxx.1
port = 0
limit {
max_connections = 16
lifetime = 0
idle_timeout = 30
}
}
authorize {
filter_username
preprocess
eap {
ok = return
}
}
authenticate {
eap
}
post-auth {
if (session-state:User-Name && reply:User-Name && request:User-Name && (reply:User-Name == request:User-Name)) {
update reply {
&User-Name !* ANY
}
}
update {
&reply: += &session-state:
}
remove_reply_message_if_eap
Post-Auth-Type REJECT {
attr_filter.access_reject
eap
remove_reply_message_if_eap
}
}
}

/etc/freeradius3/sites-enabled/inner-tunnel:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server inner-tunnel {
authorize {
eap {
ok = return
}
files
}
authenticate {
Auth-Type MS-CHAP {
mschap
}
eap
}
}

/etc/freeradius3/clients.conf:

1
2
3
4
client ap {
ipaddr = 172.16.xxx.1/24
secret = xxxxxxxx
}

/etc/freeradius3/mods-enabled/eap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
eap {
default_eap_type = peap
timer_expire = 60
ignore_unknown_eap_types = no
cisco_accounting_username_bug = no
max_sessions = ${max_requests}
tls-config tls-common {
private_key_password = whatever
private_key_file = ${certdir}/server.pem
certificate_file = ${certdir}/server.pem
ca_file = ${cadir}/ca.pem
dh_file = ${certdir}/dh
ca_path = ${cadir}
cipher_list = "DEFAULT"
cipher_server_preference = no
tls_min_version = "1.0"
tls_max_version = "1.3"
ecdh_curve = "prime256v1"
}
peap {
tls = tls-common
default_eap_type = mschapv2
copy_request_to_tunnel = no
use_tunneled_reply = no
virtual_server = "inner-tunnel"
}
mschapv2 {
}
}

/etc/freeradius3/mods-enabled/files:

1
2
Guest Cleartext-Password := "TEST"
Reply-Message := "Hello, %{User-Name}"

在目录/etc/freeradius3/mods-enabled保留如下文件:

1
2
3
4
5
6
always
attr_filter
eap
files
mschap
preprocess

配置成功后,用Python写了段代码,用于定时生成对应格式的文件,并重启FreeRADIUS服务。主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
try:
file_path = "/etc/freeradius3/mods-config/files/authorize"
with open(file_path, "w") as f:
f.truncate(0)
f.seek(0)
today = datetime.date.today()
username = "USERNAME"
password = "PASSWORD{}".format(today.strftime("%m%d"))
f.write('{username} Cleartext-Password := "{password}"\n'.format(username=username, password=password))
f.write(' Reply-Message := "Hello, %{User-Name}"\n')
for i in range(7):
username = "USERNAME-{}".format((today + datetime.timedelta(days=-i)).strftime("%m%d"))
password = "PASSWORD-{}".format((today + datetime.timedelta(days=-i)).strftime("%m%d"))
f.write('{username} Cleartext-Password := "{password}"\n'.format(username=username, password=password))
f.write(' Reply-Message := "Hello, %{User-Name}"\n')
subprocess.run("/etc/init.d/radiusd restart", shell=True, check=True)
_LOGGER.info("success")
except Exception as e:
_LOGGER.exception(e)

其密码更换策略目前为主密码一天一换,次密码保留一周,一周后过期。

结果

笔者手头上有的安卓手机(魅族)、iPad、Win10笔记本均成功的以该种方式连上了WiFi,证明配置基本完成。其中安卓手机选择添加网络后,再选择802.x EAP,EAP方法选择PEAP,阶段二身份验证选择MSCHAPV2,输入密码和身份即可连接。而iPad添加网络时安全性选择WPA2企业级,然后输入用户名、密码就行。针对Win10笔记本,配置就略显繁琐,具体的可以参见参考一节的链接文章,按详细步骤操作即可。

尾声

查阅相关资料发现,虽然在2020年6月二维码最著名的zxing库已经添加了该种方式的WiFi二维码格式,但是在2023年12月发布的WPA3 3.2规范第7章中并没有相关说明,所以各设备厂商对其的支持有待考证。就笔者手头上的几台旧设备而言,并不支持直接生成对应的WiFi二维码,且扫描按格式生成的二维码时,也不能成功连接对应的WiFi。

笔者手上的部分设备较老,故TLS最低版本只能配到1.0,即tls_min_version = "1.0",虽然在日志中看到了警告,但也没有别的办法了。

参考

  1. Windows 10 Wireless Setup
  2. zxing的Wi-Fi Network config (Android, iOS 11+)
  3. WPA3 Specification v3.2.pdf