DEBOPS.FREERADIUS(5)

NAME
debops.freeradius - Manage FreeRADIUS service using Ansible

SYNOPSIS

debops service/freeradius [--limit group,host,...] [--diff] [--check] [--tags tag1,tag2,...] [--skip-tags tag1,tag2,...] [<ansible-playbook options>] ...

DESCRIPTION

FreeRADIUS <https://freeradius.org/> is a RADIUS (Remote Authentication Dial-In User Service) server. It can be used to set up Authorization, Authentication and Accounting (AAA) services on a wired or wireless network.

The debops.freeradius Ansible role can be used to install and configure FreeRADIUS server on a Debian/Ubuntu hosts.

GETTING STARTED

  Example inventory

To install and manage FreeRADIUS on a host, it needs to be included in the [debops_service_freeradius] Ansible inventory group:

[debops_service_freeradius]
hostname

  Example playbook

If you are using this role without DebOps, here's an example Ansible playbook that uses the debops.freeradius role:

---
- name: Manage FreeRADIUS service
  collections: [ 'debops.debops', 'debops.roles01',
                 'debops.roles02', 'debops.roles03' ]
  hosts: [ 'debops_service_freeradius' ]
  become: True
  environment: '{{ inventory__environment | d({})
                   | combine(inventory__group_environment | d({}))
                   | combine(inventory__host_environment  | d({})) }}'
  roles:
    - role: ferm
      tags: [ 'role::ferm', 'skip::ferm' ]
      ferm__dependent_rules:
        - '{{ freeradius__ferm__dependent_rules }}'
    - role: cron
      tags: [ 'role::cron', 'skip::cron' ]
    - role: logrotate
      tags: [ 'role::logrotate', 'skip::logrotate' ]
      logrotate__dependent_config:
        - '{{ freeradius__logrotate__dependent_config }}'
    - role: freeradius
      tags: [ 'role::freeradius', 'skip::freeradius' ]

  Ansible tags

You can use Ansible --tags or --skip-tags parameters to limit what tasks are performed during Ansible run. This can be used after host is first configured to speed up playbook execution, when you are sure that most of the configuration has not been changed.

Available role tags:

role::freeradius
    Main role tag, should be used in the playbook to execute all of the role tasks as well as role dependencies.

  Other resources

List of other useful resources related to the debops.freeradius Ansible role:

•   Manual pages: radiusd.conf(5) <https://manpages.debian.org/radiusd.conf(5)>, unlang(5) <https://manpages.debian.org/unlang(5)>

•   FreeRADIUS Getting Started Guide <https://wiki.freeradius.org/guide/Getting-Started>

•   FreeRADIUS Technical Guide (PDF) <http://networkradius.com/doc/FreeRADIUS%20Technical%20Guide.pdf>

EXAMPLE EDUROAM SETUP

This is an example basic eduroam <https://en.wikipedia.org/wiki/Eduroam> configuration for the debops.freeradius Ansible role.

NOTE: The files are available in the DebOps monorepo, as separate YAML files in the docs/ansible/roles/debops.freeradius/examples/eduroam/ directory.

You can put these files in the Ansible inventory, in ansible/inventory/host_vars/<hostname>/ directory. After doing this and tweaking the configuration you should run the debops.freeradius and debops.resources Ansible roles against the host.

This configuration is based on the example eduroam configuration guide <https://wiki.freeradius.org/guide/eduroam> on the FreeRADIUS Wiki. You should check this page for detailed guide about this setup.

  FreeRADIUS configuration

---
# Configuration based on: https://wiki.freeradius.org/guide/eduroam
# DNS domain user by RADIUS
radius_domain: '{{ ansible_domain }}'
# Separate VLAN for external eduroam users, from other organizations
radius_guest_vlan: '101'
# Separate VLAN for internal eduroam users, from our organization
radius_local_vlan: '102'
# X.509 certificate configuration, based on DebOps PKI
radius_pki_realm: 'domain'
radius_cert_file: '/etc/pki/realms/{{ radius_pki_realm }}/default.crt'
radius_key_file: '/etc/pki/realms/{{ radius_pki_realm }}/default.key'
radius_ca_file: '/etc/pki/realms/{{ radius_pki_realm }}/CA.crt'
# Subnet on which WiFi Access Points can talk with RADIUS server
radius_access_point_subnet: '192.168.2.0/24'
# Shared passphrase for the Access Points to authenticate with the RADIUS
# Server
radius_access_point_password: '{{ lookup("password", secret
                                  + "/radius/known-secret-password") }}'
# Password of the client endpoint to authorized access to the network
radius_test_user_password: '{{ lookup("password", secret
                               + "/radius/default-test-password") }}'
# Allow access to FreeRADIUS service by WiFi Access Points
freeradius__host_allow: [ '{{ radius_access_point_subnet }}' ]
# Configuration for the 'debops.freeradius' Ansible role
freeradius__host_configuration:
  - name: 'sites-available/default'
    raw: |
      # The domain users will add to their username to have their credentials
      # routed to your institution.  You will also need to register this
      # and your RADIUS server addresses with your NRO.
      operator_name = "{{ radius_domain }}"
      # The VLAN to assign eduroam visitors
      eduroam_guest_vlan = "{{ radius_guest_vlan }}"
      # The VLAN to assign your students/staff
      eduroam_local_vlan = "{{ radius_local_vlan }}"
      server eduroam {
        listen {
          type = auth
          ipaddr = *
          port = 1812
        }
        authorize {
          # Log requests before we change them
          linelog_recv_request
          # split_username_nai is a policy in the default distribution to
          # split a username into username and domain.  We reject user-name
          # strings without domains, as they're not routable.
          split_username_nai
          if (noop || !&Stripped-User-Domain) {
            reject
          }
          # Send the request to the NRO for your region.
          # The details of the FLRs (Federation Level RADIUS servers)
          # are in proxy.conf.
          # You can make this condition as complex as you like, to
          # include additional subdomains just concatenate the conditions
          # with &&.
          if (&Stripped-User-Domain != "${operator_name}") {
            update {
              control:Load-Balance-Key := &Calling-Station-ID
              control:Proxy-To-Realm := 'eduroam_flr'
              # Operator name (RFC 5580) identifies the network the
              # request originated from. It's not absolutely necessary
              # but it helps with debugging.
              request:Operator-Name := "1${operator_name}"
            }
            return
          }
          # If the EAP module returns 'ok' or 'updated', it means it has handled
          # the request and we don't need to call any other modules in this
          # section.
          eap {
            ok = return
            updated = return
          }
        }
        pre-proxy {
          attr_filter.pre-proxy
          linelog_send_proxy_request
        }
        post-proxy {
          attr_filter.post-proxy
          linelog_recv_proxy_response
        }
        authenticate {
          eap
        }
        post-auth {
          # To implement eduroam you must:
          # - Use wireless access points or a controller which supports
          #   dynamic VLAN assignments.
          # - Have that feature enabled.
          # - Have the guest_vlan/local_vlan available to the controller,
          #   or to all your access points.
          # eduroam user traffic *MUST* be segregated, this is *NOT* optional.
          update reply {
            Tunnel-Type := VLAN
            Tunnel-Medium-Type := IEEE-802
          }
          if (&control:Proxy-To-Realm) {
            update reply {
              Tunnel-Private-Group-ID := ${eduroam_guest_vlan}
            }
          }
          else {
            update reply {
              Tunnel-Private-Group-ID := ${eduroam_local_vlan}
            }
          }
          # We're sending a response to one of OUR network devices for one of
          # OUR users so provide it with the real user-identity.
          if (&session-state:Stripped-User-Name) {
            update reply {
              User-Name := "%{session-state:Stripped-User-Name}@%{Stripped-User-Domain}"
            }
          }
          linelog_send_accept
          Post-Auth-Type REJECT {
            attr_filter.access_reject
            linelog_send_reject
          }
        }
      }
    state: 'present'
  - name: 'mods-available/eap'
    raw: |
      eap {
        # The initial EAP type requested.  Change this to peap if you're
        # using peap, or tls if you're using EAP-TLS.
        default_eap_type = ttls
        # The maximum time an EAP-Session can continue for
        timer_expire = 60
        # The maximum number of ongoing EAP sessions
        max_sessions = ${max_requests}
        tls-config tls-common {
          # The public certificate that your server will present
          certificate_file = {{ radius_cert_file }}
          # The private key for the public certificate
          private_key_file = {{ radius_key_file }}
          # The password to decrypt 'private_key_file'
          #private_key_password = whatever
          private_key_password = ''
          # The certificate of the authority that issued 'certificate_file'
          #ca_file = ${cadir}/ca.pem
          ca_file = {{ radius_ca_file }}
          # If your AP drops packets towards the client, try reducing this.
          fragment_size = 1024
          # When issuing client certificates embed the OCSP URL in the
          # certificate if you want to be able to revoke them later.
          ocsp {
            enable = yes
            override_cert_url = no
            use_nonce = yes
          }
        }
        tls {
          tls = tls-common
        }
        ttls {
          tls = tls-common
          default_eap_type = mschapv2
          virtual_server = "eduroam-inner"
        }
        peap {
          tls = tls-common
          default_eap_type = mschapv2
          virtual_server = "eduroam-inner"
        }
      }
    state: 'present'
  - name: 'mods-available/linelog'
    raw: |
      linelog linelog_recv_request {
        filename = syslog
        syslog_facility = local0
        syslog_severity = debug
        format = "action = Recv-Request, %{pairs:request:}"
      }
      linelog linelog_send_accept {
        filename = syslog
        syslog_facility = local0
        syslog_severity = debug
        format = "action = Send-Accept, %{pairs:request:}"
      }
      linelog linelog_send_reject {
        filename = syslog
        syslog_facility = local0
        syslog_severity = debug
        format = "action = Send-Reject, %{pairs:request:}"
      }
      linelog linelog_send_proxy_request {
        filename = syslog
        syslog_facility = local0
        syslog_severity = debug
        format = "action = Send-Proxy-Request, %{pairs:proxy-request:}"
      }
      linelog linelog_recv_proxy_response {
        filename = syslog
        syslog_facility = local0
        syslog_severity = debug
        format = "action = Recv-Proxy-Response, %{pairs:proxy-reply:}"
      }
    state: 'present'
  - name: 'proxy.conf'
    raw: |
      home_server eduroam_flr_server_1 {
              ipaddr = 127.0.0.1
              secret = secret
        status_check = status-server
      }
      # Only uncomment if there are two FLRS
      #home_server eduroam_flr_server_2 {
      #       ipaddr = <ip-address>
      #       secret = <secret>
      #       status_check = status-server
      #}
      home_server_pool eduroam_flr_pool {
              type = keyed-balance
              home_server = eduroam_flr_server_1
      # Only uncomment if there are two FLRS
      #       home_server = eduroam_flr_server_2
      }
      realm eduroam_flr {
              auth_pool = eduroam_flr_pool
              nostrip
      }
    state: 'absent'
    no_log: True
  - name: 'clients.conf'
    raw: |
      client localhost {
        ipaddr = 127.0.0.1
        secret = testing123
      }
      #client eduroam_flr_server_1 {
      #        ipaddr = <ip-address>
      #        secret = <secret>
      #        nastype = 'eduroam_flr'
      #}
      # As above, only uncomment if there are two federation level servers
      #client eduroam_flr_server_2 {
      #       ipaddr = <ip-address>
      #       secret = <secret>
      #       nastype = 'eduroam_flr'
      #}
      client wireless_access_points_mgmt {
        ipaddr = {{ radius_access_point_subnet }}
        # This should be long and random
        secret = {{ radius_access_point_password }}
      }
    state: 'present'
    no_log: True
  - name: 'sites-available/inner-tunnel'
    raw: |
      server eduroam-inner {
        listen {
          type = auth
          ipaddr = *
          port = 18120 # Used for testing only.  Requests proxied internally.
        }
        authorize {
          # The outer username is considered garabage for autz purposes, but
          # the domain portion of the outer and inner identities must match.
          split_username_nai
          if (noop || (&Stripped-User-Domain && \
              (&outer.Stripped-User-Domain != &Stripped-User-Domain))) {
            reject
          }
          # Make the user's real identity available to anything that needs
          # it in the outer server.
          update {
            &outer.session-state:Stripped-User-Name := &Stripped-User-Name
          }
          # EAP for PEAPv0 (EAP-MSCHAPv2)
          inner-eap {
            ok = return
          }
          # THIS IS SITE SPECIFIC
          #
          # The files module is *ONLY* used for testing.  It lets you define
          # credentials in a flat file, IT WILL NOT SCALE.
          #
          # - If you use OpenLDAP with salted password hashes you should
          #   call the 'ldap' module here and use EAP-TTLS-PAP as your EAP method.
          # - If you use OpenLDAP with cleartext passwords you should
          #   call the 'ldap' module here and use EAP-TTLS or PEAPv0.
          # - If you use an SQL DB with salted password hashes you should call
          #   the 'sql' module here and use EAP-TTLS-PAP as your EAP method.
          # - If you use an SQL DB with cleartext passwords you should call
          #   the 'sql' module here and use EAP-TTLS or PEAPv0.
          # - If you use Novell you should call the 'ldap' module here and
          #   set ``edir = yes`` in ``mods-available/ldap`` and use EAP-TTLS or
          #   PEAPv0.
          # - If you use Active Directory, you don't need anything here (remove
          #   the call to files) but you'll need to follow this
          #   [guide](freeradius-active-directory-integration-howto) and use
          #   EAP-TTLS-PAP or PEAPv0.
          # - If you're using EAP-TLS (i'm impressed!) remove the call to files.
          #
          # EAP-TTLS-PAP and PEAPv0 are equally secure/insecure depending on how the
          # supplicant is configured. PEAPv0 has a slight edge in that you need to
          # crack MSCHAPv2 to get the user's password (but this is not hard).
          files
          pap
          mschap
        }
        authenticate {
          inner-eap
          mschap
          pap
          # Comment pap, and uncomment the stanza below if you're using
          # Active Directory this will allow it to work with EAP-TTLS-PAP.
          #pap {
          #   ntlm_auth
          #}
        }
      }
    state: 'present'
  - name: 'mods-enabled/inner-eap'
    link_src: '../mods-available/inner-eap'
  - name: 'mods-available/inner-eap'
    raw: |
      eap inner-eap {
        default_eap_type = mschapv2
        timer_expire = 60
        max_sessions = ${max_requests}
        mschapv2 {
          send_error = yes
        }
      }
    state: 'present'
  - name: 'mods-config/files/authorize'
    comment: |
      This sets the same password for any user that tries to authenticate, do
      not use in production environment
    raw: |
      DEFAULT Cleartext-Password := '{{ radius_test_user_password }}'
    state: 'present'
    no_log: True

  Additional resources

The install-eapol_test script created by this configuration can be used to install the eapol_test command on either the same host as the FreeRADIUS server, or on a different, remote host, to test the connectivity over the network.

---
# Configuration based on: https://wiki.freeradius.org/guide/eduroam
# X.509 certificate configuration, based on DebOps PKI
radius_pki_realm: 'domain'
radius_cert_file: '/etc/pki/realms/{{ radius_pki_realm }}/default.crt'
radius_key_file: '/etc/pki/realms/{{ radius_pki_realm }}/default.key'
radius_ca_file: '/etc/pki/realms/{{ radius_pki_realm }}/CA.crt'
# Directory where test configuration files are stored
config_dir: '/srv/eapol-test'
# Secret passphrase for the Access Points to authenticate with the RADIUS
# Server
radius_access_point_password: '{{ lookup("password", secret
                                  + "/radius/known-secret-password") }}'
# Example user to test authentication to RADIUS
radius_test_user_identity: 'a_user@{{ ansible_domain }}'
# Password of the client endpoint to authorized access to the network
radius_test_user_password: '{{ lookup("password", secret
                               + "/radius/default-test-password") }}'
# Configuration for the 'debops.resources' Ansible role
resources__host_files:
  - content: |
      #!/bin/bash
      # Install eapol_test for testing RADIUS EAP connections
      sudo apt-get update
      sudo apt-get -yq install git build-essential \
                               libssl-dev devscripts \
                               pkg-config libnl-3-dev \
                               libnl-genl-3-dev
      git clone --depth 1 --no-single-branch https://github.com/FreeRADIUS/freeradius-server.git
      cd freeradius-server/scripts/ci/
      ./eapol_test-build.sh
      sudo cp ./eapol_test/eapol_test /usr/local/bin/
    dest: '/usr/local/bin/install-eapol_test'
    mode: '0755'
  - content: |
      #
      #   eapol_test -c eap-tls.conf -s "{{ radius_access_point_password }}" \
      #              -a <radius-ip-server>
      #
      network={
          key_mgmt=WPA-EAP
          eap=TTLS
          identity="{{ radius_test_user_identity }}"
          anonymous_identity="anonymous@{{ ansible_domain }}"
          # Uncomment to validate the server's certificate by checking
          # it was signed by this CA.
          ca_cert="{{ radius_ca_file }}"
          password="{{ radius_test_user_password }}"
          phase2="auth=PAP"
      }
    dest: '{{ config_dir }}/eap-tls.conf'
    mode: '0644'
  - content: |
      #
      #   eapol_test -c peap-mschapv2.conf -s "{{ radius_access_point_password }}" \
      #              -a <radius-ip-address>
      #
      network={
          key_mgmt=WPA-EAP
          eap=PEAP
          identity="{{ radius_test_user_identity }}"
          anonymous_identity="anonymous@{{ ansible_domain }}"
          # Uncomment to validate the server's certificate by checking
          # it was signed by this CA.
          ca_cert="{{ radius_ca_file }}"
          password="{{ radius_test_user_password }}"
          phase2="auth=MSCHAPV2 mschapv2_retry=0"
          phase1="peapver=0"
      }
    dest: '{{ config_dir }}/peap-mschapv2.conf'
    mode: '0644'
  - content: |
      #
      #   eapol_test -c tls.conf -s "{{ radius_access_point_password }}" \
      #              -a <radius-ip-address>
      #
      network={
          key_mgmt=WPA-EAP
          eap=TLS
          anonymous_identity="anonymous@{{ ansible_domain }}"
          # Uncomment to validate the server's certificate by checking
          # it was signed by this CA.
          ca_cert="{{ radius_ca_file }}"
          # supplicant's public cert
          client_cert="{{ radius_cert_file }}"
          # supplicant's private key
          private_key="{{ radius_key_file }}"
          # password to decrypt private key
          private_key_passwd=""
      }
    dest: '{{ config_dir }}/tls.conf'
    mode: '0644'

DEFAULT VARIABLE DETAILS

Some of debops.freeradius default variables have more extensive configuration than simple strings or lists, here you can find documentation and examples for them.

  freeradius__configuration

The freeradius__*_configuration variables define the contents of the FreeRADIUS configuration files located in the /etc/freeradius/ directory. The variables contain a list of YAML dictionaries, each dictionary describes one "object" (file or symlink). The dictionaries with the same name parameter are merged together; it is possible to override existing entries by later ones. This can be used to modify the configuration conditionally.

  Examples

Define a custom radiusd.conf configuration file with contents specified in a YAML text block:

freeradius__configuration:
  - name: 'radiusd.conf'
    raw: |
      # Contents of the configuration file
      # ...
      # Contents of the configuration file
    state: 'present'

Enable the LDAP support via a symlink:

freeradius__configuration:
  - name: 'mods-enabled/ldap'
    link_src: '../mods-available/ldap'

Define custom client list with passwords in the configuration file; each section can be modified separately in other configuration entries:

freeradius__configuration:
  - name: 'clients.conf'
    no_log: True
    options:
      - name: 'client_localhost'
        comment: |
          Local client, used for testing
        raw: |
          client localhost {
              ipaddr = 127.0.0.1
              secret = testing123
          }
        state: 'present'
      - name: 'client_ap_mgmt'
        raw: |
          client ap_mgmt {
              ipaddr = 192.0.2.0/24
              secret = pa$$word
          }
        state: 'present'

Include a directory from a config file:

freeradius__configuration:
  - name: 'custom.conf'
    comment: |
      Include custom configuration from a directory
    raw: |
      $INCLUDE custom-dir/
    state: 'present'

  Syntax

Each YAML dictionary entry can use specific set of parameters:

name
    Required. Name of the configuration file or symlink to manage. This parameter is used as an anchor to merge multiple configuration entries together.

    The specified configuration files are relative to the path defined in the freeradius__conf_base_path variable. You need to specify the full filenames with extensions, you can specify files in subdirectories as normal.

filename
    Optional. An alternative filename to use instead of the one in name.

owner
    Optional. Specify UNIX account which will be other owner of the generated file. If not specified, freeradius__user will be the owner.

group
    Optional. Specify UNIX group of the generated file. If not specified, freeradius__group will be set as the group.

mode
    Optional. Specify the file mode/attributes of the generated file. If not specified, 0640 will be used by default.

divert
    Optional, boolean. If True, the role will use the dpkg-divert command to move the original configuration file packaged in the freeradius-config APT package out of the way to preserve it for reference and to not create issues during package upgrades. The file will be reverted back when the state parameter is set to absent.

    The role checks what files in the FreeRADIUS configuration directory are marked as Debian Conffiles and automatically diverts/reverts them as necessary. This parameter can be used to override this behaviour.

    The diverted files have a dot (.) prepended to them, this will ensure that the files will be ignored <https://networkradius.com/doc/3.0.10/raddb/syntax/config_include.html> by the FreeRADIUS $INCLUDE dir/ directive.

divert_filename
    Optional. An alternative name of the diverted file. This parameter can be used to override the name automatically generated by the role.

link_src
    If specified, the file defined in the name parameter will be a symlink to the file defined by this parameter. You can use relative paths, for example:

    freeradius__configuration:
      - name: 'sites-enabled/default'
        link_src: '../sites-available/default'

    If the state parameter is set to absent, the symlink will be removed.

raw A string or YAML text block with FreeRADIUS configuration. It will be put in the generated configuration file as-is.

options
    An alternative way to specify the contents of the configuration file. This is a list of YAML dictionaries, each dictionary can use parameters:

name
    The name of the configuration section. If value parameter is present, it will be used as the variable name on the left side of the equal expression. If the raw parameter is present, the value of the name parameter is not used in the configuration file. This parameter is used as an anchor to merge options from multiple configuration entries.

value
    If specified, it will be used as the value on the right site of the equal expression.

raw String or YAML text block with FireeRADIUS configuration. It will be put in the generated configuration file as-is.

comment
    String or YAML text block with comments about a given configuration section.

state
    If not specified or present, a given configuration section will be included in the generated file. If absent, a given configuration section will not be included in the configuration file. If ignore, a given configuration entry will not be evaluated by the role.

    The options lists from multiple configuration entries are merged together. You can use this to create a FreeRADIUS configuration files from multiple parts that can be conditionally activated.

state
    Optional. If not specified or present, a given configuration file or symlink will be created. If absent, a given configuration file or symlink will be removed (original file will be reverted, if it was diverted). If ignore, a given configuration entry will not be evaluated by the role.

no_log
    Optional, boolean. If True, Ansible will not log the task for a given file. This is useful to enable when files contain passwords, so that they will not be leaked in the logs.

comment
    Optional. String or YAML text block with comments included at the beginning of the generated file.

AUTHOR
Maciej Delmanowski

COPYRIGHT
2014-2022, Maciej Delmanowski, Nick Janetakis, Robin Schneider and others