From 03428e0e94cfd37c96b2f902b03efa272aae9738 Mon Sep 17 00:00:00 2001 From: Ajay Tripathi Date: Sat, 21 Nov 2020 22:25:00 +0530 Subject: [PATCH] [feature] Added openwisp_radius installation --- defaults/main.yml | 25 ++++ molecule/resources/converge.yml | 4 +- tasks/apt.yml | 14 ++- tasks/freeradius.yml | 170 ++++++++++++++++++++++++++ tasks/pip.yml | 21 ++++ templates/freeradius/captiveportal.j2 | 0 templates/freeradius/rest.j2 | 30 +++++ templates/freeradius/sql.j2 | 27 ++++ templates/freeradius/sqlcounter.j2 | 41 +++++++ templates/openwisp2/settings.py | 51 ++++++-- templates/openwisp2/urls.py | 5 + 11 files changed, 374 insertions(+), 14 deletions(-) create mode 100644 tasks/freeradius.yml create mode 100644 templates/freeradius/captiveportal.j2 create mode 100644 templates/freeradius/rest.j2 create mode 100644 templates/freeradius/sql.j2 create mode 100644 templates/freeradius/sqlcounter.j2 diff --git a/defaults/main.yml b/defaults/main.yml index 3826af59..4b9bda47 100755 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,8 +1,10 @@ openwisp2_python: python3 ansible_python_interpreter: /usr/bin/python3 openwisp2_network_topology: false +openwisp2_radius: false openwisp2_controller_version: "0.7.post1" openwisp2_network_topology_version: "0.4" +openwisp2_radius_version: "0.1" openwisp2_controller_pip: false openwisp2_users_pip: false openwisp2_utils_pip: false @@ -11,6 +13,7 @@ openwisp2_django_x509_pip: false openwisp2_django_loci_pip: false openwisp2_netjsonconfig_pip: false openwisp2_network_topology_pip: false +openwisp2_radius_pip: false openwisp2_extra_python_packages: [bpython] openwisp2_extra_django_apps: [] openwisp2_extra_django_settings: {} @@ -86,3 +89,25 @@ openwisp2_celery_worker_prefetch_multiplier: 1 openwisp2_celery_task_acks_late: True openwisp2_django_celery_logging: False postfix_smtpd_relay_restrictions_override: "permit_sasl_authenticated, permit_mynetworks, check_relay_domains, reject_unauth_destination, reject" +freeradius_dir: /etc/freeradius/3.0 +freeradius_mods_available_dir: "{{ freeradius_dir }}/mods-available" +freeradius_mods_enabled_dir: "{{ freeradius_dir }}/mods-enabled" +freeradius_sites_available_dir: "{{ freeradius_dir }}/sites-available" +freeradius_sites_enabled_dir: "{{ freeradius_dir }}/sites-enabled" +freeradius_certs_dir: "{{ freeradius_dir }}/certs" +freeradius_sql: + driver: rlm_sql_postgresql + dialect: postgresql + host: localhost + port: 5432 + dbname: freeradius + user: admin + password: admin +freeradius_rest: + url: "https://{{ inventory_hostname }}/api/v1/freeradius" +openwisp2_users_auth_api: true +openwisp2_radius_sms_backend: "sendsms.backends.console.SmsBackend" +openwisp2_radius_extra_nas_types: > + [('cisco', 'Cisco Router')] +openwisp2_radius_sms_token_max_ip_daily: 25 +openwisp2_freeradius_allowed_hosts: ["localhost"] diff --git a/molecule/resources/converge.yml b/molecule/resources/converge.yml index 40b2339e..2e3ced1b 100644 --- a/molecule/resources/converge.yml +++ b/molecule/resources/converge.yml @@ -9,7 +9,9 @@ pre_tasks: - name: Update apt cache - apt: update_cache=true cache_valid_time=600 + apt: + update_cache: yes + cache_valid_time: 600 when: ansible_os_family == 'Debian' - name: Remove the .dockerenv file diff --git a/tasks/apt.yml b/tasks/apt.yml index 5f957e2a..8661495a 100644 --- a/tasks/apt.yml +++ b/tasks/apt.yml @@ -1,5 +1,6 @@ - name: Update APT package cache - apt: update_cache=yes + apt: + update_cache: yes changed_when: false retries: 5 delay: 10 @@ -71,6 +72,17 @@ until: result is success notify: reload systemd +- name: Install cairo + when: openwisp2_radius + apt: + name: + - libcairo2 + - libpango-1.0-0 + - libpangocairo-1.0-0 + - libgdk-pixbuf2.0-0 + - shared-mime-info + tags: [openwisp2, radius] + - name: Install mod-spatialite (may fail on older linux distros) when: openwisp2_database.engine == "django.contrib.gis.db.backends.spatialite" apt: name=libsqlite3-mod-spatialite diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml new file mode 100644 index 00000000..be669871 --- /dev/null +++ b/tasks/freeradius.yml @@ -0,0 +1,170 @@ + +# TODO: I want to use mysql! +- name: Freeradius system packages + when: openwisp2_radius + apt: + name: + - freeradius + - freeradius-postgresql + - freeradius-rest + state: latest + notify: restart freeradius + +- name: SQL Configuration + when: openwisp2_radius + template: + src: freeradius/sql.j2 + dest: "{{ freeradius_mods_available_dir }}/sql" + mode: 0640 + owner: freerad + group: freerad + notify: restart freeradius + +- name: Enable SQL module + when: openwisp2_radius + file: + src: "{{ freeradius_mods_available_dir }}/sql" + dest: "{{ freeradius_mods_enabled_dir }}/sql" + state: link + mode: 0640 + owner: freerad + group: freerad + +- name: SQL Counter module + when: openwisp2_radius + template: + src: freeradius/sqlcounter.j2 + dest: "{{ freeradius_mods_available_dir }}/sqlcounter" + mode: 0640 + owner: freerad + group: freerad + notify: restart freeradius + +- name: Enable SQL Counter module + when: openwisp2_radius + file: + src: "{{ freeradius_mods_available_dir }}/sqlcounter" + dest: "{{ freeradius_mods_enabled_dir }}/sqlcounter" + state: link + mode: 0640 + owner: freerad + group: freerad + +- name: Fix dailycounter.conf + when: openwisp2_radius + copy: + src: freeradius/dailycounter.conf + dest: "{{ freeradius_dir }}/mods-config/sql/counter/postgresql/dailycounter.conf" + mode: 0640 + owner: freerad + group: freerad + notify: restart freeradius + +- name: REST Configuration + when: openwisp2_radius + template: + src: freeradius/rest.j2 + dest: "{{ freeradius_mods_available_dir }}/rest" + mode: 0640 + owner: freerad + group: freerad + notify: restart freeradius + +- name: Enable REST module + when: openwisp2_radius + file: + src: "{{ freeradius_mods_available_dir }}/rest" + dest: "{{ freeradius_mods_enabled_dir }}/rest" + state: link + mode: 0640 + owner: freerad + group: freerad + +- name: Remove default site + when: openwisp2_radius + file: + dest: "{{ freeradius_sites_enabled_dir }}/default" + state: absent + +- name: Ensure inner-tunnel site is present + when: openwisp2_radius + file: + src: "{{ freeradius_sites_available_dir }}/inner-tunnel" + dest: "{{ freeradius_sites_enabled_dir }}/inner-tunnel" + state: link + mode: 0640 + owner: freerad + group: freerad + +- name: Captive portal configuration + when: openwisp2_radius + template: + src: freeradius/captiveportal.j2 + dest: "{{ freeradius_sites_available_dir }}/captiveportal" + mode: 0640 + owner: freerad + group: freerad + notify: restart freeradius + +- name: Captive portal to sites_enabled + when: openwisp2_radius + file: + src: "{{ freeradius_sites_available_dir }}/captiveportal" + dest: "{{ freeradius_sites_enabled_dir }}/captiveportal" + state: link + mode: 0640 + owner: freerad + group: freerad + notify: restart freeradius + tags: [radius] + +# openwisp-radius cron jobs +- name: delete_old_radacct + when: openwisp2_radius + cron: + name: delete_old_radacct + day: "*" + hour: 05 + minute: 30 + job: "/opt/openwisp2/env/bin/python /opt/openwisp2/manage.py delete_old_radacct 730" + tags: [openwisp2, radius] + +- name: delete_old_postauth + when: openwisp2_radius + cron: + name: delete_old_postauth + day: "*" + hour: 05 + minute: 0 + job: "/opt/openwisp2/env/bin/python /opt/openwisp2/manage.py delete_old_postauth 365" + tags: [openwisp2, radius] + +- name: cleanup_stale_radacct + when: openwisp2_radius + cron: + name: cleanup_stale_radacct + day: "*" + hour: 04 + minute: 0 + job: "/opt/openwisp2/env/bin/python /opt/openwisp2/manage.py cleanup_stale_radacct 1" + tags: [openwisp2, radius] + +- name: deactivate_expired_users + when: openwisp2_radius + cron: + name: deactivate_expired_users + day: "*" + hour: "*" + minute: "*/5" + job: "/opt/openwisp2/env/bin/python /opt/openwisp2/manage.py deactivate_expired_users" + tags: [openwisp2, radius] + +- name: delete_old_users + when: openwisp2_radius + cron: + name: delete_old_users + day: "*" + hour: "03" + minute: "30" + job: "/opt/openwisp2/env/bin/python /opt/openwisp2/manage.py delete_old_users" + tags: [openwisp2, radius] diff --git a/tasks/pip.yml b/tasks/pip.yml index 5b3d4804..3892de84 100644 --- a/tasks/pip.yml +++ b/tasks/pip.yml @@ -52,6 +52,13 @@ - "{{ openwisp2_network_topology_pip }}" when: item is defined and item is string and openwisp2_network_topology +- name: Add openwisp_radius to custom package list if set and enabled + set_fact: + openwisp2_python_packages: "{{ openwisp2_python_packages + [item] }}" + with_items: + - "{{ openwisp2_radius_pip }}" + when: item is defined and item is string and openwisp2_radius + - name: Install cryptography from pip pip: name: cryptography @@ -125,6 +132,20 @@ register: result until: result is success +- name: Install openwisp2_radius and its dependencies + when: openwisp2_radius + pip: + name: "openwisp-radius~={{ openwisp2_radius_version }}" + state: latest + virtualenv: "{{ virtualenv_path }}" + virtualenv_python: "{{ openwisp2_python }}" + virtualenv_site_packages: yes + notify: reload supervisor + retries: 5 + delay: 10 + register: result + until: result is success + - name: Install extra python packages pip: name: "{{ openwisp2_extra_python_packages }}" diff --git a/templates/freeradius/captiveportal.j2 b/templates/freeradius/captiveportal.j2 new file mode 100644 index 00000000..e69de29b diff --git a/templates/freeradius/rest.j2 b/templates/freeradius/rest.j2 new file mode 100644 index 00000000..0bfa673c --- /dev/null +++ b/templates/freeradius/rest.j2 @@ -0,0 +1,30 @@ +rest { + tls = {} + connect_uri = "{{ inventory_hostname }}" + authorize { + uri = "${..connect_uri}/api/v1/authorize/" + method = 'post' + body = 'json' + data = '{"username": "%{User-Name}", "password": "%{User-Password}"}' + tls = ${..tls} + } + + # this section can be left empty + authenticate {} + + post-auth { + uri = "${..connect_uri}/api/v1/postauth/" + method = 'post' + body = 'json' + data = '{"username": "%{User-Name}", "password": "%{User-Password}", "reply": "%{reply:Packet-Type}", "called_station_id": "%{Called-Station-ID}", "calling_station_id": "%{Calling-Station-ID}"}' + tls = ${..tls} + } + + accounting { + uri = "${..connect_uri}/api/v1/accounting/" + method = 'post' + body = 'json' + data = '{"status_type": "%{Acct-Status-Type}", "session_id": "%{Acct-Session-Id}", "unique_id": "%{Acct-Unique-Session-Id}", "username": "%{User-Name}", "realm": "%{Realm}", "nas_ip_address": "%{NAS-IP-Address}", "nas_port_id": "%{NAS-Port}", "nas_port_type": "%{NAS-Port-Type}", "session_time": "%{Acct-Session-Time}", "authentication": "%{Acct-Authentic}", "input_octets": "%{Acct-Input-Octets}", "output_octets": "%{Acct-Output-Octets}", "called_station_id": "%{Called-Station-Id}", "calling_station_id": "%{Calling-Station-Id}", "terminate_cause": "%{Acct-Terminate-Cause}", "service_type": "%{Service-Type}", "framed_protocol": "%{Framed-Protocol}", "framed_ip_address": "%{Framed-IP-Address}"}' + tls = ${..tls} + } +} diff --git a/templates/freeradius/sql.j2 b/templates/freeradius/sql.j2 new file mode 100644 index 00000000..a6e8cf93 --- /dev/null +++ b/templates/freeradius/sql.j2 @@ -0,0 +1,27 @@ +sql { + driver = "{{ freeradius_sql.driver }}" + dialect = "{{ freeradius_sql.dialect }}" + radius_db = "host={{ freeradius_sql.host }} port={{ freeradius_sql.port }} dbname={{ freeradius_sql.dbname }} user={{ freeradius_sql.user }} password={{ freeradius_sql.password }}" + acct_table1 = "radacct" + acct_table2 = "radacct" + postauth_table = "radpostauth" + authcheck_table = "radcheck" + groupcheck_table = "radgroupcheck" + authreply_table = "radreply" + groupreply_table = "radgroupreply" + usergroup_table = "radusergroup" + delete_stale_sessions = yes + client_table = "nas" + group_attribute = "SQL-Group" + $INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf + pool { + start = ${thread[pool].start_servers} + min = ${thread[pool].min_spare_servers} + max = ${thread[pool].max_servers} + spare = ${thread[pool].max_spare_servers} + uses = 0 + retry_delay = 30 + lifetime = 0 + idle_timeout = 60 + } +} diff --git a/templates/freeradius/sqlcounter.j2 b/templates/freeradius/sqlcounter.j2 new file mode 100644 index 00000000..e1900013 --- /dev/null +++ b/templates/freeradius/sqlcounter.j2 @@ -0,0 +1,41 @@ +# The dailycounter is included by default in the freeradius conf + +sqlcounter dailycounter { + sql_module_instance = sql + dialect = ${modules.sql.dialect} + + counter_name = Daily-Session-Time + check_name = Max-Daily-Session + reply_name = Session-Timeout + + key = User-Name + reset = daily + + $INCLUDE ${modconfdir}/sql/counter/${dialect}/${.:instance}.conf +} + +# The noresetcounter is included by default in the freeradius conf +sqlcounter noresetcounter { + sql_module_instance = sql + dialect = ${modules.sql.dialect} + + counter_name = Max-All-Session-Time + check_name = Max-All-Session + key = User-Name + reset = never + + $INCLUDE ${modconfdir}/sql/counter/${dialect}/${.:instance}.conf +} + +# The dailybandwidthcounter is added for django-freeradius +sqlcounter dailybandwidthcounter { + counter_name = Max-Daily-Session-Traffic + check_name = Max-Daily-Session-Traffic + sql_module_instance = sql + key = 'User-Name' + reset = daily + query = "SELECT SUM(acctinputoctets + acctoutputoctets) \ + FROM radacct \ + WHERE UserName='%{${key}}' \ + AND UNIX_TIMESTAMP(acctstarttime) + acctsessiontime > '%%b'" +} diff --git a/templates/openwisp2/settings.py b/templates/openwisp2/settings.py index 1247074c..37168847 100644 --- a/templates/openwisp2/settings.py +++ b/templates/openwisp2/settings.py @@ -45,6 +45,15 @@ 'openwisp_controller.connection', {% if openwisp2_network_topology %} 'openwisp_network_topology', +{% endif %} +{% if openwisp2_radius %} + 'django_filters', + 'rest_framework.authtoken', + 'rest_auth', + 'rest_auth.registration', + 'openwisp_radius', + 'private_storage', + 'drf_yasg', {% endif %} # admin 'django.contrib.admin', @@ -91,15 +100,32 @@ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] +{% if openwisp2_radius %} +OPENWISP_RADIUS_FREERADIUS_ALLOWED_HOSTS = {{ openwisp2_freeradius_allowed_hosts }} + +# SMS +REST_AUTH_SERIALIZERS = { + 'PASSWORD_RESET_SERIALIZER': 'openwisp_radius.api.serializers.PasswordResetSerializer', +} +REST_AUTH_REGISTER_SERIALIZERS = { + 'REGISTER_SERIALIZER': 'openwisp_radius.api.serializers.RegisterSerializer', +} +OPENWISP_RADIUS_EXTRA_NAS_TYPES = tuple({{ openwisp2_radius_extra_nas_types }}) +OPENWISP_RADIUS_SMS_TOKEN_MAX_IP_DAILY = {{ openwisp2_radius_sms_token_max_ip_daily }} +SENDSMS_BACKEND = '{{ openwisp2_radius_sms_backend }}' + +OPENWISP_USERS_AUTH_API = {{ openwisp2_users_auth_api }} +{% endif %} + ROOT_URLCONF = 'openwisp2.urls' CHANNEL_LAYERS = { 'default': { - "BACKEND": "channels_redis.core.RedisChannelLayer", - "CONFIG": {"hosts": [('localhost', 6379)]}, + 'BACKEND': 'channels_redis.core.RedisChannelLayer', + 'CONFIG': {'hosts': [('localhost', 6379)]}, }, } -ASGI_APPLICATION = "openwisp_controller.geo.channels.routing.channel_routing" +ASGI_APPLICATION = 'openwisp_controller.geo.channels.routing.channel_routing' TEMPLATES = [ { @@ -132,17 +158,17 @@ # FOR DJANGO REDIS CACHES = { - "default": { - "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": "{{ openwisp2_redis_cache_url }}", - "OPTIONS": { - "CLIENT_CLASS": "django_redis.client.DefaultClient", + 'default': { + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': '{{ openwisp2_redis_cache_url }}', + 'OPTIONS': { + 'CLIENT_CLASS': 'django_redis.client.DefaultClient', } } } -SESSION_ENGINE = "django.contrib.sessions.backends.cache" -SESSION_CACHE_ALIAS = "default" +SESSION_ENGINE = 'django.contrib.sessions.backends.cache' +SESSION_CACHE_ALIAS = 'default' SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True @@ -201,8 +227,9 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.9/howto/static-files/ -STATIC_ROOT = '%s/static' % BASE_DIR -MEDIA_ROOT = '%s/media' % BASE_DIR +STATIC_ROOT = os.path.join(BASE_DIR, 'static') +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') +PRIVATE_STORAGE_ROOT = os.path.join(MEDIA_ROOT, 'private') STATIC_URL = '/static/' MEDIA_URL = '/media/' diff --git a/templates/openwisp2/urls.py b/templates/openwisp2/urls.py index 1a4ab978..a80225cf 100644 --- a/templates/openwisp2/urls.py +++ b/templates/openwisp2/urls.py @@ -16,6 +16,11 @@ {% if openwisp2_network_topology %} url(r'^', include('openwisp_network_topology.urls')), {% endif %} + {% if openwisp2_radius %} + url(r'^', include('openwisp_radius.urls')), + url(r'^api/v1/', include('openwisp_utils.api.urls')), + url(r'^api/v1/', include('openwisp_users.api.urls')), + {% endif %} {% for extra_url in openwisp2_extra_urls %} {{ extra_url }}, {% endfor %}