The following guide demonstrates a way to use FreeRADIUS with OpenWRT in order to keep track of connecting wireless clients. Using FreeRADIUS will enable the user to accept or reject connecting clients based on their MAC address.
Even though it is common to say that MAC filtering does not provide any security, there is not much to that saying that would be rationally true. Considering the amount of bytes permitted in a MAC address, the likelihood of someone randomly guessing a full MAC address in order to connect to the network is negligible. Perhaps, the only validity to claims are due to the fact that hardware MAC addresses can be faked, but that has no bearing onto accessing a network illicitly without any knowledge of what MAC addresses are permitted within the network.
In order to perform MAC address accounting in OpenWRT, the only tool that OpenWRT provides is to add the MAC addresses to the available wireless radios using a drop-down list. With that being said, the FreeRADIUS setup described in this section should provide a robust and easy way to manage wireless connecting clients and to keep track of the clients separately rather than using the built-in OpenWRT features. Keeping the MAC accounting separate from OpenWRT makes sense, mainly in terms of separation of concerns and in particular because whilst the radios might change, the connecting clients will still be the same.
On a vanilla installed, if the RADIUS options are used in hostapd, the following errors will be returned:
Line 46: unknown configuration item 'auth_server_addr' Line 47: unknown configuration item 'auth_server_port' Line 48: unknown configuration item 'auth_server_shared_secret'
In order to fix this issue, the full wpad
package must be installed. On some platforms that use an embedded TLS library, the wpad-basic-mbedtls
package should be removed in favor of the wpad-mbedtls
package:
opkg update
opkg remove wpad-basic-mbedtls
opkg install wpad-mbedtls
To check which package is installed, the wpad
package should be listed amongst the installed packages:
opkg list | grep wpad
Now, the RADIUS options can be added, either by using UCI or manually. The options can be set manually using the hostapd_options
configuration key. Here is a full configuration from /etc/config/wireless
:
config wifi-device 'radio1' option type 'mac80211' option band '2g' # ... list hostapd_options 'auth_server_addr=192.168.1.5' list hostapd_options 'auth_server_port=1812' list hostapd_options 'auth_server_shared_secret=testing123' list hostapd_options 'macaddr_acl=2'
where:
auth_server_addr
, auth_server_port
, auth_server_shared_secret
are the RADIUS options
For the weary, hostapd_options
is defined as a list of options, as can be observed above, such that the uci
command-line tool has to be invoked multiple times in order for the options to be added to the OpenWRT configuration:
uci add_list wireless.radio0.hostapd_options='auth_server_addr=192.168.1.5' uci add_list wireless.radio0.hostapd_options='auth_server_port=1812' uci add_list wireless.radio0.hostapd_options='auth_server_shared_secret=testing123' uci add_list wireless.radio0.hostapd_options='macaddr_acl=2'
With this set up, there is not more need to mess with OpenWRT and the rest of the configuration involves setting up FreeRADIUS.
It is possible to install FreeRADIUS on a separate server as a self-standing process but if a Docker swarm is running on the local network, it is more interesting to run FreeRADIUS on the swarm. The tendency would be to run FreeRADIUS on OpenWRT itself, and packages are provided therefore, however FreeRADIUS can also be ran outside of OpenWRT as a self-standing service. One of the justifications regarding running FreeRADIUS on a Docker swarm is that FreeRADIUS on its own can be seen as a giant authentication, authorization and accounting service that has split responsibilities contrasted to wireless radio in general. In fact, FreeRADIUS is not exactly as a database, it can run as a makeshift database using flat-file storage but the data it stores are properties that do not involve wireless nor routers in particular.
For some reason, the developers of FreeRADIUS provide a Docker image that cooks-in the configuration instead of providing the configuration on a separate volume, which seems awkward. Similarly, no default configuration is provided at all and the user is expected to write it all themselves. Interestingly, the Docker image is based on Debian such that the Debian FreeRADIUS configuration can be transferred over as a starting point. Simply install the freeradius
package on a different server and then transfer the configuration over from /etc/freeradius/3.0/
to the raddb
folder that is then cooked inside the resulting image.
Here is the official filesystem layout recommended in order to build the Docker container:
. ├── Dockerfile └── raddb
where:
raddb
should contain the configuration from a fresh install of FreeRADIUS from a Debian machine.With the configuration transferred, the next step is to change the FreeRADIUS configuration in order to allow MAC authentication and authorization. This is done preferrably by using the minimal amount of changes to the default FreeRADIUS configuration.
Here is a patch file between the original default Debian FreeRADIUS configuration and the changes made to accomodate for MAC-based authentication:
diff -Naur /root/raddb-orig/clients.conf raddb/clients.conf --- /root/raddb-orig/clients.conf 2024-12-19 04:45:06.605883128 +0000 +++ raddb/clients.conf 2024-12-19 04:55:13.182944655 +0000 @@ -27,7 +27,7 @@ # the "ipaddr" or "ipv6addr" fields. For compatibility, the 1.x # format is still accepted. # -client localhost { +client all { # Only *one* of ipaddr, ipv4addr, ipv6addr may be specified for # a client. # @@ -39,7 +39,8 @@ # # If both A and AAAA records are found, A records will be # used in preference to AAAA. - ipaddr = 127.0.0.1 +# ipaddr = 127.0.0.1 + ipaddr = * # Same as ipaddr but allows v4 addresses only. Requires A # record for domain names. @@ -118,6 +119,7 @@ # longer necessary in >= 2.0 # # shortname = localhost + shortname = docker # # the following three fields are optional, but may be used by diff -Naur /root/raddb-orig/mods-available/files raddb/mods-available/files --- /root/raddb-orig/mods-available/files 2024-12-19 04:45:07.013889208 +0000 +++ raddb/mods-available/files 2024-12-19 10:38:09.925597711 +0000 @@ -15,6 +15,7 @@ # of this attribute is used to match the "name" of the # entry. #key = "%{%{Stripped-User-Name}:-%{User-Name}}" + key = %{Calling-Station-ID} # The old "users" style file is now located here. filename = ${moddir}/authorize @@ -22,6 +23,7 @@ # This is accepted for backwards compatibility # It will be removed in a future release. # usersfile = ${moddir}/authorize + usersfile = /data/authorized_macs # These are accepted for backwards compatibility. # They will be renamed in a future release. diff -Naur /root/raddb-orig/mods-enabled/files raddb/mods-enabled/files --- /root/raddb-orig/mods-enabled/files 2024-12-19 04:45:08.769915379 +0000 +++ raddb/mods-enabled/files 2024-12-19 10:38:09.925597711 +0000 @@ -15,6 +15,7 @@ # of this attribute is used to match the "name" of the # entry. #key = "%{%{Stripped-User-Name}:-%{User-Name}}" + key = %{Calling-Station-ID} # The old "users" style file is now located here. filename = ${moddir}/authorize @@ -22,6 +23,7 @@ # This is accepted for backwards compatibility # It will be removed in a future release. # usersfile = ${moddir}/authorize + usersfile = /data/authorized_macs # These are accepted for backwards compatibility. # They will be renamed in a future release. diff -Naur /root/raddb-orig/radiusd.conf raddb/radiusd.conf --- /root/raddb-orig/radiusd.conf 2024-12-19 04:45:09.009918956 +0000 +++ raddb/radiusd.conf 2024-12-19 04:56:21.075960641 +0000 @@ -510,8 +510,10 @@ # member. This can allow for some finer-grained access # controls. # - user = freerad - group = freerad +# user = freerad +# group = freerad + user = root + group = root # Core dumps are a bad thing. This should only be set to # 'yes' if you're debugging a problem with the server. diff -Naur /root/raddb-orig/sites-available/default raddb/sites-available/default --- /root/raddb-orig/sites-available/default 2024-12-19 04:45:09.157921162 +0000 +++ raddb/sites-available/default 2024-12-19 10:32:20.012466692 +0000 @@ -311,6 +311,9 @@ # and the 'raddb/mods-config/preprocess/huntgroups' files. preprocess + # Normalize MAC + rewrite_calling_station_id + # If you intend to use CUI and you require that the Operator-Name # be set for CUI generation and you want to generate CUI also # for your local clients then uncomment the operator-name @@ -420,6 +423,19 @@ # raddb/mods-config/files/authorize files + if (!ok) { + # No match was found, so reject + reject + } + else { + # The MAC address was found, so update Auth-Type + # to accept this auth. + update control { + Auth-Type := Accept + } + } + + # # Look in an SQL database. The schema of the database # is meant to mirror the "users" file. @@ -610,6 +626,9 @@ preacct { preprocess + # Normalize MAC + rewrite_calling_station_id + # # Merge Acct-[Input|Output]-Gigawords and Acct-[Input-Output]-Octets # into a single 64bit counter Acct-[Input|Output]-Octets64. diff -Naur /root/raddb-orig/sites-enabled/default raddb/sites-enabled/default --- /root/raddb-orig/sites-enabled/default 2024-12-19 04:45:09.285923069 +0000 +++ raddb/sites-enabled/default 2024-12-19 10:32:20.012466692 +0000 @@ -311,6 +311,9 @@ # and the 'raddb/mods-config/preprocess/huntgroups' files. preprocess + # Normalize MAC + rewrite_calling_station_id + # If you intend to use CUI and you require that the Operator-Name # be set for CUI generation and you want to generate CUI also # for your local clients then uncomment the operator-name @@ -420,6 +423,19 @@ # raddb/mods-config/files/authorize files + if (!ok) { + # No match was found, so reject + reject + } + else { + # The MAC address was found, so update Auth-Type + # to accept this auth. + update control { + Auth-Type := Accept + } + } + + # # Look in an SQL database. The schema of the database # is meant to mirror the "users" file. @@ -610,6 +626,9 @@ preacct { preprocess + # Normalize MAC + rewrite_calling_station_id + # # Merge Acct-[Input|Output]-Gigawords and Acct-[Input-Output]-Octets # into a single 64bit counter Acct-[Input|Output]-Octets64.
Amongst the changes, the following should probably adapted to the local scenario:
shortname
is the name of the FreeRADIUS server, here set to docker
,user
and group
are both set to root
; this is meant as a shortcut in order to be able to access the mounted volume but a proper user and group should probably be used in production,usersfile
sets a path to a file, in this case, /data/authorized_macs
that will contain a list of MAC addresses that will be allowed access and the path therefore could be adjusted to something else if need be, depending on how Docker will be invoked
Additionally, although not changed in this example configuration, the secret
configuration key, to be found inside the sites-enabled/default
file should be changed from testing123
to something else provided that the OpenWRT /etc/config/wireless
file is updated to match.
Here is the Dockerfile
that is used to build the FreeRADIUS image:
FROM freeradius/freeradius-server:latest # update package manager RUN apt-get update -y && \ apt-get upgrade -y && \ apt-get dist-upgrade -y && \ apt-get -y autoremove && \ apt-get clean # generate SSL snakeoil for freeradius RUN apt-get install ssl-cert && \ make-ssl-cert generate-default-snakeoil COPY raddb/ /etc/raddb/ VOLUME /data EXPOSE 1812-1813:1812-1813/udp ENTRYPOINT [ "freeradius", "-x", "-X", "-f" ]
where:
Next, the Docker image is built:
docker build -t registry/freeradius:latest .
where:
registry/freeradius
is a repository URLand then pushed to the registry:
docker push registry/freeradius:latest
Lastly, the image has to be run. For that purpose, here is a Docker compose file:
version: '3.9' services: freeradius: image: registry/freeradius:latest user: root ports: - 1812:1812 - 1813:1813 - 1812:1812/udp - 1813:1813/udp volumes: - /mnt/docker/data/freeradius:/data deploy: labels: - shepherd.enable=true - shepherd.auth.config=local replicas: 1 placement: max_replicas_per_node: 1
where:
/mnt/docker/data/freeradius
is the local path to a directory that will contain the authorized_macs
file with a list of MAC addresses that is mounted within the container and referenced by the FreeRADIUS configuration
In this configuration, the file /data/authorized_macs
is only read on startup, such that after adding or removing MAC addresses, the container must be restarted. In order to address this issue, such that the /data/authorized_macs
can be changed without having to manually restart Docker, an INOTIFY-based watcher can be added to the FreeRADIUS container in order to deliver a HUP signal, thereby making FreeRADIUS reload the configuration.
The result is a pretty neat combo of FreeRADIUS configured for MAC-based authentication that can be very easily deployed and takes care of everything needing only a single file containing MAC addresses to be provided and one other file to provide the FreeRADIUS secret:
In order to use the container, ensure that the volume that is mapped to the /data
inside the container contains the files secret
and macs
where secret
should contain the following:
secret = testing123
with test123
being a user supplied string representing the default FreeRADIUS password and the macs
file should contain MAC addresses in IEEE format (with a dash -
instead of colon :
) listed line-by line, as in:
bc-ad-95-d6-65-f4 c4-c9-c5-ab-11-a7 d3-b4-dc-55-87-4b e5-9d-42-7d-21-fc 3e-41-af-19-d9-30 2b-1e-1f-dc-14-46 96-2b-22-fa-f0-29 d0-d9-12-6a-23-92 61-d3-1e-c1-02-7c 53-9c-2a-9d-07-e4
After starting the container, FreeRADIUS can be monitored by attaching to the console from Docker and then reading the messages.
The Docker compose file supplied runs freeradius
with both -x
and -X
options in order to increase debugging. The general idea is that whenever a request is received by FreeRADIUS, it is processed through all the available authorization methods defined in the sites-enabled/default
file. In this case, the authorization request runs through all the authentication methods, such as pop
, mschap
and the rest, some of them having been used even in other guides, such as authentication for a PPTP dialin server:
... suffix: Checking for suffix after "@" suffix: No '@' in User-Name = "2addf183853a", looking up realm NULL suffix: No such realm "NULL" modsingle[authorize]: returned from suffix (rlm_realm) [suffix] = noop modsingle[authorize]: calling eap (rlm_eap) eap: No EAP-Message, not doing EAP modsingle[authorize]: returned from eap (rlm_eap) [eap] = noop modsingle[authorize]: calling files (rlm_files) ...
and then eventually ends up in the files
authentication method section:
... %{Calling-Station-ID} Parsed xlat tree: attribute --> Calling-Station-Id files: EXPAND %{Calling-Station-ID} files: --> 6B-77-12-96-38-1A files: users: Checking entry 6B-77-12-96-38-1A at line 15 files: users: Matched entry 6B-77-12-96-38-1A at line 15 modsingle[authorize]: returned from files (rlm_files) [files] = ok if (!ok) { if (!ok) -> FALSE else { update control { Auth-Type := Accept } # update control = noop } # else = noop ...
where the files authentication method finds a match in the list of MAC addresses and returns a positive match.
In that sense, FreeRADIUS takes a best effort approach to authentication, running through all the methods and accepting the request in case a match has been found.
One thing to watch out for that deviates slightly from the base configuration is that various devices send the MAC address with different casings. In other words, if 6B-77-12-96-38-1A
is received via an authentication request, and the file containing MAC addresses containers the lowercase equivalent 6b-77-12-96-38-1a
, then the MAC address will not match. This leads to massive confusion because FreeRADIUS will definitely not throw an error, given that the files authentication method is just a plain string match line-by-line. For that reason, the official guide mentions adding a policy file that will normalize MAC addresses received in various formats to the IEEE standard representation of MAC addresses. The configuration lifted from Debian needs to be changed as well, but the configuration already defines the regular expression and the MAC address transformation, such that the rewrite_called_station_id
configuration is added both in the authorization
and preacct
sections of the FreeRADIUS configuration. This has already been done in the patch provided in the FreeRADIUS server section.
For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.