diff --git a/README.rst b/README.rst index 3fd73b9a3..c353faf2a 100644 --- a/README.rst +++ b/README.rst @@ -49,4532 +49,41 @@ built on top of its building blocks. Other popular building blocks that are part of the OpenWISP ecosystem are: -- `openwisp-monitoring - `_: provides device - status monitoring, collection of metrics, charts, alerts, possibility to - define custom checks +- `openwisp-monitoring `_: + provides device status monitoring, collection of metrics, charts, + alerts, possibility to define custom checks - `openwisp-firmware-upgrader - `_: automated - firmware upgrades (single devices or mass network upgrades) -- `openwisp-radius `_: based - on FreeRADIUS, allows to implement network access authentication systems - like 802.1x WPA2 Enterprise, captive portal authentication, Hotspot 2.0 - (802.11u) + `_: automated firmware + upgrades (single devices or mass network upgrades) +- `openwisp-radius `_: + based on FreeRADIUS, allows to implement network access authentication + systems like 802.1x WPA2 Enterprise, captive portal authentication, + Hotspot 2.0 (802.11u) - `openwisp-network-topology - `_: provides way - to collect and visualize network topology data from dynamic mesh routing + `_: provides way to + collect and visualize network topology data from dynamic mesh routing daemons or other network software (eg: OpenVPN); it can be used in conjunction with openwisp-monitoring to get a better idea of the state of the network -- `openwisp-ipam `_: allows to - manage the assignment of IP addresses used in the network -- `openwisp-notifications - `_: allows users to - be aware of important events happening in the network. +- `openwisp-ipam `_: allows to manage + the assignment of IP addresses used in the network +- `openwisp-notifications `_: + allows users to be aware of important events happening in the network. **For a more complete overview of the OpenWISP modules and architecture**, see the `OpenWISP Architecture Overview -`_. +`_. .. image:: https://raw.githubusercontent.com/openwisp/openwisp2-docs/master/assets/design/openwisp-logo-black.svg :target: http://openwisp.org :alt: OpenWISP -**Want to help OpenWISP?** `Find out how to help us grow here -`_. +Documentation +------------- ----- - -.. contents:: **Table of Contents**: - :backlinks: none - :depth: 3 - ----- - -Project Structure & main features ---------------------------------- - -OpenWISP Controller is a python package consisting of four django apps: - -Config App -~~~~~~~~~~ - -- **configuration management** for embedded devices supporting different firmwares: - - `OpenWRT `_ - - `OpenWISP Firmware - `_ - - support for additional firmware can be added by `specifying custom - backends <#netjsonconfig-backends>`_ -- **configuration editor** based on `JSON-Schema editor - `_ -- **advanced edit mode**: edit `NetJSON `_ - *DeviceConfiguration* objects for maximum flexibility -- `configuration templates - `_: reduce repetition to - the minimum, configure default and required templates -- `configuration variables <#how-to-use-configuration-variables>`_: - reference ansible-like variables in the configuration and templates -- **template tags**: tag templates to automate different types of - auto-configurations (eg: mesh, WDS, 4G) -- **device groups**: add `devices to dedicated groups <#device-groups>`_ - to ease management of group of devices -- **simple HTTP resources**: allow devices to automatically download - configuration updates -- **VPN management**: `automatically provision VPN tunnels - <#openwisp-controller-default-auto-cert>`_, including cryptographic - keys, IP addresses -- `REST API <#rest-api-reference>`_ -- `Export/Import devices <#exportimport-device-data>`_ - -PKI App -~~~~~~~ - -The PKI app is based on `django-x509 -`_, it allows to create, import -and view x509 CAs and certificates directly from the administration -dashboard, it also adds different endpoints to the `REST API -<#rest-api-reference>`_. - -Connection App -~~~~~~~~~~~~~~ - -This app allows OpenWISP Controller to use different protocols to reach -network devices. Currently, the default connnection protocols are SSH and -SNMP, but the protocol mechanism is extensible and more protocols can be -implemented if needed. - -SSH -+++ - -The SSH connector allows the controller to initialize connections to the -devices in order perform `push operations -<#how-to-configure-push-updates>`__: - -- Sending configuration updates. -- `Executing shell commands <#sending-commands-to-devices>`_. -- Perform `firmware upgrades via the additional firmware upgrade module - `_. -- `REST API <#rest-api-reference>`_ - -The default connection protocol implemented is SSH, but other protocol -mechanism is extensible and custom protocols can be implemented as well. - -Access via SSH key is recommended, the SSH key algorithms supported are: - -- RSA -- Ed25519 - -SNMP -++++ - -The SNMP connector is useful to collect monitoring information and it's -used in openwisp-monitoring_ for performing checks to collect monitoring -information. `Read more -`_ -on how to use it. - -Geo App -~~~~~~~ - -The geographic app is based on `django-loci -`_ and allows to define the -geographic coordinates of the devices, as well as their indoor coordinates -on floorplan images. - -It also adds different endpoints to the `REST API <#rest-api-reference>`_. - -Subnet Division App -~~~~~~~~~~~~~~~~~~~ - -This app allows to automatically provision subnets and IP addresses which -will be available as `system defined configuration variables -<#system-defined-variables>`_ that can be used in templates. The purpose -of this app is to allow users to automatically provision and configure -specific subnets and IP addresses to the devices without the need of -manual intervention. - -Refer to `"How to configure automatic provisioning of subnets and IPs" -section of this documentation -<#how-to-configure-automatic-provisioning-of-subnets-and-ips>`_ to learn -about features provided by this app. - -This app is optional, if you don't need it you can avoid adding it to -``settings.INSTALLED_APPS``. - -Installation instructions -------------------------- - -Deploy it in production -~~~~~~~~~~~~~~~~~~~~~~~ - -See: - -- `ansible-openwisp2 `_ -- `docker-openwisp `_ - -Dependencies -~~~~~~~~~~~~ - -- Python >= 3.7 -- OpenSSL - -Install stable version from pypi -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Install from pypi: - -.. code-block:: shell - - pip install openwisp-controller - -Install development version -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Install tarball: - -.. code-block:: shell - - pip install https://github.com/openwisp/openwisp-controller/tarball/master - -Alternatively you can install via pip using git: - -.. code-block:: shell - - pip install -e git+git://github.com/openwisp/openwisp-controller#egg=openwisp_controller - -If you want to contribute, follow the instructions in `Installing for -development <#installing-for-development>`_. - -Installing for development -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Install the system dependencies: - -.. code-block:: shell - - sudo apt update - sudo apt install -y sqlite3 libsqlite3-dev openssl libssl-dev - sudo apt install -y gdal-bin libproj-dev libgeos-dev libspatialite-dev libsqlite3-mod-spatialite - sudo apt install -y chromium - -Fork and clone the forked repository: - -.. code-block:: shell - - git clone git://github.com//openwisp-controller - -Navigate into the cloned repository: - -.. code-block:: shell - - cd openwisp-controller/ - -Launch Redis and PostgreSQL: - -.. code-block:: shell - - docker-compose up -d redis postgres - -Setup and activate a virtual-environment. (we'll be using `virtualenv -`_) - -.. code-block:: shell - - python -m virtualenv env - source env/bin/activate - -Make sure that you are using pip version 20.2.4 before moving to the next -step: - -.. code-block:: shell - - pip install -U pip wheel setuptools - -Install development dependencies: - -.. code-block:: shell - - pip install -e . - pip install -r requirements-test.txt - npm install -g jshint stylelint - -Install WebDriver for Chromium for your browser version from -https://chromedriver.chromium.org/home and Extract ``chromedriver`` to one -of directories from your ``$PATH`` (example: ``~/.local/bin/``). - -Create database: - -.. code-block:: shell - - cd tests/ - ./manage.py migrate - ./manage.py createsuperuser - -Launch celery worker (for background jobs): - -.. code-block:: shell - - celery -A openwisp2 worker -l info - -Launch development server: - -.. code-block:: shell - - ./manage.py runserver 0.0.0.0:8000 - -You can access the admin interface at http://127.0.0.1:8000/admin/. - -Run tests with: - -.. code-block:: shell - - ./runtests.py --parallel - # To run database tests against PostgreSQL backend - POSTGRESQL=1 ./runtests.py --parallel - -Run quality assurance tests with: - -.. code-block:: shell - - ./run-qa-checks - -Install and run on docker -~~~~~~~~~~~~~~~~~~~~~~~~~ - -NOTE: This Docker image is for development purposes only. For the official -OpenWISP Docker images, see: `docker-openwisp -`_. - -Build from the Dockerfile: - -.. code-block:: shell - - docker-compose build - -Run the docker container: - -.. code-block:: shell - - docker-compose up - -Troubleshooting steps for common installation issues -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You may encounter some issues while installing GeoDjango. - -Unable to load SpatiaLite library extension? -++++++++++++++++++++++++++++++++++++++++++++ - -If you are getting below exception: - -:: - - django.core.exceptions.ImproperlyConfigured: Unable to load the SpatiaLite library extension - -then, You need to specify ``SPATIALITE_LIBRARY_PATH`` in your -``settings.py`` as explained in `django documentation regarding how to -install and configure spatialte -`_. - -Having Issues with other geospatial libraries? -++++++++++++++++++++++++++++++++++++++++++++++ - -Please refer `troubleshooting issues related to geospatial libraries -`_. - -Setup (integrate in an existing django project) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Add ``openwisp_controller`` applications to ``INSTALLED_APPS``: - -.. code-block:: python - - INSTALLED_APPS = [ - ... - # openwisp2 modules - 'openwisp_controller.config', - 'openwisp_controller.pki', - 'openwisp_controller.geo', - 'openwisp_controller.connection', - 'openwisp_controller.subnet_division', # Optional - 'openwisp_controller.notifications', - 'openwisp_users', - 'openwisp_notifications', - 'openwisp_ipam', - # openwisp2 admin theme - # (must be loaded here) - 'openwisp_utils.admin_theme', - 'admin_auto_filters', - 'django.contrib.admin', - 'django.forms', - 'import_export', - ... - ] - EXTENDED_APPS = ('django_x509', 'django_loci') - -**Note**: The order of applications in ``INSTALLED_APPS`` should be -maintained, otherwise it might not work properly. - -Other settings needed in ``settings.py``: - -.. code-block:: python - - STATICFILES_FINDERS = [ - "django.contrib.staticfiles.finders.FileSystemFinder", - "django.contrib.staticfiles.finders.AppDirectoriesFinder", - "openwisp_utils.staticfiles.DependencyFinder", - ] - - ASGI_APPLICATION = ( - "openwisp_controller.geo.channels.routing.channel_routing" - ) - CHANNEL_LAYERS = { - # in production you should use another channel layer backend - "default": {"BACKEND": "channels.layers.InMemoryChannelLayer"}, - } - - TEMPLATES = [ - { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [], - "OPTIONS": { - "loaders": [ - "django.template.loaders.filesystem.Loader", - "django.template.loaders.app_directories.Loader", - "openwisp_utils.loaders.DependencyLoader", - ], - "context_processors": [ - "django.template.context_processors.debug", - "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - "openwisp_utils.admin_theme.context_processor.menu_items", - "openwisp_notifications.context_processors.notification_api_settings", - ], - }, - } - ] - - FORM_RENDERER = "django.forms.renderers.TemplatesSetting" - -Add the URLs to your main ``urls.py``: - -.. code-block:: python - - urlpatterns = [ - # ... other urls in your project ... - # openwisp-controller urls - url(r"^admin/", admin.site.urls), - url(r"", include("openwisp_controller.urls")), - ] - -Configure caching (you may use a different cache storage if you want): - -.. code-block:: python - - CACHES = { - "default": { - "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": "redis://localhost/0", - "OPTIONS": { - "CLIENT_CLASS": "django_redis.client.DefaultClient", - }, - } - } - - SESSION_ENGINE = "django.contrib.sessions.backends.cache" - SESSION_CACHE_ALIAS = "default" - -Configure celery (you may use a different broker if you want): - -.. code-block:: python - - # here we show how to configure celery with redis but you can - # use other brokers if you want, consult the celery docs - CELERY_BROKER_URL = "redis://localhost/1" - - INSTALLED_APPS.append("djcelery_email") - EMAIL_BACKEND = "djcelery_email.backends.CeleryEmailBackend" - -If you decide to use redis (as shown in these examples), install the -required python packages: - -:: - - pip install redis django-redis - -Then run: - -.. code-block:: shell - - ./manage.py migrate - -Usage reference ---------------- - -Default Templates -~~~~~~~~~~~~~~~~~ - -When templates are flagged as default, they will be automatically assigned -to new devices. - -If there are multiple default templates, these are assigned to the device -in alphabetical order based on their names, for example, given the -following default templates: - -- Access -- Interfaces -- SSH Keys - -They will be assigned to devices in exactly that order. - -If for some technical reason (eg: one default template depends on the -presence of another default template which must be assigned earlier) you -need to change the ordering, you can simply rename the templates by -prefixing them with numbers, eg: - -- 1 Interfaces -- 2. SSH Keys -- 3. Access - -Required Templates -~~~~~~~~~~~~~~~~~~ - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/required-templates.png - :alt: Required template example - -Required templates are similar to `Default templates -<#default-templates>`__ but cannot be unassigned from a device -configuration, they can only be overridden. - -They will be always assigned earlier than default templates, so they can -be overridden if needed. - -In the example above, the "SSID" template is flagged as "(required)" and -its checkbox is always checked and disabled. - -How to use configuration variables -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Sometimes the configuration is not exactly equal on all the devices, some -parameters are unique to each device or need to be changed by the user. - -In these cases it is possible to use configuration variables in -conjunction with templates, this feature is also known as *configuration -context*, think of it like a dictionary which is passed to the function -which renders the configuration, so that it can fill variables according -to the passed context. - -The different ways in which variables are defined are described below in -the order (high to low) of their precedence: - -1. `User defined device variables <#user-defined-device-variables>`_ -2. `Predefined device variables <#predefined-device-variables>`_ -3. `Group variables <#group-variables>`_ -4. `Organization variables <#organization-variables>`_ -5. `Global variables <#global-variables>`_ -6. `Template default values <#template-default-values>`_ - -User defined device variables -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In the device configuration section you can find a section named -"Configuration variables" where it is possible to define the configuration -variables and their values, as shown in the example below: - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/device-context.png - :alt: context - -Predefined device variables -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Each device gets the following attributes passed as configuration -variables: - -- ``id`` -- ``key`` -- ``name`` -- ``mac_address`` - -Group variables -~~~~~~~~~~~~~~~ - -Variables can also be defined in `Device groups <#device-groups>`__. - -Refer the `Group configuration variables `_ -section for detailed information. - -Organization variables -~~~~~~~~~~~~~~~~~~~~~~ - -Variables can also be defined at the organization level. - -You can set the *organization variables* from the organization change page -``/admin/openwisp_users/organization//change/``, under -the **Configuration Management Settings**. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/organization-variables.png - :alt: organization variables - -Global variables -~~~~~~~~~~~~~~~~ - -Variables can also be defined globally using the -`OPENWISP_CONTROLLER_CONTEXT <#openwisp-controller-context>`_ setting. - -Template default values -~~~~~~~~~~~~~~~~~~~~~~~ - -It's possible to specify the default values of variables defined in a -template. - -This allows to achieve 2 goals: - -1. pass schema validation without errors (otherwise it would not be - possible to save the template in the first place) -2. provide good default values that are valid in most cases but can be - overridden in the device if needed - -These default values will be overridden by the `User defined device -variables <#user-defined-device-variables>`_. - -The default values of variables can be manipulated from the section -"configuration variables" in the edit template page: - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/template-default-values.png - :alt: default values - -System defined variables -~~~~~~~~~~~~~~~~~~~~~~~~ - -Predefined device variables, global variables and other variables that are -automatically managed by the system (eg: when using templates of type -VPN-client) are displayed in the admin UI as *System Defined Variables* in -read-only mode. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/system-defined-variables.png - :alt: system defined variables - -**Note:** `Group configuration variables -<#group-configuration-variables>`__ are also added to the **System Defined -Variables** of the device. - -Example usage of variables -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Here's a typical use case, the WiFi SSID and WiFi password. You don't want -to define this for every device, but you may want to allow operators to -easily change the SSID or WiFi password for a specific device without -having to re-define the whole wifi interface to avoid duplicating -information. - -This would be the template: - -.. code-block:: json - - { - "interfaces": [ - { - "type": "wireless", - "name": "wlan0", - "wireless": { - "mode": "access_point", - "radio": "radio0", - "ssid": "{{wlan0_ssid}}", - "encryption": { - "protocol": "wpa2_personal", - "key": "{{wlan0_password}}", - "cipher": "auto" - } - } - } - ] - } - -These would be the default values in the template: - -.. code-block:: json - - { - "wlan0_ssid": "SnakeOil PublicWiFi", - "wlan0_password": "Snakeoil_pwd!321654" - } - -The default values can then be overridden at `device level -<#user-defined-device-variables>`_ if needed, eg: - -.. code-block:: json - - { - "wlan0_ssid": "Room 23 ACME Hotel", - "wlan0_password": "room_23pwd!321654" - } - -How to configure push updates -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Follow the procedure described below to enable secure SSH access from -OpenWISP to your devices, this is required to enable push updates -(whenever the configuration is changed, OpenWISP will trigger the update -in the background) and/or `firmware upgrades (via the additional module -openwisp-firmware-upgrader) -`_. - -**Note**: If you have installed OpenWISP with `openwisp2 Ansbile role -`_ then you can skip the -following steps. The Ansible role automatically creates a default template -to update ``authorized_keys`` on networking devices using the default -access credentials. - -1. Generate SSH key -+++++++++++++++++++ - -First of all, we need to generate the SSH key which will be used by -OpenWISP to access the devices, to do so, you can use the following -command: - -.. code-block:: shell - - echo './sshkey' | ssh-keygen -t ed25519 -C "openwisp" - -This will create two files in the current directory, one called ``sshkey`` -(the private key) and one called ``sshkey.pub`` (the public key). - -Store the content of these files in a secure location. - -**Note:** Support for **ED25519** was added in OpenWrt 21.02 (requires -Dropbear > 2020.79). If you are managing devices with OpenWrt < 21, then -you will need to use RSA keys: - -.. code-block:: shell - - echo './sshkey' | ssh-keygen -t rsa -b 4096 -C "openwisp" - -2. Save SSH private key in OpenWISP (access credentials) -++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/add-ssh-credentials-private-key.png - :alt: add SSH private key as access credential in OpenWISP - -From the first page of OpenWISP click on "Access credentials", then click -on the **"ADD ACCESS CREDENTIALS"** button in the upper right corner -(alternatively, go to the following URL: -``/admin/connection/credentials/add/``). - -Select SSH as ``type``, enable the **Auto add** checkbox, then at the -field "Credentials type" select "SSH (private key)", now type "root" in -the ``username`` field, while in the ``key`` field you have to paste the -contents of the private key just created. - -Now hit save. - -The credentials just created will be automatically enabled for all the -devices in the system (both existing devices and devices which will be -added in the future). - -3. Add the public key to your devices -+++++++++++++++++++++++++++++++++++++ - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/add-authorized-ssh-keys-template.png - :alt: Add authorized SSH public keys template to OpenWISP (OpenWRT) - -Now we need to instruct your devices to allow OpenWISP accessing via SSH, -in order to do this we need to add the contents of the public key file -created in step 1 (``sshkey.pub``) in the file -``/etc/dropbear/authorized_keys`` on the devices, the recommended way to -do this is to create a configuration template in OpenWISP: from the first -page of OpenWISP, click on "Templates", then and click on the **"ADD -TEMPLATE"** button in the upper right corner (alternatively, go to the -following URL: ``/admin/config/template/add/``). - -Check **enabled by default**, then scroll down the configuration section, -click on "Configuration Menu", scroll down, click on "Files" then close -the menu by clicking again on "Configuration Menu". Now type -``/etc/dropbear/authorized_keys`` in the ``path`` field of the file, then -paste the contents of ``sshkey.pub`` in ``contents``. - -Now hit save. - -**There's a catch**: you will need to assign the template to any existing -device. - -4. Test it -++++++++++ - -Once you have performed the 3 steps above, you can test it as follows: - -1. Ensure there's at least one device turned on and connected to OpenWISP, - ensure this device has the "SSH Authorized Keys" assigned to it. -2. Ensure the celery worker of OpenWISP Controller is running (eg: ``ps - aux | grep celery``) -3. SSH into the device and wait (maximum 2 minutes) until - ``/etc/dropbear/authorized_keys`` appears as specified in the template. -4. While connected via SSH to the device run the following command in the - console: ``logread -f``, now try changing the device name in OpenWISP -5. Shortly after you change the name in OpenWISP, you should see some - output in the SSH console indicating another SSH access and the - configuration update being performed. - -Sending Commands to Devices -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -By default, there are three options in the **Send Command** dropdown: - -1. Reboot -2. Change Password -3. Custom Command - -While the first two options are self-explanatory, the **custom command** -option allows you to execute any command on the device as shown in the -example below. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/commands_demo.gif - :target: https://github.com/openwisp/openwisp-controller/tree/docs/docs/commands_demo.gif - :alt: Executing commands on device example - -**Note**: in order for this feature to work, a device needs to have at -least one **Access Credential** (see `How to configure push updates -<#how-to-configure-push-updates>`__). - -The **Send Command** button will be hidden until the device has at least -one **Access Credential**. - -If you need to allow your users to quickly send specific commands that are -used often in your network regardless of your users' knowledge of Linux -shell commands, you can add new commands by following instructions in the -`"How to define new options in the commands menu" -<#how-to-define-new-options-in-the-commands-menu>`_ section below. - -If you are an advanced user and want to register commands programatically, -then refer to `"Register / Unregistering commands" -<#registering--unregistering-commands>`_ section. - -How to define new options in the commands menu -++++++++++++++++++++++++++++++++++++++++++++++ - -Let's explore to define new custom commands to help users perform -additional management actions without having to be Linux/Unix experts. - -We can do so by using the ``OPENWISP_CONTROLLER_USER_COMMANDS`` django -setting. - -The following example defines a simple command that can ``ping`` an input -``destination_address`` through a network interface, ``interface_name``. - -.. code-block:: python - - # In yourproject/settings.py - - - def ping_command_callable(destination_address, interface_name=None): - command = f"ping -c 4 {destination_address}" - if interface_name: - command += f" -I {interface_name}" - return command - - - OPENWISP_CONTROLLER_USER_COMMANDS = [ - ( - "ping", - { - "label": "Ping", - "schema": { - "title": "Ping", - "type": "object", - "required": ["destination_address"], - "properties": { - "destination_address": { - "type": "string", - "title": "Destination Address", - }, - "interface_name": { - "type": "string", - "title": "Interface Name", - }, - }, - "message": "Destination Address cannot be empty", - "additionalProperties": False, - }, - "callable": ping_command_callable, - }, - ) - ] - -The above code will add the "Ping" command in the user interface as show -in the GIF below: - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/ping_command_example.gif - :target: https://github.com/openwisp/openwisp-controller/tree/docs/docs/ping_command_example.gif - :alt: Adding a "ping" command - -The ``OPENWISP_CONTROLLER_USER_COMMANDS`` setting takes a ``list`` of -``tuple`` each containing two elements. The first element of the tuple -should contain an identifier for the command and the second element should -contain a ``dict`` defining configuration of the command. - -Command Configuration -..................... - -The ``dict`` defining configuration for command should contain following -keys: - -1. ``label`` -'''''''''''' - -A ``str`` defining label for the command used internally by Django. - -2. ``schema`` -''''''''''''' - -A ``dict`` defining `JSONSchema `_ for inputs of -command. You can specify the inputs for your command, add rules for -performing validation and make inputs required or optional. - -Here is a detailed explanation of the schema used in above example: - -.. code-block:: python - - { - # Name of the command displayed in "Send Command" widget - 'title': 'Ping', - # Use type "object" if the command needs to accept inputs - # Use type "null" if the command does not accepts any input - 'type': 'object', - # Specify list of inputs that are required - 'required': ['destination_address'], - # Define the inputs for the commands along with their properties - 'properties': { - 'destination_address': { - # type of the input value - 'type': 'string', - # label used for displaying this input field - 'title': 'Destination Address', - }, - 'interface_name': { - 'type': 'string', - 'title': 'Interface Name', - }, - }, - # Error message to be shown if validation fails - 'message': 'Destination Address cannot be empty'), - # Whether specifying addtionaly inputs is allowed from the input form - 'additionalProperties': False, - } - -This example uses only handful of properties available in JSONSchema. You -can experiment with other properties of JSONSchema for schema of your -command. - -3. ``callable`` -''''''''''''''' - -A ``callable`` or ``str`` defining dotted path to a callable. It should -return the command (``str``) to be executed on the device. Inputs of the -command are passed as arguments to this callable. - -The example above includes a callable(``ping_command_callable``) for -``ping`` command. - -Registering / Unregistering Commands -++++++++++++++++++++++++++++++++++++ - -OpenWISP Controller provides registering and unregistering commands -through utility functions -``openwisp_controller.connection.commands.register_command`` and -``openwisp_notifications.types.unregister_notification_type``. You can use -these functions to register or unregister commands from your code. - -**Note**: These functions are to be used as an alternative to the -`"OPENWISP_CONTROLLER_USER_COMMANDS" -<#openwisp-controller-user-commands>`_ when `developing custom modules -based on openwisp-controller <#extending-openwisp-controller>`_ or when -developing custom third party apps. - -``register_command`` -.................... - -================== =================================================== -Parameter Description -``command_name`` A ``str`` defining identifier for the command. -``command_config`` A ``dict`` defining configuration of the command as - shown in `"Command Configuration" - <#command-configuration>`_. -================== =================================================== - -**Note:** It will raise ``ImproperlyConfigured`` exception if a command is -already registered with the same name. - -``unregister_command`` -...................... - -================ ======================================= -Parameter Description -``command_name`` A ``str`` defining name of the command. -================ ======================================= - -**Note:** It will raise ``ImproperlyConfigured`` exception if such command -does not exists. - -Device Groups -~~~~~~~~~~~~~ - -Device Groups provide features aimed at adding specific management rules -for the devices of an organization: - -- Group similar devices by having dedicated groups for access points, - routers, etc. -- Define `group metadata <#group-metadata>`_. -- Define `group configuration templates <#group-templates>`_. -- Define `group configuration variables - <#group-configuration-variables>`__. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.1/device-groups.png - :alt: Device Group example - -Group Templates -+++++++++++++++ - -Groups allow to define templates which are automatically assigned to -devices belonging to the group. When using this feature, keep in mind the -following important points: - -- Templates of any configuration backend can be selected, when a device is - assigned to a group, only the templates which matches the device - configuration backend are applied to the device. -- The system will not force group templates onto devices, this means that - users can remove the applied group templates from a specific device if - needed. -- If a device group is changed, the system will automatically remove the - group templates of the old group and apply the new templates of the new - group (this operation is implemented by leveraging the - `group_templates_changed <#group_templates_changed>`_ signal). -- If the group templates are changed, the devices which belong to the - group will be automatically updated to reflect the changes (this - operation is executed in a background task). -- In case the configuration backend of a device is changed, the system - will handle this automatically too and update the group templates - accordingly (this operation is implemented by leveraging the - `config_backend_changed <#config_backend_changed>`_ signal). -- If a device does not have a configuration defined yet, but it is - assigned to a group which has templates defined, the system will - automatically create a configuration for it using the default backend - specified in `OPENWISP_CONTROLLER_DEFAULT_BACKEND - <#OPENWISP_CONTROLLER_DEFAULT_BACKEND>`_ setting. - -**Note:** the list of templates shown in the edit group page do not -contain templates flagged as "default" or "required" to avoid redundancy -because those templates are automatically assigned by the system to new -devices. - -This feature works also when editing group templates or the group assigned -to a device via the `REST API <#change-device-group-detail>`__. - -Group Configuration Variables -+++++++++++++++++++++++++++++ - -Groups allow to define configuration variables which are automatically -added to the device's context in the **System Defined Variables**. Check -the `"How to use configuration variables" section -<#how-to-use-configuration-variables>`_ to learn about precedence of -different configuration variables. - -This feature works also when editing group templates or the group assigned -to a device via the `REST API <#change-device-group-detail>`__. - -Group Metadata -++++++++++++++ - -Groups allow to store additional information regarding a group in the -structured metadata field (which can be accessed via the REST API). - -The metadata field allows custom structure and validation to standardize -information across all groups using the -`"OPENWISP_CONTROLLER_DEVICE_GROUP_SCHEMA" -<#openwisp-controller-device-group-schema>`_ setting. - -**Note:** *Group configuration variables* and *Group metadata* serves -different purposes. The group configuration variables should be used when -the device configuration is required to be changed for particular group of -devices. Group metadata should be used to store additional data for the -devices. Group metadata is not used for configuration generation. - -Export/Import Device data -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.1/import-export/device-list.png - :alt: Import / Export - -The device list page offers two buttons to export and import device data -in different formats. - -The export feature respects any filters selected in the device list. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.1/import-export/export-page.png - :alt: Export - -For importing devices into the system, only the required fields are -needed, for example, the following CSV file will import a device named -``TestImport`` with mac address ``00:11:22:09:44:55`` in the organization -with UUID ``3cb5e18c-0312-48ab-8dbd-038b8415bd6f``: - -:: - - organization_id,name,mac_address - 3cb5e18c-0312-48ab-8dbd-038b8415bd6f,TestImport,00:11:22:09:44:55 - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.1/import-export/import-page.png - :alt: Import / Export - -Organization Limits -~~~~~~~~~~~~~~~~~~~ - -Allows configuring following limits for each organization: - -- Limit number of devices managed by the organization. - -You can change the limits from the organization's admin page: - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.1/organization-limits.png - :alt: Organization limits - -How to setup WireGuard tunnels -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Follow the procedure described below to setup WireGuard tunnels on your -devices. - -**Note:** This example uses **Shared systemwide (no organization)** option -as the organization for VPN server and VPN client template. You can use -any organization as long as VPN server, VPN client template and Device has -same organization. - -1. Create VPN server configuration for WireGuard -++++++++++++++++++++++++++++++++++++++++++++++++ - -1. Visit ``/admin/config/vpn/add/`` to add a new VPN server. -2. We will set **Name** of this VPN server ``Wireguard`` and **Host** as - ``wireguard-server.mydomain.com`` (update this to point to your - WireGuard VPN server). -3. Select ``WireGuard`` from the dropdown as **VPN Backend**. -4. When using WireGuard, OpenWISP takes care of managing IP addresses - (assigning an IP address to each VPN peer). You can create a new subnet - or select an existing one from the dropdown menu. You can also assign - an **Internal IP** to the WireGuard Server or leave it empty for - OpenWISP to configure. This IP address will be used by the WireGuard - interface on server. -5. We have set the **Webhook Endpoint** as - ``https://wireguard-server.mydomain.com:8081/trigger-update`` for this - example. You will need to update this according to you VPN upgrader - endpoint. Set **Webhook AuthToken** to any strong passphrase, this will - be used to ensure that configuration upgrades are requested from - trusted sources. - - **Note**: If you are following this tutorial for also setting up - WireGuard VPN server, just substitute ``wireguard-server.mydomain.com`` - with hostname of your VPN server and follow the steps in next section. - -6. Under the configuration section, set the name of WireGuard tunnel 1 - interface. We have used ``wg0`` in this example. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-tutorial/vpn-server-1.png - :alt: WireGuard VPN server configuration example 1 - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-tutorial/vpn-server-2.png - :alt: WireGuard VPN server configuration example 2 - -7. After clicking on **Save and continue editing**, you will see that - OpenWISP has automatically created public and private key for WireGuard - server in **System Defined Variables** along with internal IP address - information. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-tutorial/vpn-server-3.png - :alt: WireGuard VPN server configuration example 3 - -2. Deploy Wireguard VPN Server -++++++++++++++++++++++++++++++ - -If you haven't already setup WireGuard on your VPN server, this will be a -good time do so. We recommend using the `ansible-wireguard-openwisp -`_ role for -installing WireGuard since it also installs scripts that allows OpenWISP -to manage WireGuard VPN server. - -Pay attention to the VPN server attributes used in your playbook. It -should be same as VPN server configuration in OpenWISP. - -3. Create VPN client template for WireGuard VPN Server -++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -1. Visit ``/admin/config/template/add/`` to add a new template. -2. Set ``Wireguard Client`` as **Name** (you can set whatever you want) - and select ``VPN-client`` as **type** from the dropdown list. -3. The **Backend** field refers to the backend of the device this template - can be applied to. For this example, we will leave it to ``OpenWRT``. -4. Select the correct VPN server from the dropdown for the **VPN** field. - Here it is ``Wireguard``. -5. Ensure that **Automatic tunnel provisioning** is checked. This will - make OpenWISP to automatically generate public and private keys and - provision IP address for each WireGuard VPN client. -6. After clicking on **Save and continue editing** button, you will see - details of *Wireguard* VPN server in **System Defined Variables**. The - template configuration will be automatically generated which you can - tweak accordingly. We will use the automatically generated VPN client - configuration for this example. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-tutorial/template.png - :alt: WireGuard VPN client template example - -4. Apply Wireguard VPN template to devices -++++++++++++++++++++++++++++++++++++++++++ - -**Note**: This step assumes that you already have a device registered on -OpenWISP. Register or create a device before proceeding. - -1. Open the **Configuration** tab of the concerned device. -2. Select the *WireGuard Client* template. -3. Upon clicking on **Save and continue editing** button, you will see - some entries in **System Defined Variables**. It will contain internal - IP address, private and public key for the WireGuard client on the - device along with details of WireGuard VPN server. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-tutorial/device-configuration.png - :alt: WireGuard VPN device configuration example - -**Voila!** You have successfully configured OpenWISP to manage WireGuard -tunnels for your devices. - -How to setup VXLAN over WireGuard tunnels -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -By following these steps, you will be able to setup layer 2 VXLAN tunnels -encapsulated in WireGuard tunnels which work on layer 3. - -**Note:** This example uses **Shared systemwide (no organization)** option -as the organization for VPN server and VPN client template. You can use -any organization as long as VPN server, VPN client template and Device has -same organization. - -1. Create VPN server configuration for VXLAN over WireGuard -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -1. Visit ``/admin/config/vpn/add/`` to add a new VPN server. -2. We will set **Name** of this VPN server ``Wireguard VXLAN`` and - **Host** as ``wireguard-vxlan-server.mydomain.com`` (update this to - point to your WireGuard VXLAN VPN server). -3. Select ``VXLAN over WireGuard`` from the dropdown as **VPN Backend**. -4. When using VXLAN over WireGuard, OpenWISP takes care of managing IP - addresses (assigning an IP address to each VPN peer). You can create a - new subnet or select an existing one from the dropdown menu. You can - also assign an **Internal IP** to the WireGuard Server or leave it - empty for OpenWISP to configure. This IP address will be used by the - WireGuard interface on server. -5. We have set the **Webhook Endpoint** as - ``https://wireguard-vxlan-server.mydomain.com:8081/trigger-update`` for - this example. You will need to update this according to you VPN - upgrader endpoint. Set **Webhook AuthToken** to any strong passphrase, - this will be used to ensure that configuration upgrades are requested - from trusted sources. - - **Note**: If you are following this tutorial for also setting up - WireGuard VPN server, just substitute ``wireguard-server.mydomain.com`` - with hostname of your VPN server and follow the steps in next section. - -6. Under the configuration section, set the name of WireGuard tunnel 1 - interface. We have used ``wg0`` in this example. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-vxlan-tutorial/vpn-server-1.png - :alt: WireGuard VPN VXLAN server configuration example 1 - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-vxlan-tutorial/vpn-server-2.png - :alt: WireGuard VPN VXLAN server configuration example 2 - -7. After clicking on **Save and continue editing**, you will see that - OpenWISP has automatically created public and private key for WireGuard - server in **System Defined Variables** along with internal IP address - information. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-vxlan-tutorial/vpn-server-3.png - :alt: WireGuard VXLAN VPN server configuration example 3 - -2. Deploy Wireguard VXLAN VPN Server -++++++++++++++++++++++++++++++++++++ - -If you haven't already setup WireGuard on your VPN server, this will be a -good time do so. We recommend using the `ansible-wireguard-openwisp -`_ role for -installing WireGuard since it also installs scripts that allows OpenWISP -to manage WireGuard VPN server along with VXLAN tunnels. - -Pay attention to the VPN server attributes used in your playbook. It -should be same as VPN server configuration in OpenWISP. - -3. Create VPN client template for WireGuard VXLAN VPN Server -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -1. Visit ``/admin/config/template/add/`` to add a new template. -2. Set ``Wireguard VXLAN Client`` as **Name** (you can set whatever you - want) and select ``VPN-client`` as **type** from the dropdown list. -3. The **Backend** field refers to the backend of the device this template - can be applied to. For this example, we will leave it to ``OpenWRT``. -4. Select the correct VPN server from the dropdown for the **VPN** field. - Here it is ``Wireguard VXLAN``. -5. Ensure that **Automatic tunnel provisioning** is checked. This will - make OpenWISP to automatically generate public and private keys and - provision IP address for each WireGuard VPN client along with VXLAN - Network Indentifier(VNI). -6. After clicking on **Save and continue editing** button, you will see - details of *Wireguard VXLAN* VPN server in **System Defined - Variables**. The template configuration will be automatically generated - which you can tweak accordingly. We will use the automatically - generated VPN client configuration for this example. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-vxlan-tutorial/template.png - :alt: WireGuard VXLAN VPN client template example - -4. Apply Wireguard VXLAN VPN template to devices -++++++++++++++++++++++++++++++++++++++++++++++++ - -**Note**: This step assumes that you already have a device registered on -OpenWISP. Register or create a device before proceeding. - -1. Open the **Configuration** tab of the concerned device. -2. Select the *WireGuard VXLAN Client* template. -3. Upon clicking on **Save and continue editing** button, you will see - some entries in **System Defined Variables**. It will contain internal - IP address, private and public key for the WireGuard client on the - device and details of WireGuard VPN server along with VXLAN Network - Identifier(VNI) of this device. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-vxlan-tutorial/device-configuration.png - :alt: WireGuard VXLAN VPN device configuration example - -**Voila!** You have successfully configured OpenWISP to manage VXLAN over -WireGuard tunnels for your devices. - -How to setup ZeroTier Tunnels -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Follow the procedure described below to setup ZeroTier tunnels on your -devices. - -**Note:** This example uses **Shared systemwide (no organization)** option -as the organization for VPN server and VPN client template. You can use -any organization as long as VPN server, VPN client template and Device has -same organization. - -1. Configure Self-Hosted ZeroTier Network Controller -++++++++++++++++++++++++++++++++++++++++++++++++++++ - -If you haven't already set up a self-hosted Zerotier network controller on -your server, now is a good time to do so. You can start by simply -installing Zerotier on your server from the `official website -`_. - -2. Create VPN server configuration for ZeroTier -+++++++++++++++++++++++++++++++++++++++++++++++ - -1. Visit ``/admin/config/vpn/add/`` to add a new VPN server. -2. We will set **Name** of this VPN server ``ZeroTier`` and **Host** as - ``my-zerotier-server.mydomain.com:9993`` (update this to point to your - ZeroTier VPN server). -3. Select ``ZeroTier`` from the dropdown as **VPN Backend**. -4. When using ZeroTier, OpenWISP takes care of managing IP addresses - (assigning an IP address to each VPN clients (Zerotier network - members). You can create a new subnet or select an existing one from - the dropdown menu. You can also assign an **Internal IP** to the - Zerotier controller or leave it empty for OpenWISP to configure. This - IP address will be used to assign it to the Zerotier controller running - on the server. -5. Set the **Webhook AuthToken**, this will be ZeroTier authorization - token which you can obtain by running the following command on the - ZeroTier controller: - - .. code-block:: shell - - sudo cat /var/lib/zerotier-one/authtoken.secret - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/vpn-server-1.png - :alt: ZeroTier VPN server configuration example 1 - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/vpn-server-2.png - :alt: ZeroTier VPN server configuration example 2 - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/vpn-server-3.png - :alt: ZeroTier VPN server configuration example 3 - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/vpn-server-4.png - :alt: ZeroTier VPN server configuration example 4 - -6. After clicking on **Save and continue editing**, OpenWISP automatically - detects the node address of the Zerotier controller and creates a - Zerotier network. The **network_id** of this network can be viewed in - the **System Defined Variables** section, where it also provides - internal IP address information. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/vpn-server-5.png - :alt: ZeroTier VPN server configuration example 5 - -3. Create VPN client template for ZeroTier VPN Server -+++++++++++++++++++++++++++++++++++++++++++++++++++++ - -1. Visit ``/admin/config/template/add/`` to add a new template. -2. Set ``ZeroTier Client`` as **Name** (you can set whatever you want) and - select ``VPN-client`` as **type** from the dropdown list. -3. The **Backend** field refers to the backend of the device this template - can be applied to. For this example, we will leave it to ``OpenWRT``. -4. Select the correct VPN server from the dropdown for the **VPN** field. - Here it is ``ZeroTier``. -5. Ensure that the **Automatic tunnel provisioning** option is checked. - This will enable OpenWISP to automatically provision an IP address and - ZeroTier identity secrets (used for assigning member IDs) for each - ZeroTier VPN client. -6. After clicking on **Save and continue editing** button, you will see - details of *ZeroTier* VPN server in **System Defined Variables**. The - template configuration will be automatically generated which you can - tweak accordingly. We will use the automatically generated VPN client - configuration for this example. - -**Note:** OpenWISP uses `zerotier-idtool -`_ -to manage **ZeroTier identity secrets**. Please make sure that you have -`ZeroTier package installed `_ on the -server. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/template.png - :alt: ZeroTier VPN client template example - -4. Apply ZeroTier VPN template to devices -+++++++++++++++++++++++++++++++++++++++++ - -**Note**: This step assumes that you already have a device registered on -OpenWISP. Register or create a device before proceeding. - -1. Open the **Configuration** tab of the concerned device. -2. Select the *ZeroTier Client* template. -3. Upon clicking the **Save and Continue Editing** button, you will see - entries in the **System Defined Variables** section. These entries will - include **zerotier_member_id**, **identity_secret**, and the internal - **IP address** of the ZeroTier client (network member) on the device, - along with details of the VPN server. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/device-configuration-1.png - :alt: ZeroTier VPN device configuration example 1 - -4. Once the configuration is successfully applied to the device, you will - notice a new ZeroTier interface that is up and running. This interface - will have the name ``owzt89f498`` (where ``owzt`` is followed by the - last six hexadecimal characters of the ZeroTier **network ID**). - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/device-configuration-2.png - :alt: ZeroTier VPN device configuration example 2 - -**Voila!** You have successfully configured OpenWISP to manage ZeroTier -tunnels for your devices. - -How to configure automatic provisioning of subnets and IPs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The following steps will help you configure automatic provisioning of -subnets and IPs for devices. - -1. Create a Subnet and a Subnet Division Rule -+++++++++++++++++++++++++++++++++++++++++++++ - -Create a master subnet under which automatically generated subnets will be -provisioned. - -**Note**: Choose the size of the subnet appropriately considering your use -case. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/subnet.png - :alt: Creating a master subnet example - -On the same page, add a **subnet division rule** that will be used to -provision subnets under the master subnet. - -The type of subnet division rule controls when subnets and IP addresses -will be provisioned for a device. The subnet division rule types currently -implemented are described below. - -Device Subnet Division Rule -........................... - -This rule type is triggered whenever a device configuration -(``config.Config`` model) is created for the organization specified in the -rule. - -Creating a new rule of "Device" type will also provision subnets and IP -addresses for existing devices of the organization automatically. - -**Note**: a device without a configuration will not trigger this rule. - -VPN Subnet Division Rule -........................ - -This rule is triggered when a VPN client template is assigned to a device, -provided the VPN server to which the VPN client template relates to has -the same subnet for which the subnet division rule is created. - -**Note:** This rule will only work for **WireGuard** and **VXLAN over -WireGuard** VPN servers. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/subnet-division-rule.png - :alt: Creating a subnet division rule example - -In this example, **VPN subnet division rule** is used. - -2. Create a VPN Server -++++++++++++++++++++++ - -Now create a VPN Server and choose the previously created **master -subnet** as the subnet for this VPN Server. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/vpn-server.png - :alt: Creating a VPN Server example - -3. Create a VPN Client Template -+++++++++++++++++++++++++++++++ - -Create a template, setting the **Type** field to **VPN Client** and -**VPN** field to use the previously created VPN Server. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/vpn-client.png - :alt: Creating a VPN Client template example - -**Note**: You can also check the **Enable by default** field if you want -to automatically apply this template to devices that will register in -future. - -4. Apply VPN Client Template to Devices -+++++++++++++++++++++++++++++++++++++++ - -With everything in place, you can now apply the VPN Client Template to -devices. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/apply-template-to-device.png - :alt: Adding template to device example - -After saving the device, you should see all provisioned Subnets and IPs -for this device under `System Defined Variables -<#system-defined-variables>`_. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/system-defined-variables.png - :alt: Provisioned Subnets and IPs available as System Defined Variables example - -Voila! You can now use these variables in configuration of the device. -Refer to `How to use configuration variables -<#how-to-use-configuration-variables>`_ section of this documentation to -learn how to use configuration variables. - -Important notes for using Subnet Division -+++++++++++++++++++++++++++++++++++++++++ - -- In the above example Subnet, VPN Server, and VPN Client Template - belonged to the **default** organization. You can use **Systemwide - Shared** Subnet, VPN Server, or VPN Client Template too, but Subnet - Division Rule will be always related to an organization. The Subnet - Division Rule will only be triggered when such VPN Client Template will - be applied to a Device having the same organization as Subnet Division - Rule. -- You can also use the configuration variables for provisioned subnets and - IPs in the Template. Each variable will be resolved differently for - different devices. E.g. ``OW_subnet1_ip1`` will resolve to ``10.0.0.1`` - for one device and ``10.0.0.55`` for another. Every device gets its own - set of subnets and IPs. But don't forget to provide the default fall - back values in the "default values" template field (used mainly for - validation). -- The Subnet Division Rule will automatically create a reserved subnet, - this subnet can be used to provision any IP addresses that have to be - created manually. The rest of the master subnet address space **must - not** be interfered with or the automation implemented in this module - will not work. -- The above example used `VPN subnet division rule - <#vpn-subnet-division-rule>`_. Similarly, `device subnet division rule - <#device-subnet-division-rule>`_ can be used, which only requires - `creating a subnet and a subnet division rule - <#1-create-a-subnet-and-a-subnet-division-rule>`_. - -Limitations of Subnet Division -++++++++++++++++++++++++++++++ - -In the current implementation, it is not possible to change "Size", -"Number of Subnets" and "Number of IPs" fields of an existing subnet -division rule due to following reasons: - -Size -.... - -Allowing to change size of provisioned subnets of an existing subnet -division rule will require rebuilding of Subnets and IP addresses which -has possibility of breaking existing configurations. - -Number of Subnets -................. - -Allowing to decrease number of subnets of an existing subnet division rule -can create patches of unused subnets dispersed everywhere in the master -subnet. Allowing to increase number of subnets will break the continuous -allocation of subnets for every device. It can also break configuration of -devices. - -Number of IPs -............. - -Allowing to decrease number of IPs of an existing subnet division rule -will lead to deletion of IP Addresses which can break configuration of -devices being used. It **is allowed** to increase number of IPs. - -If you want to make changes to any of above fields, delete the existing -rule and create a new one. The automation will provision for all existing -devices that meets the criteria for provisioning. **WARNING**: It is -possible that devices get different subnets and IPs from previous -provisioning. - -Default Alerts / Notifications -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -===================== =================================================== -Notification Type Use -``config_error`` Fires when status of a device configuration changes - to ``error``. -``device_registered`` Fires when a new device is registered automatically - on the network. -===================== =================================================== - -REST API Reference ------------------- - -Live documentation -~~~~~~~~~~~~~~~~~~ - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/live-docu-api.png - -A general live API documentation (following the OpenAPI specification) at -``/api/v1/docs/``. - -Browsable web interface -~~~~~~~~~~~~~~~~~~~~~~~ - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/browsable-api-ui.png - -Additionally, opening any of the endpoints `listed below -<#list-of-endpoints>`_ directly in the browser will show the `browsable -API interface of Django-REST-Framework -`_, which -makes it even easier to find out the details of each endpoint. - -Authentication -~~~~~~~~~~~~~~ - -See openwisp-users: `authenticating with the user token -`_. - -When browsing the API via the `Live documentation <#live-documentation>`_ -or the `Browsable web page <#browsable-web-interface>`_, you can also use -the session authentication by logging in the django admin. - -Pagination -~~~~~~~~~~ - -All *list* endpoints support the ``page_size`` parameter that allows -paginating the results in conjunction with the ``page`` parameter. - -.. code-block:: text - - GET /api/v1/controller/template/?page_size=10 - GET /api/v1/controller/template/?page_size=10&page=2 - -List of endpoints -~~~~~~~~~~~~~~~~~ - -Since the detailed explanation is contained in the `Live documentation -<#live-documentation>`_ and in the `Browsable web page -<#browsable-web-interface>`_ of each point, here we'll provide just a list -of the available endpoints, for further information please open the URL of -the endpoint in your browser. - -List devices -++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/device/ - -**Available filters** - -You can filter a list of devices based on their configuration status using -the ``status`` (e.g modified, applied, or error). - -.. code-block:: text - - GET /api/v1/controller/device/?config__status={status} - -You can filter a list of devices based on their configuration backend -using the ``backend`` (e.g netjsonconfig.OpenWrt or -netjsonconfig.OpenWisp). - -.. code-block:: text - - GET /api/v1/controller/device/?config__backend={backend} - -You can filter a list of devices based on their organization using the -``organization_id`` or ``organization_slug``. - -.. code-block:: text - - GET /api/v1/controller/device/?organization={organization_id} - -.. code-block:: text - - GET /api/v1/controller/device/?organization_slug={organization_slug} - -You can filter a list of devices based on their configuration templates -using the ``template_id``. - -.. code-block:: text - - GET /api/v1/controller/device/?config__templates={template_id} - -You can filter a list of devices based on their device group using the -``group_id``. - -.. code-block:: text - - GET /api/v1/controller/device/?group={group_id} - -You can filter a list of devices that have a device location object using -the ``with_geo`` (eg. true or false). - -.. code-block:: text - - GET /api/v1/controller/device/?with_geo={with_geo} - -You can filter a list of devices based on their creation time using the -``creation_time``. - -.. code-block:: text - - # Created exact - GET /api/v1/controller/device/?created={creation_time} - - # Created greater than or equal to - GET /api/v1/controller/device/?created__gte={creation_time} - - # Created is less than - GET /api/v1/controller/device/?created__lt={creation_time} - -Create device -+++++++++++++ - -.. code-block:: text - - POST /api/v1/controller/device/ - -Get device detail -+++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/device/{id}/ - -Download device configuration -+++++++++++++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/device/{id}/configuration/ - -The above endpoint triggers the download of a ``tar.gz`` file containing -the generated configuration for that specific device. - -Change details of device -++++++++++++++++++++++++ - -.. code-block:: text - - PUT /api/v1/controller/device/{id}/ - -Patch details of device -+++++++++++++++++++++++ - -.. code-block:: text - - PATCH /api/v1/controller/device/{id}/ - -**Note**: To assign, unassign, and change the order of the assigned -templates add, remove, and change the order of the ``{id}`` of the -templates under the ``config`` field in the JSON response respectively. -Moreover, you can also select and unselect templates in the HTML Form of -the Browsable API. - -The required template(s) from the organization(s) of the device will added -automatically to the ``config`` and cannot be removed. - -**Example usage**: For assigning template(s) add the/their {id} to the -config of a device, - -.. code-block:: shell - - curl -X PATCH \ - http://127.0.0.1:8000/api/v1/controller/device/76b7d9cc-4ffd-4a43-b1b0-8f8befd1a7c0/ \ - -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ - -H 'content-type: application/json' \ - -d '{ - "config": { - "templates": ["4791fa4c-2cef-4f42-8bb4-c86018d71bd3"] - } - }' - -**Example usage**: For removing assigned templates, simply remove -the/their {id} from the config of a device, - -.. code-block:: shell - - curl -X PATCH \ - http://127.0.0.1:8000/api/v1/controller/device/76b7d9cc-4ffd-4a43-b1b0-8f8befd1a7c0/ \ - -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ - -H 'content-type: application/json' \ - -d '{ - "config": { - "templates": [] - } - }' - -**Example usage**: For reordering the templates simply change their order -from the config of a device, - -.. code-block:: shell - - curl -X PATCH \ - http://127.0.0.1:8000/api/v1/controller/device/76b7d9cc-4ffd-4a43-b1b0-8f8befd1a7c0/ \ - -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ - -H 'cache-control: no-cache' \ - -H 'content-type: application/json' \ - -H 'postman-token: b3f6a1cc-ff13-5eba-e460-8f394e485801' \ - -d '{ - "config": { - "templates": [ - "c5bbc697-170e-44bc-8eb7-b944b55ee88f", - "4791fa4c-2cef-4f42-8bb4-c86018d71bd3" - ] - } - }' - -Delete device -+++++++++++++ - -.. code-block:: text - - DELETE /api/v1/controller/device/{id}/ - -List device connections -+++++++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/device/{id}/connection/ - -Create device connection -++++++++++++++++++++++++ - -.. code-block:: text - - POST /api/v1/controller/device/{id}/connection/ - -Get device connection detail -++++++++++++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/device/{id}/connection/{id}/ - -Change device connection detail -+++++++++++++++++++++++++++++++ - -.. code-block:: text - - PUT /api/v1/controller/device/{id}/connection/{id}/ - -Patch device connection detail -++++++++++++++++++++++++++++++ - -.. code-block:: text - - PATCH /api/v1/controller/device/{id}/connection/{id}/ - -Delete device connection -++++++++++++++++++++++++ - -.. code-block:: text - - DELETE /api/v1/controller/device/{id}/connection/{id}/ - -List credentials -++++++++++++++++ - -.. code-block:: text - - GET /api/v1/connection/credential/ - -Create credential -+++++++++++++++++ - -.. code-block:: text - - POST /api/v1/connection/credential/ - -Get credential detail -+++++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/connection/credential/{id}/ - -Change credential detail -++++++++++++++++++++++++ - -.. code-block:: text - - PUT /api/v1/connection/credential/{id}/ - -Patch credential detail -+++++++++++++++++++++++ - -.. code-block:: text - - PATCH /api/v1/connection/credential/{id}/ - -Delete credential -+++++++++++++++++ - -.. code-block:: text - - DELETE /api/v1/connection/credential/{id}/ - -List commands of a device -+++++++++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/device/{id}/command/ - -Execute a command a device -++++++++++++++++++++++++++ - -.. code-block:: text - - POST /api/v1/controller/device/{id}/command/ - -Get command details -+++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/device/{device_id}/command/{command_id}/ - -List device groups -++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/group/ - -**Available filters** - -You can filter a list of device groups based on their organization using -the ``organization_id`` or ``organization_slug``. - -.. code-block:: text - - GET /api/v1/controller/group/?organization={organization_id} - -.. code-block:: text - - GET /api/v1/controller/group/?organization_slug={organization_slug} - -You can filter a list of device groups that have a device object using the -``empty`` (eg. true or false). - -.. code-block:: text - - GET /api/v1/controller/group/?empty={empty} - -Create device group -+++++++++++++++++++ - -.. code-block:: text - - POST /api/v1/controller/group/ - -Get device group detail -+++++++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/group/{id}/ - -Change device group detail -++++++++++++++++++++++++++ - -.. code-block:: text - - PUT /api/v1/controller/group/{id}/ - -This endpoint allows to change the `group templates <#group-templates>`_ -too. - -Get device group from certificate common name -+++++++++++++++++++++++++++++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/cert/{common_name}/group/ - -This endpoint can be used to retrieve group information and metadata by -the common name of a certificate used in a VPN client tunnel, this -endpoint is used in layer 2 tunneling solutions for firewall/captive -portals. - -It is also possible to filter device group by providing organization slug -of certificate's organization as show in the example below: - -.. code-block:: text - - GET /api/v1/controller/cert/{common_name}/group/?org={org1_slug},{org2_slug} - -Get device location -+++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/device/{id}/location/ - -Create device location -++++++++++++++++++++++ - -.. code-block:: text - - PUT /api/v1/controller/device/{id}/location/ - -You can create ``DeviceLocation`` object by using primary keys of existing -``Location`` and ``FloorPlan`` objects as shown in the example below. - -.. code-block:: json - - { - "location": "f0cb5762-3711-4791-95b6-c2f6656249fa", - "floorplan": "dfeb6724-aab4-4533-aeab-f7feb6648acd", - "indoor": "-36,264" - } - -**Note:** The ``indoor`` field represents the coordinates of the point -placed on the image from the top left corner. E.g. if you placed the -pointer on the top left corner of the floorplan image, its indoor -coordinates will be ``0,0``. - -.. code-block:: text - - curl -X PUT \ - http://127.0.0.1:8000/api/v1/controller/device/8a85cc23-bad5-4c7e-b9f4-ffe298defb5c/location/ \ - -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ - -H 'content-type: application/json' \ - -d '{ - "location": "f0cb5762-3711-4791-95b6-c2f6656249fa", - "floorplan": "dfeb6724-aab4-4533-aeab-f7feb6648acd", - "indoor": "-36,264" - }' - -You can also create related ``Location`` and ``FloorPlan`` objects for the -device directly from this endpoint. - -The following example demonstrates creating related location object in a -single request. - -.. code-block:: json - - { - "location": { - "name": "Via del Corso", - "address": "Via del Corso, Roma, Italia", - "geometry": { - "type": "Point", - "coordinates": [12.512124, 41.898903] - }, - "type": "outdoor", - } - } - -.. code-block:: text - - curl -X PUT \ - http://127.0.0.1:8000/api/v1/controller/device/8a85cc23-bad5-4c7e-b9f4-ffe298defb5c/location/ \ - -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ - -H 'content-type: application/json' \ - -d '{ - "location": { - "name": "Via del Corso", - "address": "Via del Corso, Roma, Italia", - "geometry": { - "type": "Point", - "coordinates": [12.512124, 41.898903] - }, - "type": "outdoor" - } - }' - -**Note:** You can also specify the ``geometry`` in **Well-known text -(WKT)** format, like following: - -.. code-block:: json - - { - "location": { - "name": "Via del Corso", - "address": "Via del Corso, Roma, Italia", - "geometry": "POINT (12.512124 41.898903)", - "type": "outdoor", - } - } - -Similarly, you can create ``Floorplan`` object with the same request. But, -note that a ``FloorPlan`` can be added to ``DeviceLocation`` only if the -related ``Location`` object defines an indoor location. The example below -demonstrates creating both ``Location`` and ``FloorPlan`` objects. - -.. code-block:: text - - // This is not a valid JSON object. The JSON format is - // only used for showing available fields. - { - "location.name": "Via del Corso", - "location.address": "Via del Corso, Roma, Italia", - "location.geometry.type": "Point", - "location.geometry.coordinates": [12.512124, 41.898903] - "location.type": "outdoor", - "floorplan.floor": 1, - "floorplan.image": floorplan.png, - } - -.. code-block:: text - - curl -X PUT \ - http://127.0.0.1:8000/api/v1/controller/device/8a85cc23-bad5-4c7e-b9f4-ffe298defb5c/location/ \ - -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ - -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \ - -F 'location.name=Via del Corso' \ - -F 'location.address=Via del Corso, Roma, Italia' \ - -F location.geometry.type=Point \ - -F 'location.geometry.coordinates=[12.512124, 41.898903]' \ - -F location.type=indoor \ - -F floorplan.floor=1 \ - -F 'floorplan.image=@floorplan.png' - -**Note:** The request in above example uses ``multipart content-type`` for -uploading floorplan image. - -You can also use an existing ``Location`` object and create a new -floorplan for that location using this endpoint. - -.. code-block:: text - - // This is not a valid JSON object. The JSON format is - // only used for showing available fields. - { - "location": "f0cb5762-3711-4791-95b6-c2f6656249fa", - "floorplan.floor": 1, - "floorplan.image": floorplan.png - } - -.. code-block:: text - - curl -X PUT \ - http://127.0.0.1:8000/api/v1/controller/device/8a85cc23-bad5-4c7e-b9f4-ffe298defb5c/location/ \ - -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ - -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \ - -F location=f0cb5762-3711-4791-95b6-c2f6656249fa \ - -F floorplan.floor=1 \ - -F 'floorplan.image=@floorplan.png' - -Change details of device location -+++++++++++++++++++++++++++++++++ - -.. code-block:: text - - PUT /api/v1/controller/device/{id}/location/ - -**Note:** This endpoint can be used to update related ``Location`` and -``Floorplan`` objects. Refer `examples of "Create device location" section -for information on payload format <#create-device-location>`_. - -Delete device location -++++++++++++++++++++++ - -.. code-block:: text - - DELETE /api/v1/controller/device/{id}/location/ - -Get device coordinates -++++++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/device/{id}/coordinates/ - -**Note:** This endpoint is intended to be used by devices. - -This endpoint skips multi-tenancy and permission checks if the device -``key`` is passed as ``query_param`` because the system assumes that the -device is updating it's position. - -.. code-block:: text - - curl -X GET \ - 'http://127.0.0.1:8000/api/v1/controller/device/8a85cc23-bad5-4c7e-b9f4-ffe298defb5c/coordinates/?key=10a0cb5a553c71099c0e4ef236435496' - -Update device coordinates -+++++++++++++++++++++++++ - -.. code-block:: text - - PUT /api/v1/controller/device/{id}/coordinates/ - -**Note:** This endpoint is intended to be used by devices. - -This endpoint skips multi-tenancy and permission checks if the device -``key`` is passed as ``query_param`` because the system assumes that the -device is updating it's position. - -.. code-block:: json - - { - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [12.512124, 41.898903] - }, - } - -.. code-block:: text - - curl -X PUT \ - 'http://127.0.0.1:8000/api/v1/controller/device/8a85cc23-bad5-4c7e-b9f4-ffe298defb5c/coordinates/?key=10a0cb5a553c71099c0e4ef236435496' \ - -H 'content-type: application/json' \ - -d '{ - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [12.512124, 41.898903] - }, - }' - -List locations -++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/location/ - -**Available filters** - -You can filter using ``organization_id`` or ``organization_slug`` to get -list locations that belongs to an organization. - -.. code-block:: text - - GET /api/v1/controller/location/?organization={organization_id} - -.. code-block:: text - - GET /api/v1/controller/location/?organization_slug={organization_slug} - -Create location -+++++++++++++++ - -.. code-block:: text - - POST /api/v1/controller/location/ - -If you are creating an ``indoor`` location, you can use this endpoint to -create floorplan for the location. - -The following example demonstrates creating floorplan along with location -in a single request. - -.. code-block:: text - - { - "name": "Via del Corso", - "address": "Via del Corso, Roma, Italia", - "geometry.type": "Point", - "geometry.location": [12.512124, 41.898903], - "type": "indoor", - "is_mobile": "false", - "floorplan.floor": "1", - "floorplan.image": floorplan.png, - "organization": "1f6c5666-1011-4f1d-bce9-fc6fcb4f3a05" - } - -.. code-block:: text - - curl -X POST \ - http://127.0.0.1:8000/api/v1/controller/location/ \ - -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ - -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \ - -F 'name=Via del Corso' \ - -F 'address=Via del Corso, Roma, Italia' \ - -F geometry.type=Point \ - -F 'geometry.coordinates=[12.512124, 41.898903]' \ - -F type=indoor \ - -F is_mobile=false \ - -F floorplan.floor=1 \ - -F 'floorplan.image=@floorplan.png' \ - -F organization=1f6c5666-1011-4f1d-bce9-fc6fcb4f3a05 - -**Note:** You can also specify the ``geometry`` in **Well-known text -(WKT)** format, like following: - -.. code-block:: text - - { - "name": "Via del Corso", - "address": "Via del Corso, Roma, Italia", - "geometry": "POINT (12.512124 41.898903)", - "type": "indoor", - "is_mobile": "false", - "floorplan.floor": "1", - "floorplan.image": floorplan.png, - "organization": "1f6c5666-1011-4f1d-bce9-fc6fcb4f3a05" - } - -Get location details -++++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/location/{pk}/ - -Change location details -+++++++++++++++++++++++ - -.. code-block:: text - - PUT /api/v1/controller/location/{pk}/ - -**Note**: Only the first floorplan data present can be edited or changed. -Setting the ``type`` of location to outdoor will remove all the floorplans -associated with it. - -Refer `examples of "Create location" section for information on payload -format <#create-location>`_. - -Delete location -+++++++++++++++ - -.. code-block:: text - - DELETE /api/v1/controller/location/{pk}/ - -List devices in a location -++++++++++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/location/{id}/device/ - -List locations with devices deployed (in GeoJSON format) -++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -**Note**: this endpoint will only list locations that have been assigned -to a device. - -.. code-block:: text - - GET /api/v1/controller/location/geojson/ - -**Available filters** - -You can filter using ``organization_id`` or ``organization_slug`` to get -list location of devices from that organization. - -.. code-block:: text - - GET /api/v1/controller/location/geojson/?organization_id={organization_id} - -.. code-block:: text - - GET /api/v1/controller/location/geojson/?organization_slug={organization_slug} - -List floorplans -+++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/floorplan/ - -**Available filters** - -You can filter using ``organization_id`` or ``organization_slug`` to get -list floorplans that belongs to an organization. - -.. code-block:: text - - GET /api/v1/controller/floorplan/?organization={organization_id} - -.. code-block:: text - - GET /api/v1/controller/floorplan/?organization_slug={organization_slug} - -Create floorplan -++++++++++++++++ - -.. code-block:: text - - POST /api/v1/controller/floorplan/ - -Get floorplan details -+++++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/floorplan/{pk}/ - -Change floorplan details -++++++++++++++++++++++++ - -.. code-block:: text - - PUT /api/v1/controller/floorplan/{pk}/ - -Delete floorplan -++++++++++++++++ - -.. code-block:: text - - DELETE /api/v1/controller/floorplan/{pk}/ - -List templates -++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/template/ - -**Available filters** - -You can filter a list of templates based on their organization using the -``organization_id`` or ``organization_slug``. - -.. code-block:: text - - GET /api/v1/controller/template/?organization={organization_id} - -.. code-block:: text - - GET /api/v1/controller/template/?organization_slug={organization_slug} - -You can filter a list of templates based on their backend using the -``backend`` (e.g netjsonconfig.OpenWrt or netjsonconfig.OpenWisp). - -.. code-block:: text - - GET /api/v1/controller/template/?backend={backend} - -You can filter a list of templates based on their type using the ``type`` -(eg. vpn or generic). - -.. code-block:: text - - GET /api/v1/controller/template/?type={type} - -You can filter a list of templates that are enabled by default or not -using the ``default`` (eg. true or false). - -.. code-block:: text - - GET /api/v1/controller/template/?default={default} - -You can filter a list of templates that are required or not using the -``required`` (eg. true or false). - -.. code-block:: text - - GET /api/v1/controller/template/?required={required} - -You can filter a list of templates based on their creation time using the -``creation_time``. - -.. code-block:: text - - # Created exact - - GET /api/v1/controller/template/?created={creation_time} - - # Created greater than or equal to - - GET /api/v1/controller/template/?created__gte={creation_time} - - # Created is less than - - GET /api/v1/controller/template/?created__lt={creation_time} - -Create template -+++++++++++++++ - -.. code-block:: text - - POST /api/v1/controller/template/ - -Get template detail -+++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/template/{id}/ - -Download template configuration -+++++++++++++++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/template/{id}/configuration/ - -The above endpoint triggers the download of a ``tar.gz`` file containing -the generated configuration for that specific template. - -Change details of template -++++++++++++++++++++++++++ - -.. code-block:: text - - PUT /api/v1/controller/template/{id}/ - -Patch details of template -+++++++++++++++++++++++++ - -.. code-block:: text - - PATCH /api/v1/controller/template/{id}/ - -Delete template -+++++++++++++++ - -.. code-block:: text - - DELETE /api/v1/controller/template/{id}/ - -List VPNs -+++++++++ - -.. code-block:: text - - GET /api/v1/controller/vpn/ - -**Available filters** - -You can filter a list of vpns based on their backend using the ``backend`` -(e.g openwisp_controller.vpn_backends.OpenVpn or -openwisp_controller.vpn_backends.Wireguard). - -.. code-block:: text - - GET /api/v1/controller/vpn/?backend={backend} - -You can filter a list of vpns based on their subnet using the -``subnet_id``. - -.. code-block:: text - - GET /api/v1/controller/vpn/?subnet={subnet_id} - -You can filter a list of vpns based on their organization using the -``organization_id`` or ``organization_slug``. - -.. code-block:: text - - GET /api/v1/controller/vpn/?organization={organization_id} - -.. code-block:: text - - GET /api/v1/controller/vpn/?organization_slug={organization_slug} - -Create VPN -++++++++++ - -.. code-block:: text - - POST /api/v1/controller/vpn/ - -Get VPN detail -++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/vpn/{id}/ - -Download VPN configuration -++++++++++++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/vpn/{id}/configuration/ - -The above endpoint triggers the download of a ``tar.gz`` file containing -the generated configuration for that specific VPN. - -Change details of VPN -+++++++++++++++++++++ - -.. code-block:: text - - PUT /api/v1/controller/vpn/{id}/ - -Patch details of VPN -++++++++++++++++++++ - -.. code-block:: text - - PATCH /api/v1/controller/vpn/{id}/ - -Delete VPN -++++++++++ - -.. code-block:: text - - DELETE /api/v1/controller/vpn/{id}/ - -List CA -+++++++ - -.. code-block:: text - - GET /api/v1/controller/ca/ - -Create new CA -+++++++++++++ - -.. code-block:: text - - POST /api/v1/controller/ca/ - -Import existing CA -++++++++++++++++++ - -.. code-block:: text - - POST /api/v1/controller/ca/ - -**Note**: To import an existing CA, only ``name``, ``certificate`` and -``private_key`` fields have to be filled in the ``HTML`` form or included -in the ``JSON`` format. - -Get CA Detail -+++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/ca/{id}/ - -Change details of CA -++++++++++++++++++++ - -.. code-block:: text - - PUT /api/v1/controller/ca/{id}/ - -Patch details of CA -+++++++++++++++++++ - -.. code-block:: text - - PATCH /api/v1/controller/ca/{id}/ - -Download CA(crl) -++++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/ca/{id}/crl/ - -The above endpoint triggers the download of ``{id}.crl`` file containing -up to date CRL of that specific CA. - -Delete CA -+++++++++ - -.. code-block:: text - - DELETE /api/v1/controller/ca/{id}/ - -Renew CA -++++++++ - -.. code-block:: text - - POST /api/v1/controller/ca/{id}/renew/ - -List Cert -+++++++++ - -.. code-block:: text - - GET /api/v1/controller/cert/ - -Create new Cert -+++++++++++++++ - -.. code-block:: text - - POST /api/v1/controller/cert/ - -Import existing Cert -++++++++++++++++++++ - -.. code-block:: text - - POST /api/v1/controller/cert/ - -**Note**: To import an existing Cert, only ``name``, ``ca``, -``certificate`` and ``private_key`` fields have to be filled in the -``HTML`` form or included in the ``JSON`` format. - -Get Cert Detail -+++++++++++++++ - -.. code-block:: text - - GET /api/v1/controller/cert/{id}/ - -Change details of Cert -++++++++++++++++++++++ - -.. code-block:: text - - PUT /api/v1/controller/cert/{id}/ - -Patch details of Cert -+++++++++++++++++++++ - -.. code-block:: text - - PATCH /api/v1/controller/cert/{id}/ - -Delete Cert -+++++++++++ - -.. code-block:: text - - DELETE /api/v1/controller/cert/{id}/ - -Renew Cert -++++++++++ - -.. code-block:: text - - POST /api/v1/controller/cert/{id}/renew/ - -Revoke Cert -+++++++++++ - -.. code-block:: text - - POST /api/v1/controller/cert/{id}/revoke/ - -Settings --------- - -You can change the values for the following variables in ``settings.py`` -to configure your instance of openwisp-controller. - -``OPENWISP_SSH_AUTH_TIMEOUT`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ =========== -**type**: ``int`` -**default**: ``2`` -**unit**: ``seconds`` -============ =========== - -Configure timeout to wait for an authentication response when establishing -a SSH connection. - -``OPENWISP_SSH_BANNER_TIMEOUT`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ =========== -**type**: ``int`` -**default**: ``60`` -**unit**: ``seconds`` -============ =========== - -Configure timeout to wait for the banner to be presented when establishing -a SSH connection. - -``OPENWISP_SSH_COMMAND_TIMEOUT`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ =========== -**type**: ``int`` -**default**: ``30`` -**unit**: ``seconds`` -============ =========== - -Configure timeout on blocking read/write operations when executing a -command in a SSH connection. - -``OPENWISP_SSH_CONNECTION_TIMEOUT`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ =========== -**type**: ``int`` -**default**: ``5`` -**unit**: ``seconds`` -============ =========== - -Configure timeout for the TCP connect when establishing a SSH connection. - -``OPENWISP_CONNECTORS`` -~~~~~~~~~~~~~~~~~~~~~~~ - -============ ================================================================================= -**type**: ``tuple`` -**default**: .. code-block:: python - - ( - ("openwisp_controller.connection.connectors.ssh.Ssh", "SSH"), - ( - "openwisp_controller.connection.connectors.openwrt.snmp.OpenWRTSnmp", - "OpenWRT SNMP", - ), - ( - "openwisp_controller.connection.connectors.airos.snmp.AirOsSnmp", - "Ubiquiti AirOS SNMP", - ), - ) -============ ================================================================================= - -Available connector classes. Connectors are python classes that specify -ways in which OpenWISP can connect to devices in order to launch commands. - -``OPENWISP_UPDATE_STRATEGIES`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ============================================================================ -**type**: ``tuple`` -**default**: .. code-block:: python - - ( - ( - "openwisp_controller.connection.connectors.openwrt.ssh.OpenWrt", - "OpenWRT SSH", - ), - ) -============ ============================================================================ - -Available update strategies. An update strategy is a subclass of a -connector class which defines an ``update_config`` method which is in -charge of updating the configuration of the device. - -This operation is launched in a background worker when the configuration -of a device is changed. - -It's possible to write custom update strategies and add them to this -setting to make them available in OpenWISP. - -``OPENWISP_CONFIG_UPDATE_MAPPING`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ================================================================== -**type**: ``dict`` -**default**: .. code-block:: python - - { - "netjsonconfig.OpenWrt": OPENWISP_UPDATE_STRATEGIES[0][0], - } -============ ================================================================== - -A dictionary that maps configuration backends to update strategies in -order to automatically determine the update strategy of a device -connection if the update strategy field is left blank by the user. - -``OPENWISP_CONTROLLER_BACKENDS`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ =============================================== -**type**: ``tuple`` -**default**: .. code-block:: python - - ( - ("netjsonconfig.OpenWrt", "OpenWRT"), - ("netjsonconfig.OpenWisp", "OpenWISP"), - ) -============ =============================================== - -Available configuration backends. For more information, see `netjsonconfig -backends -`_. - -``OPENWISP_CONTROLLER_VPN_BACKENDS`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ==================================================================== -**type**: ``tuple`` -**default**: .. code-block:: python - - ( - ("openwisp_controller.vpn_backends.OpenVpn", "OpenVPN"), - ("openwisp_controller.vpn_backends.Wireguard", "WireGuard"), - ( - "openwisp_controller.vpn_backends.VxlanWireguard", - "VXLAN over WireGuard", - ), - ("openwisp_controller.vpn_backends.ZeroTier", "ZeroTier"), - ) -============ ==================================================================== - -Available VPN backends for VPN Server objects. For more information, see -`netjsonconfig VPN backends -`_. - -A VPN backend must follow some basic rules in order to be compatible with -*openwisp-controller*: - -- it MUST allow at minimum and at maximum one VPN instance -- the main *NetJSON* property MUST match the lowercase version of the - class name, eg: when using the ``OpenVpn`` backend, the system will look - into ``config['openvpn']`` -- it SHOULD focus on the server capabilities of the VPN software being - used - -``OPENWISP_CONTROLLER_DEFAULT_BACKEND`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ====================================== -**type**: ``str`` -**default**: ``OPENWISP_CONTROLLER_BACKENDS[0][0]`` -============ ====================================== - -The preferred backend that will be used as initial value when adding new -``Config`` or ``Template`` objects in the admin. - -This setting defaults to the raw value of the first item in the -``OPENWISP_CONTROLLER_BACKENDS`` setting, which is -``netjsonconfig.OpenWrt``. - -Setting it to ``None`` will force the user to choose explicitly. - -``OPENWISP_CONTROLLER_DEFAULT_VPN_BACKEND`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ========================================== -**type**: ``str`` -**default**: ``OPENWISP_CONTROLLER_VPN_BACKENDS[0][0]`` -============ ========================================== - -The preferred backend that will be used as initial value when adding new -``Vpn`` objects in the admin. - -This setting defaults to the raw value of the first item in the -``OPENWISP_CONTROLLER_VPN_BACKENDS`` setting, which is -``openwisp_controller.vpn_backends.OpenVpn``. - -Setting it to ``None`` will force the user to choose explicitly. - -``OPENWISP_CONTROLLER_REGISTRATION_ENABLED`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``bool`` -**default**: ``True`` -============ ======== - -Whether devices can automatically register through the controller or not. - -This feature is enabled by default. - -Autoregistration must be supported on the devices in order to work, see -`openwisp-config automatic registration -`_ for -more information. - -``OPENWISP_CONTROLLER_CONSISTENT_REGISTRATION`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``bool`` -**default**: ``True`` -============ ======== - -Whether devices that are already registered are recognized when reflashed -or reset, hence keeping the existing configuration without creating a new -one. - -This feature is enabled by default. - -Autoregistration must be enabled also on the devices in order to work, see -`openwisp-config consistent key generation -`_ -for more information. - -``OPENWISP_CONTROLLER_REGISTRATION_SELF_CREATION`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``bool`` -**default**: ``True`` -============ ======== - -Whether devices that are not already present in the system are allowed to -register or not. - -Turn this off if you still want to use auto-registration to avoid having -to manually set the device UUID and key in its configuration file but also -want to avoid indiscriminate registration of new devices without explicit -permission. - -``OPENWISP_CONTROLLER_CONTEXT`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``dict`` -**default**: ``{}`` -============ ======== - -Additional context that is passed to the default context of each device -object. - -``OPENWISP_CONTROLLER_CONTEXT`` can be used to define system-wide -configuration variables. - -For more information regarding how to use configuration variables in -OpenWISP, see `How to use configuration variables -<#how-to-use-configuration-variables>`_. - -For technical information about how variables are handled in the lower -levels of OpenWISP, see `netjsonconfig context: configuration variables -`_. - -``OPENWISP_CONTROLLER_DEFAULT_AUTO_CERT`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``bool`` -**default**: ``True`` -============ ======== - -The default value of the ``auto_cert`` field for new ``Template`` objects. - -The ``auto_cert`` field is valid only for templates which have ``type`` -set to ``VPN`` and indicates whether configuration regarding the VPN -tunnel is provisioned automatically to each device using the template, eg: - -- when using OpenVPN, new `x509 `_ - certificates will be generated automatically using the same CA assigned - to the related VPN object -- when using WireGuard, new pair of private and public keys (using - `Curve25519 `_) will be generated, as well as - an IP address of the subnet assigned to the related VPN object -- when using `VXLAN `_ tunnels over - Wireguad, in addition to the configuration generated for WireGuard, a - new VID will be generated automatically for each device if the - configuration option "auto VNI" is turned on in the VPN object - -All these auto generated configuration options will be available as -template variables. - -The objects that are automatically created will also be removed when they -are not needed anymore (eg: when the VPN template is removed from a -configuration object). - -``OPENWISP_CONTROLLER_CERT_PATH`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ============= -**type**: ``str`` -**default**: ``/etc/x509`` -============ ============= - -The filesystem path where x509 certificate will be installed when -downloaded on routers when ``auto_cert`` is being used (enabled by -default). - -``OPENWISP_CONTROLLER_COMMON_NAME_FORMAT`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======================== -**type**: ``str`` -**default**: ``{mac_address}-{name}`` -============ ======================== - -Defines the format of the ``common_name`` attribute of VPN client -certificates that are automatically created when using VPN templates which -have ``auto_cert`` set to ``True``. A unique slug generated using -`shortuuid `_ is appended to -the common name to introduce uniqueness. Therefore, resulting common names -will have ``{OPENWISP_CONTROLLER_COMMON_NAME_FORMAT}-{unique-slug}`` -format. - -**Note:** If the ``name`` and ``mac address`` of the device are equal, the -``name`` of the device will be omitted from the common name to avoid -redundancy. - -``OPENWISP_CONTROLLER_MANAGEMENT_IP_DEVICE_LIST`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``bool`` -**default**: ``True`` -============ ======== - -In the device list page, the column ``IP`` will show the ``management_ip`` -if available, defaulting to ``last_ip`` otherwise. - -If this setting is set to ``False`` the ``management_ip`` won't be shown -in the device list page even if present, it will be shown only in the -device detail page. - -You may set this to ``False`` if for some reason the majority of your user -doesn't care about the management ip address. - -``OPENWISP_CONTROLLER_CONFIG_BACKEND_FIELD_SHOWN`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``bool`` -**default**: ``True`` -============ ======== - -This setting toggles the ``backend`` fields in add/edit pages in Device -and Template configuration, as well as the ``backend`` field/filter in -Device list and Template list. - -If this setting is set to ``False`` these items will be removed from the -UI. - -Note: This setting affects only the configuration backend and NOT the VPN -backend. - -``OPENWISP_CONTROLLER_DEVICE_NAME_UNIQUE`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``bool`` -**default**: ``True`` -============ ======== - -This setting conditionally enforces unique Device names in an -Organization. The query to enforce this is case-insensitive. - -Note: For this constraint to be optional, it is enforced on an application -level and not on database. - -``OPENWISP_CONTROLLER_HARDWARE_ID_ENABLED`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ========= -**type**: ``bool`` -**default**: ``False`` -============ ========= - -The field ``hardware_id`` can be used to store a unique hardware id, for -example a serial number. - -If this setting is set to ``True`` then this field will be shown first in -the device list page and in the add/edit device page. - -This feature is disabled by default. - -``OPENWISP_CONTROLLER_HARDWARE_ID_OPTIONS`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ============================================================= -**type**: ``dict`` -**default**: .. code-block:: python - - { - "blank": not OPENWISP_CONTROLLER_HARDWARE_ID_ENABLED, - "null": True, - "max_length": 32, - "unique": True, - "verbose_name": _("Serial number"), - "help_text": _("Serial number of this device"), - } -============ ============================================================= - -Options for the model field ``hardware_id``. - -- ``blank``: wether the field is allowed to be blank -- ``null``: wether an empty value will be stored as ``NULL`` in the - database -- ``max_length``: maximum length of the field -- ``unique``: wether the value of the field must be unique -- ``verbose_name``: text for the human readable label of the field -- ``help_text``: help text to be displayed with the field - -``OPENWISP_CONTROLLER_HARDWARE_ID_AS_NAME`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``bool`` -**default**: ``True`` -============ ======== - -When the hardware ID feature is enabled, devices will be referenced with -their hardware ID instead of their name. - -If you still want to reference devices by their name, set this to -``False``. - -``OPENWISP_CONTROLLER_DEVICE_VERBOSE_NAME`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ========================= -**type**: ``tuple`` -**default**: ``('Device', 'Devices')`` -============ ========================= - -Defines the ``verbose_name`` attribute of the ``Device`` model, which is -displayed in the admin site. The first and second element of the tuple -represent the singular and plural forms. - -For example, if we want to change the verbose name to "Hotspot", we could -write: - -.. code-block:: python - - OPENWISP_CONTROLLER_DEVICE_VERBOSE_NAME = ("Hotspot", "Hotspots") - -``OPENWISP_CONTROLLER_HIDE_AUTOMATICALLY_GENERATED_SUBNETS_AND_IPS`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ========= -**type**: ``bool`` -**default**: ``False`` -============ ========= - -Setting this to ``True`` will hide subnets and IPs generated using `subnet -division rules <#subnet-division-app>`_ from being displayed on the -changelist view of Subnet and IP admin. - -``OPENWISP_CONTROLLER_SUBNET_DIVISION_TYPES`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ================================================================================================= -**type**: ``tuple`` -**default**: .. code-block:: python - - ( - ( - "openwisp_controller.subnet_division.rule_types.device.DeviceSubnetDivisionRuleType", - "Device", - ), - ( - "openwisp_controller.subnet_division.rule_types.vpn.VpnSubnetDivisionRuleType", - "VPN", - ), - ) -============ ================================================================================================= - -`Available types for Subject Division Rule -<#device-subnet-division-rule>`_ objects. For more information on how to -write your own types, read `"Custom Subnet Division Rule Types" section of -this documentation <#custom-subnet-division-rule-types>`_ - -``OPENWISP_CONTROLLER_API`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``bool`` -**default**: ``True`` -============ ======== - -Indicates whether the API for Openwisp Controller is enabled or not. To -disable the API by default add `OPENWISP_CONTROLLER_API = False` in -`settings.py` file. - -``OPENWISP_CONTROLLER_API_HOST`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``str`` -**default**: ``None`` -============ ======== - -Allows to specify backend URL for API requests, if the frontend is hosted -separately. - -``OPENWISP_CONTROLLER_USER_COMMANDS`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``list`` -**default**: ``[]`` -============ ======== - -Allows to specify a ``list`` of tuples for adding commands as described in -`'How to define custom commands" -<#how-to-define-new-options-in-the-commands-menu>`_ section. - -``OPENWISP_CONTROLLER_ORGANIZATION_ENABLED_COMMANDS`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ============================================= -**type**: ``dict`` -**default**: .. code-block:: python - - { - # By default all commands are allowed - "__all__": "*", - } -============ ============================================= - -This setting controls the command types that are enabled on the system By -default, all command types are enabled to all the organizations, but it's -possible to disable a specific command for a specific organization as -shown in the following example: - -.. code-block:: python - - OPENWISP_CONTROLLER_ORGANIZATION_ENABLED_COMMANDS = { - "__all__": "*", - # Organization UUID: # Tuple of enabled commands - "7448a190-6e65-42bf-b8ea-bb6603e593a5": ("reboot", "change_password"), - } - -In the example above, the organization with UUID -``7448a190-6e65-42bf-b8ea-bb6603e593a5`` will allow to send only commands -of type ``reboot`` and ``change_password``, while all the other -organizations will have all command types enabled. - -``OPENWISP_CONTROLLER_DEVICE_GROUP_SCHEMA`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======================================== -**type**: ``dict`` -**default**: ``{'type': 'object', 'properties': {}}`` -============ ======================================== - -Allows specifying JSONSchema used for validating meta-data of `Device -Group <#device-groups>`__. - -``OPENWISP_CONTROLLER_SHARED_MANAGEMENT_IP_ADDRESS_SPACE`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``bool`` -**default**: ``True`` -============ ======== - -By default, the system assumes that the address space of the management -tunnel is shared among all the organizations using the system, that is, -the system assumes there's only one management VPN, tunnel or other -networking technology to reach the devices it controls. - -When set to ``True``, any device belonging to any organization will never -have the same ``management_ip`` as another device, the latest device -declaring the management IP will take the IP and any other device who -declared the same IP in the past will have the field reset to empty state -to avoid potential conflicts. - -Set this to ``False`` if every organization has its dedicated management -tunnel with a dedicated address space that is reachable by the OpenWISP -server. - -``OPENWISP_CONTROLLER_MANAGEMENT_IP_ONLY`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``bool`` -**default**: ``True`` -============ ======== - -By default, only the management IP will be used to establish connection -with the devices. - -If the devices are connecting to your OpenWISP instance using a shared -layer2 network, hence the OpenWSP server can reach the devices using the -``last_ip`` field, you can set this to ``False``. - -``OPENWISP_CONTROLLER_DSA_OS_MAPPING`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``dict`` -**default**: ``{}`` -============ ======== - -OpenWISP Controller can figure out whether it should use the new OpenWrt -syntax for DSA interfaces (Distributed Switch Architecture) introduced in -OpenWrt 21 by reading the ``os`` field of the ``Device`` object. However, -if the firmware you are using has a custom firmware identifier, the system -will not be able to figure out whether it should use the new syntax and it -will default to `OPENWISP_CONTROLLER_DSA_DEFAULT_FALLBACK -<#openwisp_controller_dsa_default_fallback>`_. - -If you want to make sure the system can parse your custom firmware -identifier properly, you can follow the example below. - -For the sake of the example, the OS identifier ``MyCustomFirmware 2.0`` -corresponds to ``OpenWrt 19.07``, while ``MyCustomFirmware 2.1`` -corresponds to ``OpenWrt 21.02``. Configuring this setting as indicated -below will allow OpenWISP to supply the right syntax automatically. - -Example: - -.. code-block:: python - - OPENWISP_CONTROLLER_DSA_OS_MAPPING = { - "netjsonconfig.OpenWrt": { - # OpenWrt >=21.02 configuration syntax will be used for - # these OS identifiers. - ">=21.02": [r"MyCustomFirmware 2.1(.*)"], - # OpenWrt <=21.02 configuration syntax will be used for - # these OS identifiers. - "<21.02": [r"MyCustomFirmware 2.0(.*)"], - } - } - -**Note**: The OS identifier should be a regular expression as shown in -above example. - -``OPENWISP_CONTROLLER_DSA_DEFAULT_FALLBACK`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ======== -**type**: ``bool`` -**default**: ``True`` -============ ======== - -The value of this setting decides whether to use DSA syntax (OpenWrt >=21 -configuration syntax) if openwisp-controller fails to make that decision -automatically. - -``OPENWISP_CONTROLLER_GROUP_PIE_CHART`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ========= -**type**: ``bool`` -**default**: ``False`` -============ ========= - -Allows to show a pie chart like the one in the screenshot. - -.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/devicegroups-piechart.png - :alt: device groups piechart - -Active groups are groups which have at least one device in them, while -emtpy groups do not have any device assigned. - -``OPENWISP_CONTROLLER_API_TASK_RETRY_OPTIONS`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -============ ========= -**type**: ``dict`` -**default**: see below -============ ========= - -.. code-block:: python - - # default value of OPENWISP_CONTROLLER_API_TASK_RETRY_OPTIONS: - - dict( - max_retries=5, # total number of retries - retry_backoff=True, # exponential backoff - retry_backoff_max=600, # 10 minutes - retry_jitter=True, # randomness into exponential backoff - ) - -This setting is utilized by background API tasks executed by `ZeroTier VPN -servers and ZeroTier VPN clients <#how-to-setup-zerotier-tunnels>`_ to -handle recoverable HTTP status codes such as 429, 500, 502, 503, and 504. -These tasks are retried with a maximum of 5 attempts with an exponential -backoff and jitter, with a maximum delay of 10 minutes. - -This feature ensures that ZeroTier Service API calls are resilient to -recoverable failures, improving the reliability of the system. - -For more information on these settings, you can refer to the `the celery -documentation regarding automatic retries for known errors. -`_ - -Signals -------- - -``config_modified`` -~~~~~~~~~~~~~~~~~~~ - -**Path**: ``openwisp_controller.config.signals.config_modified`` - -**Arguments**: - -- ``instance``: instance of ``Config`` which got its ``config`` modified -- ``previous_status``: indicates the status of the config object before - the signal was emitted -- ``action``: action which emitted the signal, can be any of the list - below: - ``config_changed``: the configuration of the config object was - changed - ``related_template_changed``: the configuration of a related - template was changed - ``m2m_templates_changed``: the assigned templates - were changed (either templates were added, removed or their order was - changed) - -This signal is emitted every time the configuration of a device is -modified. - -It does not matter if ``Config.status`` is already modified, this signal -will be emitted anyway because it signals that the device configuration -has changed. - -This signal is used to trigger the update of the configuration on devices, -when the push feature is enabled (requires Device credentials). - -The signal is also emitted when one of the templates used by the device is -modified or if the templates assigned to the device are changed. - -Special cases in which ``config_modified`` is not emitted -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -This signal is not emitted when the device is created for the first time. - -It is also not emitted when templates assigned to a config object are -cleared (``post_clear`` m2m signal), this is necessary because `sortedm2m -`_, the package we use to -implement ordered templates, uses the clear action to reorder templates -(m2m relationships are first cleared and then added back), therefore we -ignore ``post_clear`` to avoid emitting signals twice (one for the clear -action and one for the add action). Please keep this in mind if you plan -on using the clear method of the m2m manager. - -``config_status_changed`` -~~~~~~~~~~~~~~~~~~~~~~~~~ - -**Path**: ``openwisp_controller.config.signals.config_status_changed`` - -**Arguments**: - -- ``instance``: instance of ``Config`` which got its ``status`` changed - -This signal is emitted only when the configuration status of a device has -changed. - -The signal is emitted also when the m2m template relationships of a config -object are changed, but only on ``post_add`` or ``post_remove`` actions, -``post_clear`` is ignored for the same reason explained in the previous -section. - -``config_backend_changed`` -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**Path**: ``openwisp_controller.config.signals.config_backend_changed`` -**Arguments**: - -- ``instance``: instance of ``Config`` which got its ``backend`` changed -- ``old_backend``: the old backend of the config object -- ``backend``: the new backend of the config object - -It is not emitted when the device or config is created. - -``checksum_requested`` -~~~~~~~~~~~~~~~~~~~~~~ - -**Path**: ``openwisp_controller.config.signals.checksum_requested`` - -**Arguments**: - -- ``instance``: instance of ``Device`` for which its configuration - checksum has been requested -- ``request``: the HTTP request object - -This signal is emitted when a device requests a checksum via the -controller views. - -The signal is emitted just before a successful response is returned, it is -not sent if the response was not successful. - -``config_download_requested`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**Path**: ``openwisp_controller.config.signals.config_download_requested`` - -**Arguments**: - -- ``instance``: instance of ``Device`` for which its configuration has - been requested for download -- ``request``: the HTTP request object - -This signal is emitted when a device requests to download its -configuration via the controller views. - -The signal is emitted just before a successful response is returned, it is -not sent if the response was not successful. - -``is_working_changed`` -~~~~~~~~~~~~~~~~~~~~~~ - -**Path**: ``openwisp_controller.connection.signals.is_working_changed`` - -**Arguments**: - -- ``instance``: instance of ``DeviceConnection`` -- ``is_working``: value of ``DeviceConnection.is_working`` -- ``old_is_working``: previous value of ``DeviceConnection.is_working``, - either ``None`` (for new connections), ``True`` or ``False`` -- ``failure_reason``: error message explaining reason for failure in - establishing connection -- ``old_failure_reason``: previous value of - ``DeviceConnection.failure_reason`` - -This signal is emitted every time ``DeviceConnection.is_working`` changes. - -It is not triggered when the device is created for the first time. - -``management_ip_changed`` -~~~~~~~~~~~~~~~~~~~~~~~~~ - -**Path**: ``openwisp_controller.config.signals.management_ip_changed`` - -**Arguments**: - -- ``instance``: instance of ``Device`` -- ``management_ip``: value of ``Device.management_ip`` -- ``old_management_ip``: previous value of ``Device.management_ip`` - -This signal is emitted every time ``Device.management_ip`` changes. - -It is not triggered when the device is created for the first time. - -``device_registered`` -~~~~~~~~~~~~~~~~~~~~~ - -**Path**: ``openwisp_controller.config.signals.device_registered`` - -**Arguments**: - -- ``instance``: instance of ``Device`` which got registered. -- ``is_new``: boolean, will be ``True`` when the device is new, ``False`` - when the device already exists (eg: a device which gets a factory reset - will register again) - -This signal is emitted when a device registers automatically through the -controller HTTP API. - -``device_name_changed`` -~~~~~~~~~~~~~~~~~~~~~~~ - -**Path**: ``openwisp_controller.config.signals.device_name_changed`` - -**Arguments**: - -- ``instance``: instance of ``Device``. - -The signal is emitted when the device name changes. - -It is not emitted when the device is created. - -``device_group_changed`` -~~~~~~~~~~~~~~~~~~~~~~~~ - -**Path**: ``openwisp_controller.config.signals.device_group_changed`` - -**Arguments**: - -- ``instance``: instance of ``Device``. -- ``group_id``: primary key of ``DeviceGroup`` of ``Device`` -- ``old_group_id``: primary key of previous ``DeviceGroup`` of ``Device`` - -The signal is emitted when the device group changes. - -It is not emitted when the device is created. - -``group_templates_changed`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**Path**: ``openwisp_controller.config.signals.group_templates_changed`` - -**Arguments**: - -- ``instance``: instance of ``DeviceGroup``. -- ``templates``: list of ``Template`` objects assigned to ``DeviceGroup`` -- ``old_templates``: list of ``Template`` objects assigned earlier to - ``DeviceGroup`` - -The signal is emitted when the device group templates changes. - -It is not emitted when the device is created. - -``subnet_provisioned`` -~~~~~~~~~~~~~~~~~~~~~~ - -**Path**: -``openwisp_controller.subnet_division.signals.subnet_provisioned`` - -**Arguments**: - -- ``instance``: instance of ``VpnClient``. -- ``provisioned``: dictionary of ``Subnet`` and ``IpAddress`` provisioned, - ``None`` if nothing is provisioned - -The signal is emitted when subnets and IP addresses have been provisioned -for a ``VpnClient`` for a VPN server with a subnet with `subnet division -rule <#subnet-division-app>`_. - -``vpn_server_modified`` -~~~~~~~~~~~~~~~~~~~~~~~ - -**Path**: ``openwisp_controller.config.signals.vpn_server_modified`` - -**Arguments**: - -- ``instance``: instance of ``Vpn``. - -The signal is emitted when the VPN server is modified. - -``vpn_peers_changed`` -~~~~~~~~~~~~~~~~~~~~~ - -**Path**: ``openwisp_controller.config.signals.vpn_peers_changed`` - -**Arguments**: - -- ``instance``: instance of ``Vpn``. - -The signal is emitted when the peers of VPN server gets changed. - -It is only emitted for ``Vpn`` object with **WireGuard** or **VXLAN over -WireGuard** backend. - -Extending openwisp-controller ------------------------------ - -One of the core values of the OpenWISP project is `Software Reusability -`_, -for this reason *openwisp-controller* provides a set of base classes which -can be imported, extended and reused to create derivative apps. - -In order to implement your custom version of *openwisp-controller*, you -need to perform the steps described in this section. - -When in doubt, the code in the `test project -`_ -will serve you as source of truth: just replicate and adapt that code to -get a basic derivative of *openwisp-controller* working. - -If you want to add new users fields, please follow the `tutorial to extend -the openwisp-users -`_. As -an example, we have extended *openwisp-users* to *sample_users* app and -added a field ``social_security_number`` in the `sample_users/models.py -`_. - -**Premise**: if you plan on using a customized version of this module, we -suggest to start with it since the beginning, because migrating your data -from the default module to your extended version may be time consuming. - -1. Initialize your project & custom apps -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Firstly, to get started you need to create a django project: - -:: - - django-admin startproject mycontroller - -Now, you need to do is to create some new django apps which will contain -your custom version of *openwisp-controller*. - -A django project is a collection of django apps. There are 4 django apps -in the openwisp_controller project, namely config, pki, connection & geo. -You'll need to create 4 apps in your project for each app in -openwisp_controller. - -A django app is nothing more than a `python package -`_ (a directory -of python scripts), in the following examples we'll call these django app -``sample_config``, ``sample_pki``, ``sample_connection``, ``sample_geo`` & -``sample_subnet_division``. but you can name it how you want: - -:: - - django-admin startapp sample_config - django-admin startapp sample_pki - django-admin startapp sample_connection - django-admin startapp sample_geo - django-admin startapp sample_subnet_division - -Keep in mind that the command mentioned above must be called from a -directory which is available in your `PYTHON_PATH -`_ so that -you can then import the result into your project. - -For more information about how to work with django projects and django -apps, please refer to the `django documentation -`_. - -2. Install ``openwisp-controller`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Install (and add to the requirement of your project) openwisp-controller: - -:: - - pip install openwisp-controller - -3. Add your apps in INSTALLED_APPS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Now you need to add ``mycontroller.sample_config``, -``mycontroller.sample_pki``, ``mycontroller.sample_connection``, -``mycontroller.sample_geo`` & ``mycontroller.sample_subnet_division`` to -``INSTALLED_APPS`` in your ``settings.py``, ensuring also that -``openwisp_controller.config``, ``openwisp_controller.geo``, -``openwisp_controller.pki``, ``openwisp_controller.connnection`` & -``openwisp_controller.subnet_division`` have been removed: - -.. code-block:: python - - # Remember: Order in INSTALLED_APPS is important. - INSTALLED_APPS = [ - # other django installed apps - "openwisp_utils.admin_theme", - "admin_auto_filters", - # all-auth - "django.contrib.sites", - "allauth", - "allauth.account", - "allauth.socialaccount", - # openwisp2 module - # 'openwisp_controller.config', <-- comment out or delete this line - # 'openwisp_controller.pki', <-- comment out or delete this line - # 'openwisp_controller.geo', <-- comment out or delete this line - # 'openwisp_controller.connection', <-- comment out or delete this line - # 'openwisp_controller.subnet_division', <-- comment out or delete this line - "mycontroller.sample_config", - "mycontroller.sample_pki", - "mycontroller.sample_geo", - "mycontroller.sample_connection", - "mycontroller.sample_subnet_division", - "openwisp_users", - # admin - "django.contrib.admin", - # other dependencies - "sortedm2m", - "reversion", - "leaflet", - # rest framework - "rest_framework", - "rest_framework_gis", - # channels - "channels", - # django-import-export - "import_export", - ] - -Substitute ``mycontroller``, ``sample_config``, ``sample_pki``, -``sample_connection``, ``sample_geo`` & ``sample_subnet_division`` with -the name you chose in step 1. - -4. Add ``EXTENDED_APPS`` -~~~~~~~~~~~~~~~~~~~~~~~~ - -Add the following to your ``settings.py``: - -.. code-block:: python - - EXTENDED_APPS = ( - "django_x509", - "django_loci", - "openwisp_controller.config", - "openwisp_controller.pki", - "openwisp_controller.geo", - "openwisp_controller.connection", - "openwisp_controller.subnet_division", - ) - -5. Add ``openwisp_utils.staticfiles.DependencyFinder`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Add ``openwisp_utils.staticfiles.DependencyFinder`` to -``STATICFILES_FINDERS`` in your ``settings.py``: - -.. code-block:: python - - STATICFILES_FINDERS = [ - "django.contrib.staticfiles.finders.FileSystemFinder", - "django.contrib.staticfiles.finders.AppDirectoriesFinder", - "openwisp_utils.staticfiles.DependencyFinder", - ] - -6. Add ``openwisp_utils.loaders.DependencyLoader`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Add ``openwisp_utils.loaders.DependencyLoader`` to ``TEMPLATES`` in your -``settings.py``, but ensure it comes before -``django.template.loaders.app_directories.Loader``: - -.. code-block:: python - - TEMPLATES = [ - { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "OPTIONS": { - "loaders": [ - "django.template.loaders.filesystem.Loader", - "openwisp_utils.loaders.DependencyLoader", - "django.template.loaders.app_directories.Loader", - ], - "context_processors": [ - "django.template.context_processors.debug", - "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - "openwisp_utils.admin_theme.context_processor.menu_items", - "openwisp_notifications.context_processors.notification_api_settings", - ], - }, - } - ] - -5. Initial Database setup -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ensure you are using one of the available geodjango backends, eg: - -.. code-block:: python - - DATABASES = { - "default": { - "ENGINE": "openwisp_utils.db.backends.spatialite", - "NAME": "openwisp-controller.db", - } - } - -For more information about GeoDjango, please refer to the `geodjango -documentation `_. - -6. Django Channels Setup -~~~~~~~~~~~~~~~~~~~~~~~~ - -Create ``asgi.py`` in your project folder and add following lines in it: - -.. code-block:: python - - from channels.auth import AuthMiddlewareStack - from channels.routing import ProtocolTypeRouter, URLRouter - from channels.security.websocket import AllowedHostsOriginValidator - from django.core.asgi import get_asgi_application - - from openwisp_controller.routing import get_routes - - # You can also add your routes like this - from my_app.routing import my_routes - - application = ProtocolTypeRouter( - { - "http": get_asgi_application(), - "websocket": AllowedHostsOriginValidator( - AuthMiddlewareStack(URLRouter(get_routes() + my_routes)) - ), - } - ) - -7. Other Settings -~~~~~~~~~~~~~~~~~ - -Add the following settings to ``settings.py``: - -.. code-block:: python - - FORM_RENDERER = "django.forms.renderers.TemplatesSetting" - - ASGI_APPLICATION = "my_project.asgi.application" - CHANNEL_LAYERS = { - "default": {"BACKEND": "channels.layers.InMemoryChannelLayer"}, - } - -For more information about FORM_RENDERER setting, please refer to the -`FORM_RENDERER documentation -`_. For -more information about ASGI_APPLICATION setting, please refer to the -`ASGI_APPLICATION documentation -`_. -For more information about CHANNEL_LAYERS setting, please refer to the -`CHANNEL_LAYERS documentation -`_. - -6. Inherit the AppConfig class -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Please refer to the following files in the sample app of the test project: - -- sample_config: - - `sample_config/__init__.py - `_. - - `sample_config/apps.py - `_. -- sample_geo: - - `sample_geo/__init__.py - `_. - - `sample_geo/apps.py - `_. -- sample_pki: - - `sample_pki/__init__.py - `_. - - `sample_pki/apps.py - `_. -- sample_connection: - - `sample_connection/__init__.py - `_. - - `sample_connection/apps.py - `_. -- sample_subnet_division: - - `sample_subnet_division/__init__.py - `_. - - `sample_subnet_division/apps.py - `_. - -You have to replicate and adapt that code in your project. - -For more information regarding the concept of ``AppConfig`` please refer -to the `"Applications" section in the django documentation -`_. - -7. Create your custom models -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -For the purpose of showing an example, we added a simple "details" field -to the models of the sample app in the test project. - -- `sample_config models - `_ -- `sample_geo models - `_ -- `sample_pki models - `_ -- `sample_connection models - `_ -- `sample_subnet_division - `_ - -You can add fields in a similar way in your ``models.py`` file. - -**Note**: for doubts regarding how to use, extend or develop models please -refer to the `"Models" section in the django documentation -`_. - -8. Add swapper configurations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Once you have created the models, add the following to your -``settings.py``: - -.. code-block:: python - - # Setting models for swapper module - CONFIG_DEVICE_MODEL = "sample_config.Device" - CONFIG_DEVICEGROUP_MODEL = "sample_config.DeviceGroup" - CONFIG_CONFIG_MODEL = "sample_config.Config" - CONFIG_TEMPLATETAG_MODEL = "sample_config.TemplateTag" - CONFIG_TAGGEDTEMPLATE_MODEL = "sample_config.TaggedTemplate" - CONFIG_TEMPLATE_MODEL = "sample_config.Template" - CONFIG_VPN_MODEL = "sample_config.Vpn" - CONFIG_VPNCLIENT_MODEL = "sample_config.VpnClient" - CONFIG_ORGANIZATIONCONFIGSETTINGS_MODEL = ( - "sample_config.OrganizationConfigSettings" - ) - CONFIG_ORGANIZATIONLIMITS_MODEL = "sample_config.OrganizationLimits" - DJANGO_X509_CA_MODEL = "sample_pki.Ca" - DJANGO_X509_CERT_MODEL = "sample_pki.Cert" - GEO_LOCATION_MODEL = "sample_geo.Location" - GEO_FLOORPLAN_MODEL = "sample_geo.FloorPlan" - GEO_DEVICELOCATION_MODEL = "sample_geo.DeviceLocation" - CONNECTION_CREDENTIALS_MODEL = "sample_connection.Credentials" - CONNECTION_DEVICECONNECTION_MODEL = "sample_connection.DeviceConnection" - CONNECTION_COMMAND_MODEL = "sample_connection.Command" - SUBNET_DIVISION_SUBNETDIVISIONRULE_MODEL = ( - "sample_subnet_division.SubnetDivisionRule" - ) - SUBNET_DIVISION_SUBNETDIVISIONINDEX_MODEL = ( - "sample_subnet_division.SubnetDivisionIndex" - ) - -Substitute ``sample_config``, ``sample_pki``, ``sample_connection``, -``sample_geo`` & ``sample_subnet_division`` with the name you chose in -step 1. - -9. Create database migrations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Create database migrations: - -:: - - ./manage.py makemigrations - -Now, to use the default ``administrator`` and ``operator`` user groups -like the used in the openwisp_controller module, you'll manually need to -make a migrations file which would look like: - -- `sample_config/migrations/0002_default_groups_permissions.py - `_ -- `sample_geo/migrations/0002_default_group_permissions.py - `_ -- `sample_pki/migrations/0002_default_group_permissions.py - `_ -- `sample_connection/migrations/0002_default_group_permissions.py - `_ -- `sample_subnet_division/migrations/0002_default_group_permissions.py - `_ - -Create database migrations: - -:: - - ./manage.py migrate - -For more information, refer to the `"Migrations" section in the django -documentation -`_. - -10. Create the admin -~~~~~~~~~~~~~~~~~~~~ - -Refer to the ``admin.py`` file of the sample app. - -- `sample_config admin.py - `_. -- `sample_geo admin.py - `_. -- `sample_pki admin.py - `_. -- `sample_connection admin.py - `_. -- `sample_subnet_division admin.py - `_. - -To introduce changes to the admin, you can do it in two main ways which -are described below. - -**Note**: for more information regarding how the django admin works, or -how it can be customized, please refer to `"The django admin site" section -in the django documentation -`_. - -1. Monkey patching -++++++++++++++++++ - -If the changes you need to add are relatively small, you can resort to -monkey patching. - -For example: - -sample_config -............. - -.. code-block:: python - - from openwisp_controller.config.admin import ( - DeviceAdmin, - DeviceGroupAdmin, - TemplateAdmin, - VpnAdmin, - ) - - # DeviceAdmin.fields += ['example'] <-- monkey patching example - -sample_connection -................. - -.. code-block:: python - - from openwisp_controller.connection.admin import CredentialsAdmin - - # CredentialsAdmin.fields += ['example'] <-- monkey patching example - -sample_geo -.......... - -.. code-block:: python - - from openwisp_controller.geo.admin import FloorPlanAdmin, LocationAdmin - - # FloorPlanAdmin.fields += ['example'] <-- monkey patching example - -sample_pki -.......... - -.. code-block:: python - - from openwisp_controller.pki.admin import CaAdmin, CertAdmin - - # CaAdmin.fields += ['example'] <-- monkey patching example - -sample_subnet_division -...................... - -.. code-block:: python - - from openwisp_controller.subnet_division.admin import ( - SubnetDivisionRuleInlineAdmin, - ) - - # SubnetDivisionRuleInlineAdmin.fields += ['example'] <-- monkey patching example - -2. Inheriting admin classes -+++++++++++++++++++++++++++ - -If you need to introduce significant changes and/or you don't want to -resort to monkey patching, you can proceed as follows: - -sample_config -............. - -.. code-block:: python - - from django.contrib import admin - from openwisp_controller.config.admin import ( - DeviceAdmin as BaseDeviceAdmin, - TemplateAdmin as BaseTemplateAdmin, - VpnAdmin as BaseVpnAdmin, - DeviceGroupAdmin as BaseDeviceGroupAdmin, - from swapper import load_model - - Vpn = load_model('openwisp_controller', 'Vpn') - Device = load_model('openwisp_controller', 'Device') - DeviceGroup = load_model('openwisp_controller', 'DeviceGroup') - Template = load_model('openwisp_controller', 'Template') - - admin.site.unregister(Vpn) - admin.site.unregister(Device) - admin.site.unregister(DeviceGroup) - admin.site.unregister(Template) - - @admin.register(Vpn) - class VpnAdmin(BaseVpnAdmin): - # add your changes here - - @admin.register(Device) - class DeviceAdmin(BaseDeviceAdmin): - # add your changes here - - @admin.register(DeviceGroup) - class DeviceGroupAdmin(BaseDeviceGroupAdmin): - # add your changes here - - @admin.register(Template) - class TemplateAdmin(BaseTemplateAdmin): - # add your changes here - -sample_connection -................. - -.. code-block:: python - - from openwisp_controller.connection.admin import CredentialsAdmin as BaseCredentialsAdmin - from django.contrib import admin - from swapper import load_model - - Credentials = load_model('openwisp_controller', 'Credentials') - - admin.site.unregister(Credentials) - - @admin.register(Device) - class CredentialsAdmin(BaseCredentialsAdmin): - # add your changes here - -sample_geo -.......... - -.. code-block:: python - - from openwisp_controller.geo.admin import ( - FloorPlanAdmin as BaseFloorPlanAdmin, - LocationAdmin as BaseLocationAdmin - ) - from django.contrib import admin - from swapper import load_model - - Location = load_model('openwisp_controller', 'Location') - FloorPlan = load_model('openwisp_controller', 'FloorPlan') - - admin.site.unregister(FloorPlan) - admin.site.unregister(Location) - - @admin.register(FloorPlan) - class FloorPlanAdmin(BaseFloorPlanAdmin): - # add your changes here - - @admin.register(Location) - class LocationAdmin(BaseLocationAdmin): - # add your changes here - -sample_pki -.......... - -.. code-block:: python - - from openwisp_controller.geo.admin import ( - CaAdmin as BaseCaAdmin, - CertAdmin as BaseCertAdmin - ) - from django.contrib import admin - from swapper import load_model - - Ca = load_model('openwisp_controller', 'Ca') - Cert = load_model('openwisp_controller', 'Cert') - - admin.site.unregister(Ca) - admin.site.unregister(Cert) - - @admin.register(Ca) - class CaAdmin(BaseCaAdmin): - # add your changes here - - @admin.register(Cert) - class CertAdmin(BaseCertAdmin): - # add your changes here - -sample_subnet_division -...................... - -.. code-block:: python - - from openwisp_controller.subnet_division.admin import ( - SubnetAdmin as BaseSubnetAdmin, - IpAddressAdmin as BaseIpAddressAdmin, - SubnetDivisionRuleInlineAdmin as BaseSubnetDivisionRuleInlineAdmin, - ) - from django.contrib import admin - from swapper import load_model - - Subnet = load_model('openwisp_ipam', 'Subnet') - IpAddress = load_model('openwisp_ipam', 'IpAddress') - SubnetDivisionRule = load_model('subnet_division', 'SubnetDivisionRule') - - admin.site.unregister(Subnet) - admin.site.unregister(IpAddress) - admin.site.unregister(SubnetDivisionRule) - - @admin.register(Subnet) - class SubnetAdmin(BaseSubnetAdmin): - # add your changes here - - @admin.register(IpAddress) - class IpAddressAdmin(BaseIpAddressAdmin): - # add your changes here - - @admin.register(SubnetDivisionRule) - class SubnetDivisionRuleInlineAdmin(BaseSubnetDivisionRuleInlineAdmin): - # add your changes here - -11. Create root URL configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from django.contrib import admin - from openwisp_controller.config.utils import get_controller_urls - from openwisp_controller.geo.utils import get_geo_urls - - # from .sample_config import views as config_views - # from .sample_geo import views as geo_views - - urlpatterns = [ - # ... other urls in your project ... - # Use only when changing controller API views (discussed below) - # url(r'^controller/', include((get_controller_urls(config_views), 'controller'), namespace='controller')) - # Use only when changing geo API views (discussed below) - # url(r'^geo/', include((get_geo_urls(geo_views), 'geo'), namespace='geo')), - # openwisp-controller urls - url( - r"", - include( - ("openwisp_controller.config.urls", "config"), - namespace="config", - ), - ), - url(r"", include("openwisp_controller.urls")), - ] - -For more information about URL configuration in django, please refer to -the `"URL dispatcher" section in the django documentation -`_. - -12. Import the automated tests -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When developing a custom application based on this module, it's a good -idea to import and run the base tests too, so that you can be sure the -changes you're introducing are not breaking some of the existing features -of *openwisp-controller*. - -In case you need to add breaking changes, you can overwrite the tests -defined in the base classes to test your own behavior. - -See the tests in sample_app to find out how to do this. - -- `project common tests.py - `_ -- `sample_config tests.py - `_ -- `sample_geo tests.py - `_ -- `sample_geo pytest.py - `_ -- `sample_pki tests.py - `_ -- `sample_connection tests.py - `_ -- `sample_subnet_division tests.py - `_ - -For running the tests, you need to copy fixtures as well: - -- Change `sample_config` to your config app's name in `sample_config - fixtures - `_ - and paste it in the ``sample_config/fixtures/`` directory. - -You can then run tests with: - -:: - - # the --parallel flag is optional - ./manage.py test --parallel mycontroller - -Substitute ``mycontroller`` with the name you chose in step 1. - -For more information about automated tests in django, please refer to -`"Testing in Django" -`_. - -Other base classes that can be inherited and extended -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The following steps are not required and are intended for more advanced -customization. - -1. Extending the Controller API Views -+++++++++++++++++++++++++++++++++++++ - -Extending the `sample_config/views.py -`_ -is required only when you want to make changes in the controller API, -Remember to change ``config_views`` location in ``urls.py`` in point 11 -for extending views. - -For more information about django views, please refer to the `views -section in the django documentation -`_. - -2. Extending the Geo API Views -++++++++++++++++++++++++++++++ - -Extending the `sample_geo/views.py -`_ -is required only when you want to make changes in the geo API, Remember to -change ``geo_views`` location in ``urls.py`` in point 11 for extending -views. - -For more information about django views, please refer to the `views -section in the django documentation -`_. - -Custom Subnet Division Rule Types -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -It is possible to create your own `subnet division rule types -<#subnet-division-app>`_. The rule type determines when subnets and IPs -will be provisioned and when they will be destroyed. - -You can create your custom rule types by extending -``openwisp_controller.subnet_division.rule_types.base.BaseSubnetDivisionRuleType``. - -Below is an example to create a subnet division rule type that will -provision subnets and IPs when a new device is created and will delete -them upon deletion for that device. - -.. code-block:: python - - # In mycontroller/sample_subnet_division/rules_types/custom.py - - from django.db.models.signals import post_delete, post_save - from swapper import load_model - - from openwisp_controller.subnet_division.rule_types.base import ( - BaseSubnetDivisionRuleType, - ) - - Device = load_model("config", "Device") - - - class CustomRuleType(BaseSubnetDivisionRuleType): - # The signal on which provisioning should be triggered - provision_signal = post_save - # The sender of the provision_signal - provision_sender = Device - # Dispatch UID for connecting provision_signal to provision_receiver - provision_dispatch_uid = "some_unique_identifier_string" - - # The signal on which deletion should be triggered - destroyer_signal = post_delete - # The sender of the destroyer_signal - destroyer_sender = Device - # Dispatch UID for connecting destroyer_signal to destroyer_receiver - destroyer_dispatch_uid = "another_unique_identifier_string" - - # Attribute path to organization_id - # Example 1: If organization_id is direct attribute of provision_signal - # sender instance, then - # organization_id_path = 'organization_id' - # Example 2: If organization_id is indirect attribute of provision signal - # sender instance, then - # organization_id_path = 'some_attribute.another_intermediate.organization_id' - organization_id_path = "organization_id" - - # Similar to organization_id_path but for the required subnet attribute - subnet_path = "subnet" - - # An intermediate method through which you can specify conditions for provisions - @classmethod - def should_create_subnets_ips(cls, instance, **kwargs): - # Using "post_save" provision_signal, the rule should be only - # triggered when a new object is created. - return kwargs["created"] - - # You can define logic to trigger provisioning for existing objects - # using following classmethod. By default, BaseSubnetDivisionRuleType - # performs no operation for existing objects. - @classmethod - def provision_for_existing_objects(cls, rule_obj): - for device in Device.objects.filter( - organization=rule_obj.organization - ): - cls.provision_receiver(device, created=True) - -After creating a class for your custom rule type, you will need to set -`OPENWISP_CONTROLLER_SUBNET_DIVISION_TYPES -<#openwisp-controller-subnet-division-types>`_ setting as follows: - -.. code-block:: python - - OPENWISP_CONTROLLER_SUBNET_DIVISION_TYPES = ( | - ('openwisp_controller.subnet_division.rule_types.vpn.VpnSubnetDivisionRuleType', 'VPN'), - ('openwisp_controller.subnet_division.rule_types.device.DeviceSubnetDivisionRuleType', 'Device'), - ('mycontroller.sample_subnet_division.rules_types.custom.CustomRuleType', 'Custom Rule'), - ) - -Registering new notification types -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can define your own notification types using -``register_notification_type`` function from OpenWISP Notifications. - -For more information, see the relevant `documentation section about -registering notification types in openwisp-notifications -`_. - -Once a new notification type is registered, you have to use the `"notify" -signal provided in openwisp-notifications -`_ -to send notifications for this type. +- `Developer documentation `_ +- `User documentation + `_ Contributing ------------ diff --git a/docs/developer/extending.rst b/docs/developer/extending.rst new file mode 100644 index 000000000..599d81f85 --- /dev/null +++ b/docs/developer/extending.rst @@ -0,0 +1,873 @@ +Extending OpenWISP Controller +============================= + +.. include:: ../partials/developer-docs.rst + +One of the core values of the OpenWISP project is :ref:`Software +Reusability `, for this reason *OpenWISP +Controller* provides a set of base classes which can be imported, extended +and reused to create derivative apps. + +In order to implement your custom version of *OpenWISP Controller*, you +need to perform the steps described in this section. + +When in doubt, the code in the `test project +`_ +will serve you as source of truth: just replicate and adapt that code to +get a basic derivative of *OpenWISP Controller* working. + +If you want to add new users fields, please follow the :doc:`tutorial to +extend the openwisp-users module `. As an +example, we have extended *openwisp-users* to *sample_users* app and added +a field ``social_security_number`` in the `sample_users/models.py +`_. + +.. important:: + + If you plan on using a customized version of this module, we suggest + to start with it since the beginning, because migrating your data from + the default module to your extended version may be time consuming. + +.. contents:: **Table of Contents**: + :depth: 2 + :local: + +1. Initialize Your Project & Custom Apps +---------------------------------------- + +Firstly, to get started you need to create a django project: + +.. code-block:: + + django-admin startproject mycontroller + +Now, you need to do is to create some new django apps which will contain +your custom version of *OpenWISP Controller*. + +A django project is a collection of django apps. There are 4 django apps +in the openwisp_controller project, namely config, pki, connection & geo. +You'll need to create 4 apps in your project for each app in +openwisp_controller. + +A django app is nothing more than a `python package +`_ (a directory +of python scripts), in the following examples we'll call these django app +``sample_config``, ``sample_pki``, ``sample_connection``, ``sample_geo`` & +``sample_subnet_division``. but you can name it how you want: + +.. code-block:: + + django-admin startapp sample_config + django-admin startapp sample_pki + django-admin startapp sample_connection + django-admin startapp sample_geo + django-admin startapp sample_subnet_division + +Keep in mind that the command mentioned above must be called from a +directory which is available in your `PYTHON_PATH +`_ so that +you can then import the result into your project. + +For more information about how to work with django projects and django +apps, please refer to the `django documentation +`_. + +2. Install ``openwisp-controller`` +---------------------------------- + +Install (and add to the requirement of your project) openwisp-controller: + +.. code-block:: + + pip install openwisp-controller + +3. Add Your Apps to ``INSTALLED_APPS`` +-------------------------------------- + +Now you need to add ``mycontroller.sample_config``, +``mycontroller.sample_pki``, ``mycontroller.sample_connection``, +``mycontroller.sample_geo`` & ``mycontroller.sample_subnet_division`` to +``INSTALLED_APPS`` in your ``settings.py``, ensuring also that +``openwisp_controller.config``, ``openwisp_controller.geo``, +``openwisp_controller.pki``, ``openwisp_controller.connnection`` & +``openwisp_controller.subnet_division`` have been removed: + +.. code-block:: python + + # Remember: Order in INSTALLED_APPS is important. + INSTALLED_APPS = [ + # other django installed apps + "openwisp_utils.admin_theme", + "admin_auto_filters", + # all-auth + "django.contrib.sites", + "allauth", + "allauth.account", + "allauth.socialaccount", + # openwisp2 module + # 'openwisp_controller.config', <-- comment out or delete this line + # 'openwisp_controller.pki', <-- comment out or delete this line + # 'openwisp_controller.geo', <-- comment out or delete this line + # 'openwisp_controller.connection', <-- comment out or delete this line + # 'openwisp_controller.subnet_division', <-- comment out or delete this line + "mycontroller.sample_config", + "mycontroller.sample_pki", + "mycontroller.sample_geo", + "mycontroller.sample_connection", + "mycontroller.sample_subnet_division", + "openwisp_users", + # admin + "django.contrib.admin", + # other dependencies + "sortedm2m", + "reversion", + "leaflet", + # rest framework + "rest_framework", + "rest_framework_gis", + # channels + "channels", + # django-import-export + "import_export", + ] + +Substitute ``mycontroller``, ``sample_config``, ``sample_pki``, +``sample_connection``, ``sample_geo`` & ``sample_subnet_division`` with +the name you chose in step 1. + +4. Add ``EXTENDED_APPS`` +------------------------ + +Add the following to your ``settings.py``: + +.. code-block:: python + + EXTENDED_APPS = ( + "django_x509", + "django_loci", + "openwisp_controller.config", + "openwisp_controller.pki", + "openwisp_controller.geo", + "openwisp_controller.connection", + "openwisp_controller.subnet_division", + ) + +5. Add ``openwisp_utils.staticfiles.DependencyFinder`` +------------------------------------------------------ + +Add ``openwisp_utils.staticfiles.DependencyFinder`` to +``STATICFILES_FINDERS`` in your ``settings.py``: + +.. code-block:: python + + STATICFILES_FINDERS = [ + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", + "openwisp_utils.staticfiles.DependencyFinder", + ] + +6. Add ``openwisp_utils.loaders.DependencyLoader`` +-------------------------------------------------- + +Add ``openwisp_utils.loaders.DependencyLoader`` to ``TEMPLATES`` in your +``settings.py``, but ensure it comes before +``django.template.loaders.app_directories.Loader``: + +.. code-block:: python + + TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "OPTIONS": { + "loaders": [ + "django.template.loaders.filesystem.Loader", + "openwisp_utils.loaders.DependencyLoader", + "django.template.loaders.app_directories.Loader", + ], + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + "openwisp_utils.admin_theme.context_processor.menu_items", + "openwisp_notifications.context_processors.notification_api_settings", + ], + }, + } + ] + +5. Initial Database Setup +------------------------- + +Ensure you are using one of the available geodjango backends, eg: + +.. code-block:: python + + DATABASES = { + "default": { + "ENGINE": "openwisp_utils.db.backends.spatialite", + "NAME": "openwisp-controller.db", + } + } + +For more information about GeoDjango, please refer to the `geodjango +documentation `_. + +6. Django Channels Setup +------------------------ + +Create ``asgi.py`` in your project folder and add following lines in it: + +.. code-block:: python + + from channels.auth import AuthMiddlewareStack + from channels.routing import ProtocolTypeRouter, URLRouter + from channels.security.websocket import AllowedHostsOriginValidator + from django.core.asgi import get_asgi_application + + from openwisp_controller.routing import get_routes + + # You can also add your routes like this + from my_app.routing import my_routes + + application = ProtocolTypeRouter( + { + "http": get_asgi_application(), + "websocket": AllowedHostsOriginValidator( + AuthMiddlewareStack(URLRouter(get_routes() + my_routes)) + ), + } + ) + +7. Other Settings +----------------- + +Add the following settings to ``settings.py``: + +.. code-block:: python + + FORM_RENDERER = "django.forms.renderers.TemplatesSetting" + + ASGI_APPLICATION = "my_project.asgi.application" + CHANNEL_LAYERS = { + "default": {"BACKEND": "channels.layers.InMemoryChannelLayer"}, + } + +For more information about FORM_RENDERER setting, please refer to the +`FORM_RENDERER documentation +`_. For +more information about ASGI_APPLICATION setting, please refer to the +`ASGI_APPLICATION documentation +`_. +For more information about CHANNEL_LAYERS setting, please refer to the +`CHANNEL_LAYERS documentation +`_. + +6. Inherit the AppConfig Class +------------------------------ + +Please refer to the following files in the sample app of the test project: + +- ``sample_config``: + - `sample_config/__init__.py + `_. + - `sample_config/apps.py + `_. +- ``sample_geo``: + - `sample_geo/__init__.py + `_. + - `sample_geo/apps.py + `_. +- ``sample_pki``: + - `sample_pki/__init__.py + `_. + - `sample_pki/apps.py + `_. +- ``sample_connection``: + - `sample_connection/__init__.py + `_. + - `sample_connection/apps.py + `_. +- ``sample_subnet_division``: + - `sample_subnet_division/__init__.py + `_. + - `sample_subnet_division/apps.py + `_. + +You have to replicate and adapt that code in your project. + +For more information regarding the concept of ``AppConfig`` please refer +to the `"Applications" section in the django documentation +`_. + +7. Create Your Custom Models +---------------------------- + +For the purpose of showing an example, we added a simple "details" field +to the models of the sample app in the test project. + +- `sample_config models + `_ +- `sample_geo models + `_ +- `sample_pki models + `_ +- `sample_connection models + `_ +- `sample_subnet_division + `_ + +You can add fields in a similar way in your ``models.py`` file. + +.. note:: + + If you have any doubt regarding how to use, extend or develop models + please refer to the `"Models" section in the django documentation + `_. + +8. Add Swapper Configurations +----------------------------- + +Once you have created the models, add the following to your +``settings.py``: + +.. code-block:: python + + # Setting models for swapper module + CONFIG_DEVICE_MODEL = "sample_config.Device" + CONFIG_DEVICEGROUP_MODEL = "sample_config.DeviceGroup" + CONFIG_CONFIG_MODEL = "sample_config.Config" + CONFIG_TEMPLATETAG_MODEL = "sample_config.TemplateTag" + CONFIG_TAGGEDTEMPLATE_MODEL = "sample_config.TaggedTemplate" + CONFIG_TEMPLATE_MODEL = "sample_config.Template" + CONFIG_VPN_MODEL = "sample_config.Vpn" + CONFIG_VPNCLIENT_MODEL = "sample_config.VpnClient" + CONFIG_ORGANIZATIONCONFIGSETTINGS_MODEL = ( + "sample_config.OrganizationConfigSettings" + ) + CONFIG_ORGANIZATIONLIMITS_MODEL = "sample_config.OrganizationLimits" + DJANGO_X509_CA_MODEL = "sample_pki.Ca" + DJANGO_X509_CERT_MODEL = "sample_pki.Cert" + GEO_LOCATION_MODEL = "sample_geo.Location" + GEO_FLOORPLAN_MODEL = "sample_geo.FloorPlan" + GEO_DEVICELOCATION_MODEL = "sample_geo.DeviceLocation" + CONNECTION_CREDENTIALS_MODEL = "sample_connection.Credentials" + CONNECTION_DEVICECONNECTION_MODEL = "sample_connection.DeviceConnection" + CONNECTION_COMMAND_MODEL = "sample_connection.Command" + SUBNET_DIVISION_SUBNETDIVISIONRULE_MODEL = ( + "sample_subnet_division.SubnetDivisionRule" + ) + SUBNET_DIVISION_SUBNETDIVISIONINDEX_MODEL = ( + "sample_subnet_division.SubnetDivisionIndex" + ) + +Substitute ``sample_config``, ``sample_pki``, ``sample_connection``, +``sample_geo`` & ``sample_subnet_division`` with the name you chose in +step 1. + +9. Create Database Migrations +----------------------------- + +Create database migrations: + +.. code-block:: + + ./manage.py makemigrations + +Now, to use the default ``administrator`` and ``operator`` user groups +like the used in the openwisp_controller module, you'll manually need to +make a migrations file which would look like: + +- `sample_config/migrations/0002_default_groups_permissions.py + `_ +- `sample_geo/migrations/0002_default_group_permissions.py + `_ +- `sample_pki/migrations/0002_default_group_permissions.py + `_ +- `sample_connection/migrations/0002_default_group_permissions.py + `_ +- `sample_subnet_division/migrations/0002_default_group_permissions.py + `_ + +Create database migrations: + +.. code-block:: + + ./manage.py migrate + +For more information, refer to the `"Migrations" section in the django +documentation +`_. + +10. Create the Admin +-------------------- + +Refer to the ``admin.py`` file of the sample app. + +- `sample_config admin.py + `_. +- `sample_geo admin.py + `_. +- `sample_pki admin.py + `_. +- `sample_connection admin.py + `_. +- `sample_subnet_division admin.py + `_. + +To introduce changes to the admin, you can do it in two main ways which +are described below. + +**Note**: for more information regarding how the django admin works, or +how it can be customized, please refer to `"The django admin site" section +in the django documentation +`_. + +1. Monkey Patching +~~~~~~~~~~~~~~~~~~ + +If the changes you need to add are relatively small, you can resort to +monkey patching. + +For example: + +``sample_config`` ++++++++++++++++++ + +.. code-block:: python + + from openwisp_controller.config.admin import ( + DeviceAdmin, + DeviceGroupAdmin, + TemplateAdmin, + VpnAdmin, + ) + + DeviceAdmin.fields += ["example"] # <-- monkey patching example + +``sample_connection`` ++++++++++++++++++++++ + +.. code-block:: python + + from openwisp_controller.connection.admin import CredentialsAdmin + + CredentialsAdmin.fields += ["example"] # <-- monkey patching example + +``sample_geo`` +++++++++++++++ + +.. code-block:: python + + from openwisp_controller.geo.admin import FloorPlanAdmin, LocationAdmin + + FloorPlanAdmin.fields += ["example"] # <-- monkey patching example + +``sample_pki`` +++++++++++++++ + +.. code-block:: python + + from openwisp_controller.pki.admin import CaAdmin, CertAdmin + + CaAdmin.fields += ["example"] # <-- monkey patching example + +``sample_subnet_division`` +++++++++++++++++++++++++++ + +.. code-block:: python + + from openwisp_controller.subnet_division.admin import ( + SubnetDivisionRuleInlineAdmin, + ) + + SubnetDivisionRuleInlineAdmin.fields += [ + "example" + ] # <-- monkey patching example + +2. Inheriting admin classes +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you need to introduce significant changes and/or you don't want to +resort to monkey patching, you can proceed as follows: + +``sample_config`` ++++++++++++++++++ + +.. code-block:: python + + from django.contrib import admin + from openwisp_controller.config.admin import ( + DeviceAdmin as BaseDeviceAdmin, + TemplateAdmin as BaseTemplateAdmin, + VpnAdmin as BaseVpnAdmin, + DeviceGroupAdmin as BaseDeviceGroupAdmin, + ) + from swapper import load_model + + Vpn = load_model("openwisp_controller", "Vpn") + Device = load_model("openwisp_controller", "Device") + DeviceGroup = load_model("openwisp_controller", "DeviceGroup") + Template = load_model("openwisp_controller", "Template") + + admin.site.unregister(Vpn) + admin.site.unregister(Device) + admin.site.unregister(DeviceGroup) + admin.site.unregister(Template) + + + @admin.register(Vpn) + class VpnAdmin(BaseVpnAdmin): + # add your changes here + pass + + + @admin.register(Device) + class DeviceAdmin(BaseDeviceAdmin): + # add your changes here + pass + + + @admin.register(DeviceGroup) + class DeviceGroupAdmin(BaseDeviceGroupAdmin): + # add your changes here + pass + + + @admin.register(Template) + class TemplateAdmin(BaseTemplateAdmin): + # add your changes here + pass + +``sample_connection`` ++++++++++++++++++++++ + +.. code-block:: python + + from openwisp_controller.connection.admin import ( + CredentialsAdmin as BaseCredentialsAdmin, + ) + from django.contrib import admin + from swapper import load_model + + Credentials = load_model("openwisp_controller", "Credentials") + + admin.site.unregister(Credentials) + + + @admin.register(Device) + class CredentialsAdmin(BaseCredentialsAdmin): + pass + # add your changes here + +``sample_geo`` +++++++++++++++ + +.. code-block:: python + + from openwisp_controller.geo.admin import ( + FloorPlanAdmin as BaseFloorPlanAdmin, + LocationAdmin as BaseLocationAdmin, + ) + from django.contrib import admin + from swapper import load_model + + Location = load_model("openwisp_controller", "Location") + FloorPlan = load_model("openwisp_controller", "FloorPlan") + + admin.site.unregister(FloorPlan) + admin.site.unregister(Location) + + + @admin.register(FloorPlan) + class FloorPlanAdmin(BaseFloorPlanAdmin): + pass + # add your changes here + + + @admin.register(Location) + class LocationAdmin(BaseLocationAdmin): + pass + # add your changes here + +``sample_pki`` +++++++++++++++ + +.. code-block:: python + + from openwisp_controller.geo.admin import ( + CaAdmin as BaseCaAdmin, + CertAdmin as BaseCertAdmin, + ) + from django.contrib import admin + from swapper import load_model + + Ca = load_model("openwisp_controller", "Ca") + Cert = load_model("openwisp_controller", "Cert") + + admin.site.unregister(Ca) + admin.site.unregister(Cert) + + + @admin.register(Ca) + class CaAdmin(BaseCaAdmin): + pass + # add your changes here + + + @admin.register(Cert) + class CertAdmin(BaseCertAdmin): + pass + # add your changes here + +``sample_subnet_division`` +++++++++++++++++++++++++++ + +.. code-block:: python + + from openwisp_controller.subnet_division.admin import ( + SubnetAdmin as BaseSubnetAdmin, + IpAddressAdmin as BaseIpAddressAdmin, + SubnetDivisionRuleInlineAdmin as BaseSubnetDivisionRuleInlineAdmin, + ) + from django.contrib import admin + from swapper import load_model + + Subnet = load_model("openwisp_ipam", "Subnet") + IpAddress = load_model("openwisp_ipam", "IpAddress") + SubnetDivisionRule = load_model("subnet_division", "SubnetDivisionRule") + + admin.site.unregister(Subnet) + admin.site.unregister(IpAddress) + admin.site.unregister(SubnetDivisionRule) + + + @admin.register(Subnet) + class SubnetAdmin(BaseSubnetAdmin): + pass + # add your changes here + + + @admin.register(IpAddress) + class IpAddressAdmin(BaseIpAddressAdmin): + pass + # add your changes here + + + @admin.register(SubnetDivisionRule) + class SubnetDivisionRuleInlineAdmin(BaseSubnetDivisionRuleInlineAdmin): + pass + # add your changes here + +11. Create Root URL Configuration +--------------------------------- + +.. code-block:: python + + from django.contrib import admin + from openwisp_controller.config.utils import get_controller_urls + from openwisp_controller.geo.utils import get_geo_urls + + # from .sample_config import views as config_views + # from .sample_geo import views as geo_views + + urlpatterns = [ + # ... other urls in your project ... + # Use only when changing controller API views (discussed below) + # url(r'^controller/', include((get_controller_urls(config_views), 'controller'), namespace='controller')) + # Use only when changing geo API views (discussed below) + # url(r'^geo/', include((get_geo_urls(geo_views), 'geo'), namespace='geo')), + # openwisp-controller urls + url( + r"", + include( + ("openwisp_controller.config.urls", "config"), + namespace="config", + ), + ), + url(r"", include("openwisp_controller.urls")), + ] + +For more information about URL configuration in django, please refer to +the `"URL dispatcher" section in the django documentation +`_. + +12. Import the Automated Tests +------------------------------ + +When developing a custom application based on this module, it's a good +idea to import and run the base tests too, so that you can be sure the +changes you're introducing are not breaking some of the existing features +of *OpenWISP Controller*. + +In case you need to add breaking changes, you can overwrite the tests +defined in the base classes to test your own behavior. + +See the tests in sample_app to find out how to do this. + +- `project common tests.py + `_ +- `sample_config tests.py + `_ +- `sample_geo tests.py + `_ +- `sample_geo pytest.py + `_ +- `sample_pki tests.py + `_ +- `sample_connection tests.py + `_ +- `sample_subnet_division tests.py + `_ + +For running the tests, you need to copy fixtures as well: + +- Change `sample_config` to your config app's name in `sample_config + fixtures + `_ + and paste it in the ``sample_config/fixtures/`` directory. + +You can then run tests with: + +.. code-block:: + + # the --parallel flag is optional + ./manage.py test --parallel mycontroller + +Substitute ``mycontroller`` with the name you chose in step 1. + +For more information about automated tests in django, please refer to +`"Testing in Django" +`_. + +Other Base Classes that Can Be Inherited and Extended +----------------------------------------------------- + +The following steps are not required and are intended for more advanced +customization. + +1. Extending the Controller API Views +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Extending the `sample_config/views.py +`_ +is required only when you want to make changes in the controller API, +Remember to change ``config_views`` location in ``urls.py`` in point 11 +for extending views. + +For more information about django views, please refer to the `views +section in the django documentation +`_. + +2. Extending the Geo API Views +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Extending the `sample_geo/views.py +`_ +is required only when you want to make changes in the geo API, Remember to +change ``geo_views`` location in ``urls.py`` in point 11 for extending +views. + +For more information about django views, please refer to the `views +section in the django documentation +`_. + +.. _custom_subnet_division_rule_types: + +Custom Subnet Division Rule Types +--------------------------------- + +It is possible to create your own :doc:`subnet division rule types +<../user/subnet-division-rules>`. The rule type determines when subnets +and IPs will be provisioned and when they will be destroyed. + +You can create your custom rule types by extending +``openwisp_controller.subnet_division.rule_types.base.BaseSubnetDivisionRuleType``. + +Below is an example to create a subnet division rule type that will +provision subnets and IPs when a new device is created and will delete +them upon deletion for that device. + +.. code-block:: python + + # In mycontroller/sample_subnet_division/rules_types/custom.py + + from django.db.models.signals import post_delete, post_save + from swapper import load_model + + from openwisp_controller.subnet_division.rule_types.base import ( + BaseSubnetDivisionRuleType, + ) + + Device = load_model("config", "Device") + + + class CustomRuleType(BaseSubnetDivisionRuleType): + # The signal on which provisioning should be triggered + provision_signal = post_save + # The sender of the provision_signal + provision_sender = Device + # Dispatch UID for connecting provision_signal to provision_receiver + provision_dispatch_uid = "some_unique_identifier_string" + + # The signal on which deletion should be triggered + destroyer_signal = post_delete + # The sender of the destroyer_signal + destroyer_sender = Device + # Dispatch UID for connecting destroyer_signal to destroyer_receiver + destroyer_dispatch_uid = "another_unique_identifier_string" + + # Attribute path to organization_id + # Example 1: If organization_id is direct attribute of provision_signal + # sender instance, then + # organization_id_path = 'organization_id' + # Example 2: If organization_id is indirect attribute of provision signal + # sender instance, then + # organization_id_path = 'some_attribute.another_intermediate.organization_id' + organization_id_path = "organization_id" + + # Similar to organization_id_path but for the required subnet attribute + subnet_path = "subnet" + + # An intermediate method through which you can specify conditions for provisions + @classmethod + def should_create_subnets_ips(cls, instance, **kwargs): + # Using "post_save" provision_signal, the rule should be only + # triggered when a new object is created. + return kwargs["created"] + + # You can define logic to trigger provisioning for existing objects + # using following classmethod. By default, BaseSubnetDivisionRuleType + # performs no operation for existing objects. + @classmethod + def provision_for_existing_objects(cls, rule_obj): + for device in Device.objects.filter( + organization=rule_obj.organization + ): + cls.provision_receiver(device, created=True) + +After creating a class for your custom rule type, you will need to set +:ref:`OPENWISP_CONTROLLER_SUBNET_DIVISION_TYPES +` setting as follows: + +.. code-block:: python + + OPENWISP_CONTROLLER_SUBNET_DIVISION_TYPES = ( + ( + "openwisp_controller.subnet_division.rule_types.vpn.VpnSubnetDivisionRuleType", + "VPN", + ), + ( + "openwisp_controller.subnet_division.rule_types.device.DeviceSubnetDivisionRuleType", + "Device", + ), + ( + "mycontroller.sample_subnet_division.rules_types.custom.CustomRuleType", + "Custom Rule", + ), + ) + +More Utilities to Extend OpenWISP Controller +-------------------------------------------- + +See :doc:`utils`. diff --git a/docs/developer/index.rst b/docs/developer/index.rst new file mode 100644 index 000000000..99915fc96 --- /dev/null +++ b/docs/developer/index.rst @@ -0,0 +1,16 @@ +Developer Docs +============== + +.. include:: ../partials/developer-docs.rst + +.. toctree:: + :maxdepth: 2 + + ./installation.rst + ./utils.rst + ./extending.rst + +Other useful resources: + + - :doc:`../user/rest-api` + - :doc:`../user/settings` diff --git a/docs/developer/installation.rst b/docs/developer/installation.rst new file mode 100644 index 000000000..45b598cfe --- /dev/null +++ b/docs/developer/installation.rst @@ -0,0 +1,203 @@ +Developer Installation Instructions +=================================== + +.. include:: ../partials/developer-docs.rst + +.. contents:: **Table of Contents**: + :depth: 2 + :local: + +Dependencies +------------ + +- Python >= 3.8 +- OpenSSL + +Installing for Development +-------------------------- + +Install the system dependencies: + +.. code-block:: shell + + sudo apt update + sudo apt install -y sqlite3 libsqlite3-dev openssl libssl-dev + sudo apt install -y gdal-bin libproj-dev libgeos-dev libspatialite-dev libsqlite3-mod-spatialite + sudo apt install -y chromium-browser + +Fork and clone the forked repository: + +.. code-block:: shell + + git clone git://github.com//openwisp-controller + +Navigate into the cloned repository: + +.. code-block:: shell + + cd openwisp-controller/ + +Launch Redis and PostgreSQL: + +.. code-block:: shell + + docker-compose up -d redis postgres + +Setup and activate a virtual-environment (we'll be using `virtualenv +`_): + +.. code-block:: shell + + python -m virtualenv env + source env/bin/activate + +Make sure that your base python packages are up to date before moving to +the next step: + +.. code-block:: shell + + pip install -U pip wheel setuptools + +Install development dependencies: + +.. code-block:: shell + + pip install -e . + pip install -r requirements-test.txt + sudo npm install -g jshint stylelint + +Install WebDriver for Chromium for your browser version from +https://chromedriver.chromium.org/home and Extract ``chromedriver`` to one +of directories from your ``$PATH`` (example: ``~/.local/bin/``). + +Create database: + +.. code-block:: shell + + cd tests/ + ./manage.py migrate + ./manage.py createsuperuser + +Launch celery worker (for background jobs): + +.. code-block:: shell + + celery -A openwisp2 worker -l info + +Launch development server: + +.. code-block:: shell + + ./manage.py runserver 0.0.0.0:8000 + +You can access the admin interface at ``http://127.0.0.1:8000/admin/``. + +Run tests with: + +.. code-block:: shell + + ./runtests.py --parallel + +Some tests, such as the Selenium UI tests, require a PostgreSQL database +to run. If you don't have a PostgreSQL database running on your system, +you can use :ref:`the Docker Compose configuration provided in this +repository `. Once set up, you can run these +specific tests as follows: + +.. code-block:: shell + + # Run database tests against PostgreSQL backend + POSTGRESQL=1 ./runtests.py --parallel + + # Run only specific selenium tests classes + cd tests/ + DJANGO_SETTINGS_MODULE=openwisp2.postgresql_settings ./manage.py test openwisp_controller.config.tests.test_selenium.TestDeviceAdmin + +Run quality assurance tests with: + +.. code-block:: shell + + ./run-qa-checks + +Alternative Sources +------------------- + +Pypi +~~~~ + +To install the latest stable version from pypi: + +.. code-block:: shell + + pip install openwisp-controller + +Github +~~~~~~ + +To install the latest development version tarball via HTTPs: + +.. code-block:: shell + + pip install https://github.com/openwisp/openwisp-controller/tarball/master + +Alternatively you can use the git protocol: + +.. code-block:: shell + + pip install -e git+git://github.com/openwisp/openwisp-controller#egg=openwisp_controller + +.. _controller_dev_docker: + +Install and Run on Docker +------------------------- + +.. warning:: + + This Docker image is for development purposes only. + + For the official OpenWISP Docker images, see: :doc:`docker-openwisp + `. + +Build from the Dockerfile: + +.. code-block:: shell + + docker-compose build + +Run the docker container: + +.. code-block:: shell + + docker-compose up + +Troubleshooting Steps for Common Installation Issues +---------------------------------------------------- + +You may encounter some issues while installing GeoDjango. + +Unable to Load SpatiaLite library Extension? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you are incurring in the following exception: + +.. code-block:: + + django.core.exceptions.ImproperlyConfigured: Unable to load the SpatiaLite library extension + +You need to specify ``SPATIALITE_LIBRARY_PATH`` in your ``settings.py`` as +explained in `django documentation regarding how to install and configure +spatialte +`_. + +Having Issues with Other Geospatial Libraries? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Please refer `troubleshooting issues related to geospatial libraries +`_. + +.. important:: + + If you want to add OpenWISP Controller to an existing Django project, + then you can refer to the `test project in the openwisp-controller + repository + `_. diff --git a/docs/developer/utils.rst b/docs/developer/utils.rst new file mode 100644 index 000000000..53b9d1f9b --- /dev/null +++ b/docs/developer/utils.rst @@ -0,0 +1,332 @@ +Code Utilities +============== + +.. include:: ../partials/developer-docs.rst + +.. contents:: **Table of Contents**: + :depth: 2 + :local: + +.. _registering_unregistering_commands: + +Registering / Unregistering Commands +------------------------------------ + +OpenWISP Controller allows to register new command options or unregister +existing command options through two utility functions: + +- ``openwisp_controller.connection.commands.register_command`` +- ``openwisp_controller.connection.commands.unregister_command`` + +You can use these functions to register new custom commands or unregister +existing commands from your code. + +.. note:: + + These functions are to be used as an alternative to the + :ref:`OPENWISP_CONTROLLER_USER_COMMANDS` setting when :doc:`extending + openwisp-controller ` or when developing custom + applications based on OpenWISP Controller. + +``register_command`` +~~~~~~~~~~~~~~~~~~~~ + +================== ============================================== +Parameter Description +``command_name`` A ``str`` defining identifier for the command. +``command_config`` A ``dict`` like the one shown in :ref:`Command + Configuration: schema `. +================== ============================================== + +**Note:** It will raise ``ImproperlyConfigured`` exception if a command is +already registered with the same name. + +``unregister_command`` +~~~~~~~~~~~~~~~~~~~~~~ + +================ ======================================= +Parameter Description +``command_name`` A ``str`` defining name of the command. +================ ======================================= + +**Note:** It will raise ``ImproperlyConfigured`` exception if such command +does not exists. + +Controller Notifications +------------------------ + +The notification types registered and used by OpenWISP Controller are +listed in the following table. + +===================== =============================================== +Notification Type Use +``config_error`` Fires when the status of a device configuration + changes to ``error``. +``device_registered`` Fires when a new device registers itself. +===================== =============================================== + +Registering Notification Types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can define your own notification types using +``register_notification_type`` function from OpenWISP Notifications. + +For more information, see the relevant :doc:`documentation section about +registering notification types in the Notifications module +`. + +Once a new notification type is registered, you can use the :doc:`"notify" +signal provided by the Notifications module +` to send notifications with +this new type. + +Signals +------- + +.. include:: /partials/signals-note.rst + +``config_modified`` +~~~~~~~~~~~~~~~~~~~ + +**Path**: ``openwisp_controller.config.signals.config_modified`` + +**Arguments**: + +- ``instance``: instance of ``Config`` which got its ``config`` modified +- ``previous_status``: indicates the status of the config object before + the signal was emitted +- ``action``: action which emitted the signal, can be any of the list + below: - ``config_changed``: the configuration of the config object was + changed - ``related_template_changed``: the configuration of a related + template was changed - ``m2m_templates_changed``: the assigned templates + were changed (either templates were added, removed or their order was + changed) + +This signal is emitted every time the configuration of a device is +modified. + +It does not matter if ``Config.status`` is already modified, this signal +will be emitted anyway because it signals that the device configuration +has changed. + +This signal is used to trigger the update of the configuration on devices, +when the push feature is enabled (requires Device credentials). + +The signal is also emitted when one of the templates used by the device is +modified or if the templates assigned to the device are changed. + +Special cases in which ``config_modified`` is not emitted ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +This signal is not emitted when the device is created for the first time. + +It is also not emitted when templates assigned to a config object are +cleared (``post_clear`` m2m signal), this is necessary because `sortedm2m +`_, the package we use to +implement ordered templates, uses the clear action to reorder templates +(m2m relationships are first cleared and then added back), therefore we +ignore ``post_clear`` to avoid emitting signals twice (one for the clear +action and one for the add action). Please keep this in mind if you plan +on using the clear method of the m2m manager. + +``config_status_changed`` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Path**: ``openwisp_controller.config.signals.config_status_changed`` + +**Arguments**: + +- ``instance``: instance of ``Config`` which got its ``status`` changed + +This signal is emitted only when the configuration status of a device has +changed. + +The signal is emitted also when the m2m template relationships of a config +object are changed, but only on ``post_add`` or ``post_remove`` actions, +``post_clear`` is ignored for the same reason explained in the previous +section. + +.. _config_backend_changed: + +``config_backend_changed`` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Path**: ``openwisp_controller.config.signals.config_backend_changed`` +**Arguments**: + +- ``instance``: instance of ``Config`` which got its ``backend`` changed +- ``old_backend``: the old backend of the config object +- ``backend``: the new backend of the config object + +It is not emitted when the device or config is created. + +``checksum_requested`` +~~~~~~~~~~~~~~~~~~~~~~ + +**Path**: ``openwisp_controller.config.signals.checksum_requested`` + +**Arguments**: + +- ``instance``: instance of ``Device`` for which its configuration + checksum has been requested +- ``request``: the HTTP request object + +This signal is emitted when a device requests a checksum via the +controller views. + +The signal is emitted just before a successful response is returned, it is +not sent if the response was not successful. + +``config_download_requested`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Path**: ``openwisp_controller.config.signals.config_download_requested`` + +**Arguments**: + +- ``instance``: instance of ``Device`` for which its configuration has + been requested for download +- ``request``: the HTTP request object + +This signal is emitted when a device requests to download its +configuration via the controller views. + +The signal is emitted just before a successful response is returned, it is +not sent if the response was not successful. + +``is_working_changed`` +~~~~~~~~~~~~~~~~~~~~~~ + +**Path**: ``openwisp_controller.connection.signals.is_working_changed`` + +**Arguments**: + +- ``instance``: instance of ``DeviceConnection`` +- ``is_working``: value of ``DeviceConnection.is_working`` +- ``old_is_working``: previous value of ``DeviceConnection.is_working``, + either ``None`` (for new connections), ``True`` or ``False`` +- ``failure_reason``: error message explaining reason for failure in + establishing connection +- ``old_failure_reason``: previous value of + ``DeviceConnection.failure_reason`` + +This signal is emitted every time ``DeviceConnection.is_working`` changes. + +It is not triggered when the device is created for the first time. + +``management_ip_changed`` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Path**: ``openwisp_controller.config.signals.management_ip_changed`` + +**Arguments**: + +- ``instance``: instance of ``Device`` +- ``management_ip``: value of ``Device.management_ip`` +- ``old_management_ip``: previous value of ``Device.management_ip`` + +This signal is emitted every time ``Device.management_ip`` changes. + +It is not triggered when the device is created for the first time. + +``device_registered`` +~~~~~~~~~~~~~~~~~~~~~ + +**Path**: ``openwisp_controller.config.signals.device_registered`` + +**Arguments**: + +- ``instance``: instance of ``Device`` which got registered. +- ``is_new``: boolean, will be ``True`` when the device is new, ``False`` + when the device already exists (eg: a device which gets a factory reset + will register again) + +This signal is emitted when a device registers automatically through the +controller HTTP API. + +``device_name_changed`` +~~~~~~~~~~~~~~~~~~~~~~~ + +**Path**: ``openwisp_controller.config.signals.device_name_changed`` + +**Arguments**: + +- ``instance``: instance of ``Device``. + +The signal is emitted when the device name changes. + +It is not emitted when the device is created. + +``device_group_changed`` +~~~~~~~~~~~~~~~~~~~~~~~~ + +**Path**: ``openwisp_controller.config.signals.device_group_changed`` + +**Arguments**: + +- ``instance``: instance of ``Device``. +- ``group_id``: primary key of ``DeviceGroup`` of ``Device`` +- ``old_group_id``: primary key of previous ``DeviceGroup`` of ``Device`` + +The signal is emitted when the device group changes. + +It is not emitted when the device is created. + +.. _group_templates_changed: + +``group_templates_changed`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Path**: ``openwisp_controller.config.signals.group_templates_changed`` + +**Arguments**: + +- ``instance``: instance of ``DeviceGroup``. +- ``templates``: list of ``Template`` objects assigned to ``DeviceGroup`` +- ``old_templates``: list of ``Template`` objects assigned earlier to + ``DeviceGroup`` + +The signal is emitted when the device group templates changes. + +It is not emitted when the device is created. + +``subnet_provisioned`` +~~~~~~~~~~~~~~~~~~~~~~ + +**Path**: +``openwisp_controller.subnet_division.signals.subnet_provisioned`` + +**Arguments**: + +- ``instance``: instance of ``VpnClient``. +- ``provisioned``: dictionary of ``Subnet`` and ``IpAddress`` provisioned, + ``None`` if nothing is provisioned + +The signal is emitted when subnets and IP addresses have been provisioned +for a ``VpnClient`` for a VPN server with a subnet with :doc:`subnet +division rule <../user/subnet-division-rules>`. + +``vpn_server_modified`` +~~~~~~~~~~~~~~~~~~~~~~~ + +**Path**: ``openwisp_controller.config.signals.vpn_server_modified`` + +**Arguments**: + +- ``instance``: instance of ``Vpn``. + +The signal is emitted when the VPN server is modified. + +``vpn_peers_changed`` +~~~~~~~~~~~~~~~~~~~~~ + +**Path**: ``openwisp_controller.config.signals.vpn_peers_changed`` + +**Arguments**: + +- ``instance``: instance of ``Vpn``. + +The signal is emitted when the peers of VPN server gets changed. + +It is only emitted for ``Vpn`` object with **WireGuard** or **VXLAN over +WireGuard** backend. diff --git a/docs/images/architecture-v2-openwisp-controller.png b/docs/images/architecture-v2-openwisp-controller.png new file mode 100644 index 000000000..c1059c64a Binary files /dev/null and b/docs/images/architecture-v2-openwisp-controller.png differ diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 000000000..40d4e9301 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,57 @@ +Controller +========== + +.. seealso:: + + **Source code**: `github.com/openwisp/openwisp-controller + `_. + +OpenWISP Controller is responsible of of managing the core resources of +the network and allows automating several aspects like adoption, +provisioning, VPN tunnel configuration, generation of X509 certificates, +subnet and IP address allocation and more. + +For a full introduction please refer to :doc:`user/intro`. + +The following diagram illustrates the role of the Controller module within +the OpenWISP architecture. + +.. figure:: images/architecture-v2-openwisp-controller.png + :target: ../_images/architecture-v2-openwisp-controller.png + :align: center + :alt: OpenWISP Architecture: Controller module + + **OpenWISP Architecture: highlighted controller module** + +.. important:: + + For an enhanced viewing experience, open the image above in a new + browser tab. + + Refer to :doc:`/general/architecture` for more information. + +.. toctree:: + :caption: Controller Module Usage Docs + :maxdepth: 1 + + user/intro.rst + user/templates.rst + user/variables.rst + user/device-groups.rst + user/push-operations.rst + user/shell-commands.rst + user/import-export.rst + user/organization-limits.rst + user/wireguard.rst + user/vxlan-wireguard.rst + user/zerotier.rst + user/openvpn.rst + user/subnet-division-rules.rst + user/rest-api.rst + user/settings.rst + +.. toctree:: + :caption: Controller Module Developer Docs + :maxdepth: 2 + + Developer Docs Index diff --git a/docs/partials/developer-docs.rst b/docs/partials/developer-docs.rst new file mode 100644 index 000000000..9cba48fbb --- /dev/null +++ b/docs/partials/developer-docs.rst @@ -0,0 +1,12 @@ +.. note:: + + This documentation page is aimed at developers who want to customize, + change or extend the code of OpenWISP Controller in order to modify + its behavior (eg: for personal or commercial purposes or to fix a bug, + implement a new feature or contribute to the project in general). + + If you aren't a developer and you are looking for information on how + to use OpenWISP, please refer to: + + - :doc:`General OpenWISP Quickstart ` + - :doc:`OpenWISP Controller User Docs ` diff --git a/docs/partials/shared-object.rst b/docs/partials/shared-object.rst new file mode 100644 index 000000000..ca13c1805 --- /dev/null +++ b/docs/partials/shared-object.rst @@ -0,0 +1,8 @@ +.. note:: + + This guide creates the VPN server and VPN client templates as **Shared + systemwide (no organization)** objects. This allows any device of any + organization to use the automation. + + If needed, you can use any organization as long as the VPN server, the + VPN client template, and devices have the same organization. diff --git a/docs/user/device-groups.rst b/docs/user/device-groups.rst new file mode 100644 index 000000000..0bca0c3cd --- /dev/null +++ b/docs/user/device-groups.rst @@ -0,0 +1,91 @@ +Device Groups +============= + +Device groups allow to group similar devices together, the groups usually +share not only a common characteristic but also some kind of +organizational need: they need to have specific configuration templates, +variables and/or associated metadata which differs from the rest of the +network. + +.. contents:: **Features provided by Device Groups:** + :depth: 2 + :local: + +.. figure:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.1/device-groups.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.1/device-groups.png + :alt: Device Group example + +.. _device_group_templates: + +Group Templates +--------------- + +Groups allow to define templates which are automatically assigned to +devices belonging to the group. When using this feature, keep in mind the +following important points: + +- Templates of any configuration backend can be selected, when a device is + assigned to a group, only the templates which matches the device + configuration backend are applied to the device. +- The system will not force group templates onto devices, this means that + users can remove the applied group templates from a specific device if + needed. +- If a device group is changed, the system will automatically remove the + group templates of the old group and apply the new templates of the new + group (this operation is implemented by leveraging the + :ref:`group_templates_changed` signal). +- If the group templates are changed, the devices which belong to the + group will be automatically updated to reflect the changes (this + operation is executed in a background task). +- In case the configuration backend of a device is changed, the system + will handle this automatically too and update the group templates + accordingly (this operation is implemented by leveraging the + :ref:`config_backend_changed` signal). +- If a device does not have a configuration defined yet, but it is + assigned to a group which has templates defined, the system will + automatically create a configuration for it using the default backend + specified in the :ref:`OPENWISP_CONTROLLER_DEFAULT_BACKEND` setting. + +**Note:** the list of templates shown in the edit group page do not +contain templates flagged as :ref:`"default" ` or +:ref:`"required" ` to avoid redundancy because those +templates are automatically assigned by the system to new devices. + +This feature works also when editing group templates or the group assigned +to a device via the :ref:`REST API `. + +.. _device_group_variables: + +Group Configuration Variables +----------------------------- + +Groups allow to define configuration variables which are automatically +added to the device's context in the **System Defined Variables**. Check +the :doc:`./variables` section to learn more about precedence of different +configuration variables. + +This feature also works when editing group templates or the group assigned +to a device via the :ref:`REST API `. + +Group Metadata +-------------- + +Groups allow to store additional information regarding a group in the +structured metadata field (which can be accessed via the REST API). + +The metadata field allows custom structure and validation to standardize +information across all groups using the +:ref:`OPENWISP_CONTROLLER_DEVICE_GROUP_SCHEMA` setting. + +Variables vs Metadata +--------------------- + +*Group configuration variables* and *Group metadata* serves different +purposes. + +The group configuration variables should be used when the device +configuration is required to be changed for particular group of devices. + +Group metadata should be used to store additional data for the device +group, this data can be fetched and/or tweaked via the REST API if needed. +Group metadata is not designed to be used for configuration purposes. diff --git a/docs/user/import-export.rst b/docs/user/import-export.rst new file mode 100644 index 000000000..c2d00f03a --- /dev/null +++ b/docs/user/import-export.rst @@ -0,0 +1,35 @@ +Import/Export Device Data +========================= + +.. figure:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.1/import-export/device-list.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.1/import-export/device-list.png + :alt: Import / Export + +The device list page offers two buttons to export and import device data +in different formats. + +Importing +--------- + +For importing devices into the system, only the required fields are +needed, for example, the following CSV file will import a device named +``TestImport`` with mac address ``00:11:22:09:44:55`` in the organization +with UUID ``3cb5e18c-0312-48ab-8dbd-038b8415bd6f``: + +.. code-block:: + + organization,name,mac_address + 3cb5e18c-0312-48ab-8dbd-038b8415bd6f,TestImport,00:11:22:09:44:55 + +.. figure:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.1/import-export/import-page.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.1/import-export/import-page.png + :alt: Import / Export + +Exporting +--------- + +The export feature respects any filters selected in the device list. + +.. figure:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.1/import-export/export-page.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.1/import-export/export-page.png + :alt: Export diff --git a/docs/user/intro.rst b/docs/user/intro.rst new file mode 100644 index 000000000..d9238f71f --- /dev/null +++ b/docs/user/intro.rst @@ -0,0 +1,117 @@ +Controller: Structure & Features +================================ + +OpenWISP Controller is a Python package which ships five Django apps. + +.. contents:: **These Django apps are listed below**: + :depth: 1 + :local: + +Config App +---------- + +The config app is the core of the controller module and implements all the +following features: + +- **Configuration management** for embedded devices supporting: + - `OpenWrt `_ + - `OpenWISP Firmware + `_ + - additional firmware can be added by :ref:`specifying custom + configuration backends ` +- **Configuration editor** based on `JSON-Schema editor + `_ +- **Advanced edit mode**: edit `NetJSON `_ + *DeviceConfiguration* objects for maximum flexibility +- :doc:`templates`: reduce repetition to the minimum, configure default + and required templates +- :doc:`variables`: reference variables in the configuration and templates +- :doc:`device-groups`: define different set of default configuration and + metadata in device groups +- :ref:`Template Tags `: define different sets of default + templates (eg: mesh, WDS, 4G) +- **HTTP resources**: allow devices to automatically check for and + download configuration updates +- **VPN management**: automatically provision VPN tunnel configurations, + including cryptographic keys and IP addresses, eg: :doc:`OpenVPN + `, :doc:`WireGuard ` +- :doc:`import-export` + +It exposes various :doc:`REST API endpoints `. + +PKI App +------- + +The PKI app is based on `django-x509 +`_, allowing you to create, +import, and view x509 CAs and certificates directly from the +administration dashboard. + +It exposes various :doc:`REST API endpoints `. + +Connection App +-------------- + +This app enables OpenWISP Controller to use different protocols to reach +network devices. Currently, the default connection protocols are SSH and +SNMP, but the protocol mechanism is extensible, allowing for +implementation of additional protocols if needed. + +It exposes various :doc:`REST API endpoints `. + +SSH +~~~ + +The SSH connector allows the controller to initialize connections to the +devices in order to perform :doc:`push operations `, +e.g.: + +- Sending configuration updates. +- :doc:`Executing shell commands `. +- Perform firmware upgrades via the additional :doc:`firmware upgrade + module `. + +The default connection protocol implemented is SSH, but other protocol +mechanism is extensible and custom protocols can be implemented as well. + +Access via SSH key is recommended, the SSH key algorithms supported are: + +- RSA +- Ed25519 + +SNMP +~~~~ + +The SNMP connector is useful to collect monitoring information and it's +used in :doc:`OpenWISP Monitoring ` for performing +checks to collect monitoring information. `Read more +`_ +on how to use it. + +Geo App +------- + +The geographic app is based on `django-loci +`_ and allows to define the +geographic coordinates of the devices, as well as their indoor coordinates +on floorplan images. + +It exposes various :doc:`REST API endpoints `. + +Subnet Division App +------------------- + +.. note:: + + This app is optional, if you don't need it you can avoid adding it to + ``settings.INSTALLED_APPS``. + +This app allows to automatically provision subnets and IP addresses which +will be available as :ref:`system defined configuration variables +` that can be used in :doc:`templates`. + +The purpose of this app is to allow users to automatically provision and +configure specific subnets and IP addresses to the devices without the +need of manual intervention. + +Refer to :doc:`subnet-division-rules` for more information. diff --git a/docs/user/openvpn.rst b/docs/user/openvpn.rst new file mode 100644 index 000000000..d644ec807 --- /dev/null +++ b/docs/user/openvpn.rst @@ -0,0 +1,223 @@ +Automating OpenVPN Tunnels +========================== + +In this guide, we will explore how to set up the automatic provisioning +and management of **OpenVPN tunnels**. + +.. contents:: **Table of Contents**: + :backlinks: none + :depth: 3 + +Setting up the OpenVPN Server +----------------------------- + +The first step is to install the OpenVPN server. In this tutorial, to +perform this step we will use Ansible. + +If you already have experience installing an OpenVPN server, feel free to +use any method you prefer. + +.. important:: + + If you have already set up your OpenVPN server or prefer to install + the OpenVPN server using a different method, you can skip forward to + :ref:`import_ca_and_server_cert`. + +For simplicity, **the OpenVPN server must be installed on the same server +where OpenWISP is also installed**. + +While it is possible to install the OpenVPN server on a different server, +it requires additional steps not covered in this tutorial. + +1. Install Ansible and Required Ansible Roles +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Install Ansible **on your local machine** (please ensure that you do not +install it on the server). + +To **install Ansible**, we suggest following the official `Ansible +installation guide +`_. + +After installing Ansible, you need to install Git (example for Linux +Debian/Ubuntu systems): + +.. code-block:: bash + + sudo apt-get install git + +After installing both Ansible and Git, install the required roles: + +.. code-block:: bash + + ansible-galaxy install git+https://github.com/Stouts/Stouts.openvpn,3.0.0 nkakouros.easyrsa + +2. Create Inventory File and Playbook YAML +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create an Ansible inventory file named ``inventory`` **on your local +machine** (not on the server) with the following contents: + +.. code-block:: + + [openvpn] + your_server_domain_or_ip + +For example, if your server IP is ``192.168.56.2``: + +.. code-block:: + + [openvpn] + 192.168.56.2 + +In the same directory where you created the ``inventory`` file, create a +file named ``playbook.yml`` with the following content: + +.. code-block:: yaml + + - hosts: openvpn + vars: + # EasyRSA + easyrsa_generate_dh: true + easyrsa_servers: + - name: server + easyrsa_clients: [] + easyrsa_pki_dir: /etc/easyrsa/pki + + # OpenVPN + openvpn_keydir: "{{ easyrsa_pki_dir }}" + openvpn_clients: [] + openvpn_use_pam: false + roles: + - role: nkakouros.easyrsa + - role: Stouts.openvpn + +.. hint:: + + You can further customize the configuration using the role variables. + Read more about other options in `EasyRSA + `_ and + `OpenVPN `_. + +3. Run the Playbook +~~~~~~~~~~~~~~~~~~~ + +Run the Ansible playbook: + +.. code-block:: bash + + ansible-playbook -i inventory playbook.yml -b -k -K --become-method=su + +.. _import_ca_and_server_cert: + +Import the CA and the Server Certificate in OpenWISP +---------------------------------------------------- + +.. important:: + + If you chose an alternative installation method for OpenVPN and you + did not create the CA and certificate yet, you can create the + certificates from scratch via the OpenWISP web interface instead of + importing them. + + Follow the instructions below and instead of selecting + :guilabel:`Import Existing` as :guilabel:`Operation Type`, select + :guilabel:`Create new`. + + You also won't need to copy any file from the server as OpenWISP + generates the x509 certificates automatically. + +To import the CA and Server Certificate into OpenWISP, you need to access +your server via ``ssh`` or any other method that suits you. + +Change your directory to ``/etc/easyrsa/pki/``. + +.. note:: + + If you incurr inthe following error: ``-bash: cd: /etc/easyrsa/pki: + Permission denied``, you may need to log in as the root user. + +Import the CA +~~~~~~~~~~~~~ + +In your OpenWISP dashboard, go to ``/admin/pki/ca/add/``. + +In :guilabel:`Operation Type`, choose :guilabel:`Import Existing`. + +Get your CA certificate from the ``ca.crt`` file and the private key from +the ``private/ca.key`` file, then enter them in the respective fields. + +Import the Server Certificate +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In your OpenWISP dashboard, go to ``/admin/pki/cert/add/``. + +In :guilabel:`Operation Type`, choose :guilabel:`Import Existing` and in +**CA**, choose the CA you just created. + +Get your server certificate from the ``issued/server.crt`` file and the +server private key from the ``private/server.key`` file, then enter them +in the respective fields. + +Create the VPN Server in OpenWISP +--------------------------------- + +In the OpenWISP dashboard, go to ``/admin/config/vpn/add/``. + +In the :guilabel:`Host` field, enter your server IP address. In the +:guilabel:`Certification Authority` and :guilabel:`X509 Certificate` +fields, select the CA and certificate you created in the previous step. + +Under :guilabel:`Configuration`, click on :guilabel:`Configuration Menu`, +then change :guilabel:`Server (Bridged)` to :guilabel:`Server (Routed)`. + +Setting up a Bridged Server is similar to setting up a Routed Server but +is not covered in this tutorial. + +Adjust the rest of the VPN configuration to match the settings in +``/etc/openvpn/server.conf``. + +.. tip:: + + You can verify if your VPN configuration matches the ``server.conf`` + file by using the :guilabel:`Preview Configuration` button at the top + right corner of the page. + +Create the VPN-Client Template in OpenWISP +------------------------------------------ + +In your OpenWISP dashboard, go to ``/admin/config/template/add/``. + +Set the :guilabel:`Type` to :guilabel:`VPN-client`. + +Once the :guilabel:`VPN` field appears, select the VPN you created in the +previous step. + +Ensure the :guilabel:`Automatic tunnel provisioning` flag remains enabled. + +If this template is for your management VPN or the default VPN option, we +recommend checking the :guilabel:`Enabled by default` flag. For more +information about this flag, refer to :ref:`default_templates`. + +Now, save the template. + +After saving the template, you can tweak the VPN Client configuration, +which is automatically generated to be compatible with the server +configuration. + +Finally you can add the new template to your devices. + +.. tip:: + + If you need to troubleshoot any issue, increase the verbosity of the + OpenVPN logging, both on the server and the clients, and check both + logs (on the server and on the client). + +.. seealso:: + + You may also want to explore other automated VPN tunnel provisioning + options: + + - :doc:`Wireguard ` + - :doc:`Wireguard over VXLAN ` + - :doc:`Zerotier ` diff --git a/docs/user/organization-limits.rst b/docs/user/organization-limits.rst new file mode 100644 index 000000000..2b3a9d98b --- /dev/null +++ b/docs/user/organization-limits.rst @@ -0,0 +1,17 @@ +Organization Limits +=================== + +You can restrict the number of devices managed by each organization. + +To set these limits: + +1. Navigate to **USERS & ORGANIZATIONS** on the left-hand navigation menu. +2. Go to **Organizations**. +3. Click on the specific organization you want to limit. +4. In the **CONTROLLER LIMIT** section, set the desired limit. + +Refer to the screenshot below for guidance: + +.. figure:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.1/organization-limits.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/1.1/organization-limits.png + :alt: Organization limits diff --git a/docs/user/push-operations.rst b/docs/user/push-operations.rst new file mode 100644 index 000000000..54368b37a --- /dev/null +++ b/docs/user/push-operations.rst @@ -0,0 +1,117 @@ +Configuring Push Operations +=========================== + +.. contents:: **Table of Contents**: + :depth: 2 + :local: + +Introduction +------------ + +.. important:: + + If you have installed OpenWISP with the `ansbile-openwisp2 role + `_ you can skip the + following steps, which are handled automatically by the ansible role + during the first installation. + +The Ansible role automatically creates a default template to update +``authorized_keys`` on networking devices using the default access +credentials. + +Follow the procedure described below to enable secure SSH access from +OpenWISP to your devices, this is required to enable push operations +(whenever the configuration is changed, OpenWISP will trigger the update +in the background) and/or :doc:`firmware upgrades (via the additional +module openwisp-firmware-upgrader) `. + +1. Generate SSH Key +------------------- + +First of all, we need to generate the SSH key which will be used by +OpenWISP to access the devices, to do so, you can use the following +command: + +.. code-block:: shell + + echo './sshkey' | ssh-keygen -t ed25519 -C "openwisp" + +This will create two files in the current directory, one called ``sshkey`` +(the private key) and one called ``sshkey.pub`` (the public key). + +Store the content of these files in a secure location. + +**Note:** Support for **ED25519** was added in OpenWrt 21.02 (requires +Dropbear > 2020.79). If you are managing devices with OpenWrt < 21, then +you will need to use RSA keys: + +.. code-block:: shell + + echo './sshkey' | ssh-keygen -t rsa -b 4096 -C "openwisp" + +2. Save SSH Private Key in "Access Credentials" +----------------------------------------------- + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/add-ssh-credentials-private-key.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/add-ssh-credentials-private-key.png + :alt: add SSH private key as access credential in OpenWISP + +From the first page of OpenWISP click on "CONFIGURATIONS" in the left +navigation menu, then "Access credentials", then click on the **"ADD +ACCESS CREDENTIALS"** button in the upper right corner (alternatively, go +to the following URL path: ``/admin/connection/credentials/add/``). + +Select SSH as ``type``, enable the **Auto add** checkbox, then at the +field "Credentials type" select "SSH (private key)", now type "root" in +the ``username`` field, while in the ``key`` field you have to paste the +contents of the private key just created. + +Now hit save. + +The credentials just created will be automatically enabled for all the +devices in the system (both existing devices and devices which will be +added in the future). + +3. Add the Public Key to Your Devices +------------------------------------- + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/add-authorized-ssh-keys-template.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/add-authorized-ssh-keys-template.png + :alt: Add authorized SSH public keys template to OpenWISP (OpenWrt) + +Now we need to instruct your devices to allow OpenWISP accessing via SSH, +in order to do this we need to add the contents of the public key file +created in step 1 (``sshkey.pub``) in the file +``/etc/dropbear/authorized_keys`` on the devices, the recommended way to +do this is to create a configuration template in OpenWISP: from the first +page of OpenWISP, click on "CONFIGURATIONS" in the left navigation menu, +then and click on the **"ADD TEMPLATE"** button in the upper right corner +(alternatively, go to the following URL: ``/admin/config/template/add/``). + +Check **enabled by default**, then scroll down the configuration section, +click on "Configuration Menu", scroll down, click on "Files" then close +the menu by clicking again on "Configuration Menu". Now type +``/etc/dropbear/authorized_keys`` in the ``path`` field of the file, then +paste the contents of ``sshkey.pub`` in ``contents``. + +Now hit save. + +**There's a catch**: you will need to assign the template to any existing +device. + +4. Test It +---------- + +Once you have performed the 3 steps above, you can test it as follows: + +1. Ensure there's at least one device turned on and connected to OpenWISP, + ensure this device has the "SSH Authorized Keys" assigned to it. +2. Ensure the celery worker of OpenWISP Controller is running (eg: ``ps + aux | grep celery``) +3. SSH into the device and wait (maximum 2 minutes) until + ``/etc/dropbear/authorized_keys`` appears as specified in the template. +4. While connected via SSH to the device run the following command in the + console: ``logread -f``, now try changing the device name in OpenWISP +5. Shortly after you change the name in OpenWISP, you should see some + output in the SSH console indicating another SSH access and the + configuration update being performed. diff --git a/docs/user/rest-api.rst b/docs/user/rest-api.rst new file mode 100644 index 000000000..b10dcdfbc --- /dev/null +++ b/docs/user/rest-api.rst @@ -0,0 +1,1152 @@ +REST API Reference +================== + +.. contents:: **Table of contents**: + :depth: 1 + :local: + +.. _controller_live_documentation: + +Live Documentation +------------------ + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/live-docu-api.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/live-docu-api.png + +A general live API documentation (following the OpenAPI specification) at +``/api/v1/docs/``. + +.. _controller_browsable_web_interface: + +Browsable Web Interface +----------------------- + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/browsable-api-ui.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/browsable-api-ui.png + +Additionally, opening any of the endpoints :ref:`listed below +` directly in the browser will show the +`browsable API interface of Django-REST-Framework +`_, which +makes it even easier to find out the details of each endpoint. + +Authentication +-------------- + +See :ref:`authenticating_rest_api`. + +When browsing the API via the :ref:`controller_live_documentation` or the +:ref:`controller_browsable_web_interface`, you can also use the session +authentication by logging in the django admin. + +Pagination +---------- + +All *list* endpoints support the ``page_size`` parameter that allows +paginating the results in conjunction with the ``page`` parameter. + +.. code-block:: text + + GET /api/v1/controller/template/?page_size=10 + GET /api/v1/controller/template/?page_size=10&page=2 + +.. _controller_rest_endpoints: + +List of Endpoints +----------------- + +Since the detailed explanation is contained in the +:ref:`controller_live_documentation` and in the +:ref:`controller_browsable_web_interface` of each point, here we'll +provide just a list of the available endpoints, for further information +please open the URL of the endpoint in your browser. + +List Devices +~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/device/ + +**Available filters** + +You can filter a list of devices based on their configuration status using +the ``status`` (e.g modified, applied, or error). + +.. code-block:: text + + GET /api/v1/controller/device/?config__status={status} + +You can filter a list of devices based on their configuration backend +using the ``backend`` (e.g netjsonconfig.OpenWrt or +netjsonconfig.OpenWisp). + +.. code-block:: text + + GET /api/v1/controller/device/?config__backend={backend} + +You can filter a list of devices based on their organization using the +``organization_id`` or ``organization_slug``. + +.. code-block:: text + + GET /api/v1/controller/device/?organization={organization_id} + +.. code-block:: text + + GET /api/v1/controller/device/?organization_slug={organization_slug} + +You can filter a list of devices based on their configuration templates +using the ``template_id``. + +.. code-block:: text + + GET /api/v1/controller/device/?config__templates={template_id} + +You can filter a list of devices based on their device group using the +``group_id``. + +.. code-block:: text + + GET /api/v1/controller/device/?group={group_id} + +You can filter a list of devices that have a device location object using +the ``with_geo`` (eg. true or false). + +.. code-block:: text + + GET /api/v1/controller/device/?with_geo={with_geo} + +You can filter a list of devices based on their creation time using the +``creation_time``. + +.. code-block:: text + + # Created exact + GET /api/v1/controller/device/?created={creation_time} + + # Created greater than or equal to + GET /api/v1/controller/device/?created__gte={creation_time} + + # Created is less than + GET /api/v1/controller/device/?created__lt={creation_time} + +Create Device +~~~~~~~~~~~~~ + +.. code-block:: text + + POST /api/v1/controller/device/ + +Get Device Detail +~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/device/{id}/ + +Download Device Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/device/{id}/configuration/ + +The above endpoint triggers the download of a ``tar.gz`` file containing +the generated configuration for that specific device. + +Change Details of Device +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PUT /api/v1/controller/device/{id}/ + +Patch Details of Device +~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PATCH /api/v1/controller/device/{id}/ + +**Note**: To assign, unassign, and change the order of the assigned +templates add, remove, and change the order of the ``{id}`` of the +templates under the ``config`` field in the JSON response respectively. +Moreover, you can also select and unselect templates in the HTML Form of +the Browsable API. + +The required template(s) from the organization(s) of the device will added +automatically to the ``config`` and cannot be removed. + +**Example usage**: For assigning template(s) add the/their {id} to the +config of a device, + +.. code-block:: shell + + curl -X PATCH \ + http://127.0.0.1:8000/api/v1/controller/device/76b7d9cc-4ffd-4a43-b1b0-8f8befd1a7c0/ \ + -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ + -H 'content-type: application/json' \ + -d '{ + "config": { + "templates": ["4791fa4c-2cef-4f42-8bb4-c86018d71bd3"] + } + }' + +**Example usage**: For removing assigned templates, simply remove +the/their {id} from the config of a device, + +.. code-block:: shell + + curl -X PATCH \ + http://127.0.0.1:8000/api/v1/controller/device/76b7d9cc-4ffd-4a43-b1b0-8f8befd1a7c0/ \ + -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ + -H 'content-type: application/json' \ + -d '{ + "config": { + "templates": [] + } + }' + +**Example usage**: For reordering the templates simply change their order +from the config of a device, + +.. code-block:: shell + + curl -X PATCH \ + http://127.0.0.1:8000/api/v1/controller/device/76b7d9cc-4ffd-4a43-b1b0-8f8befd1a7c0/ \ + -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ + -H 'cache-control: no-cache' \ + -H 'content-type: application/json' \ + -H 'postman-token: b3f6a1cc-ff13-5eba-e460-8f394e485801' \ + -d '{ + "config": { + "templates": [ + "c5bbc697-170e-44bc-8eb7-b944b55ee88f", + "4791fa4c-2cef-4f42-8bb4-c86018d71bd3" + ] + } + }' + +Delete Device +~~~~~~~~~~~~~ + +.. code-block:: text + + DELETE /api/v1/controller/device/{id}/ + +List Device Connections +~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/device/{id}/connection/ + +Create Device Connection +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + POST /api/v1/controller/device/{id}/connection/ + +Get Device Connection Detail +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/device/{id}/connection/{id}/ + +Change Device Connection Detail +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PUT /api/v1/controller/device/{id}/connection/{id}/ + +Patch Device Connection Detail +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PATCH /api/v1/controller/device/{id}/connection/{id}/ + +Delete Device Connection +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + DELETE /api/v1/controller/device/{id}/connection/{id}/ + +List Credentials +~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/connection/credential/ + +Create Credential +~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + POST /api/v1/connection/credential/ + +Get Credential Detail +~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/connection/credential/{id}/ + +Change Credential Detail +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PUT /api/v1/connection/credential/{id}/ + +Patch Credential Detail +~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PATCH /api/v1/connection/credential/{id}/ + +Delete Credential +~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + DELETE /api/v1/connection/credential/{id}/ + +List Commands of a Device +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/device/{id}/command/ + +Execute a Command a Device +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + POST /api/v1/controller/device/{id}/command/ + +Get Command Details +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/device/{device_id}/command/{command_id}/ + +List Device Groups +~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/group/ + +**Available filters** + +You can filter a list of device groups based on their organization using +the ``organization_id`` or ``organization_slug``. + +.. code-block:: text + + GET /api/v1/controller/group/?organization={organization_id} + +.. code-block:: text + + GET /api/v1/controller/group/?organization_slug={organization_slug} + +You can filter a list of device groups that have a device object using the +``empty`` (eg. true or false). + +.. code-block:: text + + GET /api/v1/controller/group/?empty={empty} + +Create Device Group +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + POST /api/v1/controller/group/ + +Get Device Group Detail +~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/group/{id}/ + +.. _change_device_group_detail: + +Change Device Group Detail +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PUT /api/v1/controller/group/{id}/ + +This endpoint allows to change the :ref:`device_group_templates` too. + +Get Device Group from Certificate Common Name +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/cert/{common_name}/group/ + +This endpoint can be used to retrieve group information and metadata by +the common name of a certificate used in a VPN client tunnel, this +endpoint is used in layer 2 tunneling solutions for firewall/captive +portals. + +It is also possible to filter device group by providing organization slug +of certificate's organization as show in the example below: + +.. code-block:: text + + GET /api/v1/controller/cert/{common_name}/group/?org={org1_slug},{org2_slug} + +Get Device Location +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/device/{id}/location/ + +.. _create_device_location: + +Create Device Location +~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PUT /api/v1/controller/device/{id}/location/ + +You can create ``DeviceLocation`` object by using primary keys of existing +``Location`` and ``FloorPlan`` objects as shown in the example below. + +.. code-block:: json + + { + "location": "f0cb5762-3711-4791-95b6-c2f6656249fa", + "floorplan": "dfeb6724-aab4-4533-aeab-f7feb6648acd", + "indoor": "-36,264" + } + +**Note:** The ``indoor`` field represents the coordinates of the point +placed on the image from the top left corner. E.g. if you placed the +pointer on the top left corner of the floorplan image, its indoor +coordinates will be ``0,0``. + +.. code-block:: text + + curl -X PUT \ + http://127.0.0.1:8000/api/v1/controller/device/8a85cc23-bad5-4c7e-b9f4-ffe298defb5c/location/ \ + -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ + -H 'content-type: application/json' \ + -d '{ + "location": "f0cb5762-3711-4791-95b6-c2f6656249fa", + "floorplan": "dfeb6724-aab4-4533-aeab-f7feb6648acd", + "indoor": "-36,264" + }' + +You can also create related ``Location`` and ``FloorPlan`` objects for the +device directly from this endpoint. + +The following example demonstrates creating related location object in a +single request. + +.. code-block:: json + + { + "location": { + "name": "Via del Corso", + "address": "Via del Corso, Roma, Italia", + "geometry": { + "type": "Point", + "coordinates": [12.512124, 41.898903] + }, + "type": "outdoor", + } + } + +.. code-block:: text + + curl -X PUT \ + http://127.0.0.1:8000/api/v1/controller/device/8a85cc23-bad5-4c7e-b9f4-ffe298defb5c/location/ \ + -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ + -H 'content-type: application/json' \ + -d '{ + "location": { + "name": "Via del Corso", + "address": "Via del Corso, Roma, Italia", + "geometry": { + "type": "Point", + "coordinates": [12.512124, 41.898903] + }, + "type": "outdoor" + } + }' + +**Note:** You can also specify the ``geometry`` in **Well-known text +(WKT)** format, like following: + +.. code-block:: json + + { + "location": { + "name": "Via del Corso", + "address": "Via del Corso, Roma, Italia", + "geometry": "POINT (12.512124 41.898903)", + "type": "outdoor", + } + } + +Similarly, you can create ``Floorplan`` object with the same request. But, +note that a ``FloorPlan`` can be added to ``DeviceLocation`` only if the +related ``Location`` object defines an indoor location. The example below +demonstrates creating both ``Location`` and ``FloorPlan`` objects. + +.. code-block:: text + + // This is not a valid JSON object. The JSON format is + // only used for showing available fields. + { + "location.name": "Via del Corso", + "location.address": "Via del Corso, Roma, Italia", + "location.geometry.type": "Point", + "location.geometry.coordinates": [12.512124, 41.898903] + "location.type": "outdoor", + "floorplan.floor": 1, + "floorplan.image": floorplan.png, + } + +.. code-block:: text + + curl -X PUT \ + http://127.0.0.1:8000/api/v1/controller/device/8a85cc23-bad5-4c7e-b9f4-ffe298defb5c/location/ \ + -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ + -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \ + -F 'location.name=Via del Corso' \ + -F 'location.address=Via del Corso, Roma, Italia' \ + -F location.geometry.type=Point \ + -F 'location.geometry.coordinates=[12.512124, 41.898903]' \ + -F location.type=indoor \ + -F floorplan.floor=1 \ + -F 'floorplan.image=@floorplan.png' + +**Note:** The request in above example uses ``multipart content-type`` for +uploading floorplan image. + +You can also use an existing ``Location`` object and create a new +floorplan for that location using this endpoint. + +.. code-block:: text + + // This is not a valid JSON object. The JSON format is + // only used for showing available fields. + { + "location": "f0cb5762-3711-4791-95b6-c2f6656249fa", + "floorplan.floor": 1, + "floorplan.image": floorplan.png + } + +.. code-block:: text + + curl -X PUT \ + http://127.0.0.1:8000/api/v1/controller/device/8a85cc23-bad5-4c7e-b9f4-ffe298defb5c/location/ \ + -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ + -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \ + -F location=f0cb5762-3711-4791-95b6-c2f6656249fa \ + -F floorplan.floor=1 \ + -F 'floorplan.image=@floorplan.png' + +Change Details of Device Location +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PUT /api/v1/controller/device/{id}/location/ + +**Note:** This endpoint can be used to update related ``Location`` and +``Floorplan`` objects. Refer to the :ref:`examples in the "Create device +location" section ` for information on payload +format. + +Delete Device Location +~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + DELETE /api/v1/controller/device/{id}/location/ + +Get Device Coordinates +~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/device/{id}/coordinates/ + +**Note:** This endpoint is intended to be used by devices. + +This endpoint skips multi-tenancy and permission checks if the device +``key`` is passed as ``query_param`` because the system assumes that the +device is updating it's position. + +.. code-block:: text + + curl -X GET \ + 'http://127.0.0.1:8000/api/v1/controller/device/8a85cc23-bad5-4c7e-b9f4-ffe298defb5c/coordinates/?key=10a0cb5a553c71099c0e4ef236435496' + +Update Device Coordinates +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PUT /api/v1/controller/device/{id}/coordinates/ + +**Note:** This endpoint is intended to be used by devices. + +This endpoint skips multi-tenancy and permission checks if the device +``key`` is passed as ``query_param`` because the system assumes that the +device is updating it's position. + +.. code-block:: json + + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [12.512124, 41.898903] + }, + } + +.. code-block:: text + + curl -X PUT \ + 'http://127.0.0.1:8000/api/v1/controller/device/8a85cc23-bad5-4c7e-b9f4-ffe298defb5c/coordinates/?key=10a0cb5a553c71099c0e4ef236435496' \ + -H 'content-type: application/json' \ + -d '{ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [12.512124, 41.898903] + }, + }' + +List Locations +~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/location/ + +**Available filters** + +You can filter using ``organization_id`` or ``organization_slug`` to get +list locations that belongs to an organization. + +.. code-block:: text + + GET /api/v1/controller/location/?organization={organization_id} + +.. code-block:: text + + GET /api/v1/controller/location/?organization_slug={organization_slug} + +Create Location +~~~~~~~~~~~~~~~ + +.. code-block:: text + + POST /api/v1/controller/location/ + +If you are creating an ``indoor`` location, you can use this endpoint to +create floorplan for the location. + +The following example demonstrates creating floorplan along with location +in a single request. + +.. code-block:: text + + { + "name": "Via del Corso", + "address": "Via del Corso, Roma, Italia", + "geometry.type": "Point", + "geometry.location": [12.512124, 41.898903], + "type": "indoor", + "is_mobile": "false", + "floorplan.floor": "1", + "floorplan.image": floorplan.png, + "organization": "1f6c5666-1011-4f1d-bce9-fc6fcb4f3a05" + } + +.. code-block:: text + + curl -X POST \ + http://127.0.0.1:8000/api/v1/controller/location/ \ + -H 'authorization: Bearer dc8d497838d4914c9db9aad9b6ec66f6c36ff46b' \ + -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \ + -F 'name=Via del Corso' \ + -F 'address=Via del Corso, Roma, Italia' \ + -F geometry.type=Point \ + -F 'geometry.coordinates=[12.512124, 41.898903]' \ + -F type=indoor \ + -F is_mobile=false \ + -F floorplan.floor=1 \ + -F 'floorplan.image=@floorplan.png' \ + -F organization=1f6c5666-1011-4f1d-bce9-fc6fcb4f3a05 + +**Note:** You can also specify the ``geometry`` in **Well-known text +(WKT)** format, like following: + +.. code-block:: text + + { + "name": "Via del Corso", + "address": "Via del Corso, Roma, Italia", + "geometry": "POINT (12.512124 41.898903)", + "type": "indoor", + "is_mobile": "false", + "floorplan.floor": "1", + "floorplan.image": floorplan.png, + "organization": "1f6c5666-1011-4f1d-bce9-fc6fcb4f3a05" + } + +Get Location Details +~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/location/{pk}/ + +Change Location Details +~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PUT /api/v1/controller/location/{pk}/ + +**Note**: Only the first floorplan data present can be edited or changed. +Setting the ``type`` of location to outdoor will remove all the floorplans +associated with it. + +Refer to the :ref:`examples in the "Create device location" section +` for information on payload format. + +Delete Location +~~~~~~~~~~~~~~~ + +.. code-block:: text + + DELETE /api/v1/controller/location/{pk}/ + +List Devices in a Location +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/location/{id}/device/ + +List Locations with Devices Deployed (in GeoJSON Format) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Note**: this endpoint will only list locations that have been assigned +to a device. + +.. code-block:: text + + GET /api/v1/controller/location/geojson/ + +**Available filters** + +You can filter using ``organization_id`` or ``organization_slug`` to get +list location of devices from that organization. + +.. code-block:: text + + GET /api/v1/controller/location/geojson/?organization_id={organization_id} + +.. code-block:: text + + GET /api/v1/controller/location/geojson/?organization_slug={organization_slug} + +List Floorplans +~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/floorplan/ + +**Available filters** + +You can filter using ``organization_id`` or ``organization_slug`` to get +list floorplans that belongs to an organization. + +.. code-block:: text + + GET /api/v1/controller/floorplan/?organization={organization_id} + +.. code-block:: text + + GET /api/v1/controller/floorplan/?organization_slug={organization_slug} + +Create Floorplan +~~~~~~~~~~~~~~~~ + +.. code-block:: text + + POST /api/v1/controller/floorplan/ + +Get Floorplan Details +~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/floorplan/{pk}/ + +Change Floorplan Details +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PUT /api/v1/controller/floorplan/{pk}/ + +Delete floorplan +~~~~~~~~~~~~~~~~ + +.. code-block:: text + + DELETE /api/v1/controller/floorplan/{pk}/ + +List Templates +~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/template/ + +**Available filters** + +You can filter a list of templates based on their organization using the +``organization_id`` or ``organization_slug``. + +.. code-block:: text + + GET /api/v1/controller/template/?organization={organization_id} + +.. code-block:: text + + GET /api/v1/controller/template/?organization_slug={organization_slug} + +You can filter a list of templates based on their backend using the +``backend`` (e.g netjsonconfig.OpenWrt or netjsonconfig.OpenWisp). + +.. code-block:: text + + GET /api/v1/controller/template/?backend={backend} + +You can filter a list of templates based on their type using the ``type`` +(eg. vpn or generic). + +.. code-block:: text + + GET /api/v1/controller/template/?type={type} + +You can filter a list of templates that are enabled by default or not +using the ``default`` (eg. true or false). + +.. code-block:: text + + GET /api/v1/controller/template/?default={default} + +You can filter a list of templates that are required or not using the +``required`` (eg. true or false). + +.. code-block:: text + + GET /api/v1/controller/template/?required={required} + +You can filter a list of templates based on their creation time using the +``creation_time``. + +.. code-block:: text + + # Created exact + + GET /api/v1/controller/template/?created={creation_time} + + # Created greater than or equal to + + GET /api/v1/controller/template/?created__gte={creation_time} + + # Created is less than + + GET /api/v1/controller/template/?created__lt={creation_time} + +Create Template +~~~~~~~~~~~~~~~ + +.. code-block:: text + + POST /api/v1/controller/template/ + +Get Template Detail +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/template/{id}/ + +Download Template Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/template/{id}/configuration/ + +The above endpoint triggers the download of a ``tar.gz`` file containing +the generated configuration for that specific template. + +Change Details of Template +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PUT /api/v1/controller/template/{id}/ + +Patch Details of Template +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PATCH /api/v1/controller/template/{id}/ + +Delete Template +~~~~~~~~~~~~~~~ + +.. code-block:: text + + DELETE /api/v1/controller/template/{id}/ + +List VPNs +~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/vpn/ + +**Available filters** + +You can filter a list of vpns based on their backend using the ``backend`` +(e.g openwisp_controller.vpn_backends.OpenVpn or +openwisp_controller.vpn_backends.Wireguard). + +.. code-block:: text + + GET /api/v1/controller/vpn/?backend={backend} + +You can filter a list of vpns based on their subnet using the +``subnet_id``. + +.. code-block:: text + + GET /api/v1/controller/vpn/?subnet={subnet_id} + +You can filter a list of vpns based on their organization using the +``organization_id`` or ``organization_slug``. + +.. code-block:: text + + GET /api/v1/controller/vpn/?organization={organization_id} + +.. code-block:: text + + GET /api/v1/controller/vpn/?organization_slug={organization_slug} + +Create VPN +~~~~~~~~~~ + +.. code-block:: text + + POST /api/v1/controller/vpn/ + +Get VPN detail +~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/vpn/{id}/ + +Download VPN Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/vpn/{id}/configuration/ + +The above endpoint triggers the download of a ``tar.gz`` file containing +the generated configuration for that specific VPN. + +Change Details of VPN +~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PUT /api/v1/controller/vpn/{id}/ + +Patch Details of VPN +~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PATCH /api/v1/controller/vpn/{id}/ + +Delete VPN +~~~~~~~~~~ + +.. code-block:: text + + DELETE /api/v1/controller/vpn/{id}/ + +List CA +~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/ca/ + +Create New CA +~~~~~~~~~~~~~ + +.. code-block:: text + + POST /api/v1/controller/ca/ + +Import Existing CA +~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + POST /api/v1/controller/ca/ + +**Note**: To import an existing CA, only ``name``, ``certificate`` and +``private_key`` fields have to be filled in the ``HTML`` form or included +in the ``JSON`` format. + +Get CA Detail +~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/ca/{id}/ + +Change Details of CA +~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PUT /api/v1/controller/ca/{id}/ + +Patch Details of CA +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PATCH /api/v1/controller/ca/{id}/ + +Download CA(crl) +~~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/ca/{id}/crl/ + +The above endpoint triggers the download of ``{id}.crl`` file containing +up to date CRL of that specific CA. + +Delete CA +~~~~~~~~~ + +.. code-block:: text + + DELETE /api/v1/controller/ca/{id}/ + +Renew CA +~~~~~~~~ + +.. code-block:: text + + POST /api/v1/controller/ca/{id}/renew/ + +List Cert +~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/cert/ + +Create New Cert +~~~~~~~~~~~~~~~ + +.. code-block:: text + + POST /api/v1/controller/cert/ + +Import Existing Cert +~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + POST /api/v1/controller/cert/ + +**Note**: To import an existing Cert, only ``name``, ``ca``, +``certificate`` and ``private_key`` fields have to be filled in the +``HTML`` form or included in the ``JSON`` format. + +Get Cert Detail +~~~~~~~~~~~~~~~ + +.. code-block:: text + + GET /api/v1/controller/cert/{id}/ + +Change Details of Cert +~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PUT /api/v1/controller/cert/{id}/ + +Patch Details of Cert +~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + PATCH /api/v1/controller/cert/{id}/ + +Delete Cert +~~~~~~~~~~~ + +.. code-block:: text + + DELETE /api/v1/controller/cert/{id}/ + +Renew Cert +~~~~~~~~~~ + +.. code-block:: text + + POST /api/v1/controller/cert/{id}/renew/ + +Revoke Cert +~~~~~~~~~~~ + +.. code-block:: text + + POST /api/v1/controller/cert/{id}/revoke/ diff --git a/docs/user/settings.rst b/docs/user/settings.rst new file mode 100644 index 000000000..47698239e --- /dev/null +++ b/docs/user/settings.rst @@ -0,0 +1,733 @@ +Settings +======== + +.. include:: /partials/settings-note.rst + +``OPENWISP_SSH_AUTH_TIMEOUT`` +----------------------------- + +============ =========== +**type**: ``int`` +**default**: ``2`` +**unit**: ``seconds`` +============ =========== + +Configure timeout to wait for an authentication response when establishing +a SSH connection. + +``OPENWISP_SSH_BANNER_TIMEOUT`` +------------------------------- + +============ =========== +**type**: ``int`` +**default**: ``60`` +**unit**: ``seconds`` +============ =========== + +Configure timeout to wait for the banner to be presented when establishing +a SSH connection. + +``OPENWISP_SSH_COMMAND_TIMEOUT`` +-------------------------------- + +============ =========== +**type**: ``int`` +**default**: ``30`` +**unit**: ``seconds`` +============ =========== + +Configure timeout on blocking read/write operations when executing a +command in a SSH connection. + +``OPENWISP_SSH_CONNECTION_TIMEOUT`` +----------------------------------- + +============ =========== +**type**: ``int`` +**default**: ``5`` +**unit**: ``seconds`` +============ =========== + +Configure timeout for the TCP connect when establishing a SSH connection. + +``OPENWISP_CONNECTORS`` +----------------------- + +============ ================================================================================= +**type**: ``tuple`` +**default**: .. code-block:: python + + ( + ("openwisp_controller.connection.connectors.ssh.Ssh", "SSH"), + ( + "openwisp_controller.connection.connectors.openwrt.snmp.OpenWRTSnmp", + "OpenWRT SNMP", + ), + ( + "openwisp_controller.connection.connectors.airos.snmp.AirOsSnmp", + "Ubiquiti AirOS SNMP", + ), + ) +============ ================================================================================= + +Available connector classes. Connectors are python classes that specify +ways in which OpenWISP can connect to devices in order to launch commands. + +``OPENWISP_UPDATE_STRATEGIES`` +------------------------------ + +============ ============================================================================ +**type**: ``tuple`` +**default**: .. code-block:: python + + ( + ( + "openwisp_controller.connection.connectors.openwrt.ssh.OpenWrt", + "OpenWRT SSH", + ), + ) +============ ============================================================================ + +Available update strategies. An update strategy is a subclass of a +connector class which defines an ``update_config`` method which is in +charge of updating the configuration of the device. + +This operation is launched in a background worker when the configuration +of a device is changed. + +It's possible to write custom update strategies and add them to this +setting to make them available in OpenWISP. + +``OPENWISP_CONFIG_UPDATE_MAPPING`` +---------------------------------- + +============ ================================================================== +**type**: ``dict`` +**default**: .. code-block:: python + + { + "netjsonconfig.OpenWrt": OPENWISP_UPDATE_STRATEGIES[0][0], + } +============ ================================================================== + +A dictionary that maps configuration backends to update strategies in +order to automatically determine the update strategy of a device +connection if the update strategy field is left blank by the user. + +.. _openwisp_controller_backends: + +``OPENWISP_CONTROLLER_BACKENDS`` +-------------------------------- + +============ =============================================== +**type**: ``tuple`` +**default**: .. code-block:: python + + ( + ("netjsonconfig.OpenWrt", "OpenWRT"), + ("netjsonconfig.OpenWisp", "OpenWISP"), + ) +============ =============================================== + +Available configuration backends. For more information, see `netjsonconfig +backends +`_. + +``OPENWISP_CONTROLLER_VPN_BACKENDS`` +------------------------------------ + +============ ==================================================================== +**type**: ``tuple`` +**default**: .. code-block:: python + + ( + ("openwisp_controller.vpn_backends.OpenVpn", "OpenVPN"), + ("openwisp_controller.vpn_backends.Wireguard", "WireGuard"), + ( + "openwisp_controller.vpn_backends.VxlanWireguard", + "VXLAN over WireGuard", + ), + ("openwisp_controller.vpn_backends.ZeroTier", "ZeroTier"), + ) +============ ==================================================================== + +Available VPN backends for VPN Server objects. For more information, see +`netjsonconfig VPN backends +`_. + +A VPN backend must follow some basic rules in order to be compatible with +*openwisp-controller*: + +- it MUST allow at minimum and at maximum one VPN instance +- the main *NetJSON* property MUST match the lowercase version of the + class name, eg: when using the ``OpenVpn`` backend, the system will look + into ``config['openvpn']`` +- it SHOULD focus on the server capabilities of the VPN software being + used + +.. _openwisp_controller_default_backend: + +``OPENWISP_CONTROLLER_DEFAULT_BACKEND`` +--------------------------------------- + +============ ====================================== +**type**: ``str`` +**default**: ``OPENWISP_CONTROLLER_BACKENDS[0][0]`` +============ ====================================== + +The preferred backend that will be used as initial value when adding new +``Config`` or ``Template`` objects in the admin. + +This setting defaults to the raw value of the first item in the +``OPENWISP_CONTROLLER_BACKENDS`` setting, which is +``netjsonconfig.OpenWrt``. + +Setting it to ``None`` will force the user to choose explicitly. + +``OPENWISP_CONTROLLER_DEFAULT_VPN_BACKEND`` +------------------------------------------- + +============ ========================================== +**type**: ``str`` +**default**: ``OPENWISP_CONTROLLER_VPN_BACKENDS[0][0]`` +============ ========================================== + +The preferred backend that will be used as initial value when adding new +``Vpn`` objects in the admin. + +This setting defaults to the raw value of the first item in the +``OPENWISP_CONTROLLER_VPN_BACKENDS`` setting, which is +``openwisp_controller.vpn_backends.OpenVpn``. + +Setting it to ``None`` will force the user to choose explicitly. + +``OPENWISP_CONTROLLER_REGISTRATION_ENABLED`` +-------------------------------------------- + +============ ======== +**type**: ``bool`` +**default**: ``True`` +============ ======== + +Whether devices can automatically register through the controller or not. + +This feature is enabled by default. + +Auto-registration must be supported on the devices in order to work, see +:doc:`openwisp-config automatic registration +` for more information. + +.. _openwisp_controller_consistent_registration: + +``OPENWISP_CONTROLLER_CONSISTENT_REGISTRATION`` +----------------------------------------------- + +============ ======== +**type**: ``bool`` +**default**: ``True`` +============ ======== + +Whether devices that are already registered are recognized when reflashed +or reset, hence keeping the existing configuration without creating a new +one. + +This feature is enabled by default. + +Auto-registration must be enabled also on the devices in order to work, +see :ref:`openwisp-config consistent key generation +` for more information. + +``OPENWISP_CONTROLLER_REGISTRATION_SELF_CREATION`` +-------------------------------------------------- + +============ ======== +**type**: ``bool`` +**default**: ``True`` +============ ======== + +Whether devices that are not already present in the system are allowed to +register or not. + +Turn this off if you still want to use auto-registration to avoid having +to manually set the device UUID and key in its configuration file but also +want to avoid indiscriminate registration of new devices without explicit +permission. + +.. _context_setting: + +``OPENWISP_CONTROLLER_CONTEXT`` +------------------------------- + +============ ======== +**type**: ``dict`` +**default**: ``{}`` +============ ======== + +Additional context that is passed to the default context of each device +object. + +``OPENWISP_CONTROLLER_CONTEXT`` can be used to define system-wide +configuration variables. + +For more information regarding how to use configuration variables in +OpenWISP, refer to :doc:`variables`. + +For technical information about how variables are handled in the lower +levels of OpenWISP, see `netjsonconfig context: configuration variables +`_. + +``OPENWISP_CONTROLLER_DEFAULT_AUTO_CERT`` +----------------------------------------- + +============ ======== +**type**: ``bool`` +**default**: ``True`` +============ ======== + +The default value of the ``auto_cert`` field for new ``Template`` objects. + +The ``auto_cert`` field is valid only for templates which have ``type`` +set to ``VPN`` and indicates whether configuration regarding the VPN +tunnel is provisioned automatically to each device using the template, eg: + +- when using OpenVPN, new `x509 `_ + certificates will be generated automatically using the same CA assigned + to the related VPN object +- when using WireGuard, new pair of private and public keys (using + `Curve25519 `_) will be generated, as well as + an IP address of the subnet assigned to the related VPN object +- when using `VXLAN `_ tunnels over + Wireguad, in addition to the configuration generated for WireGuard, a + new VID will be generated automatically for each device if the + configuration option "auto VNI" is turned on in the VPN object + +All these auto generated configuration options will be available as +template variables. + +The objects that are automatically created will also be removed when they +are not needed anymore (eg: when the VPN template is removed from a +configuration object). + +``OPENWISP_CONTROLLER_CERT_PATH`` +--------------------------------- + +============ ============= +**type**: ``str`` +**default**: ``/etc/x509`` +============ ============= + +The filesystem path where x509 certificate will be installed when +downloaded on routers when ``auto_cert`` is being used (enabled by +default). + +``OPENWISP_CONTROLLER_COMMON_NAME_FORMAT`` +------------------------------------------ + +============ ======================== +**type**: ``str`` +**default**: ``{mac_address}-{name}`` +============ ======================== + +Defines the format of the ``common_name`` attribute of VPN client +certificates that are automatically created when using VPN templates which +have ``auto_cert`` set to ``True``. A unique slug generated using +`shortuuid `_ is appended to +the common name to introduce uniqueness. Therefore, resulting common names +will have ``{OPENWISP_CONTROLLER_COMMON_NAME_FORMAT}-{unique-slug}`` +format. + +**Note:** If the ``name`` and ``mac address`` of the device are equal, the +``name`` of the device will be omitted from the common name to avoid +redundancy. + +``OPENWISP_CONTROLLER_MANAGEMENT_IP_DEVICE_LIST`` +------------------------------------------------- + +============ ======== +**type**: ``bool`` +**default**: ``True`` +============ ======== + +In the device list page, the column ``IP`` will show the ``management_ip`` +if available, defaulting to ``last_ip`` otherwise. + +If this setting is set to ``False`` the ``management_ip`` won't be shown +in the device list page even if present, it will be shown only in the +device detail page. + +You may set this to ``False`` if for some reason the majority of your user +doesn't care about the management ip address. + +``OPENWISP_CONTROLLER_CONFIG_BACKEND_FIELD_SHOWN`` +-------------------------------------------------- + +============ ======== +**type**: ``bool`` +**default**: ``True`` +============ ======== + +This setting toggles the ``backend`` fields in add/edit pages in Device +and Template configuration, as well as the ``backend`` field/filter in +Device list and Template list. + +If this setting is set to ``False`` these items will be removed from the +UI. + +Note: This setting affects only the configuration backend and NOT the VPN +backend. + +``OPENWISP_CONTROLLER_DEVICE_NAME_UNIQUE`` +------------------------------------------ + +============ ======== +**type**: ``bool`` +**default**: ``True`` +============ ======== + +This setting conditionally enforces unique Device names in an +Organization. The query to enforce this is case-insensitive. + +Note: For this constraint to be optional, it is enforced on an application +level and not on database. + +.. _openwisp_controller_hardware_id_enabled: + +``OPENWISP_CONTROLLER_HARDWARE_ID_ENABLED`` +------------------------------------------- + +============ ========= +**type**: ``bool`` +**default**: ``False`` +============ ========= + +The field ``hardware_id`` can be used to store a unique hardware id, for +example a serial number. + +If this setting is set to ``True`` then this field will be shown first in +the device list page and in the add/edit device page. + +This feature is disabled by default. + +``OPENWISP_CONTROLLER_HARDWARE_ID_OPTIONS`` +------------------------------------------- + +============ ============================================================= +**type**: ``dict`` +**default**: .. code-block:: python + + { + "blank": not OPENWISP_CONTROLLER_HARDWARE_ID_ENABLED, + "null": True, + "max_length": 32, + "unique": True, + "verbose_name": _("Serial number"), + "help_text": _("Serial number of this device"), + } +============ ============================================================= + +Options for the model field ``hardware_id``. + +- ``blank``: wether the field is allowed to be blank +- ``null``: wether an empty value will be stored as ``NULL`` in the + database +- ``max_length``: maximum length of the field +- ``unique``: wether the value of the field must be unique +- ``verbose_name``: text for the human readable label of the field +- ``help_text``: help text to be displayed with the field + +``OPENWISP_CONTROLLER_HARDWARE_ID_AS_NAME`` +------------------------------------------- + +============ ======== +**type**: ``bool`` +**default**: ``True`` +============ ======== + +When the hardware ID feature is enabled, devices will be referenced with +their hardware ID instead of their name. + +If you still want to reference devices by their name, set this to +``False``. + +``OPENWISP_CONTROLLER_DEVICE_VERBOSE_NAME`` +------------------------------------------- + +============ ========================= +**type**: ``tuple`` +**default**: ``('Device', 'Devices')`` +============ ========================= + +Defines the ``verbose_name`` attribute of the ``Device`` model, which is +displayed in the admin site. The first and second element of the tuple +represent the singular and plural forms. + +For example, if we want to change the verbose name to "Hotspot", we could +write: + +.. code-block:: python + + OPENWISP_CONTROLLER_DEVICE_VERBOSE_NAME = ("Hotspot", "Hotspots") + +``OPENWISP_CONTROLLER_HIDE_AUTOMATICALLY_GENERATED_SUBNETS_AND_IPS`` +-------------------------------------------------------------------- + +============ ========= +**type**: ``bool`` +**default**: ``False`` +============ ========= + +Setting this to ``True`` will hide subnets and IP addresses generated by +:doc:`subnet division rules ` from being displayed +in the list of Subnets and IP addresses in the admin dashboard. + +.. _openwisp_controller_subnet_division_types: + +``OPENWISP_CONTROLLER_SUBNET_DIVISION_TYPES`` +--------------------------------------------- + +============ ================================================================================================= +**type**: ``tuple`` +**default**: .. code-block:: python + + ( + ( + "openwisp_controller.subnet_division.rule_types.device.DeviceSubnetDivisionRuleType", + "Device", + ), + ( + "openwisp_controller.subnet_division.rule_types.vpn.VpnSubnetDivisionRuleType", + "VPN", + ), + ) +============ ================================================================================================= + +Available types for :doc:`Subject Division Rule ` +objects. + +For more information on how to write your own types, please refer to: +:ref:`custom_subnet_division_rule_types`. + +``OPENWISP_CONTROLLER_API`` +--------------------------- + +============ ======== +**type**: ``bool`` +**default**: ``True`` +============ ======== + +Indicates whether the API for Openwisp Controller is enabled or not. To +disable the API by default add ``OPENWISP_CONTROLLER_API = False`` in your +project ``settings.py`` file. + +``OPENWISP_CONTROLLER_API_HOST`` +-------------------------------- + +============ ======== +**type**: ``str`` +**default**: ``None`` +============ ======== + +Allows to specify backend URL for API requests, if the frontend is hosted +separately. + +.. _openwisp_controller_user_commands: + +``OPENWISP_CONTROLLER_USER_COMMANDS`` +------------------------------------- + +============ ======== +**type**: ``list`` +**default**: ``[]`` +============ ======== + +Allows to specify a ``list`` of tuples for adding commands as described in +the section: :ref:`defining_new_menu_options`. + +``OPENWISP_CONTROLLER_ORGANIZATION_ENABLED_COMMANDS`` +----------------------------------------------------- + +============ ============================================= +**type**: ``dict`` +**default**: .. code-block:: python + + { + # By default all commands are allowed + "__all__": "*", + } +============ ============================================= + +This setting controls the command types that are enabled on the system By +default, all command types are enabled to all the organizations, but it's +possible to disable a specific command for a specific organization as +shown in the following example: + +.. code-block:: python + + OPENWISP_CONTROLLER_ORGANIZATION_ENABLED_COMMANDS = { + "__all__": "*", + # Organization UUID: # Tuple of enabled commands + "7448a190-6e65-42bf-b8ea-bb6603e593a5": ("reboot", "change_password"), + } + +In the example above, the organization with UUID +``7448a190-6e65-42bf-b8ea-bb6603e593a5`` will allow to send only commands +of type ``reboot`` and ``change_password``, while all the other +organizations will have all command types enabled. + +.. _openwisp_controller_device_group_schema: + +``OPENWISP_CONTROLLER_DEVICE_GROUP_SCHEMA`` +------------------------------------------- + +============ ======================================== +**type**: ``dict`` +**default**: ``{'type': 'object', 'properties': {}}`` +============ ======================================== + +Allows specifying JSONSchema used for validating the meta-data of +:doc:`device-groups`. + +``OPENWISP_CONTROLLER_SHARED_MANAGEMENT_IP_ADDRESS_SPACE`` +---------------------------------------------------------- + +============ ======== +**type**: ``bool`` +**default**: ``True`` +============ ======== + +By default, the system assumes that the address space of the management +tunnel is shared among all the organizations using the system, that is, +the system assumes there's only one management VPN, tunnel or other +networking technology to reach the devices it controls. + +When set to ``True``, any device belonging to any organization will never +have the same ``management_ip`` as another device, the latest device +declaring the management IP will take the IP and any other device who +declared the same IP in the past will have the field reset to empty state +to avoid potential conflicts. + +Set this to ``False`` if every organization has its dedicated management +tunnel with a dedicated address space that is reachable by the OpenWISP +server. + +.. _openwisp_controller_management_ip_only: + +``OPENWISP_CONTROLLER_MANAGEMENT_IP_ONLY`` +------------------------------------------ + +============ ======== +**type**: ``bool`` +**default**: ``True`` +============ ======== + +By default, only the management IP will be used to establish connection +with the devices. + +If the devices are connecting to your OpenWISP instance using a shared +layer2 network, hence the OpenWSP server can reach the devices using the +``last_ip`` field, you can set this to ``False``. + +``OPENWISP_CONTROLLER_DSA_OS_MAPPING`` +-------------------------------------- + +============ ======== +**type**: ``dict`` +**default**: ``{}`` +============ ======== + +OpenWISP Controller can figure out whether it should use the new OpenWrt +syntax for DSA interfaces (Distributed Switch Architecture) introduced in +OpenWrt 21 by reading the ``os`` field of the ``Device`` object. However, +if the firmware you are using has a custom firmware identifier, the system +will not be able to figure out whether it should use the new syntax and it +will default to :ref:`OPENWISP_CONTROLLER_DSA_DEFAULT_FALLBACK +`. + +If you want to make sure the system can parse your custom firmware +identifier properly, you can follow the example below. + +For the sake of the example, the OS identifier ``MyCustomFirmware 2.0`` +corresponds to ``OpenWrt 19.07``, while ``MyCustomFirmware 2.1`` +corresponds to ``OpenWrt 21.02``. Configuring this setting as indicated +below will allow OpenWISP to supply the right syntax automatically. + +Example: + +.. code-block:: python + + OPENWISP_CONTROLLER_DSA_OS_MAPPING = { + "netjsonconfig.OpenWrt": { + # OpenWrt >=21.02 configuration syntax will be used for + # these OS identifiers. + ">=21.02": [r"MyCustomFirmware 2.1(.*)"], + # OpenWrt <=21.02 configuration syntax will be used for + # these OS identifiers. + "<21.02": [r"MyCustomFirmware 2.0(.*)"], + } + } + +**Note**: The OS identifier should be a regular expression as shown in +above example. + +.. _openwisp_controller_dsa_default_fallback: + +``OPENWISP_CONTROLLER_DSA_DEFAULT_FALLBACK`` +-------------------------------------------- + +============ ======== +**type**: ``bool`` +**default**: ``True`` +============ ======== + +The value of this setting decides whether to use DSA syntax (OpenWrt >=21 +configuration syntax) if openwisp-controller fails to make that decision +automatically. + +``OPENWISP_CONTROLLER_GROUP_PIE_CHART`` +--------------------------------------- + +============ ========= +**type**: ``bool`` +**default**: ``False`` +============ ========= + +Allows to show a pie chart like the one in the screenshot. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/devicegroups-piechart.png + :alt: device groups piechart + +Active groups are groups which have at least one device in them, while +emtpy groups do not have any device assigned. + +``OPENWISP_CONTROLLER_API_TASK_RETRY_OPTIONS`` +---------------------------------------------- + +============ ========= +**type**: ``dict`` +**default**: see below +============ ========= + +.. code-block:: python + + # default value of OPENWISP_CONTROLLER_API_TASK_RETRY_OPTIONS: + + dict( + max_retries=5, # total number of retries + retry_backoff=True, # exponential backoff + retry_backoff_max=600, # 10 minutes + retry_jitter=True, # randomness into exponential backoff + ) + +This setting is utilized by background API tasks executed by +:doc:`ZeroTier VPN servers and ZeroTier VPN clients ` to handle +recoverable HTTP status codes such as 429, 500, 502, 503, and 504. + +These tasks are retried with a maximum of 5 attempts with an exponential +backoff and jitter, with a maximum delay of 10 minutes. + +This feature ensures that ZeroTier Service API calls are resilient to +recoverable failures, improving the reliability of the system. + +For more information on these settings, you can refer to the `the celery +documentation regarding automatic retries for known errors. +`_ diff --git a/docs/user/shell-commands.rst b/docs/user/shell-commands.rst new file mode 100644 index 000000000..6d5165939 --- /dev/null +++ b/docs/user/shell-commands.rst @@ -0,0 +1,177 @@ +Sending Commands to Devices +=========================== + +.. contents:: **Table of Contents**: + :depth: 3 + :local: + +Default Commands +---------------- + +By default, there are three options in the **Send Command** dropdown: + +1. Reboot +2. Change Password +3. Custom Command + +While the first two options are self-explanatory, the **custom command** +option allows you to execute any command on the device as shown in the +example below. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/commands_demo.gif + :target: https://github.com/openwisp/openwisp-controller/tree/docs/docs/commands_demo.gif + :alt: Executing commands on device example + +**Note**: in order for this feature to work, a device needs to have at +least one **Access Credential** (see :doc:`How to configure push updates +`). + +The **Send Command** button will be hidden until the device has at least +one **Access Credential**. + +If you need to allow your users to quickly send specific commands that are +used often in your network regardless of your users' knowledge of Linux +shell commands, you can add new commands by following instructions in the +:ref:`defining_new_menu_options` section below. + +.. note:: + + If you're an advanced user and want to learn how to register commands + programmatically, refer to the + :ref:`registering_unregistering_commands` section. + +.. _defining_new_menu_options: + +Defining New Options in the Commands Menu +----------------------------------------- + +Let's explore to define new custom commands to help users perform +additional management actions without having to be Linux/Unix experts. + +We can do so by using the ``OPENWISP_CONTROLLER_USER_COMMANDS`` django +setting. + +The following example defines a simple command that can ``ping`` an input +``destination_address`` through a network interface, ``interface_name``. + +.. code-block:: python + + # In yourproject/settings.py + + + def ping_command_callable(destination_address, interface_name=None): + command = f"ping -c 4 {destination_address}" + if interface_name: + command += f" -I {interface_name}" + return command + + + OPENWISP_CONTROLLER_USER_COMMANDS = [ + ( + "ping", + { + "label": "Ping", + "schema": { + "title": "Ping", + "type": "object", + "required": ["destination_address"], + "properties": { + "destination_address": { + "type": "string", + "title": "Destination Address", + }, + "interface_name": { + "type": "string", + "title": "Interface Name", + }, + }, + "message": "Destination Address cannot be empty", + "additionalProperties": False, + }, + "callable": ping_command_callable, + }, + ) + ] + +The above code will add the *Ping* command in the user interface as show +in the GIF below: + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/ping_command_example.gif + :target: https://github.com/openwisp/openwisp-controller/tree/docs/docs/ping_command_example.gif + :alt: Adding a *ping* command + +The ``OPENWISP_CONTROLLER_USER_COMMANDS`` setting takes a ``list`` of +``tuple`` each containing two elements. The first element of the tuple +should contain an identifier for the command and the second element should +contain a ``dict`` defining configuration of the command. + +.. _comand_configuration: + +Command Configuration +~~~~~~~~~~~~~~~~~~~~~ + +The ``dict`` defining configuration for command should contain following +keys: + +1. ``label`` +++++++++++++ + +A ``str`` defining label for the command used internally by Django. + +2. ``schema`` ++++++++++++++ + +A ``dict`` defining `JSONSchema `_ for inputs of +command. You can specify the inputs for your command, add rules for +performing validation and make inputs required or optional. + +Here is a detailed explanation of the schema used in above example: + +.. code-block:: python + + { + # Name of the command displayed in *Send Command* widget + "title": "Ping", + # Use type *object* if the command needs to accept inputs + # Use type *null* if the command does not accepts any input + "type": "object", + # Specify list of inputs that are required + "required": ["destination_address"], + # Define the inputs for the commands along with their properties + "properties": { + "destination_address": { + # type of the input value + "type": "string", + # label used for displaying this input field + "title": "Destination Address", + }, + "interface_name": { + "type": "string", + "title": "Interface Name", + }, + }, + # Error message to be shown if validation fails + "message": "Destination Address cannot be empty", + # Whether specifying addtionaly inputs is allowed from the input form + "additionalProperties": False, + } + +This example uses only handful of properties available in JSONSchema. You +can experiment with other properties of JSONSchema for schema of your +command. + +3. ``callable`` ++++++++++++++++ + +A ``callable`` or ``str`` defining dotted path to a callable. It should +return the command (``str``) to be executed on the device. Inputs of the +command are passed as arguments to this callable. + +The example above includes a callable(``ping_command_callable``) for +``ping`` command. + +How to register or unregister commands +-------------------------------------- + +Refer to :ref:`registering_unregistering_commands` in the developer +documentation. diff --git a/docs/user/subnet-division-rules.rst b/docs/user/subnet-division-rules.rst new file mode 100644 index 000000000..d9e8deef6 --- /dev/null +++ b/docs/user/subnet-division-rules.rst @@ -0,0 +1,188 @@ +Automating Subnet and IP Address Provisioning +============================================= + +This guide helps you automate provisioning subnets and IP addresses for +your network devices. + +.. contents:: **Table of Contents**: + :depth: 2 + :local: + +.. _step1_rule: + +1. Create a Subnet and a Subnet Division Rule +--------------------------------------------- + +Create a master subnet. + +This is the parent subnet from which automatically generated subnets will +be provisioned. + +.. note:: + + Choose a subnet size appropriate for the needs of your network. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/subnet.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/subnet.png + :alt: Creating a master subnet example + +On the same page, add a **subnet division rule**. This rule defines the +criteria for automatically provisioning subnets under the master subnet. + +The type of subnet division rule determines when subnets and IP addresses +are assigned to devices. + +The currently supported rule types are described below. + +.. note:: + + For information on how to write your own subnet division rule types, + please refer to: :ref:`custom_subnet_division_rule_types`. + +.. _device_rule: + +Device Subnet Division Rule +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This rule triggers when a device configuration (``config.Config`` model) +is created for the organization specified in the rule. + +.. note:: + + If a device object is created without any related configuration + object, it will not trigger this rule. + +Creating a new *"Device"* rule will also automatically provision subnets +and IP addresses for existing devices within the organization. + +.. _vpn_rule: + +VPN Subnet Division Rule +~~~~~~~~~~~~~~~~~~~~~~~~ + +This rule triggers when a template flagged as *VPN-client* is assigned to +a device configuration, but only if the VPN server associated with the +VPN-client template uses the same subnet to which the subnet division rule +is assignated to. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/subnet-division-rule.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/subnet-division-rule.png + :alt: Creating a subnet division rule example + +In this example, **VPN subnet division rule** is used. + +2. Create a VPN Server +---------------------- + +Now create a VPN Server and choose the previously created **master +subnet** as the subnet for this VPN Server. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/vpn-server.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/vpn-server.png + :alt: Creating a VPN Server example + +3. Create a VPN Client Template +------------------------------- + +Create a template, setting the **Type** field to **VPN Client** and +**VPN** field to use the previously created VPN Server. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/vpn-client.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/vpn-client.png + :alt: Creating a VPN Client template example + +**Note**: You can also check the **Enable by default** field if you want +to automatically apply this template to devices that will register in +future. + +4. Apply VPN Client Template to Devices +--------------------------------------- + +With everything in place, you can now apply the VPN Client Template to +devices. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/apply-template-to-device.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/apply-template-to-device.png + :alt: Adding template to device example + +After saving the device, you should see all provisioned Subnets and IPs +for this device under :ref:`System Defined Variables +`. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/system-defined-variables.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/subnet-division-rule/system-defined-variables.png + :alt: Provisioned Subnets and IPs available as System Defined Variables example + +You can now use these :doc:`variables` in the configuration of devices of +your network. + +Important notes for using Subnet Division +----------------------------------------- + +- In the example provided, the Subnet, VPN Server, and VPN Client Template + were associated with the **default** organization. You can also utilize + **Systemwide Shared** Subnet, VPN Server, or VPN Client Template; + however, remember that the Subnet Division Rule will always be linked to + an organization. It will only be triggered when a VPN Client Template is + applied to a Device with the same organization as the Subnet Division + Rule. +- Configuration variables can be used for provisioned subnets and IPs in + the Template. Each variable will resolve differently for different + devices. For example, ``OW_subnet1_ip1`` will resolve to ``10.0.0.1`` + for one device and ``10.0.0.55`` for another. Every device receives its + own set of subnets and IPs. Ensure to provide default fallback values in + the *default values* template field (mainly used for validation). +- The Subnet Division Rule automatically creates a reserved subnet, which + can be utilized to provision any IP addresses that need to be created + manually. The remaining address space of the master subnet must not be + interfered with, or the automation implemented in this module will not + function. +- The example provided used the :ref:`VPN subnet division rule + `. Similarly, the :ref:`device subnet division rule + ` can be employed, requiring only :ref:`the creation of a + subnet and a subnet division rule `. + +Limitations of Subnet Division Rules +------------------------------------ + +In the current implementation, it is not possible to change *Size*, +*Number of Subnets* and *Number of IPs* fields of an existing subnet +division rule due to following reasons: + +Size +~~~~ + +Allowing to change size of provisioned subnets of an existing subnet +division rule will require rebuilding of Subnets and IP addresses which +has possibility of breaking existing configurations. + +Number of Subnets +~~~~~~~~~~~~~~~~~ + +Allowing to decrease number of subnets of an existing subnet division rule +can create patches of unused subnets dispersed everywhere in the master +subnet. Allowing to increase number of subnets will break the continuous +allocation of subnets for every device. It can also break configuration of +devices. + +Number of IPs +~~~~~~~~~~~~~ + +**Decreasing the number of IPs** in an existing subnet division rule is +not allowed as it can lead to deletion of IP addresses, potentially +breaking configurations of existing devices. + +**Increasing the number of IPs is allowed**. + +If you need to modify any of these fields (**Size**, **Number of +Subnets**, or **Number of IPs**), we recommend to proceed as follows: + +1. Delete the existing rule. +2. Create a new rule. + +The automation will provision new subnets and addresses according to the +new parameters to any existing devices that are eligible to the subnet +division rule. + +However, be aware that existing devices **will probably be reassigned +different subnets and IP addresses** than the ones used previously. diff --git a/docs/user/templates.rst b/docs/user/templates.rst new file mode 100644 index 000000000..375e708ff --- /dev/null +++ b/docs/user/templates.rst @@ -0,0 +1,212 @@ +Configuration Templates +======================= + +.. contents:: **Table of Contents**: + :depth: 3 + :local: + +What is a Template? +------------------- + +Templates are designed to store configuration that can be reused by some +or all the devices in the system. + +Updating the configuration stored in a template allows to update the +configuration of all the devices that have that template assigned. + +This means that configuration can be defined only once for multiple +devices, and if the need to update a specific piece of configuration +arises, it can be easily achieved by updating the template. + +Template Ordering and Override +------------------------------ + +A device can use multiple templates, **the order in which templates are +assigned to each device matters**: templates assigned last can override +templates assigned earlier, the order can be changed by drag and dropping +the template in the device configuration page as in the animated +screenshot below. + +.. image:: /images/templates/template-ordering.gif + :target: ../../_images/template-ordering.gif + :align: center + :alt: Template ordering: drag and drop to change order + +The device configuration can also override what is defined in templates. + +Overriding means redefining a specific configuration section in a way that +overwrites the template. + +**Overriding involves some form of duplication of information, which is +not great, it should be used as a last resort**. The recommended way to +define parts of the configuration that are specific for each device is to +use :doc:`Configuration variables <./variables>`. + +.. _controller_shared_vs_org: + +Shared Templates vs Organization Specific +----------------------------------------- + +Templates can be *organization specific* or *shared* (no organization +specified). + +.. image:: /images/templates/organization-specific-vs-shared.gif + :target: ../../_images/organization-specific-vs-shared.gif + :align: center + :alt: Shared templates vs organization specific + +**Organization specific templates** will be available and usable only +within the same organization which they are assigned to. + +If no organization is specified when creating a template, a shared +template will be created, **shared templates are available to any +organization in the system**. + +Here are a few typical use cases of shared templates: + +- Management VPN +- Authorized SSH keys belonging to network administrators +- Crontab with generic periodic management operations + +.. _default_templates: + +Default Templates +----------------- + +.. image:: /images/templates/default-templates.gif + :target: ../../_images/default-templates.gif + :align: center + :alt: Templates enabled by default + +When templates are flagged as **"Enabled by default"**, they will be +automatically assigned to new devices. + +This is a very powerful feature: **once default templates are correctly +configured to implement the use case you need, you will only have to +register a device into OpenWISP for it to auto-configure itself**. + +Moreover, you can change the default templates any time you need, which is +the reason this feature has replaced the practice of storing default +configuration in firmware images (which would need to be recompiled and +redistributed): with default templates, the default firmware image only +needs to contain the bare minimum configuration to connect to OpenWISP, +once the device connects to OpenWISP it will download and apply the +default templates without the need of manual intervention from the network +operators. + +An organization specific template flagged as default will be automatically +assigned to any new device which will be created in the same organization. + +A shared default template instead will be automatically assigned to all +the new devices which will be created in the system, regardless of +organization. + +.. _required_templates: + +Required Templates +------------------ + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/required-templates.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/required-templates.png + :alt: Required template example + +Required templates are similar to :ref:`default_templates` but cannot be +unassigned from a device configuration, they can only be overridden. + +They will be always assigned earlier than default templates, so they can +be overridden if needed. + +In the example above, the "SSID" template is flagged as "(required)" and +its checkbox is always checked and disabled. + +Device Group Templates +---------------------- + +:ref:`default_templates` are an incredibly useful tool, but they're +limited: **only one set of default templates can be created** per each +organization. + +With :ref:`device_group_templates` it is possible to specify a set of +default templates for each device group. + +.. _templates_tags: + +Template Tags +------------- + +.. image:: /images/templates/template-tags.gif + :target: ../../_images/template-tags.gif + :align: center + :alt: Template tags + +In some cases, you may have multiple set of default settings to use, let's +explain this with a practical example: you may have 2 different device +types in your network: + +- Mesh routers: they connect to one another, forming a wireless mesh + network +- Dumb access points: they connect to the mesh routers on the LAN port and + offer internet access which is routed via the mesh network by the + routers + +In this example case, the default configuration to use in each device type +can greatly differ. + +In such a setup, default templates would only contain configuration which +is common to both device types, while configuration which is specific for +each type would be stored in specific templates which are then tagged with +specific keywords: + +- ``mesh``: tag to use for mesh configuration +- ``dumb-ap``: tag to use for dumb AP configuration + +The :ref:`openwisp-config ` +configuration of each device type must specify the correct tag before each +device registers in the system. + +Here's the sample ``/etc/config/openwisp`` configuration for mesh devices: + +.. code-block:: + + config controller 'http' + option url 'https://openwisp2.mynetwork.com' + option shared_secret 'mySharedSecret123' + option tags 'mesh' + +Once devices with the above configuration will register into the system, +any template tagged as ``mesh`` (as in the screenshot below) will be +assigned to them. + +.. image:: /images/templates/mesh-template-tag.png + :target: ../../_images/mesh-template-tag.png + :align: center + :alt: Template tags: mesh example + +The sample ``/etc/config/openwisp`` configuration for dumb access points +is the following: + +.. code-block:: + + config controller 'http' + option url 'https://openwisp2.mynetwork.com' + option shared_secret 'mySharedSecret123' + option tags 'dumb-ap' + +Once devices with the above configuration will register into the system, +any template tagged as ``dumb-ap`` (as in the screenshot below) will be +assigned to them. + +.. image:: /images/templates/dumb-ap-template-tag.png + :target: ../../_images/dumb-ap-template-tag.png + :align: center + :alt: Template tags: dumb AP example + +Implementation Details of Templates +----------------------------------- + +Templates are implemented under the hood by the OpenWISP configuration +engine: netjsonconfig. + +For more advanced technical information about templates, consult the +netjsonconfig documentation: `Basic Concepts, Template +`_. diff --git a/docs/user/variables.rst b/docs/user/variables.rst new file mode 100644 index 000000000..ce2043551 --- /dev/null +++ b/docs/user/variables.rst @@ -0,0 +1,171 @@ +Configuration Variables +======================= + +Sometimes the configuration is not exactly equal on all the devices, some +parameters are unique to each device or need to be changed by the user. + +In these cases it is possible to use configuration variables in +conjunction with templates, this feature is also known as *configuration +context*, think of it like a dictionary which is passed to the function +which renders the configuration, so that it can fill variables according +to the passed context. + +Different Types of Variables +---------------------------- + +The different ways in which variables are defined are described below in +the order (high to low) of their precedence. + +.. contents:: + :depth: 2 + :local: + +.. _user_defined_variables: + +1. User Defined Device Variables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the device configuration section you can find a section named +"Configuration variables" where it is possible to define the configuration +variables and their values, as shown in the example below: + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/device-context.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/device-context.png + :alt: context + +2. Predefined Device Variables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each device gets the following attributes passed as configuration +variables: + +- ``id`` +- ``key`` +- ``name`` +- ``mac_address`` + +3. Group Variables +~~~~~~~~~~~~~~~~~~ + +Variables can also be defined in :doc:`./device-groups`. + +Refer to :ref:`device_group_variables` for more information. + +4. Organization Variables +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Variables can also be defined at the organization level. + +You can set the *organization variables* from the organization change page +``/admin/openwisp_users/organization//change/``, under +the **Configuration Management Settings**. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/organization-variables.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/organization-variables.png + :alt: organization variables + +5. Global Variables +~~~~~~~~~~~~~~~~~~~ + +Variables can also be defined globally using the :ref:`context_setting` +setting, see also :doc:`How to Edit Django Settings +<../../../../user/django-settings>`. + +6. Template Default Values +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It's possible to specify the default values of variables defined in a +template. + +This allows to achieve 2 goals: + +1. pass schema validation without errors (otherwise it would not be + possible to save the template in the first place) +2. provide good default values that are valid in most cases but can be + overridden in the device if needed + +These default values will be overridden by the :ref:`User defined device +variables `. + +The default values of variables can be manipulated from the section +"configuration variables" in the edit template page: + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/template-default-values.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/template-default-values.png + :alt: default values + +.. _system_defined_variables: + +7. System Defined Variables +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Predefined device variables, global variables and other variables that are +automatically managed by the system (eg: when using templates of type +VPN-client) are displayed in the admin UI as *System Defined Variables* in +read-only mode. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/system-defined-variables.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/system-defined-variables.png + :alt: system defined variables + +Example Usage of Variables +-------------------------- + +Here's a typical use case, the WiFi SSID and WiFi password. You don't want +to define this for every device, but you may want to allow operators to +easily change the SSID or WiFi password for a specific device without +having to re-define the whole wifi interface to avoid duplicating +information. + +This would be the template: + +.. code-block:: json + + { + "interfaces": [ + { + "type": "wireless", + "name": "wlan0", + "wireless": { + "mode": "access_point", + "radio": "radio0", + "ssid": "{{wlan0_ssid}}", + "encryption": { + "protocol": "wpa2_personal", + "key": "{{wlan0_password}}", + "cipher": "auto" + } + } + } + ] + } + +These would be the default values in the template: + +.. code-block:: json + + { + "wlan0_ssid": "SnakeOil PublicWiFi", + "wlan0_password": "Snakeoil_pwd!321654" + } + +The default values can then be overridden at :ref:`device level +` if needed, eg: + +.. code-block:: json + + { + "wlan0_ssid": "Room 23 ACME Hotel", + "wlan0_password": "room_23pwd!321654" + } + +Implementation Details of Variables +----------------------------------- + +Variables are implemented under the hood by the OpenWISP configuration +engine: netjsonconfig. + +For more advanced technical information about variables, consult the +netjsonconfig documentation: `Basic Concepts, Context (configuration +variables) +`_. diff --git a/docs/user/vxlan-wireguard.rst b/docs/user/vxlan-wireguard.rst new file mode 100644 index 000000000..719813f38 --- /dev/null +++ b/docs/user/vxlan-wireguard.rst @@ -0,0 +1,125 @@ +Automating VXLAN over WireGuard Tunnels +======================================= + +By following these steps, you will be able to setup layer 2 VXLAN tunnels +encapsulated in `WireGuard `_ tunnels which +work on layer 3. + +.. include:: ../partials/shared-object.rst + +.. contents:: **Table of Contents**: + :depth: 2 + :local: + +1. Create VPN Server Configuration for VXLAN Over WireGuard +----------------------------------------------------------- + +1. Visit ``/admin/config/vpn/add/`` to add a new VPN server. +2. We will set **Name** of this VPN server ``Wireguard VXLAN`` and + **Host** as ``wireguard-vxlan-server.mydomain.com`` (update this to + point to your WireGuard VXLAN VPN server). +3. Select ``VXLAN over WireGuard`` from the dropdown as **VPN Backend**. +4. When using VXLAN over WireGuard, OpenWISP takes care of managing IP + addresses (assigning an IP address to each VPN peer). You can create a + new subnet or select an existing one from the dropdown menu. You can + also assign an **Internal IP** to the WireGuard Server or leave it + empty for OpenWISP to configure. This IP address will be used by the + WireGuard interface on server. +5. We have set the **Webhook Endpoint** as + ``https://wireguard-vxlan-server.mydomain.com:8081/trigger-update`` for + this example. You will need to update this according to you VPN + upgrader endpoint. Set **Webhook AuthToken** to any strong passphrase, + this will be used to ensure that configuration upgrades are requested + from trusted sources. + + **Note**: If you are following this tutorial for also setting up + WireGuard VPN server, just substitute ``wireguard-server.mydomain.com`` + with hostname of your VPN server and follow the steps in next section. + +6. Under the configuration section, set the name of WireGuard tunnel 1 + interface. We have used ``wg0`` in this example. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-vxlan-tutorial/vpn-server-1.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-vxlan-tutorial/vpn-server-1.png + :alt: WireGuard VPN VXLAN server configuration example 1 + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-vxlan-tutorial/vpn-server-2.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-vxlan-tutorial/vpn-server-2.png + :alt: WireGuard VPN VXLAN server configuration example 2 + +7. After clicking on **Save and continue editing**, you will see that + OpenWISP has automatically created public and private key for WireGuard + server in **System Defined Variables** along with internal IP address + information. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-vxlan-tutorial/vpn-server-3.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-vxlan-tutorial/vpn-server-3.png + :alt: WireGuard VXLAN VPN server configuration example 3 + +2. Deploy Wireguard VXLAN VPN Server +------------------------------------ + +If you haven't already set up WireGuard on your VPN server, this is a good +time to do so. We recommend using the `ansible-wireguard-openwisp +`_ role for +installing WireGuard since it also installs scripts that allow OpenWISP to +manage the WireGuard VPN server along with VXLAN tunnels. + +Pay attention to the VPN server attributes used in your playbook. It +should be the same as the VPN server configuration in OpenWISP. + +3. Create VPN Client Template for WireGuard VXLAN VPN Server +------------------------------------------------------------ + +1. Visit ``/admin/config/template/add/`` to add a new template. +2. Set ``Wireguard VXLAN Client`` as **Name** (you can set whatever you + want) and select ``VPN-client`` as **type** from the dropdown list. +3. The **Backend** field refers to the backend of the device this template + can be applied to. For this example, we will leave it as ``OpenWrt``. +4. Select the correct VPN server from the dropdown for the **VPN** field. + Here it is ``Wireguard VXLAN``. +5. Ensure that **Automatic tunnel provisioning** is checked. This will + make OpenWISP automatically generate public and private keys and + provision IP addresses for each WireGuard VPN client along with the + VXLAN Network Identifier (VNI). +6. After clicking on **Save and continue editing** button, you will see + details of the *Wireguard VXLAN* VPN server in **System Defined + Variables**. The template configuration will be automatically generated + which you can tweak accordingly. We will use the automatically + generated VPN client configuration for this example. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-vxlan-tutorial/template.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-vxlan-tutorial/template.png + :alt: WireGuard VXLAN VPN client template example + +4. Apply Wireguard VXLAN VPN Template to Devices +------------------------------------------------ + +.. note:: + + This step assumes that you already have a device registered on + OpenWISP. Register or create a device before proceeding. + +1. Open the **Configuration** tab of the concerned device. +2. Select the *WireGuard VXLAN Client* template. +3. Upon clicking on **Save and continue editing** button, you will see + some entries in **System Defined Variables**. It will contain internal + IP address, private and public key for the WireGuard client on the + device and details of WireGuard VPN server along with VXLAN Network + Identifier(VNI) of this device. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-vxlan-tutorial/device-configuration.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-vxlan-tutorial/device-configuration.png + :alt: WireGuard VXLAN VPN device configuration example + +**Voila!** You have successfully configured OpenWISP to manage VXLAN over +WireGuard tunnels for your devices. + +.. seealso:: + + You may also want to explore other automated VPN tunnel provisioning + options: + + - :doc:`Wireguard ` + - :doc:`Zerotier ` + - :doc:`OpenVPN ` diff --git a/docs/user/wireguard.rst b/docs/user/wireguard.rst new file mode 100644 index 000000000..5e99668d7 --- /dev/null +++ b/docs/user/wireguard.rst @@ -0,0 +1,123 @@ +Automating WireGuard Tunnels +============================ + +This guide will help you to set up the automatic provisioning of +`WireGuard `_ tunnels for your devices. + +.. include:: ../partials/shared-object.rst + +.. contents:: **Table of Contents**: + :depth: 2 + :local: + +1. Create VPN Server Configuration for WireGuard +------------------------------------------------ + +1. Visit ``/admin/config/vpn/add/`` to add a new VPN server. +2. Set the **Name** of this VPN server as ``WireGuard`` and the **Host** + as ``wireguard-server.mydomain.com`` (update this to point to your + WireGuard VPN server). +3. Select ``WireGuard`` from the dropdown as the **VPN Backend**. +4. When using WireGuard, OpenWISP takes care of managing IP addresses, + assigning an IP address to each VPN peer. Create a new subnet or select + an existing one from the dropdown menu. You can also assign an + **Internal IP** to the WireGuard Server or leave it empty for OpenWISP + to configure. This IP address will be used by the WireGuard interface + on the server. +5. Set the **Webhook Endpoint** as + ``https://wireguard-server.mydomain.com:8081/trigger-update`` for this + example. Update this according to your VPN upgrader endpoint. Set + **Webhook AuthToken** to any strong passphrase; this will be used to + ensure that configuration upgrades are requested from trusted sources. + + **Note**: If you are setting up a WireGuard VPN server, substitute + ``wireguard-server.mydomain.com`` with the hostname of your VPN server + and follow the steps in the next section. + +6. Under the configuration section, set the name of the WireGuard tunnel 1 + interface. In this example, we have used ``wg0``. + +.. figure:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-tutorial/vpn-server-1.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-tutorial/vpn-server-1.png + :alt: WireGuard VPN server configuration example 1 + +.. figure:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-tutorial/vpn-server-2.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-tutorial/vpn-server-2.png + :alt: WireGuard VPN server configuration example 2 + +7. After clicking on **Save and continue editing**, you will see that + OpenWISP has automatically created public and private keys for the + WireGuard server in **System Defined Variables**, along with internal + IP address information. + +.. figure:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-tutorial/vpn-server-3.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-tutorial/vpn-server-3.png + :alt: WireGuard VPN server configuration example 3 + +2. Deploy WireGuard VPN Server +------------------------------ + +If you haven't already set up WireGuard on your VPN server, this would be +a good time to do so. + +We recommend using the `ansible-wireguard-openwisp +`_ role for +installing WireGuard, as it also installs scripts that allow OpenWISP to +manage the WireGuard VPN server. + +Ensure that the VPN server attributes used in your playbook match the VPN +server configuration in OpenWISP. + +3. Create VPN Client Template for WireGuard VPN Server +------------------------------------------------------ + +1. Visit ``/admin/config/template/add/`` to add a new template. +2. Set ``WireGuard Client`` as **Name** (you can set whatever you want) + and select ``VPN-client`` as **type** from the dropdown list. +3. The **Backend** field refers to the backend of the device this template + can be applied to. For this example, we will leave it to ``OpenWrt``. +4. Select the correct VPN server from the dropdown for the **VPN** field. + Here it is ``WireGuard``. +5. Ensure that **Automatic tunnel provisioning** is checked. This will + make OpenWISP to automatically generate public and private keys and + provision IP address for each WireGuard VPN client. +6. After clicking on **Save and continue editing** button, you will see + details of *WireGuard* VPN server in **System Defined Variables**. The + template configuration will be automatically generated which you can + tweak accordingly. We will use the automatically generated VPN client + configuration for this example. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-tutorial/template.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-tutorial/template.png + :alt: WireGuard VPN client template example + +4. Apply WireGuard VPN Template to Devices +------------------------------------------ + +.. note:: + + This step assumes that you already have a device registered on + OpenWISP. Register or create a device before proceeding. + +1. Open the **Configuration** tab of the concerned device. +2. Select the *WireGuard Client* template. +3. Upon clicking on **Save and continue editing** button, you will see + some entries in **System Defined Variables**. It will contain internal + IP address, private and public key for the WireGuard client on the + device along with details of WireGuard VPN server. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-tutorial/device-configuration.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/wireguard-tutorial/device-configuration.png + :alt: WireGuard VPN device configuration example + +**Voila!** You have successfully configured OpenWISP to manage WireGuard +tunnels for your devices. + +.. seealso:: + + You may also want to explore other automated VPN tunnel provisioning + options: + + - :doc:`Wireguard over VXLAN ` + - :doc:`Zerotier ` + - :doc:`OpenVPN ` diff --git a/docs/user/zerotier.rst b/docs/user/zerotier.rst new file mode 100644 index 000000000..54249f0af --- /dev/null +++ b/docs/user/zerotier.rst @@ -0,0 +1,156 @@ +Automating ZeroTier Tunnels +=========================== + +.. raw:: html + +

+ +

+ +Follow the procedure described below to set up `ZeroTier +`_ tunnels on your devices. + +.. include:: ../partials/shared-object.rst + +.. contents:: **Table of Contents**: + :depth: 2 + :local: + +1. Configure Self-Hosted ZeroTier Network Controller +---------------------------------------------------- + +If you haven't already set up a self-hosted ZeroTier network controller on +your server, now is a good time to do so. You can start by simply +installing ZeroTier on your server from the `official website +`_. + +2. Create VPN Server Configuration for ZeroTier +----------------------------------------------- + +1. Visit ``/admin/config/vpn/add/`` to add a new VPN server. +2. We will set **Name** of this VPN server ``ZeroTier`` and **Host** as + ``my-zerotier-server.mydomain.com:9993`` (update this to point to your + ZeroTier VPN server). +3. Select ``ZeroTier`` from the dropdown as **VPN Backend**. +4. When using ZeroTier, OpenWISP takes care of managing IP addresses + (assigning an IP address to each VPN client (ZeroTier network + members)). You can create a new subnet or select an existing one from + the dropdown menu. You can also assign an **Internal IP** to the + ZeroTier controller or leave it empty for OpenWISP to configure. This + IP address will be used to assign it to the ZeroTier controller running + on the server. +5. Set the **Webhook AuthToken**, this will be the ZeroTier authorization + token which you can obtain by running the following command on the + ZeroTier controller: + + .. code-block:: shell + + sudo cat /var/lib/zerotier-one/authtoken.secret + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/vpn-server-1.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/vpn-server-1.png + :alt: ZeroTier VPN server configuration example 1 + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/vpn-server-2.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/vpn-server-2.png + :alt: ZeroTier VPN server configuration example 2 + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/vpn-server-3.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/vpn-server-3.png + :alt: ZeroTier VPN server configuration example 3 + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/vpn-server-4.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/vpn-server-4.png + :alt: ZeroTier VPN server configuration example 4 + +6. After clicking on **Save and continue editing**, OpenWISP automatically + detects the node address of the ZeroTier controller and creates a + ZeroTier network. The **network_id** of this network can be viewed in + the **System Defined Variables** section, where it also provides + internal IP address information. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/vpn-server-5.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/vpn-server-5.png + :alt: ZeroTier VPN server configuration example 5 + +3. Create VPN Client Template for ZeroTier VPN Server +----------------------------------------------------- + +1. Visit ``/admin/config/template/add/`` to add a new template. +2. Set ``ZeroTier Client`` as **Name** (you can set whatever you want) and + select ``VPN-client`` as **type** from the dropdown list. +3. The **Backend** field refers to the backend of the device this template + can be applied to. For this example, we will leave it to ``OpenWrt``. +4. Select the correct VPN server from the dropdown for the **VPN** field. + Here it is ``ZeroTier``. +5. Ensure that the **Automatic tunnel provisioning** option is checked. + This will enable OpenWISP to automatically provision an IP address and + ZeroTier identity secrets (used for assigning member IDs) for each + ZeroTier VPN client. +6. After clicking on **Save and continue editing** button, you will see + details of *ZeroTier* VPN server in **System Defined Variables**. The + template configuration will be automatically generated which you can + tweak accordingly. We will use the automatically generated VPN client + configuration for this example. + +.. note:: + + OpenWISP uses `zerotier-idtool + `_ + to manage **ZeroTier identity secrets**. Please make sure that you + have `ZeroTier package installed + `_ on the server. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/template.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/template.png + :alt: ZeroTier VPN client template example + +4. Apply ZeroTier VPN Template to Devices +----------------------------------------- + +.. note:: + + This step assumes that you already have a device registered on + OpenWISP. Register or create a device before proceeding. + +1. Open the **Configuration** tab of the concerned device. +2. Select the *ZeroTier Client* template. +3. Upon clicking the **Save and Continue Editing** button, you will see + entries in the **System Defined Variables** section. These entries will + include **zerotier_member_id**, **identity_secret**, and the internal + **IP address** of the ZeroTier client (network member) on the device, + along with details of the VPN server. + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/device-configuration-1.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/device-configuration-1.png + :alt: ZeroTier VPN device configuration example 1 + +4. Once the configuration is successfully applied to the device, you will + notice a new ZeroTier interface that is up and running. This interface + will have the name ``owzt89f498`` (where ``owzt`` is followed by the + last six hexadecimal characters of the ZeroTier **network ID**). + +.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/device-configuration-2.png + :target: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/device-configuration-2.png + :alt: ZeroTier VPN device configuration example 2 + +**Congratulations!** You've successfully configured OpenWISP to manage +ZeroTier tunnels on your devices. + +.. seealso:: + + You may also want to explore other automated VPN tunnel provisioning + options: + + - :doc:`Wireguard ` + - :doc:`Wireguard over VXLAN ` + - :doc:`OpenVPN ` diff --git a/pyproject.toml b/pyproject.toml index eb261c779..c1f5a1ad4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ omit = [ ] [tool.docstrfmt] -extend_exclude = ["**/*.py", "README.rst"] +extend_exclude = ["**/*.py"] [tool.isort] known_third_party = ["django", "django_x509"]