From 601e22e4c72c114bd07e20406db015f06b2763a0 Mon Sep 17 00:00:00 2001 From: Gagan Deep Date: Thu, 9 May 2024 00:10:17 +0530 Subject: [PATCH 01/11] [feature] Allowed deploying WPA Enterprise 2 TTLS-PAP #462 Closes #462 --- README.md | 30 ++++++++ defaults/main.yml | 4 ++ tasks/freeradius.yml | 11 +++ tasks/freeradius_eap.yml | 55 +++++++++++++++ templates/freeradius/eap/eap.j2 | 38 ++++++++++ templates/freeradius/eap/inner_tunnel.j2 | 84 +++++++++++++++++++++++ templates/freeradius/eap/openwisp_site.j2 | 79 +++++++++++++++++++++ 7 files changed, 301 insertions(+) create mode 100644 tasks/freeradius_eap.yml create mode 100644 templates/freeradius/eap/eap.j2 create mode 100644 templates/freeradius/eap/inner_tunnel.j2 create mode 100644 templates/freeradius/eap/openwisp_site.j2 diff --git a/README.md b/README.md index 60fa871c..4d0b6de2 100644 --- a/README.md +++ b/README.md @@ -1255,6 +1255,36 @@ Below are listed all the variables you can customize (you may also want to take # 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, 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 + # 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" diff --git a/defaults/main.yml b/defaults/main.yml index 13a0c2e8..07aa0293 100755 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -197,6 +197,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" diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml index d6851df6..45bbac64 100644 --- a/tasks/freeradius.yml +++ b/tasks/freeradius.yml @@ -200,3 +200,14 @@ owner: freerad group: freerad notify: Restart freeradius + +- debug: + msg: "{{ freeradius_eap_orgs }}" + +- 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] diff --git a/tasks/freeradius_eap.yml b/tasks/freeradius_eap.yml new file mode 100644 index 00000000..c35219c3 --- /dev/null +++ b/tasks/freeradius_eap.yml @@ -0,0 +1,55 @@ +--- + +- 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/eap-org_{{ org.name }}" + 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/eap-org_{{ org.name }}" + dest: "{{ freeradius_dir }}/mods-enabled/eap-org_{{ org.name }}" + state: link + notify: Restart freeradius + tags: [freeradius_eap] diff --git a/templates/freeradius/eap/eap.j2 b/templates/freeradius/eap/eap.j2 new file mode 100644 index 00000000..ccd92506 --- /dev/null +++ b/templates/freeradius/eap/eap.j2 @@ -0,0 +1,38 @@ +eap eap-org_{{ org.name }} { + default_eap_type = ttls + timer_expire = 60 + ignore_unknown_eap_types = no + cisco_accounting_username_bug = no + max_sessions = ${max_requests} + + tls-config tls-common { + # make sure to have a valid SSL certificate for production usage + private_key_password = whatever + private_key_file = ${certdir}/server.pem + certificate_file = ${certdir}/server.pem + ca_file = ${cadir}/ca.pem + dh_file = ${certdir}/dh + ca_path = ${cadir} + cipher_list = "DEFAULT" + cipher_server_preference = no + ecdh_curve = "prime256v1" + + cache { + enable = no + } + + ocsp { + enable = no + override_cert_url = yes + url = "http://127.0.0.1/ocsp/" + } + } + + ttls { + tls = tls-common + default_eap_type = pap + copy_request_to_tunnel = yes + use_tunneled_reply = yes + virtual_server = "inner_tunnel-org_{{ org.name }}" + } +} diff --git a/templates/freeradius/eap/inner_tunnel.j2 b/templates/freeradius/eap/inner_tunnel.j2 new file mode 100644 index 00000000..ddc3fe23 --- /dev/null +++ b/templates/freeradius/eap/inner_tunnel.j2 @@ -0,0 +1,84 @@ +server inner_tunnel-org_{{ org.name }} { + listen { + ipaddr = 127.0.0.1 + port = {{ org.inner_tunnel_auth_port }} + type = auth + } + + api_token_header = "Authorization: Bearer {{ org.uuid }} {{ org.radius_token }}" + authorize { + filter_username + update control { &REST-HTTP-Header += "${...api_token_header}" } + rest + eap-org_{{ org.name }} { + ok = return + } + + chap + mschap + suffix + + update control { + &Proxy-To-Realm := LOCAL + } + + eap { + ok = return + } + + -ldap + + pap + + expiration + logintime + } + + authenticate { + Auth-Type PAP { + pap + } + + Auth-Type CHAP { + chap + } + + Auth-Type MS-CHAP { + mschap + } + eap + } + + session {} + + post-auth { + if (0) { + update reply { + User-Name !* ANY + Message-Authenticator !* ANY + EAP-Message !* ANY + Proxy-State !* ANY + MS-MPPE-Encryption-Types !* ANY + MS-MPPE-Encryption-Policy !* ANY + MS-MPPE-Send-Key !* ANY + MS-MPPE-Recv-Key !* ANY + } + update { + &outer.session-state: += &reply: + } + } + + Post-Auth-Type REJECT { + attr_filter.access_reject + update outer.session-state { + &Module-Failure-Message := &request:Module-Failure-Message + } + } + } + + pre-proxy {} + post-proxy { + eap-org_{{ org.name }} + eap + } +} diff --git a/templates/freeradius/eap/openwisp_site.j2 b/templates/freeradius/eap/openwisp_site.j2 new file mode 100644 index 00000000..dbb43bdf --- /dev/null +++ b/templates/freeradius/eap/openwisp_site.j2 @@ -0,0 +1,79 @@ +server openwisp_site-org_{{ org.name }} { + listen { + type = auth + ipaddr = {{ org.listen_ipaddr | default(freeradius_openwisp_site_listen_ipaddr) }} + port = {{ org.auth_port }} + limit { + max_connections = 16 + lifetime = 0 + idle_timeout = 30 + } + } + + listen { + ipaddr = {{ org.listen_ipaddr | default(freeradius_openwisp_site_listen_ipaddr) }} + port = {{ org.acct_port }} + type = acct + limit {} + } + + api_token_header = "Authorization: Bearer {{ org.uuid }} {{ org.radius_token }}" + authorize { + eap-org_{{ org.name }} { + ok = return + } + update control { &REST-HTTP-Header += "${...api_token_header}" } + filter_username + rest + expiration + logintime + } + + authenticate { + Auth-Type eap-org_{{ org.name }} { + eap-org_{{ org.name }} + } + Auth-Type PAP { + pap + } + + Auth-Type CHAP { + chap + } + + Auth-Type MS-CHAP { + mschap + } + + Auth-Type EAP { + eap + } + } + + preacct { + preprocess + acct_unique + suffix + files + } + + accounting { + update control { &REST-HTTP-Header += "${...api_token_header}" } + rest + } + + session {} + + post-auth { + update control { &REST-HTTP-Header += "${...api_token_header}" } + rest + + Post-Auth-Type REJECT { + update control { &REST-HTTP-Header += "${....api_token_header}" } + rest + } + } + + pre-proxy {} + post-proxy {} +} From a5651d508d2786782b2e9a216c8b33ff8b1d46c4 Mon Sep 17 00:00:00 2001 From: Gagan Deep Date: Thu, 16 May 2024 19:14:39 +0530 Subject: [PATCH 02/11] [docs] Added section for configuring FreeRADIUS for WPA Enterprise --- README.md | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/README.md b/README.md index 4d0b6de2..edfb8eba 100644 --- a/README.md +++ b/README.md @@ -562,6 +562,69 @@ 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 + # 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 + ``` + +**Note**: In the example playbook 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. + Configuring CORS Headers ------------------------ From a4773979e6bf163741c5443b3433e54595692ac3 Mon Sep 17 00:00:00 2001 From: Gagan Deep Date: Fri, 17 May 2024 01:00:57 +0530 Subject: [PATCH 03/11] [feature] Allow to use custom certificates with FreeRADIUS EAP --- README.md | 88 ++++++++++++++++++++++++++++++++- templates/freeradius/eap/eap.j2 | 4 +- 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index edfb8eba..0db8b06a 100644 --- a/README.md +++ b/README.md @@ -607,6 +607,11 @@ installs FreeRADIUS, and configures it for WPA Enterprise (EAP-TTLS-PAP): 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 certificate and private key as follows. + # Ensure that the certificate and private key can be read by the "freerad" user. + cert: /etc/freeradius/certs/cert.pem + private_key: /etc/freeradius/certs/key.pem # You can add as many organizations as you want - name: demo uuid: 00000000-0000-0000-0000-000000000001 @@ -614,9 +619,12 @@ installs FreeRADIUS, and configures it for WPA Enterprise (EAP-TTLS-PAP): auth_port: 1832 acct_port: 1833 inner_tunnel_auth_port: 18330 - ``` + # If you omit the "cert" and "private_key" keys, + # the FreeRADIUS site will use the default certificates + # located in /etc/freeradius/certs. +``` -**Note**: In the example playbook above, custom ports 1822, 1823, and 18230 +**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 @@ -625,6 +633,82 @@ 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: "" + certbot_certs: + - 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: "" + 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 ------------------------ diff --git a/templates/freeradius/eap/eap.j2 b/templates/freeradius/eap/eap.j2 index ccd92506..38c6c83d 100644 --- a/templates/freeradius/eap/eap.j2 +++ b/templates/freeradius/eap/eap.j2 @@ -8,8 +8,8 @@ eap eap-org_{{ org.name }} { tls-config tls-common { # make sure to have a valid SSL certificate for production usage private_key_password = whatever - private_key_file = ${certdir}/server.pem - certificate_file = ${certdir}/server.pem + private_key_file = {{ org.private_key | default('${certdir}/server.pem') }} + certificate_file = {{ org.cert | default('${certdir}/server.pem') }} ca_file = ${cadir}/ca.pem dh_file = ${certdir}/dh ca_path = ${cadir} From fd8c6589b05dfcc022681b5308469797d9b8cf96 Mon Sep 17 00:00:00 2001 From: Gagan Deep Date: Fri, 24 May 2024 18:50:18 +0530 Subject: [PATCH 04/11] [req-changes] Wrapped long text lines to 75 characters --- README.md | 58 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 0db8b06a..8de4a7de 100644 --- a/README.md +++ b/README.md @@ -565,15 +565,16 @@ look for the word "radius" in the ### 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). +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. +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): @@ -592,24 +593,31 @@ installs FreeRADIUS, and configures it for WPA Enterprise (EAP-TTLS-PAP): # 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. + # 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. + # 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. + # 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 + # 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 + # 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 certificate and private key as follows. - # Ensure that the certificate and private key can be read by the "freerad" user. + # If you want to use a custom certificate for FreeRADIUS + # EAP module, you can specify the path to the certificate and + # private key as follows. + # Ensure that the certificate and private key can be read by + # the "freerad" user. cert: /etc/freeradius/certs/cert.pem private_key: /etc/freeradius/certs/key.pem # You can add as many organizations as you want @@ -629,20 +637,30 @@ 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. +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. +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. +- 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. +**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 From 6ad58a1d4e2dce09de2fcfc64210a1ee385a3a2f Mon Sep 17 00:00:00 2001 From: Gagan Deep Date: Fri, 24 May 2024 18:51:27 +0530 Subject: [PATCH 05/11] [req-changes] Removed debug task --- tasks/freeradius.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml index 45bbac64..345e0f7f 100644 --- a/tasks/freeradius.yml +++ b/tasks/freeradius.yml @@ -201,9 +201,6 @@ group: freerad notify: Restart freeradius -- debug: - msg: "{{ freeradius_eap_orgs }}" - - name: Copy configuration for WPA Enterprise TTLS include_tasks: tasks/freeradius_eap.yml loop: "{{ freeradius_eap_orgs }}" From 098b58da7be5011d972f479a6ea19c10be25c000 Mon Sep 17 00:00:00 2001 From: Gagan Deep Date: Fri, 24 May 2024 21:11:38 +0530 Subject: [PATCH 06/11] [req-changes] Added EAP configuration in the test playbook --- molecule/resources/converge.yml | 12 ++++++++++++ molecule/resources/verify.yml | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/molecule/resources/converge.yml b/molecule/resources/converge.yml index c91955d6..a721af4f 100644 --- a/molecule/resources/converge.yml +++ b/molecule/resources/converge.yml @@ -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 @@ -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 diff --git a/molecule/resources/verify.yml b/molecule/resources/verify.yml index f8804fc2..5cffad38 100644 --- a/molecule/resources/verify.yml +++ b/molecule/resources/verify.yml @@ -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 From 7d9158c0f65dfe616081c3ee1b85c2a4535c233a Mon Sep 17 00:00:00 2001 From: Gagan Deep Date: Mon, 3 Jun 2024 20:09:30 +0530 Subject: [PATCH 07/11] [req-changes] Updated EAP template Made file names consistent --- README.md | 39 +++++++++++++++++------ defaults/main.yml | 1 + tasks/freeradius.yml | 2 ++ tasks/freeradius_eap.yml | 6 ++-- templates/freeradius/eap/eap.j2 | 19 ++++++++--- templates/freeradius/eap/inner_tunnel.j2 | 6 ++-- templates/freeradius/eap/openwisp_site.j2 | 8 ++--- 7 files changed, 57 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 8de4a7de..383d7da9 100644 --- a/README.md +++ b/README.md @@ -614,12 +614,17 @@ installs FreeRADIUS, and configures it for WPA Enterprise (EAP-TTLS-PAP): # 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 certificate and - # private key as follows. - # Ensure that the certificate and private key can be read by - # the "freerad" user. + # 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 @@ -627,7 +632,7 @@ installs FreeRADIUS, and configures it for WPA Enterprise (EAP-TTLS-PAP): auth_port: 1832 acct_port: 1833 inner_tunnel_auth_port: 18330 - # If you omit the "cert" and "private_key" keys, + # If you omit the certificate fields, # the FreeRADIUS site will use the default certificates # located in /etc/freeradius/certs. ``` @@ -1417,13 +1422,16 @@ 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, 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. + # 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 @@ -1438,6 +1446,19 @@ Below are listed all the variables you can customize (you may also want to take 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. diff --git a/defaults/main.yml b/defaults/main.yml index 07aa0293..cb1060f7 100755 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -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 diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml index 345e0f7f..a66c4cb8 100644 --- a/tasks/freeradius.yml +++ b/tasks/freeradius.yml @@ -190,6 +190,7 @@ mode: 0640 owner: freerad group: freerad + when: freeradius_deploy_openwisp_site notify: Restart freeradius - name: Inner tunnel @@ -199,6 +200,7 @@ mode: 0640 owner: freerad group: freerad + when: freeradius_deploy_openwisp_site notify: Restart freeradius - name: Copy configuration for WPA Enterprise TTLS diff --git a/tasks/freeradius_eap.yml b/tasks/freeradius_eap.yml index c35219c3..408a4ddd 100644 --- a/tasks/freeradius_eap.yml +++ b/tasks/freeradius_eap.yml @@ -39,7 +39,7 @@ - name: Copy {{ org.name }} custom EAP configuration in mods-available template: src: "{{ freeradius_eap_template_src }}" - dest: "{{ freeradius_dir }}/mods-available/eap-org_{{ org.name }}" + dest: "{{ freeradius_dir }}/mods-available/{{ org.name }}_eap" owner: freerad group: freerad mode: '0644' @@ -48,8 +48,8 @@ - name: Create a symlink in mods-enabled ansible.builtin.file: - src: "{{ freeradius_dir }}/mods-available/eap-org_{{ org.name }}" - dest: "{{ freeradius_dir }}/mods-enabled/eap-org_{{ org.name }}" + src: "{{ freeradius_dir }}/mods-available/{{ org.name }}_eap" + dest: "{{ freeradius_dir }}/mods-enabled/{{ org.name }}_eap" state: link notify: Restart freeradius tags: [freeradius_eap] diff --git a/templates/freeradius/eap/eap.j2 b/templates/freeradius/eap/eap.j2 index 38c6c83d..fa311c48 100644 --- a/templates/freeradius/eap/eap.j2 +++ b/templates/freeradius/eap/eap.j2 @@ -1,4 +1,4 @@ -eap eap-org_{{ org.name }} { +eap {{ org.name }}_eap { default_eap_type = ttls timer_expire = 60 ignore_unknown_eap_types = no @@ -10,12 +10,21 @@ eap eap-org_{{ org.name }} { private_key_password = whatever private_key_file = {{ org.private_key | default('${certdir}/server.pem') }} certificate_file = {{ org.cert | default('${certdir}/server.pem') }} - ca_file = ${cadir}/ca.pem - dh_file = ${certdir}/dh + ca_file = {{ org.ca | default('${cadir}/ca.pem') }} + dh_file = {{ org.dh | default('${certdir}/dh') }} ca_path = ${cadir} cipher_list = "DEFAULT" cipher_server_preference = no - ecdh_curve = "prime256v1" + tls_min_version = "1.2" + tls_max_version = "1.2" + check_crl = no + check_cert_issuer = no + fragment_size = 2048 + auto_chain = yes + + {% if 'tls_config_extra' in org %} + {{ org.tls_config_extra }} + {% endif %} cache { enable = no @@ -33,6 +42,6 @@ eap eap-org_{{ org.name }} { default_eap_type = pap copy_request_to_tunnel = yes use_tunneled_reply = yes - virtual_server = "inner_tunnel-org_{{ org.name }}" + virtual_server = "{{ org.name }}_eap_inner_tunnel" } } diff --git a/templates/freeradius/eap/inner_tunnel.j2 b/templates/freeradius/eap/inner_tunnel.j2 index ddc3fe23..89500f2f 100644 --- a/templates/freeradius/eap/inner_tunnel.j2 +++ b/templates/freeradius/eap/inner_tunnel.j2 @@ -1,4 +1,4 @@ -server inner_tunnel-org_{{ org.name }} { +server {{ org.name }}_eap_inner_tunnel { listen { ipaddr = 127.0.0.1 port = {{ org.inner_tunnel_auth_port }} @@ -10,7 +10,7 @@ server inner_tunnel-org_{{ org.name }} { filter_username update control { &REST-HTTP-Header += "${...api_token_header}" } rest - eap-org_{{ org.name }} { + {{ org.name }}_eap { ok = return } @@ -78,7 +78,7 @@ server inner_tunnel-org_{{ org.name }} { pre-proxy {} post-proxy { - eap-org_{{ org.name }} + {{ org.name }}_eap eap } } diff --git a/templates/freeradius/eap/openwisp_site.j2 b/templates/freeradius/eap/openwisp_site.j2 index dbb43bdf..1af20abb 100644 --- a/templates/freeradius/eap/openwisp_site.j2 +++ b/templates/freeradius/eap/openwisp_site.j2 @@ -1,4 +1,4 @@ -server openwisp_site-org_{{ org.name }} { +server {{ org.name }}_eap_openwisp_site { listen { type = auth ipaddr = {{ org.listen_ipaddr | default(freeradius_openwisp_site_listen_ipaddr) }} @@ -19,7 +19,7 @@ server openwisp_site-org_{{ org.name }} { api_token_header = "Authorization: Bearer {{ org.uuid }} {{ org.radius_token }}" authorize { - eap-org_{{ org.name }} { + {{ org.name }}_eap { ok = return } update control { &REST-HTTP-Header += "${...api_token_header}" } @@ -30,8 +30,8 @@ server openwisp_site-org_{{ org.name }} { } authenticate { - Auth-Type eap-org_{{ org.name }} { - eap-org_{{ org.name }} + Auth-Type {{ org.name }}_eap { + {{ org.name }}_eap } Auth-Type PAP { pap From 7b22d8bc584acac3d391d7bbf83f12ae235ed4f2 Mon Sep 17 00:00:00 2001 From: Gagan Deep Date: Mon, 24 Jun 2024 22:53:56 +0530 Subject: [PATCH 08/11] [fix] Gernerate default certificates and DH key --- tasks/freeradius_eap.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tasks/freeradius_eap.yml b/tasks/freeradius_eap.yml index 408a4ddd..43c18af9 100644 --- a/tasks/freeradius_eap.yml +++ b/tasks/freeradius_eap.yml @@ -1,5 +1,11 @@ --- +- name: Generate default DH key and certificates + command: + cmd: make destroycerts dh all + chdir: /etc/freeradius/certs + tags: [freeradius_eap] + - name: "Copy {{ org.name }} EAP openwisp_site" template: src: "{{ freeradius_eap_openwisp_site_template_src }}" From ab862a2a29197ddc9d621ad3d9f8f296055eac87 Mon Sep 17 00:00:00 2001 From: Gagan Deep Date: Mon, 24 Jun 2024 23:07:36 +0530 Subject: [PATCH 09/11] [chores] Fixed molecule config --- molecule/default/molecule.yml | 3 ++- molecule/local/molecule.yml | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml index c0ac9349..da1dc533 100644 --- a/molecule/default/molecule.yml +++ b/molecule/default/molecule.yml @@ -15,7 +15,8 @@ platforms: image: "geerlingguy/docker-${MOLECULE_DISTRO}-ansible:${tag:-latest}" command: ${MOLECULE_DOCKER_COMMAND:-""} volumes: - - /sys/fs/cgroup:/sys/fs/cgroup:ro + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host privileged: true pre_build_image: true provisioner: diff --git a/molecule/local/molecule.yml b/molecule/local/molecule.yml index 9b273ff8..a029cca5 100644 --- a/molecule/local/molecule.yml +++ b/molecule/local/molecule.yml @@ -15,21 +15,24 @@ platforms: image: "geerlingguy/docker-ubuntu2204-ansible:latest" command: ${MOLECULE_DOCKER_COMMAND:-""} volumes: - - /sys/fs/cgroup:/sys/fs/cgroup:ro + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host privileged: true pre_build_image: true - name: "openwisp2-ubuntu2004" image: "geerlingguy/docker-ubuntu2004-ansible:latest" command: ${MOLECULE_DOCKER_COMMAND:-""} volumes: - - /sys/fs/cgroup:/sys/fs/cgroup:ro + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host privileged: true pre_build_image: true - name: "openwisp2-debian11" image: "geerlingguy/docker-debian11-ansible:latest" command: ${MOLECULE_DOCKER_COMMAND:-""} volumes: - - /sys/fs/cgroup:/sys/fs/cgroup:ro + - /sys/fs/cgroup:/sys/fs/cgroup:rw + cgroupns_mode: host privileged: true pre_build_image: true provisioner: From c0e716115bad4e8437c82bad295845384148947a Mon Sep 17 00:00:00 2001 From: Gagan Deep Date: Mon, 24 Jun 2024 23:25:28 +0530 Subject: [PATCH 10/11] [fix] Skip idempotence for make cert --- tasks/freeradius_eap.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tasks/freeradius_eap.yml b/tasks/freeradius_eap.yml index 43c18af9..6115b0e1 100644 --- a/tasks/freeradius_eap.yml +++ b/tasks/freeradius_eap.yml @@ -1,9 +1,19 @@ --- + + - name: Generate default DH key and certificates - command: - cmd: make destroycerts dh all - chdir: /etc/freeradius/certs + 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" From 10cec73f64fe38f31daa3bff7d59f98397067a28 Mon Sep 17 00:00:00 2001 From: Gagan Deep Date: Wed, 3 Jul 2024 14:50:50 +0530 Subject: [PATCH 11/11] [skip ci] Removed blank lines --- tasks/freeradius_eap.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/tasks/freeradius_eap.yml b/tasks/freeradius_eap.yml index 6115b0e1..21dc5262 100644 --- a/tasks/freeradius_eap.yml +++ b/tasks/freeradius_eap.yml @@ -1,7 +1,5 @@ --- - - - name: Generate default DH key and certificates block: - name: "Check if {{ freeradius_dir }}/certs/dh exists"