Skip to content

Resilient configuration for FreeRADIUS authenticating against Microsoft Entra ID

License

Notifications You must be signed in to change notification settings

UniBuc-DITC/freeradius-entra-id-redundant-setup

Repository files navigation

RADIUS protocol with Microsoft Entra ID back end

Description

This repository documents how to allow RADIUS-based clients to authenticate against a Microsoft Entra ID (formerly Azure AD) tenant with the help of FreeRADIUS.

The configuration described here also adds support for account lockout based on repeatedly failed login attempts and the secure caching of authentication credentials (password hashes).

Tools used

Setup guide

  1. Follow the official getting started guide to install FreeRADIUS on your server.

    You can check that the basic setup works by authenticating as an user with a hardcoded password, as described in the Initial tests section of the guide. See the clients.conf and the authorize files for an example config.

    The test-connection.sh script can also be used for this purpose; it is a thin wrapper around the radtest command. Example uses:

    • Try to authenticate using the username user and the password pass (default credentials for the hardcoded user defined in authorize):
     ./test-connection.sh
    • Try to authenticate using the given username and password:
    ./test-connection.sh username password
  2. Follow the instructions from the freeradius-oauth2-perl repo to allow FreeRADIUS to authenticate requests against Microsoft Entra ID.

    At this point, you should be able to test that authentication works with the help of the radtest command or the test-connection.sh script:

    ./test-connection.sh user@tenant.onmicrosoft.com <password>
  3. Install PostgreSQL and Redis on your server. Both services should be configured to start automatically (since FreeRADIUS will depend on them).

    It is imperative to set up a firewall in order to block external connections to PostgreSQL or Redis.

  4. You might also need to install some additional dependencies:

    • If you're using the official FreeRADIUS Docker image, it already comes bundled with all the possible modules.

    • On Ubuntu Server, you can install the packages required for PostgreSQL and Redis support using:

    sudo apt install freeradius-postgresql freeradius-redis

Configure account lockout after repeatedly failed login attempts

  1. Create a PostgreSQL user and database for FreeRADIUS.

    With Docker, this can be done automatically by configuring the POSTGRES_USER, POSTGRES_PASSWORD and POSTGRES_DB environment variables (see compose.yaml for an example).

    Otherwise, you can do it easily using the command line tool psql. Start an interactive PostgreSQL session by using

    sudo -u postgres psql

    and then run

    CREATE USER freeradius WITH PASSWORD '<some secure password>';
    CREATE DATABASE freeradius;
    GRANT ALL PRIVILEGES ON DATABASE freeradius TO freeradius;

    to create a new user called freeradius, with a secure password choosen by you, with complete access to the newly created freeradius database.

  2. Initialize the required tables in the newly-created database.

    If using Docker, you can define a database initialization script to be run when the container is first created. See the create-database.sh script and the corresponding lines in compose.yaml.

    Otherwise, connect to the database by running

    psql --host=localhost --dbname=freeradius --username=freeradius

    and inputting the password you've defined above, then create the failed_logins table by running:

    CREATE TABLE failed_logins (
        id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
        username text NOT NULL CHECK (username <> ''),
        time timestamptz NOT NULL
    );
    CREATE INDEX failed_logins_username_index ON failed_logins (username);
  3. Enable the SQL module for FreeRADIUS, by linking the corresponding file from the mods-available directory to the mods-enabled directory (e.g. something like ln -s /etc/freeradius/mods-available/sql /etc/freeradius/mods-enabled/).

  4. Configure the SQL module (you can use the configuration from this repo as a guiding example).

    You'll want to set dialect = "postgresql", driver = "rlm_sql_${dialect}" (the exact name of the driver depends on which distribution of FreeRADIUS you're using), as well as the server, port, login, password and radius_db attributes (use the values you've defined when setting up the PostgreSQL database).

    Since we're only interested in using PostgreSQL to track failed login attempts, we can disable other integrations by setting read_groups = no, read_profiles = no and read_clients = no. Also remember to comment out / remove any references to the sql module in your default site's config file (see the config in this repo for an example).

  5. Set up the lockout policy. This is mostly based on the official guide for adding account lockout to FreeRADIUS.

    Copy the lockout policy file to your /etc/freeradius/policy.d directory and update the default site's config:

    authorize {
      lockout_check
      ...
    }
    post-auth {
      Post-Auth-Type REJECT {
        lockout_incr
      }
      ...
    }
    

    The default lockout policy is configured to block all login attempts (even ones with correct credentials) after 5 failed attempts in the last 10 minutes. This is done to discourage brute force / password guessing attacks.

  6. Verify that the newly configured lockout policy works as expected. Try to connect once with valid credentials:

    ./test-connection.sh user@tenant.onmicrosoft.com <password>

    Then at least 5 times using the wrong password:

    for i in {1..5}
    do
      ./test-connection.sh user@tenant.onmicrosoft.com bad-password
    done

    At this point, even using the correct password should fail:

    ./test-connection.sh user@tenant.onmicrosoft.com <password>

    You will have to wait 10 minutes or clear the database to be able to succesfully log in again.

Use Redis for persistently caching password hashes

  1. It's a good idea to secure your Redis instance with a password (which is not done by default). See this SO answer for instructions, or adapt the redis.conf file from this repo.

  2. Configure the freeradius-oauth-perl module to use Redis as a cache, instead of the in-memory RB tree implementation.

    This can be done by updating the module file (usually located at /opt/freeradius-oauth2-perl/module if you've followed the official installation instructions). Replace it with the variant of the file from this repo, or make the changes yourself:

    cache oauth2_cache {
      ...
    
      # Use Redis instead of `rlm_cache_rbtree`
      driver = "rlm_cache_redis"
    
      redis {
        server = 'redis' # Use `localhost` if your Redis instance is on the same machine
        port = 6379
        query_timeout = 5
        pool = redis
      }
    
      ...
    }

    There is currently a bug with the way FreeRADIUS serializes dates stored in external caches. Until it gets fixed, you will also have to update the dictionary file:

    # ATTRIBUTE	OAuth2-Password-Last-Modified	3000	date # This line has been commented out...
    ATTRIBUTE	OAuth2-Password-Last-Modified	3000	string # ...with this line taking its place.

    Depending on how you've set up the freeradius-oauth-perl module, you might also have to update the corresponding line in the /etc/freeradius/dictionary file.

  3. Verify that the new caching config works correctly. Restart your FreeRADIUS instance, then try to connect twice using valid credentials:

    ./test-connection.sh user@tenant.onmicrosoft.com <password>
    ./test-connection.sh user@tenant.onmicrosoft.com <password>

    The second time around, authentication should be nearly instant. You should find some similar log messages (if you're running FreeRADIUS in debug mode):

    rlm_redis (redis): Reserved connection (0)
    (3) oauth2_cache: Found entry for "user@tenant.onmicrosoft.com"
    (3) oauth2_cache: Merging cache entry into request
    ...
    (3) pap: Login attempt with password
    (3) pap: Comparing with "known-good" SSHA2-512-Password
    (3) pap: User authenticated successfully
    

It is woth mentioning that Redis has support for data persistency and it will (by default) regularly dump its contents to disk. More information can be found in the Redis documentation.

Set up multiple FreeRADIUS instances for redundancy

  1. One way of ensuring redundancy is to spin up multiple FreeRADIUS instances (with identical configurations) and place them behind a load balancer.

    The compose.yaml file exemplifies the creation of a cluster of two FreeRADIUS instances, both sharing the same database and password cache. Either one can be the target of a RADIUS authentication request.

    We use NGINX with its UDP Load Balancing feature to ensure the RADIUS requests get distributed to the back end servers in a round-robin fashion. See the nginx.conf file for the required settings.

Contributing

See the contributing instructions for information on how to set up your local development environment to work on this repo.

License

The code and configuration files in this repo are licensed under the GNU Affero General Public License, see the license file for details.

About

Resilient configuration for FreeRADIUS authenticating against Microsoft Entra ID

Resources

License

Stars

Watchers

Forks