Important

The development of django-freeradius has moved to openwisp-radius, we highly suggest users and readers of this documentation to migrate or use the more up to date documentation of openwisp-radius.

django-freeradius

https://travis-ci.org/openwisp/django-freeradius.svg https://coveralls.io/repos/openwisp/django-freeradius/badge.svg Requirements Status https://badge.fury.io/py/django-freeradius.svg

Django-freeradius is part of the OpenWISP project.

http://netjsonconfig.openwisp.org/en/latest/_images/openwisp.org.svg

Important

The development of django-freeradius has moved to openwisp-radius, we highly suggest users and readers of this documentation to migrate or use the more up to date documentation of openwisp-radius.

Setup

Create a virtual environment

Please use a python virtual environment. It keeps everybody on the same page, helps reproducing bugs and resolving problems.

We highly suggest to use virtualenvwrapper, please refer to the official virtualenvwrapper installation page and come back here when ready to proceed.

# create virtualenv
mkvirtualenv radius

Note

If you encounter an error like Python could not import the module virtualenvwrapper, add VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 and run source virtualenvwrapper.sh again :)

Install required system packages

Install packages required by Weasyprint for your OS:

Install stable version from pypi

Install from pypi:

pip install django-freeradius

Install development version

Install tarball:

pip install https://github.com/openwisp/django-freeradius/tarball/master

Alternatively you can install via pip using git:

pip install -e git+git://github.com/openwisp/django-freeradius#egg=django-freeradius

If you want to contribute, install your cloned fork:

git clone git@github.com:<your_fork>/django-freeradius.git
cd django-freeradius
python setup.py develop

Setup (integrate in an existing django project)

In the django settings.py file of your project, do the following:

  • add django_freeradius and django_filters to INSTALLED_APPS
  • set DJANGO_FREERADIUS_API_TOKEN (see API Token for more information):
INSTALLED_APPS = [
    # other apps
    'django_freeradius',
    'django_filters',
]

DJANGO_FREERADIUS_API_TOKEN = '<a-long-secret-value-of-your-choice>'

Add the URLs to your main urls.py:

urlpatterns = [
    # ... other urls in your project ...

    # django-freeradius urls
    # keep the namespace argument unchanged
    url(r'^', include('django_freeradius.urls', namespace='freeradius')),
]

Then run:

./manage.py migrate

Migrating an existing freeradius database

If you already have a freeradius 3 database with the default schema, you should be able to use it with django-freeradius (and openwisp-radius) easily:

  1. first of all, back up your existing database;
  2. configure django to connect to your existing database;
  3. fake the first migration (which only replicates the default freeradius schema) and then launch the rest of migrations normally, see the examples below to see how to do this.

django-freeradius

./manage.py migrate --fake django_freeradius 0001_initial_freeradius
./manage.py migrate

openwisp-radius

In case you are using openwisp-radius:

./manage.py migrate --fake openwisp_radius 0001_initial_freeradius
./manage.py migrate

Installing for development

Install python3-dev and gcc:

sudo apt-get install python3-dev gcc

Install sqlite:

sudo apt-get install sqlite3 libsqlite3-dev libpq-dev

Install mysqlclient:

sudo apt-get install libmysqlclient-dev libssl-dev

Note

If you are on Debian 10 or 9 you may need to install default-libmysqlclient-dev instead

Install your forked repo:

git clone git://github.com/<your_username>/django-freeradius
cd django-freeradius/
python setup.py develop

Install test requirements:

pip install -r requirements-test.txt

Create database:

cd tests/
./manage.py migrate
./manage.py createsuperuser

Launch development server:

./manage.py runserver

You can access the admin interface at http://127.0.0.1:8000/admin/.

Run tests with:

./runtests.py

Troubleshooting

If you encounter any issue during installation, run:

pip install -r requirements.txt -r requirements-test.txt instead of pip install -r requirements-test.txt

instead of pip install -r requirements-test.txt

Automating management commands

Some management commands are necessary to enable certain features and also facilitate database cleanup. In a production environment, it is highly recommended to automate the usage of these commands by using cron jobs.

Edit the crontab with:

crontab -e

Add and modify the following lines accordingly:

# This command deletes RADIUS accounting sessions older than 365 days
30 04 * * * <virtualenv_path>/bin/python <full/path/to>/manage.py delete_old_radacct 365

# This command deletes RADIUS post-auth logs older than 365 days
30 04 * * * <virtualenv_path>/bin/python <full/path/to>/manage.py delete_old_postauth 365

# This command closes stale RADIUS sessions that have remained open for 15 days
30 04 * * * <virtualenv_path>/bin/python <full/path/to>/manage.py cleanup_stale_radacct 15

# This command deactivates expired user accounts which were created temporarily
# (eg: for en event) and have an expiration date set.
30 04 * * * <virtualenv_path>/bin/python <full/path/to>/manage.py deactivate_expired_users

# This command deletes users that have expired (and should have
# been deactivated by deactivate_expired_users) for more than
# 18 months (which is the default duration)
30 04 * * * <virtualenv_path>/bin/python <full/path/to>/manage.py delete_old_users

Be sure to replace <virtualenv_path> with the absolute path to the Python virtual environment.

Also, change <full/path/to> to the directory where manage.py is.

To get the absolute path to manage.py when django-freeradius is installed for development, navigate to the base directory of the cloned fork. Then, run:

cd tests/
pwd

More information can be found at the management commands page.

Important

The development of django-freeradius has moved to openwisp-radius, we highly suggest users and readers of this documentation to migrate or use the more up to date documentation of openwisp-radius.

Available settings

DJANGO_FREERADIUS_EDITABLE_ACCOUNTING

Default: False

Whether radacct entries are editable from the django admin or not.

DJANGO_FREERADIUS_EDITABLE_POSTAUTH

Default: False

Whether postauth logs are editable from the django admin or not.

DJANGO_FREERADIUS_GROUPCHECK_ADMIN

Default: False

Direct editing of group checks items is disabled by default because these can be edited through inline items in the Radius Group admin (Freeradius > Groups).

This is done with the aim of simplifying the admin interface and avoid overwhelming users with too many options.

If for some reason you need to enable direct editing of group checks you can do so by setting this to True.

DJANGO_FREERADIUS_GROUPREPLY_ADMIN

Default: False

Direct editing of group reply items is disabled by default because these can be edited through inline items in the Radius Group admin (Freeradius > Groups).

This is done with the aim of simplifying the admin interface and avoid overwhelming users with too many options.

If for some reason you need to enable direct editing of group replies you can do so by setting this to True.

DJANGO_FREERADIUS_USERGROUP_ADMIN

Default: False

Direct editing of user group items (radusergroup) is disabled by default because these can be edited through inline items in the User admin (Users and Organizations > Users).

This is done with the aim of simplifying the admin interface and avoid overwhelming users with too many options.

If for some reason you need to enable direct editing of user group items you can do so by setting this to True.

DJANGO_FREERADIUS_DEFAULT_SECRET_FORMAT

Default: NT-Password

The default encryption format for storing radius check values.

DJANGO_FREERADIUS_DISABLED_SECRET_FORMATS

Default: []

A list of disabled encryption formats, by default all formats are enabled in order to keep backward compatibility with legacy systems.

DJANGO_FREERADIUS_RADCHECK_SECRET_VALIDATORS

Default:

{'regexp_lowercase': '[a-z]+',
 'regexp_uppercase': '[A-Z]+',
 'regexp_number': '[0-9]+',
 'regexp_special': '[\!\%\-_+=\[\]\
                   {\}\:\,\.\?\<\>\(\)\;]+'}

Regular expressions regulating the password validation; by default the following character families are required:

  • a lowercase character
  • an uppercase character
  • a number
  • a special character

DJANGO_FREERADIUS_BATCH_DEFAULT_PASSWORD_LENGTH

Default: 8

The default password length of the auto generated passwords while batch addition of users from the csv.

DJANGO_FREERADIUS_BATCH_DELETE_EXPIRED

Default: 18

It is the number of months after which the expired users are deleted.

DJANGO_FREERADIUS_BATCH_PDF_TEMPLATE

It is the template used to generate the pdf when users are being generated using the batch add users feature using the prefix.

The value should be the absolute path to the template of the pdf.

DJANGO_FREERADIUS_API_TOKEN

See API Token.

DJANGO_FREERADIUS_DISPOSABLE_RADIUS_USER_TOKEN

Default: True

Radius user tokens are used for authorizing users.

When this setting is True radius user tokens are deleted right after a successful authorization is performed. This reduces the possibility of attackers reusing the access tokens and posing as other users if they manage to intercept it somehow.

DJANGO_FREERADIUS_API_AUTHORIZE_REJECT

Default: False

Indicates wether the Authorize API view will return {"control:Auth-Type": "Reject"} or not.

Rejecting an authorization request explicitly will prevent freeradius from attempting to perform authorization with other mechanisms (eg: radius checks, LDAP, etc.).

When set to False, if an authorization request fails, the API will respond with None, which will allow freeradius to keep attempting to authorize the request with other freeradius modules.

Set this to True if you are performing authorization exclusively through the REST API.

DJANGO_FREERADIUS_API_ACCOUNTING_AUTO_GROUP

Default: True

When this setting is enabled, every accounting instance saved from the API will have its groupname attribute automatically filled in. The value filled in will be the groupname of the RadiusUserGroup of the highest priority among the RadiusUserGroups related to the user with the username as in the accounting instance. In the event there is no user in the database corresponding to the username in the accounting instance, the failure will be logged with info level but the accounting will be saved as usual.

DJANGO_FREERADIUS_EXTRA_NAS_TYPES

Default: tuple()

This setting can be used to add custom NAS types that can be used from the admin interface when managing NAS instances.

For example, you want a custom NAS type called cisco, you would add the following to your project settings.py:

DJANGO_FREERADIUS_EXTRA_NAS_TYPES = (
    ('cisco', 'Cisco Router'),
)

Sending emails to users

Emails can be sent to users whose usernames or passwords have been autogenerated. The content of these emails can be customized with the settings explained below.

DJANGO_FREERADIUS_BATCH_MAIL_SUBJECT

Default: Credentials

It is the subject of the mail to be sent to the users. Eg: Login Credentials.

DJANGO_FREERADIUS_BATCH_MAIL_MESSAGE

Default: username: {}, password: {}

The message should be a string in the format Your username is {} and password is {}.

The text could be anything but should have the format string operator {} for .format operations to work.

DJANGO_FREERADIUS_BATCH_MAIL_SENDER

Default: settings.DEFAULT_FROM_EMAIL

It is the sender email which is also to be configured in the SMTP settings. The default sender email is a common setting from the Django core settings under DEFAULT_FROM_EMAIL. Currently, DEFAULT_FROM_EMAIL is set to to webmaster@localhost.

Important

The development of django-freeradius has moved to openwisp-radius, we highly suggest users and readers of this documentation to migrate or use the more up to date documentation of openwisp-radius.

Installation and configuration of Freeradius 3

This guide explains how to install and configure freeradius 3 in order to make it work with django-freeradius.

Note

The guide is written for debian based systems, other linux distributions can work as well but the name of packages and files may be different.

How to install freeradius 3

First of all, become root:

sudo -s

Let’s add the PPA repository for the Freeradius 3.x stable branch:

Note

If you use a recent version of Debian like Stretch (9) or Ubuntu Bionic (18), you should skip the following command and use the official repositories.

apt-add-repository ppa:freeradius/stable-3.0

Update the list of available packages:

apt update

These packages are always needed:

apt install freeradius freeradius-rest

If you use MySQL:

apt install freeradius-mysql

If you use PostgreSQL:

apt install freeradius-postgresql

Configuring Freeradius 3

For a complete reference on how to configure freeradius please read the Freeradius wiki, configuration files and their configuration tutorial.

Note

The path to freeradius configuration could be different on your system. This article use the /etc/freeradius/ directory that ships with recent debian distributions and its derivatives

Refer to the mods-available documentation for the available configuration values.

Enable the configured modules

First of all enable the sql, rest and sqlcounter modules:

ln -s /etc/freeradius/mods-available/sql /etc/freeradius/mods-enabled/sql
ln -s /etc/freeradius/mods-available/rest /etc/freeradius/mods-enabled/rest
ln -s /etc/freeradius/mods-available/sqlcounter /etc/freeradius/mods-enabled/sqlcounter

Configure the SQL module

Once you have configured properly an SQL server, e.g. PostgreSQL:, and you can connect with a username and password edit the file /etc/freeradius/mods-available/sql to configure Freeradius to use the relational database.

Change the configuration for driver, dialect, server, port, login, password, radius_db as you need to fit your SQL server configuration.

Refer to the sql module documentation for the available configuration values.

Example configuration using the PostgreSQL database:

# /etc/freeradius/mods-available/sql

driver = "rlm_sql_postgresql"
dialect = "postgresql"

# Connection info:
server = "localhost"
port = 5432
login = "<user>"
password = "<password>"
radius_db = "radius"

Configure the SQL counters

The sqlcounter module is used to enforce session limits.

The mods-available/sqlcounter should look like the following:

# /etc/freeradius/mods-available/sqlcounter

# The dailycounter is included by default in the freeradius conf
sqlcounter dailycounter {
    sql_module_instance = sql
    dialect = ${modules.sql.dialect}

    counter_name = Daily-Session-Time
    check_name = Max-Daily-Session
    reply_name = Session-Timeout

    key = User-Name
    reset = daily

    $INCLUDE ${modconfdir}/sql/counter/${dialect}/${.:instance}.conf
}

# The noresetcounter is included by default in the freeradius conf
sqlcounter noresetcounter {
    sql_module_instance = sql
    dialect = ${modules.sql.dialect}

    counter_name = Max-All-Session-Time
    check_name = Max-All-Session
    key = User-Name
    reset = never

    $INCLUDE ${modconfdir}/sql/counter/${dialect}/${.:instance}.conf
}

# The dailybandwidthcounter is added for django-freeradius
sqlcounter dailybandwidthcounter {
   counter_name = Max-Daily-Session-Traffic
   check_name = Max-Daily-Session-Traffic
   sql_module_instance = sql
   key = 'User-Name'
   reset = daily
   query = "SELECT SUM(acctinputoctets + acctoutputoctets) \
            FROM radacct \
            WHERE UserName='%{${key}}' \
            AND UNIX_TIMESTAMP(acctstarttime) + acctsessiontime > '%%b'"
}

Note

If your freeradius installation fails to start with an error similar to:

/etc/raddb/sites-enabled/default[440]: Failed to find "dailycounter" as a module or policy.

We need enable the sqlcounter in a special way. The modules section of radiusd.conf should look as shown below. This is because of a bug in freeradius. This should be solved in a future release of freeradius.

# /etc/freeradius/radiusd.conf
modules {
    # ..
    $INCLUDE mods-enabled
    $INCLUDE mods-available/sqlcounter
    # ..
}

Configure the REST module

Configure the rest module by editing the file /etc/freeradius/mods-enabled/rest, substituting <url> with your django project’s URL, (for example, if you are testing a development environment, the URL could be http://127.0.0.1:8000, otherwise in production could be something like https://openwisp2.mydomain.org)-

Refer to the rest module documentation for the available configuration values.

# /etc/freeradius/mods-enabled/rest

connect_uri = "<url>"

authorize {
    uri = "${..connect_uri}/api/v1/authorize/"
    method = 'post'
    body = 'json'
    data = '{"username": "%{User-Name}", "password": "%{User-Password}"}'
    tls = ${..tls}
}

# this section can be left empty
authenticate {}

post-auth {
    uri = "${..connect_uri}/api/v1/postauth/"
    method = 'post'
    body = 'json'
    data = '{"username": "%{User-Name}", "password": "%{User-Password}", "reply": "%{reply:Packet-Type}", "called_station_id": "%{Called-Station-ID}", "calling_station_id": "%{Calling-Station-ID}"}'
    tls = ${..tls}
}

accounting {
    uri = "${..connect_uri}/api/v1/accounting/"
    method = 'post'
    body = 'json'
    data = '{"status_type": "%{Acct-Status-Type}", "session_id": "%{Acct-Session-Id}", "unique_id": "%{Acct-Unique-Session-Id}", "username": "%{User-Name}", "realm": "%{Realm}", "nas_ip_address": "%{NAS-IP-Address}", "nas_port_id": "%{NAS-Port}", "nas_port_type": "%{NAS-Port-Type}", "session_time": "%{Acct-Session-Time}", "authentication": "%{Acct-Authentic}", "input_octets": "%{Acct-Input-Octets}", "output_octets": "%{Acct-Output-Octets}", "called_station_id": "%{Called-Station-Id}", "calling_station_id": "%{Calling-Station-Id}", "terminate_cause": "%{Acct-Terminate-Cause}", "service_type": "%{Service-Type}", "framed_protocol": "%{Framed-Protocol}", "framed_ip_address": "%{Framed-IP-Address}"}'
    tls = ${..tls}
}

Configure the site

Configure the authorize, authenticate and postauth section as follows, substituting the occurrences of <api_token> with the value of DJANGO_FREERADIUS_API_TOKEN:

# /etc/freeradius/sites-enabled/default

server default {

    api_token_header = "Authorization: Bearer <api_token>"

    authorize {
        update control { &REST-HTTP-Header += "${...api_token_header}" }
        rest
        sql
        dailycounter
        noresetcounter
        dailybandwidthcounter
    }

    # this section can be left empty
    authenticate {}

    post-auth {
        update control { &REST-HTTP-Header += "${...api_token_header}" }
        rest

        Post-Auth-Type REJECT {
            update control { &REST-HTTP-Header += "${....api_token_header}" }
            rest
        }
    }

    accounting {
        update control { &REST-HTTP-Header += "${...api_token_header}" }
        rest
    }
}

Please also ensure that acct_unique is present in tge pre-accounting section:

preacct {
    # ...
    acct_unique
    # ...
}

Restart freeradius to make the configuration effective

Restart freeradius to load the new configuration:

service freeradius restart
# alternatively if you are using systemd
systemctl restart freeradius

In case of errors you can run freeradius in debug mode by running freeradius -X in order to find out the reason of the failure.

A common problem, especially during development and testing, is that the django-freeradius application may not be running, in that case you can find out how to run the django development server in the Install for development section.

Also make sure that this server runs on the port specified in /etc/freeradius/mods-enabled/rest.

You may also want to take a look at the Freeradius documentation for further information that is freeradius specific.

Reconfigure the development environment using PostgreSQL

You’ll have to reconfigure the development environment as well before being able to use django-freeradius for managing the freeradius databases.

If you have installed for development, create a file tests/local_settings.py and add the following code to configure the database:

# django-freeradius/tests/local_settings.py
  DATABASES = {
     'default': {
         'ENGINE': 'django.db.backends.postgresql_psycopg2',
         'NAME': '<db_name>',
         'USER': '<db_user>',
         'PASSWORD': '<db_password>',
         'HOST': '127.0.0.1',
         'PORT': '5432'
     },
  }

Make sure the database by the name <db_name> is created and also the role <db_user> with <db_password> as password.

Radius Checks: is_active & valid_until

Django-Freeradius provides the possibility to extend the freeradius query in order to introduce is_active and valid_until checks.

An example using MySQL is:

# /etc/freeradius/mods-config/sql/main/mysql/queries.conf
authorize_check_query = "SELECT id, username, attribute, value, op \
                         FROM ${authcheck_table} \
                         WHERE username = '%{SQL-User-Name}' \
                         AND is_active = TRUE \
                         AND valid_until >= CURDATE() \
                         ORDER BY id"

Using Radius Checks for Authorization Information

Traditionally, when using an SQL backend with Freeradius, user authorization information such as User-Name and “known good” password are stored using the radcheck table provided by Freeradius’ default SQL schema. Django-Freeradius utilizes Freeradius’ rlm_rest module in order to take advantage of the built in user management and authentication capabilities of Django. (See Configure the REST module and User authentication in Django)

For existing Freeradius deployments or in cases where it is preferred to utilize Freeradius’ radcheck table for storing user credentials it is possible to utilize rlm_sql in parallel with (or instead of) rlm_rest for authorization.

Note

Bypassing the Django-Freeradius’ REST API for authorization means you will have to manually create Radius Check ‘password’ entries for each user you want to authenticate with Freeradius.

Password hashing

By default Django will use PBKDF2 to store all passwords in the database. (See Password management in Django). The default password hashing and storage algorithms in Django are not compatible with those used by Freeradius. Therefore, a default set of Freeradius compatible password storage methods have been provided for deployments that make use of Radius Checks for user credentials.

  • Cleartext-Password
  • NT-Password
  • LM-Password
  • MD5-Password
  • SMD5-Password
  • SHA-Password
  • SSHA-Password
  • Crypt-Password

Note

Only the Crypt-Password hashing attribute is recommended for new entries as it makes use of the sha512_crypt feature supported by most Unix/Linux operating systems. (See passlib.hash) The other password hashing algorithms have been provided for backward compatibility.

Configuration

To configure support for accessing user credentials with Radius Checks ensure the authorize section of your site as follows contains the sql module:

# /etc/freeradius/sites-available/default

authorize {
    # ...
    sql  # <-- the sql module
    # ...
}

Now you can add new Radius Check entries with one of the supported hashing/storage methods mentioned above.

Additional Password Formats

Freeradius supports additional password hashing algorithms which are listed in the Freeradius rlm_pap documentation. If your existing deployment makes use of one of these or you would like to request an addition to Django-Freeradius please see the documentation section on Contributing.

Keep in mind that using Radius Checks for accessing user credentials is considered an edge case in Django-Freeradius. Full compatibility with new and existing features is not guaranteed.

Debugging

In this section we will explain how to debug your freeradius instance.

Start freeradius in debug mode

When debugging we suggest you to open up a dedicated terminal window to run freeradius in debug mode:

# we need to stop the main freeradius process first
service freeradius stop
# alternatively if you are using systemd
systemctl stop freeradius
# launch freeradius in debug mode
freeradius -X

Testing authentication and authorization

You can do this with radtest:

# radtest <username> <password> <host> 10 <secret>
radtest admin admin localhost 10 testing123

A successful authentication will return similar output:

Sent Access-Request Id 215 from 0.0.0.0:34869 to 127.0.0.1:1812 length 75
    User-Name = "admin"
    User-Password = "admin"
    NAS-IP-Address = 127.0.0.1
    NAS-Port = 10
    Message-Authenticator = 0x00
    Cleartext-Password = "admin"
Received Access-Accept Id 215 from 127.0.0.1:1812 to 0.0.0.0:0 length 20

While an unsuccessful one will look like the following:

Sent Access-Request Id 85 from 0.0.0.0:51665 to 127.0.0.1:1812 length 73
    User-Name = "foo"
    User-Password = "bar"
    NAS-IP-Address = 127.0.0.1
    NAS-Port = 10
    Message-Authenticator = 0x00
    Cleartext-Password = "bar"
Received Access-Reject Id 85 from 127.0.0.1:1812 to 0.0.0.0:0 length 20
(0) -: Expected Access-Accept got Access-Reject

Alternatively, you can use radclient which allows more complex tests; in the following example we show how to test an authentication request which includes Called-Station-ID and Calling-Station-ID:

user="foo"
pass="bar"
called="00-11-22-33-44-55:localhost"
calling="00:11:22:33:44:55"
request="User-Name=$user,User-Password=$pass,Called-Station-ID=$called,Calling-Station-ID=$calling"
echo $request | radclient localhost auth testing123

Testing accounting

You can do this with radclient, but first of all you will have to create a text file like the following one:

# /tmp/accounting.txt

Acct-Session-Id = "35000006"
User-Name = "jim"
NAS-IP-Address = 172.16.64.91
NAS-Port = 1
NAS-Port-Type = Async
Acct-Status-Type = Interim-Update
Acct-Authentic = RADIUS
Service-Type = Login-User
Login-Service = Telnet
Login-IP-Host = 172.16.64.25
Acct-Delay-Time = 0
Acct-Session-Time = 261
Acct-Input-Octets = 9900909
Acct-Output-Octets = 10101010101
Called-Station-Id = 00-27-22-F3-FA-F1:hostname
Calling-Station-Id = 5c:7d:c1:72:a7:3b

Then you can call radclient:

radclient -f /tmp/accounting.txt -x 127.0.0.1 acct testing123

You should get the following output:

Sent Accounting-Request Id 83 from 0.0.0.0:51698 to 127.0.0.1:1813 length 154
    Acct-Session-Id = "35000006"
    User-Name = "jim"
    NAS-IP-Address = 172.16.64.91
    NAS-Port = 1
    NAS-Port-Type = Async
    Acct-Status-Type = Interim-Update
    Acct-Authentic = RADIUS
    Service-Type = Login-User
    Login-Service = Telnet
    Login-IP-Host = 172.16.64.25
    Acct-Delay-Time = 0
    Acct-Session-Time = 261
    Acct-Input-Octets = 9900909
    Acct-Output-Octets = 1511075509
    Called-Station-Id = "00-27-22-F3-FA-F1:hostname"
    Calling-Station-Id = "5c:7d:c1:72:a7:3b"
Received Accounting-Response Id 83 from 127.0.0.1:1813 to 0.0.0.0:0 length 20

Customizing your configuration

You can further customize your freeradius configuration and exploit the many features of freeradius but you will need to test how your configuration plays with django-freeradius.

Important

The development of django-freeradius has moved to openwisp-radius, we highly suggest users and readers of this documentation to migrate or use the more up to date documentation of openwisp-radius.

Management commands

These management commands are necessary for enabling certain features and for database cleanup.

Example usage:

cd tests/
./manage.py <command> <args>

In this page we list the management commands currently available in django-freeradius.

delete_old_radacct

This command deletes RADIUS accounting sessions older than <days>.

./manage.py delete_old_radacct <days>

For example:

./manage.py delete_old_radacct 365

delete_old_postauth

This command deletes RADIUS post-auth logs older than <days>.

./manage.py delete_old_postauth <days>

For example:

./manage.py delete_old_postauth 365

cleanup_stale_radacct

This command closes stale RADIUS sessions that have remained open for the number of specified <days>.

./manage.py cleanup_stale_radacct <days>

For example:

./manage.py cleanup_stale_radacct 15

deactivate_expired_users

This command deactivates expired user accounts which were created temporarily (eg: for en event) and have an expiration date set.

./manage.py deactivate_expired_users

delete_old_users

This command deletes users that have expired (and should have been deactivated by deactivate_expired_users) for more than the specified <duration_in_months>.

./manage.py delete_old_users --older-than-months <duration_in_months>

Note that the default duration is set to 18 months.

Important

The development of django-freeradius has moved to openwisp-radius, we highly suggest users and readers of this documentation to migrate or use the more up to date documentation of openwisp-radius.

Importing users

This feature can be used for importing users from a csv file. There are many features included in it such as:

  • Importing users in batches: all of the users of a particular csv file would be stored in batches and can be retrieved/ deleted easily using the batch functions.
  • Set an expiration date: Expiration date can be set for a batch after which the users would not able to authenticate to the RADIUS Server.
  • Autogenerate usernames and passwords: The usernames and passwords are automatically generated if they aren’t provided in the csv file. Usernames are generated from the email address whereas passwords are generated randomly and their lengths can be customized.
  • Passwords are accepted in both cleartext and hash formats from the CSV.
  • Send mails to users whose passwords have been generated automatically.

It can be done using both a management command and the admin interface.

batch_add_users

This command imports users from a csv file. Usage is as shown below.

./manage.py batch_add_users --name <name_of_batch> \
                            --file <filepath> \
                            --expiration <expiration_date> \
                            --password-length <password_length>

Note that the expiration and password-length are optional parameters which default to never and 8 respectively.

Using the admin interface

Selecting the CSV as the strategy and uploading the CSV file is all one will have to do to import the CSV file from the admin interface. It can be checked at /admin/radiusbatch/add.

It is important to take care of the following when importing users from the CSV.

CSV Format

The CSV shall be of the format:

username,password,email,firstname,lastname

Imported users with hashed passwords

The hashes are directly stored in the database if they are of the django hash format.

For example, a password myPassword123, hashed using salted SHA1 algorithm, will look like:

pbkdf2_sha256$100000$cKdP39chT3pW$2EtVk4Hhm1V65GNfYAA5AHj0uyD60f2CmqumqiB/gRk=

So a full CSV line containing that password would be:

username,pbkdf2_sha256$100000$cKdP39chT3pW$2EtVk4Hhm1V65GNfYAA5AHj0uyD60f2CmqumqiB/gRk=,email@email.com,firstname,lastname

Importing users with clear-text passwords

Clear-text passwords must be flagged with the prefix cleartext$.

For example, if we want to use the password qwerty, we must use: cleartext$qwerty.

Autogeneration of usernames and passwords

Email is the only mandatory field of the CSV file.

Other fields like username and password will be auto-generated if omitted.

Batch mail settings

Emails can be sent to users whose usernames or passwords have been autogenerated and contents of these emails can be customized too. Here are some defined settings for doing that:

Important

The development of django-freeradius has moved to openwisp-radius, we highly suggest users and readers of this documentation to migrate or use the more up to date documentation of openwisp-radius.

Generating users

Many a times, a network admin might need to generate temporary users for events etc. This feature can be used for generating users by specifying a prefix and the number of users to be generated. There are many features included in it such as:

  • Generating users in batches: all of the users of a particular prefix would be stored in batches and can be retrieved/ deleted easily using the batch functions.
  • Set an expiration date: Expiration date can be set for a batch after which the users would not able to authenticate to the RADIUS Server.
  • PDF: Get the usernames and passwords generated outputted into a PDF.

This can be accomplished from both the admin interface and the management command.

prefix_add_users

This command generates users whose usernames start with a particular prefix. Usage is as shown below.

./manage.py prefix_add_users --name <name_of_batch> \
                             --prefix <prefix> \
                             --n <number_of_users>
                             --expiration <expiration_date> \
                             --password-length <password_length>

Note that the expiration and password-length are optional parameters which default to never and 8 respectively.

Adding from admin inteface

At the url /admin/django_freeradius/radiusbatch/add one can directly generate users using the prefix and the number of users. A PDF can be downloaded immediately after the users have been generated.

Important

The development of django-freeradius has moved to openwisp-radius, we highly suggest users and readers of this documentation to migrate or use the more up to date documentation of openwisp-radius.

Enforcing session limits

The default freeradius schema does not include a table where groups are stored, but django-freeradius adds a model called RadiusGroup and alters the default freeradius schema to add some optional foreign-keys from other tables like:

  • radgroupcheck
  • radgroupreply
  • radusergroup

These foreign keys make it easier to automate many synchronization and integrity checks between the RadiusGroup table and its related tables but they are not strictly mandatory from the database point of view: their value can be NULL and their presence and validation is handled at application level, this makes it easy to use existing freeradius databases.

For each group, checks and replies can be specified directly in the edit page of a Radius Group (admin > groups > add group or change group).

Default groups

Some groups are created automatically by django-freeradius during the initial migrations:

  • users: this is the deafult group which limits users sessions to 3 hours and 300 MB (daily)
  • power-users: this group does not have any check, therefore users who are members of this group won’t be limited in any way

You can customize the checks and the replies of these groups, as well as create new groups according to your needs and preferences.

Note on the default group: keep in mind that the group flagged as default will by automatically assigned to new users, it cannot be deleted nor it can be flagged as non-default: to set another group as default simply check that group as the deafult one, save and django-freeradius will remove the default flag from the old default group.

Freeradius configuration

Ensure the sqlcounter module is enabled and configured as described in Configure the SQL counters.

Important

The development of django-freeradius has moved to openwisp-radius, we highly suggest users and readers of this documentation to migrate or use the more up to date documentation of openwisp-radius.

Registration of new users

Django-freeradius does not ship logic related to registration of new users because there are many good django packages that are aimed at solving that solution.

We recommend using django-rest-auth which provides registration of new users via REST API so you can implement registration and password reset directly from your captive page.

Setup

Install django-rest-auth and django-allauth:

pip install django-rest-auth django-allauth

Add the following to your settings.py:

INSTALLED_APPS = [
    # ... other apps ..
    # apps needed for registration
    'rest_framework.authtoken',
    'rest_auth',
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'rest_auth.registration',
]

SITE_ID = 1

Add the rest-auth urls to your main urls.py:

urlpatterns = [
    # ...
    url(r'^api/v1/rest-auth/', include('rest_auth.urls')),
    url(r'^api/v1/registration/', include('rest_auth.registration.urls'))
]

Registration in openwisp-radius

In openwisp-radius the dependencies and required settings are the same but the additional registration URL route does not need to be added to urls.py because a default route with the built-in registration view is shipped. This is done because the registration needs to take into account multi-tenancy, that is, the system must know which organization the user has to be assigned to when the registration is completed.

In openwisp-radius, the registration URL is:

/api/v1/registration/<organization_slug>/

Important

The development of django-freeradius has moved to openwisp-radius, we highly suggest users and readers of this documentation to migrate or use the more up to date documentation of openwisp-radius.

Social Login

Social login is supported by generating an additional temporary token right after users perform the social sign-in, the user is then redirected to the captive page with two querystring parameters: username and token.

The captive page must recognize these two parameters and automatically perform the submit action of the login form: username should obviously used for the username field, while token should be used for the password field.

The internal REST API of django-freeradius will recognize the token and authorize the user.

This kind of implementation allows to implement the social login with any captive portal which already supports the RADIUS protocol because it’s totally transparent for it, that is, the captive portal doesn’t even know the user is signing-in with a social network.

Setup

Install django-allauth:

pip install django-allauth

Ensure your settings.py looks like the following (we will show how to configure of the facebook social provider):

INSTALLED_APPS = [
    # ... other apps ..
    # apps needed for social login
    'rest_framework.authtoken',
    'django.contrib.sites',
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    # showing facebook as an example
    # to configure social login with other social networks
    # refer to the django-allauth documentation
    'allauth.socialaccount.providers.facebook',
]

SITE_ID = 1

# showing facebook as an example
# to configure social login with other social networks
# refer to the django-allauth documentation
SOCIALACCOUNT_PROVIDERS = {
    'facebook': {
        'METHOD': 'oauth2',
        'SCOPE': ['email', 'public_profile'],
        'AUTH_PARAMS': {'auth_type': 'reauthenticate'},
        'INIT_PARAMS': {'cookie': True},
        'FIELDS': [
            'id',
            'email',
            'name',
            'first_name',
            'last_name',
            'verified',
        ],
        'VERIFIED_EMAIL': True,
    }
}

Ensure your main urls.py contains the allauth.urls:

urlpatterns = [
    # .. other urls ...
    url(r'^accounts/', include('allauth.urls')),
]

Configure the social account application

Refer to the django-allauth documentation to find out how to complete the configuration of a sample facebook login app.

Captive page button example

Following the previous example configuration with facebook, in your captive page you will need an HTML button similar to the ones in the following examples.

django-freeradius

<a href="https://openwisp2.mywifiproject.com/accounts/facebook/login/?next=%2Ffreeradius%2Fsocial-login%2F%3Fcp%3Dhttps%3A%2F%2Fcaptivepage.mywifiproject.com%2F%26last%3D"
   class="button">Log in with Facebook
</a>

Substitute openwisp2.mywifiproject.com and captivepage.mywifiproject.com with the hostname of your django-freeradius instance and your captive page respectively.

openwisp-radius

This example works for openwisp-radius (multitenant version of django-freeradius), which needs the slug of the organization to assign the new user to the right organization:

<a href="https://openwisp2.mywifiproject.com/accounts/facebook/login/?next=%2Ffreeradius%2Fsocial-login%2Fdefault%2F%3Fcp%3Dhttps%3A%2F%2Fcaptivepage.mywifiproject.com%2F%26last%3D"
   class="button">Log in with Facebook
</a>

Substitute openwisp2.mywifiproject.com, captivepage.mywifiproject.com and default with the hostname of your openwisp-radius instance, your captive page and the organization slug respectively.

Important

The development of django-freeradius has moved to openwisp-radius, we highly suggest users and readers of this documentation to migrate or use the more up to date documentation of openwisp-radius.

API Documentation

django-freeradius provides an API that can be used by freeradius to perform the following operations:

  • Authorize
  • Accounting
  • Post Auth

The API also provides other features that can be useful to perform integrations with third-party software:

  • Batch User Creation
  • Login (Obtain User Auth Token)

API Token

Only requests containing the right API token will able to talk to the API endpoints.

Remember to set API token of your instance by setting DJANGO_FREERADIUS_API_TOKEN in your django settings.py.

It is highly recommended that you use a hard to guess value, longer than 15 characters containing both letters and numbers. Eg:

DJANGO_FREERADIUS_API_TOKEN = "165f9a790787fc38e5cc12c1640db2300648d9a2"

HTTP clients must send this token, either in the form of a bearer token or in the form of a query string parameter as shown below.

  • Bearer token (recommended):

    curl -X POST http://localhost:8000/api/v1/authorize/ \
         -H "Authorization: Bearer <token>" \
         -d "username=<username>&password=<password>"
    
  • Querystring:

    curl -X POST http://localhost:8000/api/v1/authorize/?token=<token> \
         -d "username=<username>&password=<password>"
    

Requests which contain an invalid token will receive a 403 HTTP error.

For information on how to configure FreeRADIUS to send the bearer token, see Configure the REST module.

Accounting

/api/v1/accounting/

GET

Returns a list of accounting objects

GET /api/v1/accounting/
[
  {
      "called_station_id": "00-27-22-F3-FA-F1:hostname",
      "nas_port_type": "Async",
      "groupname": null,
      "id": 1,
      "realm": "",
      "terminate_cause": "User_Request",
      "nas_ip_address": "172.16.64.91",
      "authentication": "RADIUS",
      "stop_time": null,
      "nas_port_id": "1",
      "service_type": "Login-User",
      "username": "admin",
      "update_time": null,
      "connection_info_stop": null,
      "start_time": "2018-03-10T14:44:17.234035+01:00",
      "output_octets": 1513075509,
      "calling_station_id": "5c:7d:c1:72:a7:3b",
      "input_octets": 9900909,
      "interval": null,
      "session_time": 261,
      "session_id": "35000006",
      "connection_info_start": null,
      "framed_protocol": "test",
      "framed_ip_address": "127.0.0.1",
      "unique_id": "75058e50"
  }
]

POST

Add or update accounting information (start, interim-update, stop); does not return any JSON response so that freeradius will avoid processing the response without generating warnings

Param Description
session_id Session ID
unique_id Accounting unique ID
username Username
groupname Group name
realm Realm
nas_ip_address NAS IP address
nas_port_id NAS port ID
nas_port_type NAS port type
start_time Start time
update_time Update time
stop_time Stop time
interval Interval
session_time Session Time
authentication Authentication
connection_info_start Connection Info Start
connection_info_stop Connection Info Stop
input_octets Input Octets
output_octets Output Octets
called_station_id Called station ID
calling_station_id Calling station ID
terminate_cause Termination Cause
service_type Service Type
framed_protocol Framed protocol
framed_ip_address framed IP address
Filters

The JSON objects returned using the GET endpoint can be filtered/queried using specific parameters.

Filter Parameters Description
username Username
called_station_id Called Station ID
calling_station_id Calling Station ID
start_time Start time (greater or equal to)
stop_time Stop time (less or equal to)
is_open If stop_time is null

Authorize

/api/v1/authorize/

Responds to only POST, used for authorizing a given username and password.

POST /api/v1/authorize/ HTTP/1.1 username=testuser&password=testpassword
Param Description
username Username for the given user
password Password for the given user

See also DJANGO_FREERADIUS_API_AUTHORIZE_REJECT.

PostAuth

/api/v1/postauth/

Sets the response data to None in order to instruct FreeRADIUS to avoid processing the response body.

Responds only to POST.

Batch user creation

/api/v1/batch/

Note

This API endpoint allows to use the features described in Importing users and Generating users.

Responds only to POST, used to save a RadiusBatch instance. It returns the information of the batch operation and the list of the users generated. It is possible to generate the users of the RadiusBatch with two different strategies: csv or prefix.

The csv method needs the following parameters:

Param Description
name Name of the operation
strategy “csv”
csvfile file with the users
expiration_date date of expiration of the users

These others are for the prefix method:

Param Description
name name of the operation
strategy prefix
prefix prefix for the generation of users
number_of_users number of users
expiration_date date of expiration of the users

Login (Obtain User Auth Token)

/api/v1/account/token/

Note

This endpoint does not require the sending of the API Token described in the beginning of this document.

Responds only to POST, this endpoint is enabled only if rest_framework.authtoken is in settings.INSTALLED_APPS (which is optional).

Returns the user access token, which can be used to authenticate the user via the freeradius authorization mechanism.

Parameters:

Param Description
username string
password string

Important

The development of django-freeradius has moved to openwisp-radius, we highly suggest users and readers of this documentation to migrate or use the more up to date documentation of openwisp-radius.

How to extend django-freeradius

django-freeeadius provieds set of models, admin and API classes which can be imported, extended and hence customized by third party apps.

Update Settings

Update the settings to trigger the swapper:

# In settings.py of your project

DJANGO_FREERADIUS_RADIUSREPLY_MODEL = "my_radius_app.RadiusReply"
DJANGO_FREERADIUS_RADIUSGROUPREPLY_MODEL = "my_radius_app.RadiusGroupReply"
DJANGO_FREERADIUS_RADIUSCHECK_MODEL = "my_radius_app.RadiusCheck"
DJANGO_FREERADIUS_RADIUSGROUPCHECK_MODEL = "my_radius_app.RadiusGroupCheck"
DJANGO_FREERADIUS_RADIUSACCOUNTING_MODEL = "my_radius_app.RadiusAccounting"
DJANGO_FREERADIUS_NAS_MODEL = "my_radius_app.Nas"
DJANGO_FREERADIUS_RADIUSUSERGROUP_MODEL = "my_radius_app.RadiusUserGroup"
DJANGO_FREERADIUS_RADIUSPOSTAUTHENTICATION_MODEL = "my_radius_app.RadiusPostAuth"

# where my_radius_app is name of your app extending django_freeradius

Extend models

Apart from extending implemented models, django_freeradius also provides flexibility to extend abstract class models from django-freeradius.base.models.

Example:

# In my_radius_app/models.py

from django.db import models
from django_freeradius.base.models import AbstractRadiusCheck

class RadiusCheck(AbstractRadiusCheck):
    # modify/extend the default behavour here
    custom_field = models.TextField()

Similary, you can extend other model classes from django_freeradius.base.models.

Extend admin

Similar to models, abstract admin classes from django_freeradius.base.admin can also be extended to avoid duplicate code.

# In my_radius_app/admin.py

from django.contrib import admin
from .models import RadiusCheck
from django_freeradius.base.admin import AbstractRadiusAccountingAdmin

class RadiusCheckAdmin(AbstractRadiusCheckAdmin):
    model = RadiusCheck
    # modify/extend default behaviour here
    fields = AbstractRadiusCheckAdmin.fields + ['custom_field']
    list_display = AbstractRadiusCheckAdmin.list_display + ['custom_field']

admin.site.register(RadiusCheck, RadiusCheckAdmin)

Note

For a real world implementation of extending django-freeradius.base.admin, refer openswisp-radius.admin

Extend AppConfig

You can also extend AppConfig class from django_freeradius.apps.DjangoFreeradiusConfig and provide support for your signals and hooks.

# In my_radius_app/apps.py

from django.conf import settings
from django_freeradius.apps import DjangoFreeradiusConfig
from django.core.exceptions import ImproperlyConfigured

API_TOKEN = settings.DJANGO_FREERADIUS_API_TOKEN

class MyRadiusAppConfig(DjangoFreeradiusConfig):
    name = 'my_radius_app'

    # Overiding DjangoFreeradiusConfig.check_settings
    # just for the sake of example, we add a check which ensures the
    # DJANGO_FREERADIUS_API_TOKEN settings is defined and is at
    # least 20 characters long.
    def check_settings(self):
        if API_TOKEN and len(API_TOKEN) < 20 or not API_TOKEN:
            def check_settings(self):
    if API_TOKEN and len(API_TOKEN) < 20 or not API_TOKEN:
        raise ImproperlyConfigured(
            'Security error: DJANGO_FREERADIUS_API_TOKEN is either not set or is less than 20 characters.')

Extend API views

You can also extend API views from django_freeradius.api.views to your suit your models.

# In my_radius_app/api/views.py

from django_freeradius.api.views import AuthorizeView, AuthorizeView

class RadiusTokenAuthentication(TokenAuthentication):
    # modify/extend default behaviour here
    pass

class RadiusAuthorizeView(AuthorizeView):
    # use your modified authentication class
    authentication_classes = (RadiusTokenAuthentication,)

authorize = RadiusAuthorizeView.as_view()

Note

For a real world implementation of extending django-freeradius.api, refer openwisp-radius.api

Important

The development of django-freeradius has moved to openwisp-radius, we highly suggest users and readers of this documentation to migrate or use the more up to date documentation of openwisp-radius.

Contributing

Thank you for taking the time to contribute to django-freeradius.

Follow these guidelines to speed up the process.

Note

In order to have your contribution accepted faster, please read the OpenWISP contributing guidelines and make sure to follow its guidelines.

Setup

Once you have chosen an issue to work on, setup your machine for development

Ensure test coverage does not decrease

First of all, install the test requirements:

workon radius  # activate virtualenv
pip install --no-cache-dir -U -r requirements-test.txt

When you introduce changes, ensure test coverage is not decreased with:

coverage run --source=django_freeradius runtests.py

Follow style conventions (PEP8, isort, JSLint)

First of all, install the test requirements:

workon radius  # activate virtualenv
pip install --no-cache-dir -U -r requirements-test.txt
npm install -g jslint

Before committing your work check that your changes are not breaking the style conventions with:

./runflake8
./runisort
jslint ./django_freeradius/static/django-freeradius/js/*.js

For more information, please see:

Update the documentation

If you introduce new features or change existing documented behavior, please remember to update the documentation!

The documentation is located in the /docs directory of the repository.

To do work on the docs, proceed with the following steps:

workon radius  # activate virtualenv
pip install sphinx
cd docs
make html

Send pull request

Now is time to push your changes to github and open a pull request!

Important

The development of django-freeradius has moved to openwisp-radius, we highly suggest users and readers of this documentation to migrate or use the more up to date documentation of openwisp-radius.

Motivations and Goals

In this page we explain the goals of this project and the motivations that led us on this path.

Motivations

The old version of OpenWISP (which we call OpenWISP 1) had a freeradius module which provided several interesting features:

  • user registration
  • account verification with several methods
  • user management
  • password reset
  • basic general statistics
  • basic user account page with user’s statistics

But it also had important problems:

  • it was not written with automated testing in mind, so there was a lot of code which the maintainers didn’t want to touch because of fear of breaking existing features
  • it was not written with an international user-base in mind, it contained a great deal of code which was specific to a single country (Italy)
  • it was hard to extend, even small changes required changing its core code
  • the user management code was implemented in a different way compared to other openwisp1 modules, which added a lot of maintenance overhead
  • it used outdated dependencies which over time became vulnerable and were hard to replace
  • it did not perform hashing of user passwords
  • the documentation did not explain how to properly install and configure the software

Similar problems were affecting other modules of OpenWISP 1, that’s why over time we got convinced the best thing was to start fresh using best practices since the start.

Project goals

The main aim of this project is to offer a web application and documentation that helps people from all over the world to implement a wifi network that can use freeradius to authenticate its users, either via captive portal authentication or WPA2 enterprise, BUT this doesn’t mean we want to lock the software to this use case: we want to keep the software generic enough so it can be useful to implement other use cases that are related to networking connectivity and network management; just keep in mind our main aim if you plan to contribute to django-freeradius please.

Other goals are listed below:

  • replace the user management system of OpenWISP 1 by providing a similar feature set
  • provide a web interface to manage a freeradius database
  • provide abstract models and admin classes that can be imported, extended and reused in third party apps
  • provide ways to extend the logic of django-freeradius without changing its core
  • ensure the code is written with an international audience in mind
  • maintain a very good automated test suite
  • reuse the django user management logic which is very robust and stable
  • ensure passwords are hashed with strong algorithms and freeradius can authorize/authenticate using these hashes (that’s why we recommend using the rml_rest freeradius module with the REST API of django-freeradius)
  • integrate django-freeradius with the rest of the openwisp2 ecosystem
  • provide good documentation on how to install the project, configure it with freeradius and use its most important features

Indices and tables