Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Allowed deploying WPA Enterprise 2 TTLS-PAP #462 #471

Merged
merged 12 commits into from
Jul 3, 2024
Merged
216 changes: 216 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,176 @@ When the playbook is done running, if you got no errors you can login at:
look for the word "radius" in the
[Role variables](#role-variables) section of this document.

### Configuring FreeRADIUS for WPA Enterprise (EAP-TTLS-PAP)

You can use OpenWISP RADIUS for setting up WPA Enterprise (EAP-TTLS-PAP)
authentication. This allows to authenticate on WiFi networks using Django
user credentials. Prior to proceeding, ensure you've reviewed the tutorial
on [Setting Up WPA Enterprise (EAP-TTLS-PAP) authentication](https://openwisp.io/docs/tutorials/wpa-enterprise.html).
This documentation section complements the tutorial and focuses solely on
demonstrating the ansible role's capabilities to configure FreeRADIUS.

**Note**: The ansible role supports OpenWISP's multi-tenancy by creating
individual FreeRADIUS sites for each organization. You must include
configuration details for **each organization** that will use WPA
Enterprise.

Here's an example playbook which enables OpenWISP RADIUS module,
installs FreeRADIUS, and configures it for WPA Enterprise (EAP-TTLS-PAP):

```yaml
- hosts: openwisp2
become: "{{ become | default('yes') }}"
roles:
- openwisp.openwisp2
vars:
openwisp2_radius: true
openwisp2_freeradius_install: true
# Define a list of dictionaries detailing each organization's
# name, UUID, RADIUS token, and ports for authentication,
# accounting, and the inner tunnel. These details will be used
# to create FreeRADIUS sites tailored for WPA Enterprise
# (EAP-TTLS-PAP) authentication per organization.
freeradius_eap_orgs:
# A reference name for the organization,
# used in FreeRADIUS configurations.
# Don't use spaces or special characters.
- name: openwisp
# UUID of the organization.
# You can retrieve this from the organization admin
# in the OpenWISP web interface.
uuid: 00000000-0000-0000-0000-000000000000
# Radius token of the organization.
# You can retrieve this from the organization admin
# in the OpenWISP web interface.
radius_token: secret-radius-token
# Port used by the authentication service for
# this FreeRADIUS site
auth_port: 1822
# Port used by the accounting service for this FreeRADIUS site
acct_port: 1823
# Port used by the authentication service of inner tunnel
# for this FreeRADIUS site
inner_tunnel_auth_port: 18230
# If you want to use a custom certificate for FreeRADIUS
# EAP module, you can specify the path to the CA, server
# certificate, and private key, and DH key as follows.
# Ensure that these files can be read by the "freerad" user.
cert: /etc/freeradius/certs/cert.pem
private_key: /etc/freeradius/certs/key.pem
ca: /etc/freeradius/certs/ca.crt
dh: /etc/freeradius/certs/dh
tls_config_extra: |
private_key_password = whatever
ecdh_curve = "prime256v1"

# You can add as many organizations as you want
- name: demo
uuid: 00000000-0000-0000-0000-000000000001
radius_secret: demo-radius-token
auth_port: 1832
acct_port: 1833
inner_tunnel_auth_port: 18330
# If you omit the certificate fields,
# the FreeRADIUS site will use the default certificates
# located in /etc/freeradius/certs.
```

**Note**: In the example above, custom ports 1822, 1823, and 18230
are utilized for FreeRADIUS authentication, accounting, and inner tunnel
authentication, respectively. These custom ports are specified because the
Ansible role creates a common FreeRADIUS site for all organizations, which
also supports captive portal functionality. This common site is configured
to listen on the default FreeRADIUS ports 1812, 1813, and 18120. Therefore,
when configuring WPA Enterprise authentication for each organization,
unique ports must be provided to ensure proper isolation and functionality.

#### Using Let's Encrypt Certificate for WPA Enterprise (EAP-TTLS-PAP)

In this section, we demonstrate how to utilize Let's Encrypt certificates
for WPA Enterprise (EAP-TTLS-PAP) authentication. Similar to the
[Automatic SSL certificate](#automatic-ssl-certificate), we use
[geerlingguy.certbot](https://galaxy.ansible.com/geerlingguy/certbot/)
role to automatically install and renew a valid SSL certificate.

The following example playbook achieves the following goals:

- Provision a separate Let's Encrypt certificate for the
`freeradius.yourdomain.com` hostname. This certificate will be
utilized by the FreeRADIUS site for WPA Enterprise authentication.
- Create a renewal hook to set permissions on the generated certificate
so the FreeRADIUS server can read it.

**Note**: You can also use the same SSL certificate for both Nginx and
FreeRADIUS, but it's crucial to understand the security implications.
Please exercise caution and refer to the example playbook comments for
guidance.

```yaml
- hosts: openwisp2
become: "{{ become | default('yes') }}"
roles:
- geerlingguy.certbot
- openwisp.openwisp2
vars:
# certbot configuration
certbot_auto_renew_minute: "20"
certbot_auto_renew_hour: "5"
certbot_create_if_missing: true
certbot_auto_renew_user: "<privileged-users-to-renew-certs>"
certbot_certs:
- email: "<paste-your-email>"
domains:
- "{{ inventory_hostname }}"
# If you choose to re-use the same certificate for both services,
# you can omit the following item in your playbook.
- email: "<paste-your-email>"
domains:
- "freeradius.yourdomain.com"
# Configuration to use Let's Encrypt certificate for OpenWISP server (Nnginx)
openwisp2_ssl_cert: "/etc/letsencrypt/live/{{ inventory_hostname }}/fullchain.pem"
openwisp2_ssl_key: "/etc/letsencrypt/live/{{ inventory_hostname }}/privkey.pem"
# Configuration for openwisp-radius
openwisp2_radius: true
openwisp2_freeradius_install: true
freeradius_eap_orgs:
- name: demo
uuid: 00000000-0000-0000-0000-000000000001
radius_secret: demo-radius-token
auth_port: 1832
acct_port: 1833
inner_tunnel_auth_port: 18330
# Update the cert_file and private_key paths to point to the
# Let's Encrypt certificate.
cert: /etc/letsencrypt/live/freeradius.yourdomain.com/fullchain.pem
private_key: /etc/letsencrypt/live/freeradius.yourdomain.com/privkey.pem
# If you choose to re-use the same certificate for both services,
# your configuration would look like this
# cert: /etc/letsencrypt/live/{{ inventory_hostname }}/fullchain.pem
# private_key: /etc/letsencrypt/live/{{ inventory_hostname }}/privkey.pem
tasks:
# Tasks to ensure the Let's Encrypt certificate can be read by the FreeRADIUS server.
# If you are using the same certificate for both services, you need to
# replace "freeradius.yourdomain.com" with "{{ inventory_hostname }}"
# in the following task.
- name: "Create a renewal hook for setting permissions on /etc/letsencrypt/live/freeradius.yourdomain.com"
copy:
content: |
#!/bin/bash
chown -R root:freerad /etc/letsencrypt/live/ /etc/letsencrypt/archive/
chmod 0750 /etc/letsencrypt/live/ /etc/letsencrypt/archive/
chmod -R 0640 /etc/letsencrypt/archive/freeradius.yourdomain.com/
chmod 0750 /etc/letsencrypt/archive/freeradius.yourdomain.com/
dest: /etc/letsencrypt/renewal-hooks/post/chown_freerad
owner: root
group: root
mode: '0700'
register: chown_freerad_result
- name: Change the ownership of the certificate files
when: chown_freerad_result.changed
command: /etc/letsencrypt/renewal-hooks/post/chown_freerad
```

Configuring CORS Headers
------------------------

Expand Down Expand Up @@ -1254,9 +1424,55 @@ Below are listed all the variables you can customize (you may also want to take
# Sets the source path of the template that contains freeradius site configuration.
# Defaults to "templates/freeradius/openwisp_site.j2" shipped in the role.
freeradius_openwisp_site_template_src: custom_freeradius_site.j2
# Whether to deploy the default openwisp_site for FreeRADIUS.
# Defaults to true.
freeradius_deploy_openwisp_site: false
# FreeRADIUS listen address for the openwisp_site.
# Defaults to "*", i.e. listen on all interfaces.
freeradius_openwisp_site_listen_ipaddr: "10.8.0.1"
# A list of dict that includes organization's name, UUID, RADIUS token,
# TLS configuration, and ports for authentication, accounting, and inner tunnel.
# This list of dict is used to generate FreeRADIUS sites that support
# WPA Enterprise (EAP-TTLS-PAP) authentication.
# Defaults to an empty list.
freeradius_eap_orgs:
# The name should not contain spaces or special characters
- name: openwisp
# UUID of the organization can be retrieved from the OpenWISP admin
uuid: 00000000-0000-0000-0000-000000000000
# Radius token of the organization can be retrieved from the OpenWISP admin
radius_token: secret-radius-token
# Port used by the authentication service for this FreeRADIUS site
auth_port: 1832
# Port used by the accounting service for this FreeRADIUS site
acct_port: 1833
# Port used by the authentication service of inner tunnel for this FreeRADIUS site
inner_tunnel_auth_port: 18330
# CA certificate for the FreeRADIUS site
ca: /etc/freeradius/certs/ca.crt
# TLS certificate for the FreeRADIUS site
cert: /etc/freeradius/certs/cert.pem
# TLS private key for the FreeRADIUS site
private_key: /etc/freeradius/certs/key.pem
# Diffie-Hellman key for the FreeRADIUS site
dh: /etc/freeradius/certs/dh
# Extra instructions for the "tls-config" section of the EAP module
# for the FreeRADIUS site
tls_config_extra: |
private_key_password = whatever
ecdh_curve = "prime256v1"
# Sets the source path of the template that contains freeradius site configuration
# for WPA Enterprise (EAP-TTLS-PAP) authentication.
# Defaults to "templates/freeradius/eap/openwisp_site.j2" shipped in the role.
freeradius_eap_openwisp_site_template_src: custom_eap_openwisp_site.j2
# Sets the source path of the template that contains freeradius inner tunnel
# configuration for WPA Enterprise (EAP-TTLS-PAP) authentication.
# Defaults to "templates/freeradius/eap/inner_tunnel.j2" shipped in the role.
freeradius_eap_inner_tunnel_template_src: custom_eap_inner_tunnel.j2
# Sets the source path of the template that contains freeradius EAP configuration
# for WPA Enterprise (EAP-TTLS-PAP) authentication.
# Defaults to "templates/freeradius/eap/eap.j2" shipped in the role.
freeradius_eap_template_src: custom_eap.j2
cron_delete_old_notifications: "'hour': 0, 'minute': 0"
cron_deactivate_expired_users: "'hour': 0, 'minute': 5"
cron_delete_old_radiusbatch_users: "'hour': 0, 'minute': 10"
Expand Down
5 changes: 5 additions & 0 deletions defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ freeradius_mods_config_dir: "{{ freeradius_dir }}/mods-config"
freeradius_sites_available_dir: "{{ freeradius_dir }}/sites-available"
freeradius_sites_enabled_dir: "{{ freeradius_dir }}/sites-enabled"
freeradius_openwisp_site_template_src: freeradius/openwisp_site.j2
freeradius_deploy_openwisp_site: true
freeradius_db_map:
django.contrib.gis.db.backends.spatialite:
driver: rlm_sql_sqlite
Expand All @@ -197,6 +198,10 @@ freeradius_rest:
freeradius_expire_attr_after_seconds: 86400
freeradius_safe_characters: "+@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"
freeradius_openwisp_site_listen_ipaddr: "*"
freeradius_eap_orgs: []
freeradius_eap_openwisp_site_template_src: freeradius/eap/openwisp_site.j2
freeradius_eap_inner_tunnel_template_src: freeradius/eap/inner_tunnel.j2
freeradius_eap_template_src: freeradius/eap/eap.j2
cron_delete_old_notifications: "'hour': 0, 'minute': 0"
cron_deactivate_expired_users: "'hour': 0, 'minute': 5"
cron_delete_old_radiusbatch_users: "'hour': 0, 'minute': 10"
Expand Down
12 changes: 12 additions & 0 deletions molecule/resources/converge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
openwisp2_uwsgi_extra_conf: |
single-interpreter=True
openwisp2_usage_metric_collection: false
freeradius_eap_orgs:
- name: openwisp
uuid: 00000000-0000-0000-0000-000000000000
radius_token: secret-radius-token
auth_port: 1822
acct_port: 1823
inner_tunnel_auth_port: 18230

pre_tasks:
- name: Update apt cache
Expand All @@ -20,6 +27,11 @@
cache_valid_time: 600
when: ansible_os_family == 'Debian'

- name: Install net-tools
apt:
name:
- net-tools

- name: Remove the .dockerenv file
file:
path: /.dockerenv
Expand Down
6 changes: 6 additions & 0 deletions molecule/resources/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,9 @@
- name: Show OpenWisp log
debug:
var: openwisp_log

- name: Check if FreeRADIUS is listening on WPA Enterprise site ports
shell: "netstat -tuln | grep -Eq '1822|1823|18230'"
register: freeradius_eap_ports # Register the output and return code
failed_when: freeradius_eap_ports.rc
changed_when: false
10 changes: 10 additions & 0 deletions tasks/freeradius.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@
mode: 0640
owner: freerad
group: freerad
when: freeradius_deploy_openwisp_site
notify: Restart freeradius

- name: Inner tunnel
Expand All @@ -195,4 +196,13 @@
mode: 0640
owner: freerad
group: freerad
when: freeradius_deploy_openwisp_site
notify: Restart freeradius

- name: Copy configuration for WPA Enterprise TTLS
include_tasks: tasks/freeradius_eap.yml
loop: "{{ freeradius_eap_orgs }}"
loop_control:
loop_var: org
when: freeradius_eap_orgs
tags: [freeradius_eap]
69 changes: 69 additions & 0 deletions tasks/freeradius_eap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---

- name: Generate default DH key and certificates
block:
- name: "Check if {{ freeradius_dir }}/certs/dh exists"
ansible.builtin.stat:
path: "{{ freeradius_dir }}/certs/dh"
register: cert_dh_exists
- name: Generate DH key and certificates
command:
cmd: make destroycerts dh all
chdir: "{{ freeradius_dir }}/certs"
when: not cert_dh_exists.stat.exists
notify: Restart freeradius
tags: [freeradius_eap]

- name: "Copy {{ org.name }} EAP openwisp_site"
template:
src: "{{ freeradius_eap_openwisp_site_template_src }}"
dest: "{{ freeradius_dir }}/sites-available/{{ org.name }}_eap_openwisp_site"
owner: freerad
group: freerad
mode: '0644'
notify: Restart freeradius
tags: [freeradius_eap]

- name: "Create a symlink in sites-enabled for {{ org.name }} EAP openwisp_site"
ansible.builtin.file:
src: "{{ freeradius_dir }}/sites-available/{{ org.name }}_eap_openwisp_site"
dest: "{{ freeradius_dir }}/sites-enabled/{{ org.name }}_eap_openwisp_site"
state: link
notify: Restart freeradius
tags: [freeradius_eap]

- name: "Copy {{ org.name }} eap_inner_tunnel"
template:
src: "{{ freeradius_eap_inner_tunnel_template_src }}"
dest: "{{ freeradius_dir }}/sites-available/{{ org.name }}_eap_inner_tunnel"
owner: freerad
group: freerad
mode: '0644'
notify: Restart freeradius
tags: [freeradius_eap]

- name: "Create a symlink in sites-enabled for {{ org.name }} eap_inner_tunnel"
ansible.builtin.file:
src: "{{ freeradius_dir }}/sites-available/{{ org.name }}_eap_inner_tunnel"
dest: "{{ freeradius_dir }}/sites-enabled/{{ org.name }}_eap_inner_tunnel"
state: link
notify: Restart freeradius
tags: [freeradius_eap]

- name: Copy {{ org.name }} custom EAP configuration in mods-available
template:
src: "{{ freeradius_eap_template_src }}"
dest: "{{ freeradius_dir }}/mods-available/{{ org.name }}_eap"
owner: freerad
group: freerad
mode: '0644'
notify: Restart freeradius
tags: [freeradius_eap]

- name: Create a symlink in mods-enabled
ansible.builtin.file:
src: "{{ freeradius_dir }}/mods-available/{{ org.name }}_eap"
dest: "{{ freeradius_dir }}/mods-enabled/{{ org.name }}_eap"
state: link
notify: Restart freeradius
tags: [freeradius_eap]
Loading
Loading