Install FreeRadius On Debian 10

Posted on 2020-11-21 in how-to

本文假定系统为全新安装的 DEBIAN 10,正常安装DEBIAN,开启SSH,SUDO功能。 通过安装 FreeRadius 和 PostgreSQL,支持常规的安装。 本安装教程最主要的功能在于支持 H3C 的 802.1x,并且限制用户允许登录访问的设备数量。 TODO: 对客户端启用证书的功能待下次测试并完善

一、安装

# apt install freeradius freeradius-postgresql postgresql postgresql-client

二、配置 PostgreSQL

参考链接: https://wiki.freeradius.org/guide/SQL-HOWTO-for-freeradius-3.x-on-Debian-Ubuntu

切换至 postgres 用户,执行 psql

$ sudo -i -u postgres
$ psql

通过 psql 创建数据库角色与数据库

>> CREATE ROLE radius LOGIN PASSWORD 'radpass';
>> CREATE DATABASE radius WITH owner = radius;

退出 postgres 用户,回到 recozo 用户环境

# cd /etc/freeradius/3.0/mods-config/sql/main/postgresql
# psql -h localhost -U radius radius < schema.sql
# psql -h localhost -U radius radius < setup.sql      -- 如果执行了 CREATE ROLE 可以不执行

三、配置 FreeRadius 3 启用 sql

参考链接: https://networkradius.com/doc/3.0.10/raddb/mods-available/eap/peap.html

# vi /etc/freeradius/3.0/sites-available/default
--------------------EDIT--------------------
    uncomment sql in authorize, accounting, session, post-auth section
--------------------------------------------

# vi /etc/freeradius/3.0/sites-available/inner-tunnel
--------------------EDIT--------------------
    uncomment sql in authorize, session, post-auth section
--------------------------------------------

# vi /etc/freeradius/3.0/mods-available/sql
--------------------EDIT--------------------
sql {
    driver = "rlm_sql_postgresql"
    dialect = "postgresql"

    # Connection info:
    server = "localhost"
    port = 5432
    login = "radius"
    password = "setup.sql中设置的口令"

    # Database table configuration for everything except Oracle

    radius_db = "radius"

    # Set to ‘yes’ to read radius clients from the database (‘nas’ table)
    # Clients will ONLY be read on server startup.
    read_clients = yes

    # Table to keep radius client info
    client_table = "nas"
--------------------------------------------

# cd /etc/freeradius/3.0/mods-enabled
# ln -s ../mods-available/sql sql
# chown -h freerad:freerad /etc/freeradius/3.0/mods-enabled/sql
# cd /etc/freeradius/3.0/mods-config/sql/main/postgresql

# vi rczmacsetup.sql
--------------------ADD---------------------
/*
* Table structure for table 'rczmaclimit'
*/
CREATE TABLE rczmaclimit (
        UserName                text PRIMARY KEY,
        MacLimit                integer NOT NULL DEFAULT 1,  -- 0 Unlimited
        Remark                  text NOT NULL DEFAULT ''
);
GRANT SELECT on rczmaclimit TO radius;

/*
* Table structure for table 'rczmaccheck'
*/
CREATE TABLE rczmaccheck (
        id                      serial PRIMARY KEY,
        UserName                text NOT NULL,
        CallingStationId        text NOT NULL,
        AuthDate                timestamp with time zone NOT NULL default now()
);
create index rczmaccheck_UserName on rczmaccheck (UserName,CallingStationId);
GRANT SELECT, INSERT, UPDATE on rczmaccheck TO radius;
GRANT USAGE, SELECT ON SEQUENCE rczmaccheck_id_seq TO radius;


/*
* Stored Procedure for authorize_check_query
*/
CREATE OR REPLACE FUNCTION rczmac_authorize_check(rczuserName TEXT, rczcallingStationId TEXT)
RETURNS TABLE (id integer, UserName text, Attribute text, Value text, Op varchar(2))
LANGUAGE plpgsql
AS $$
DECLARE
    isOk        boolean DEFAULT false;
    tmpMacLimit    integer;
    tmpMacTotal    integer;
BEGIN
    IF EXISTS(
        SELECT *
        FROM rczmaccheck
        WHERE rczmaccheck.UserName = rczmac_authorize_check.rczuserName
        AND rczmaccheck.CallingStationId = rczmac_authorize_check.rczcallingStationId
        )
    THEN
        RAISE LOG '用户: % MAC: % ,MAC已登记', rczmac_authorize_check.rczuserName, rczmac_authorize_check.rczcallingStationId;
        isOk = true;
    ELSE
        SELECT MacLimit INTO tmpMacLimit
        FROM rczmaclimit
        WHERE rczmaclimit.UserName = rczmac_authorize_check.rczuserName;

        IF NOT FOUND THEN
            RAISE LOG '用户: % MAC: % ,MAC上限无记录', rczmac_authorize_check.rczuserName, rczmac_authorize_check.rczcallingStationId;
            tmpMacLimit = 1;
        END IF;

        IF tmpMacLimit = 0 THEN
            RAISE LOG '用户: % MAC: % ,MAC上限为0', rczmac_authorize_check.rczuserName, rczmac_authorize_check.rczcallingStationId;
            isOk = true;
        ELSE
            SELECT COUNT(*) INTO tmpMacTotal
            FROM rczmaccheck
            WHERE rczmaccheck.UserName = rczmac_authorize_check.rczuserName;

            IF tmpMacLimit > tmpMacTotal THEN
                RAISE LOG '用户: % MAC: % ,MAC未登记且未达到上限', rczmac_authorize_check.rczuserName, rczmac_authorize_check.rczcallingStationId;
                isOk = true;
            ELSE
                RAISE LOG '用户: % MAC: % ,MAC达到上限禁止登录', rczmac_authorize_check.rczuserName, rczmac_authorize_check.rczcallingStationId;
                isOk = false;
            END IF;
        END IF;
    END IF;

    RAISE LOG '用户: % 的MAC限制检查结果为: %', rczmac_authorize_check.rczuserName, isOk;

    RETURN QUERY
    -- 如果 isOk 不通过,不返回用户检查项
    SELECT radcheck.id, radcheck.UserName, radcheck.Attribute, radcheck.Value, radcheck.Op
    FROM radcheck
    WHERE radcheck.UserName = rczmac_authorize_check.rczuserName AND isOk
    ORDER BY id;
END;
$$;

/*
* Stored Procedure for post-auth-query
*/
CREATE OR REPLACE PROCEDURE rczmac_post_auth(rczuserName TEXT, rczPass TEXT, rczReply TEXT, rczcallingStationId TEXT)
LANGUAGE plpgsql
AS $$
DECLARE
    tmpAuthDate     timestamp DEFAULT now();
BEGIN
    INSERT INTO radpostauth(username, pass, reply, CallingStationId, authdate)
    VALUES (rczuserName, rczPass, rczReply, rczcallingStationId, tmpAuthDate);

    IF rczReply = 'Access-Accept' THEN
        IF EXISTS(
            SELECT *
            FROM rczmaccheck
            WHERE rczmaccheck.UserName = rczmac_post_auth.rczuserName
            AND rczmaccheck.CallingStationId = rczmac_post_auth.rczcallingStationId
            )
        THEN
            RAISE LOG '用户: % MAC: % ,MAC已存在', rczmac_post_auth.rczuserName, rczmac_post_auth.rczcallingStationId;
        ELSE
            RAISE LOG '用户: % MAC: % ,MAC已新增', rczmac_post_auth.rczuserName, rczmac_post_auth.rczcallingStationId;

            INSERT INTO rczmaccheck(UserName, CallingStationId, AuthDate)
            VALUES (rczuserName, rczcallingStationId, tmpAuthDate);
        END IF;
    END IF;
END;
$$;
--------------------------------------------

# psql -h localhost -U radius radius < rczmacsetup.sql

# vi /etc/freeradius/3.0/mods-config/sql/main/postgresql/queries.conf
--------------------EDIT--------------------
#  Authorization Queries
authorize_check_query = "\
    SELECT * FROM rczmac_authorize_check( \
        '%{User-Name}', \
        '%{Calling-Station-Id}')"

# postauth_query                - Insert some info after authentication
post-auth {
    query = "CALL rczmac_post_auth( \
                '%{User-Name}', \
                '%{%{User-Password}:-Chap-Password}', \
                '%{reply:Packet-Type}', \
                '%{Calling-Station-Id}')"
--------------------------------------------

# vi /etc/freeradius/3.0/sites-available/inner-tunnel
--------------------ADD---------------------
#
#  Look in an SQL database.  The schema of the database
#  is meant to mirror the "users" file.
#
#  See "Authorization Queries" in sql.conf
update request {
        &Calling-Station-Id := outer.request:Calling-Station-Id
}
sql
--------------------------------------------

# systemctl enable freeradius
# systemctl restart freeradius

四、验证SQL是否安装正确(可选)

开二个终端,分别运行 FreeRadius 和 测试

终端一

# systemctl stop freeradius
# freeradius -X

终端二

# psql -h localhost -U postgres radius
radius=# insert into nas (nasname, shortname, secret, description) values ('xxx.xxx.xxx.xxx', 'Short Name', 'secure secret', 'description');        -- 加入要允许访问的NAS设备信息
radius=# insert into radcheck (username,attribute,op,value) values('username', 'Cleartext-Password', ':=', 'password');     -- 加入要允许访问的用户信息
radius=# \q
# radtest username password localhost 0 testing123

五、生成 eapol_test 工具并验证 eap (可选)

开二个终端,分别运行 FreeRadius 和 测试

终端一

# systemctl stop freeradius
# freeradius -X

终端二首先生成 eapol_test

$ sudo apt install pkg-config
$ sudo apt install build-essential
$ sudo apt install libssl-dev
$ sudo apt install libnl-genl-3-dev
$ sudo apt install libdbus-1-dev
$ wget https://w1.fi/releases/wpa_supplicant-2.9.tar.gz
$ tar -zxf wpa_supplicant-2.9.tar.gz
$ cd wpa_supplicant-2.9/wpa_supplicant/
$ cp defconfig .config
$ vi .config
-------------------EDIT---------------------
    CONFIG_EAPOL_TEST=y
--------------------------------------------
$ make eapol_test
/* 在全新安装的 Debian 11 中编译出错,原来在 Debian 10 下没有出错 */
/* https://patchwork.ozlabs.org/project/hostap/patch/20191018020419.28686-2-masashi.honma@gmail.com/ */
/* 2022-01-05 */
$ mkdir ~/bin
$ cp eapol_test ~/bin

继续在终端二测试 eap 功能

$ cd ~/bin
$ vi eap-ttls-pap.conf
-------------------ADD----------------------
network={
    key_mgmt=WPA-EAP
    eap=TTLS
    identity="username"
    anonymous_identity="anonymous"
    password="password"
    phase2="auth=PAP"
}
--------------------------------------------
$ ./eapol_test -c eap-ttls-pap.conf -s testing123

六、生成 eap 证书(可选,但是强烈建议)

参考链接: https://networkradius.com/doc/FreeRADIUS-Implementation-Ch6.pdf

参考链接: http://deployingradius.com/documents/configuration/certificates.html

一般来说,802.1X 下建议使用自己的CA,生成自签名证书并将根证书安装在用户终端上。 如果用户终端不安装根证书或使用第三方证书的话,存在以下安全隐患: 1、不安装根证书时,存在伪造服务器的可能(创建一个同名SSID的无线,使用伪造的radius服务器),导致用户名与密码泄露; 2、如果启用 EAP-TLS 并使用第三方根证书的话,此时只要是该机构签发的用户证书均有效,导致用户证书不受控。 一般情况下这不是我们想要的结果;因此,如果要使用其它CA的证书,请仔细考虑清楚。

注意:debian 下的 freeradius 会自动对 EAP 使用 ssl-cert 所生成的 ssl-cert-snakeoil 证书。 该证书是基于debian的自签名证书,主要用于方便需要创建SSL证书的软件包安装, 如果 snakeoil 证书过期,可以使用以下命令重新生成 sudo make-ssl-cert generate-default-snakeoil --force-overwrite , 也就是不用进行证书生成操作,也可以保证 freeradius 支持 EAP。 不过在生产环境下不应该使用该证书,而应根据实际生成或使用对应的证书。 测试中发现在 debian 环境下的 freeradius -X 不会自动生成证书(这点似乎与freeradius 官方的说明不一致)。

删除原来生成的证书(生产环境下慎用)

# cd /etc/freeradius/3.0/certs
# rm -f *.pem *.der *.csr *.crt *.key *.p12 serial* index.txt*

生成根证书

# vi /etc/freeradius/3.0/certs/ca.cnf
--------------------EDIT--------------------
[ CA_default ]
default_days            = 3600

[ req ]
input_password = whateverCA
output_password = whateverCA

[certificate_authority]
countryName             = CN
stateOrProvinceName     = Jiangxi
localityName            = Pingxiang
organizationName        = Organization Name
emailAddress            = radius@example.com
commonName              = "XXXXXX Certificate Authority"
--------------------------------------------

# make ca.pem
# make ca.der

生成服务器证书(countryName, stateOrProvinceName, localityName 要与根证书一致??)

# vi /etc/freeradius/3.0/certs/server.cnf
--------------------EDIT--------------------
[ CA_default ]
default_days            = 3600

[ req ]
input_password = whateverSVR
output_password = whateverSVR

[server]
countryName             = CN
stateOrProvinceName     = Jiangxi
localityName            = Pingxiang
organizationName        = Organization Name
emailAddress            = radius@example.com
commonName              = "XXXXXX Server Certificate"
--------------------------------------------

# make server.pem

修改 eap 配置以使用新创建的证书

# vi /etc/freeradius/3.0/mods-available/eap
--------------------EDIT--------------------
tls-config tls-common {
    private_key_password = whateverSVR
    private_key_file = ${cadir}/server.key
    certificate_file = ${cadir}/server.pem
    ca_file = ${cadir}/ca.pem
}
--------------------------------------------

# chown freerad:freerad server.key
# chown freerad:freerad server.pem
# chown freerad:freerad ca.pem

# systemctl restart freeradius

七、在 H3C 设备上配置 Radius 并启用(无线)802.1x

八、配置终端的 802.1x 访问

参考链接: https://schoolsysadmin.blogspot.com/2016/03/freeradius-production-ssl-certificates.html

将根CA文件(ca.der)公开,方便用户在终端设备上导入;

windows 下配置无线