Package: openwisp-controller
OpenWISP Controller is a configuration manager that allows to automate several
networking tasks like adoption, provisioning, management VPN configuration,
X509 certificates automatic generation, revocation of x509 certificates and
a lot more features.OpenWISP is not only an application designed for end users, but can also be
used as a framework on which custom network automation solutions can be 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 checksopenwisp-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)openwisp-network-topology:
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 networkopenwisp-ipam:
allows to manage the assignment of IP addresses used in the networkopenwisp-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 theOpenWISP Architecture Overview.Want to help OpenWISP?Find out how to help us grow here.Table of Contents:Project Structure & main featuresConfig AppPKI AppConnection AppGeo AppSubnet Division AppInstallation instructionsDeploy it in productionDependenciesInstall stable version from pypiInstall development versionInstalling for developmentInstall and run on dockerTroubleshooting steps for common installation issuesUnable to load SpatiaLite library extension?Having Issues with other geospatial libraries?Setup (integrate in an existing django project)Usage referenceDefault TemplatesRequired TemplatesHow to use configuration variablesPredefined device variablesUser defined device variablesTemplate default valuesGlobal variablesSystem defined variablesExample usage of variablesHow to configure push updates1. Generate SSH key2. Save SSH private key in OpenWISP (access credentials)3. Add the public key to your devices4. Test itSending Commands to DevicesHow to define new options in the commands menuRegistering / Unregistering CommandsDevice GroupsHow to setup WireGuard tunnels1. Create VPN server configuration for WireGuard2. Deploy Wireguard VPN Server3. Create VPN client template for WireGuard VPN Server4. Apply Wireguard VPN template to devicesHow to setup VXLAN over WireGuard tunnels1. Create VPN server configuration for VXLAN over WireGuard2. Deploy Wireguard VXLAN VPN Server3. Create VPN client template for WireGuard VXLAN VPN Server4. Apply Wireguard VXLAN VPN template to devicesHow to configure automatic provisioning of subnets and IPs1. Create a Subnet and a Subnet Division Rule2. Create a VPN Server3. Create a VPN Client Template4. Apply VPN Client Template to DevicesImportant notes for using Subnet DivisionLimitations of Subnet DivisionDefault Alerts / NotificationsREST API ReferenceLive documentationBrowsable web interfaceAuthenticationPaginationList of endpointsList devicesCreate deviceGet device detailDownload device configurationChange details of devicePatch details of deviceDelete deviceList device connectionsCreate device connectionGet device connection detailChange device connection detailPatch device connection detailDelete device connectionList credentialsCreate credentialGet credential detailChange credential detailPatch credential detailDelete credentialList commands of a deviceExecute a command a deviceGet command detailsList device groupsCreate device groupGet device group detailGet device group from certificate common nameGet device locationCreate device locationChange details of device locationDelete device locationGet device coordinatesUpdate device coordinatesList locationsCreate locationGet location detailsChange location detailsDelete locationList devices in a locationList locations with devices deployed (in GeoJSON format)List floorplansCreate floorplanGet floorplan detailsChange floorplan detailsDelete floorplanList templatesCreate templateGet template detailDownload template configurationChange details of templatePatch details of templateDelete templateList VPNsCreate VPNGet VPN detailDownload VPN configurationChange details of VPNPatch details of VPNDelete VPNList CACreate new CAImport existing CAGet CA DetailChange details of CAPatch details of CADownload CA(crl)Delete CARenew CAList CertCreate new CertImport existing CertGet Cert DetailChange details of CertPatch details of CertDelete CertRenew CertRevoke CertSettingsOPENWISP_SSH_AUTH_TIMEOUTOPENWISP_SSH_BANNER_TIMEOUTOPENWISP_SSH_COMMAND_TIMEOUTOPENWISP_SSH_CONNECTION_TIMEOUTOPENWISP_CONNECTORSOPENWISP_UPDATE_STRATEGIESOPENWISP_CONFIG_UPDATE_MAPPINGOPENWISP_CONTROLLER_BACKENDSOPENWISP_CONTROLLER_VPN_BACKENDSOPENWISP_CONTROLLER_DEFAULT_BACKENDOPENWISP_CONTROLLER_DEFAULT_VPN_BACKENDOPENWISP_CONTROLLER_REGISTRATION_ENABLEDOPENWISP_CONTROLLER_CONSISTENT_REGISTRATIONOPENWISP_CONTROLLER_REGISTRATION_SELF_CREATIONOPENWISP_CONTROLLER_CONTEXTOPENWISP_CONTROLLER_DEFAULT_AUTO_CERTOPENWISP_CONTROLLER_CERT_PATHOPENWISP_CONTROLLER_COMMON_NAME_FORMATOPENWISP_CONTROLLER_MANAGEMENT_IP_DEVICE_LISTOPENWISP_CONTROLLER_CONFIG_BACKEND_FIELD_SHOWNOPENWISP_CONTROLLER_DEVICE_NAME_UNIQUEOPENWISP_CONTROLLER_HARDWARE_ID_ENABLEDOPENWISP_CONTROLLER_HARDWARE_ID_OPTIONSOPENWISP_CONTROLLER_HARDWARE_ID_AS_NAMEOPENWISP_CONTROLLER_DEVICE_VERBOSE_NAMEOPENWISP_CONTROLLER_HIDE_AUTOMATICALLY_GENERATED_SUBNETS_AND_IPSOPENWISP_CONTROLLER_SUBNET_DIVISION_TYPESOPENWISP_CONTROLLER_APIOPENWISP_CONTROLLER_API_HOSTOPENWISP_CONTROLLER_USER_COMMANDSOPENWISP_CONTROLLER_DEVICE_GROUP_SCHEMAOPENWISP_CONTROLLER_SHARED_MANAGEMENT_IP_ADDRESS_SPACEOPENWISP_CONTROLLER_DSA_OS_MAPPINGOPENWISP_CONTROLLER_DSA_DEFAULT_FALLBACKSignalsconfig_modifiedSpecial cases in whichconfig_modifiedis not emittedconfig_status_changedchecksum_requestedconfig_download_requestedis_working_changedmanagement_ip_changeddevice_registereddevice_name_changeddevice_group_changedsubnet_provisionedvpn_server_modifiedvpn_peers_changedExtending openwisp-controller1. Initialize your project & custom apps2. Installopenwisp-controller3. Add your apps in INSTALLED_APPS4. AddEXTENDED_APPS5. Addopenwisp_utils.staticfiles.DependencyFinder6. Addopenwisp_utils.loaders.DependencyLoader5. Initial Database setup6. Django Channels Setup7. Other Settings6. Inherit the AppConfig class7. Create your custom models8. Add swapper configurations9. Create database migrations10. Create the admin1. Monkey patching2. Inheriting admin classes11. Create root URL configuration12. Import the automated testsOther base classes that can be inherited and extended1. Extending the Controller API Views2. Extending the Geo API ViewsCustom Subnet Division Rule TypesRegistering new notification typesContributingChangelogLicenseSupportProject Structure & main featuresOpenWISP Controller is a python package consisting of four django apps:Config Appconfiguration managementfor embedded devices supporting different firmwares:OpenWRTOpenWISP Firmwaresupport for additional firmware can be added byspecifying custom backendsconfiguration editorbased onJSON-Schema editoradvanced edit mode: editNetJSONDeviceConfigurationobjects for maximum flexibilityconfiguration templates:
reduce repetition to the minimum, configure default and required templatesconfiguration variables:
reference ansible-like variables in the configuration and templatestemplate tags: tag templates to automate different types of auto-configurations (eg: mesh, WDS, 4G)device groups: adddevices to dedicated groupsto
ease management of group of devicessimple HTTP resources: allow devices to automatically download configuration updatesVPN management:automatically provision VPN tunnels,
including cryptographic keys, IP addressesREST APIPKI AppThe PKI app is based ondjango-x509,
it allows to create, import and view x509 CAs and certificates directly from
the administration dashboard, it also adds different endpoints to theREST API.Connection AppThis app enables the controller to instantiate connections to the devices
in order performpush operations:Sending configuration updates.Executing shell commands.Performfirmware upgrades via the additional firmware upgrade module.REST APIThe 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:RSAEd25519Geo AppThe geographic app is based ondjango-lociand allows to define the geographic coordinates of the devices,
as well as their indoor coordinates on floorplan images.It also adds different endpoints to theREST API.Subnet Division AppThis app allows to automatically provision subnets and IP addresses which will be
available assystem defined configuration variablesthat 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 documentationto learn about features provided by this app.This app is optional, if you don’t need it you can avoid adding it tosettings.INSTALLED_APPS.Installation instructionsDeploy it in productionSee:ansible-openwisp2docker-openwispDependenciesPython >= 3.7OpenSSLInstall stable version from pypiInstall from pypi:pipinstallopenwisp-controllerInstall development versionInstall tarball:pipinstallhttps://github.com/openwisp/openwisp-controller/tarball/masterAlternatively you can install via pip using git:pipinstall-egit+git://github.com/openwisp/openwisp-controller#egg=openwisp_controllerIf you want to contribute, follow the instructions inInstalling for development.Installing for developmentInstall the system dependencies:sudoaptupdatesudoaptinstall-ysqlite3libsqlite3-devopenssllibssl-devsudoaptinstall-ygdal-binlibproj-devlibgeos-devlibspatialite-devlibsqlite3-mod-spatialitesudoaptinstall-ychromiumFork and clone the forked repository:gitclonegit://github.com/<your_fork>/openwisp-controllerNavigate into the cloned repository:cdopenwisp-controller/Launch Redis:docker-composeup-dredisSetup and activate a virtual-environment. (we’ll be usingvirtualenv)python-mvirtualenvenvsourceenv/bin/activateMake sure that you are using pip version 20.2.4 before moving to the next step:pipinstall-UpipwheelsetuptoolsInstall development dependencies:pipinstall-e.pipinstall-rrequirements-test.txtnpminstall-gjshintstylelintInstall WebDriver for Chromium for your browser version fromhttps://chromedriver.chromium.org/homeand Extractchromedriverto one of directories from your$PATH(example:~/.local/bin/).Create database:cdtests/./manage.pymigrate./manage.pycreatesuperuserLaunch celery worker (for background jobs):celery-Aopenwisp2worker-linfoLaunch development server:./manage.pyrunserver0.0.0.0:8000You can access the admin interface athttp://127.0.0.1:8000/admin/.Run tests with:./runtests.py--parallelRun quality assurance tests with:./run-qa-checksInstall and run on dockerNOTE: This Docker image is for development purposes only.
For the official OpenWISP Docker images, see:docker-openwisp.Build from the Dockerfile:docker-composebuildRun the docker container:docker-composeupTroubleshooting steps for common installation issuesYou 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 extensionthen, You need to specifySPATIALITE_LIBRARY_PATHin yoursettings.pyas explained indjango documentation regarding how to install and configure spatialte.Having Issues with other geospatial libraries?Please refertroubleshooting issues related to geospatial libraries.Setup (integrate in an existing django project)Addopenwisp_controllerapplications toINSTALLED_APPS: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','django.contrib.admin','django.forms',...]EXTENDED_APPS=('django_x509','django_loci')Note: The order of applications inINSTALLED_APPSshould be maintained,
otherwise it might not work properly.Other settings needed insettings.py: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 mainurls.py:urlpatterns=[# ... other urls in your project ...# openwisp-controller urlsurl(r'^admin/',admin.site.urls),url(r'',include('openwisp_controller.urls')),]Configure caching (you may use a different cache storage if you want):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):# here we show how to configure celery with redis but you can# use other brokers if you want, consult the celery docsCELERY_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-redisThen run:./manage.pymigrateUsage referenceDefault TemplatesWhen 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:AccessInterfacesSSH KeysThey 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 InterfacesSSH KeysAccessRequired TemplatesRequired templates are similar toDefault templatesbut 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 variablesSometimes 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 asconfiguration 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.Predefined device variablesEach device gets the following attributes passed as configuration variables:idkeynamemac_addressUser defined device variablesIn 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:Template default valuesIt’s possible to specify the default values of variables defined in a template.This allows to achieve 2 goals:pass schema validation without errors (otherwise it would not be possible
to save the template in the first place)provide good default values that are valid in most cases but can be
overridden in the device if neededThese default values will be overridden by theUser defined device variables.The default values of variables can be manipulated from the section
“configuration variables” in the edit template page:Global variablesVariables can also be defined globally using theOPENWISP_CONTROLLER_CONTEXTsetting.System defined variablesPredefined 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 asSystem Defined Variablesin read-only mode.Example usage of variablesHere’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:{"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:{"wlan0_ssid":"SnakeOil PublicWiFi","wlan0_password":"Snakeoil_pwd!321654"}The default values can then be overridden atdevice levelif needed, eg:{"wlan0_ssid":"Room 23 ACME Hotel","wlan0_password":"room_23pwd!321654"}How to configure push updatesFollow 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/orfirmware upgrades (via the additional module openwisp-firmware-upgrader).Note: If you have installed OpenWISP withopenwisp2 Ansbile rolethen you can skip the following steps. The Ansible role automatically creates a
default template to updateauthorized_keyson networking devices using the
default access credentials.1. Generate SSH keyFirst 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:echo'./sshkey'|ssh-keygen-trsa-b4096-C"openwisp"This will create two files in the current directory, one calledsshkey(the private key) and one calledsshkey.pub(the public key).Store the content of these files in a secure location.2. Save SSH private key in OpenWISP (access credentials)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 astype, enable theAuto addcheckbox, then at the field
“Credentials type” select “SSH (private key)”, now type “root” in theusernamefield,
while in thekeyfield 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 devicesNow 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_keyson 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/).Checkenabled 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_keysin thepathfield of the file, then paste the contents ofsshkey.pubincontents.Now hit save.There’s a catch: you will need to assign the template to any existing device.4. Test itOnce you have performed the 3 steps above, you can test it as follows:Ensure there’s at least one device turned on and connected to OpenWISP, ensure
this device has the “SSH Authorized Keys” assigned to it.Ensure the celery worker of OpenWISP Controller is running (eg:ps aux | grep celery)SSH into the device and wait (maximum 2 minutes) until/etc/dropbear/authorized_keysappears as specified in the template.While connected via SSH to the device run the following command in the console:logread-f, now try changing the device name in OpenWISPShortly 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 DevicesBy default, there are three options in theSend Commanddropdown:RebootChange PasswordCustom CommandWhile the first two options are self-explanatory, thecustom commandoption
allows you to execute any command on the device as shown in the example below.Note: in order for this feature to work, a device needs to have at least
oneAccess Credential(seeHow to configure push updates).TheSend Commandbutton will be hidden until the device
has at least oneAccess 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”section below.If you are an advanced user and want to register commands programatically, then refer to“Register / Unregistering commands”section.How to define new options in the commands menuLet’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 theOPENWISP_CONTROLLER_USER_COMMANDSdjango setting.The following example defines a simple command that canpingan inputdestination_addressthrough a network interface,interface_name.# In yourproject/settings.pydefping_command_callable(destination_address,interface_name=None):command=f'ping -c 4{destination_address}'ifinterface_name:command+=f' -I{interface_name}'returncommandOPENWISP_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:TheOPENWISP_CONTROLLER_USER_COMMANDSsetting takes alistoftupleeach containing two elements. The first element of the tuple should contain an
identifier for the command and the second element should contain adictdefining configuration of the command.Command ConfigurationThedictdefining configuration for command should contain following keys:1.labelAstrdefining label for the command used internally by Django.2.schemaAdictdefiningJSONSchemafor 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:{# 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.callableAcallableorstrdefining 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) forpingcommand.Registering / Unregistering CommandsOpenWISP Controller provides registering and unregistering commands
through utility functionsopenwisp_controller.connection.commands.register_commandandopenwisp_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”whendeveloping custom modules based on openwisp-controlleror when developing custom third party
apps.register_commandParameterDescriptioncommand_nameAstrdefining identifier for the command.command_configAdictdefining configuration of the command
as shown in“Command Configuration”.Note:It will raiseImproperlyConfiguredexception if a command is already
registered with the same name.unregister_commandParameterDescriptioncommand_nameAstrdefining name of the command.Note:It will raiseImproperlyConfiguredexception if such command does not exists.Device GroupsDevice Groups provide an easy way to organize devices of a particular organization.
Device Groups provide the following features:Group similar devices by having dedicated groups for access points, routers, etc.Store additional information regarding a group in the structured metadata field.Customize structure and validation of metadata field of DeviceGroup to standardize
information across all groups using“OPENWISP_CONTROLLER_DEVICE_GROUP_SCHEMA”setting.How to setup WireGuard tunnelsFollow the procedure described below to setup WireGuard tunnels on your devices.Note:This example usesShared 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 WireGuardVisit/admin/config/vpn/add/to add a new VPN server.We will setNameof this VPN serverWireguardandHostaswireguard-server.mydomain.com(update this to point to your
WireGuard VPN server).SelectWireGuardfrom the dropdown asVPN Backend.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 anInternal IPto the WireGuard Server or leave it empty for OpenWISP to
configure. This IP address will be used by the WireGuard interface on
server.We have set theWebhook Endpointashttps://wireguard-server.mydomain.com:8081/trigger-updatefor this example. You will need to update this according to you VPN upgrader
endpoint. SetWebhook AuthTokento 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 substitutewireguard-server.mydomain.comwith hostname
of your VPN server and follow the steps in next section.Under the configuration section, set the name of WireGuard tunnel 1 interface.
We have usedwg0in this example.After clicking onSave and continue editing, you will see that OpenWISP
has automatically created public and private key for WireGuard server inSystem Defined Variablesalong with internal IP address information.2. Deploy Wireguard VPN ServerIf you haven’t already setup WireGuard on your VPN server, this will be a good
time do so. We recommend using theansible-wireguard-openwisprole 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 ServerVisit/admin/config/template/add/to add a new template.SetWireguard ClientasName(you can set whatever you want) and
selectVPN-clientastypefrom the dropdown list.TheBackendfield refers to the backend of the device this template can
be applied to. For this example, we will leave it toOpenWRT.Select the correct VPN server from the dropdown for theVPNfield. Here
it isWireguard.Ensure thatAutomatic tunnel provisioningis checked. This will make
OpenWISP to automatically generate public and private keys and provision IP
address for each WireGuard VPN client.After clicking onSave and continue editingbutton, you will see details
ofWireguardVPN server inSystem 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.4. Apply Wireguard VPN template to devicesNote: This step assumes that you already have a device registered on
OpenWISP. Register or create a device before proceeding.Open theConfigurationtab of the concerned device.Select theWireGuard Clienttemplate.Upon clicking onSave and continue editingbutton, you will see some
entries inSystem 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.Voila!You have successfully configured OpenWISP to manage WireGuard
tunnels for your devices.How to setup VXLAN over WireGuard tunnelsBy 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 usesShared 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 WireGuardVisit/admin/config/vpn/add/to add a new VPN server.We will setNameof this VPN serverWireguard VXLANandHostaswireguard-vxlan-server.mydomain.com(update this to point to your
WireGuard VXLAN VPN server).SelectVXLAN over WireGuardfrom the dropdown asVPN Backend.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 anInternal IPto the WireGuard Server or leave it empty for OpenWISP to
configure. This IP address will be used by the WireGuard interface on
server.We have set theWebhook Endpointashttps://wireguard-vxlan-server.mydomain.com:8081/trigger-updatefor this example. You will need to update this according to you VPN upgrader
endpoint. SetWebhook AuthTokento 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 substitutewireguard-server.mydomain.comwith hostname
of your VPN server and follow the steps in next section.Under the configuration section, set the name of WireGuard tunnel 1 interface.
We have usedwg0in this example.After clicking onSave and continue editing, you will see that OpenWISP
has automatically created public and private key for WireGuard server inSystem Defined Variablesalong with internal IP address information.2. Deploy Wireguard VXLAN VPN ServerIf you haven’t already setup WireGuard on your VPN server, this will be a good
time do so. We recommend using theansible-wireguard-openwisprole 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 ServerVisit/admin/config/template/add/to add a new template.SetWireguard VXLAN ClientasName(you can set whatever you want) and
selectVPN-clientastypefrom the dropdown list.TheBackendfield refers to the backend of the device this template can
be applied to. For this example, we will leave it toOpenWRT.Select the correct VPN server from the dropdown for theVPNfield. Here
it isWireguard VXLAN.Ensure thatAutomatic tunnel provisioningis 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).After clicking onSave and continue editingbutton, you will see details
ofWireguard VXLANVPN server inSystem 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.4. Apply Wireguard VXLAN VPN template to devicesNote: This step assumes that you already have a device registered on
OpenWISP. Register or create a device before proceeding.Open theConfigurationtab of the concerned device.Select theWireGuard VXLAN Clienttemplate.Upon clicking onSave and continue editingbutton, you will see some
entries inSystem 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.Voila!You have successfully configured OpenWISP to manage VXLAN over
WireGuard tunnels for your devices.How to configure automatic provisioning of subnets and IPsThe following steps will help you configure automatic provisioning of subnets and IPs
for devices.1. Create a Subnet and a Subnet Division RuleCreate a master subnet under which automatically generated subnets will be provisioned.Note: Choose the size of the subnet appropriately considering your use case.On the same page, add asubnet division rulethat 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 RuleThis rule type is triggered whenever a device configuration (config.Configmodel)
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 RuleThis 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 forWireGuardandVXLAN over WireGuardVPN servers.In this example,VPN subnet division ruleis used.2. Create a VPN ServerNow create a VPN Server and choose the previously createdmaster subnetas the subnet for
this VPN Server.3. Create a VPN Client TemplateCreate a template, setting theTypefield toVPN ClientandVPNfield to use the
previously created VPN Server.Note: You can also check theEnable by defaultfield if you want to automatically
apply this template to devices that will register in future.4. Apply VPN Client Template to DevicesWith everything in place, you can now apply the VPN Client Template to devices.After saving the device, you should see all provisioned Subnets and IPs for this device
underSystem Defined Variables.Voila! You can now use these variables in configuration of the device. Refer toHow to use configuration variablessection of this documentation to learn how to use configuration variables.Important notes for using Subnet DivisionIn the above example Subnet, VPN Server, and VPN Client Template belonged to thedefaultorganization.
You can useSystemwide SharedSubnet, 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_ip1will resolve to10.0.0.1for one device and10.0.0.55for 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 spacemust notbe interfered with or the automation implemented in this module
will not work.The above example usedVPN subnet division rule. Similarly,device subnet division rulecan be used, which only requirescreating a subnet and a subnet division rule.Limitations of Subnet DivisionIn 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:SizeAllowing 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 SubnetsAllowing 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 IPsAllowing 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.
Itis allowedto 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 / NotificationsNotification TypeUseconfig_errorFires when status of a device configuration changes toerror.device_registeredFires when a new device is registered automatically on the network.REST API ReferenceLive documentationA general live API documentation (following the OpenAPI specification) at/api/v1/docs/.Browsable web interfaceAdditionally, opening any of the endpointslisted belowdirectly in the browser will show thebrowsable API interface of Django-REST-Framework,
which makes it even easier to find out the details of each endpoint.AuthenticationSee openwisp-users:authenticating with the user token.When browsing the API via theLive documentationor theBrowsable web page, you can also use
the session authentication by logging in the django admin.PaginationAlllistendpoints support thepage_sizeparameter that allows paginating
the results in conjunction with thepageparameter.GET /api/v1/controller/template/?page_size=10
GET /api/v1/controller/template/?page_size=10&page=2List of endpointsSince the detailed explanation is contained in theLive documentationand in theBrowsable web pageof 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 devicesGET /api/v1/controller/device/Create devicePOST /api/v1/controller/device/Get device detailGET /api/v1/controller/device/{id}/Download device configurationGET /api/v1/controller/device/{id}/configuration/The above endpoint triggers the download of atar.gzfile containing the generated configuration for that specific device.Change details of devicePUT /api/v1/controller/device/{id}/Patch details of devicePATCH /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 theconfigfield 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 theconfigand cannot be removed.Example usage: For assigning template(s) add the/their {id} to the config of a device,curl-XPATCH\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,curl-XPATCH\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,curl-XPATCH\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 deviceDELETE /api/v1/controller/device/{id}/List device connectionsGET /api/v1/controller/device/{id}/connection/Create device connectionPOST /api/v1/controller/device/{id}/connection/Get device connection detailGET /api/v1/controller/device/{id}/connection/{id}/Change device connection detailPUT /api/v1/controller/device/{id}/connection/{id}/Patch device connection detailPATCH /api/v1/controller/device/{id}/connection/{id}/Delete device connectionDELETE /api/v1/controller/device/{id}/connection/{id}/List credentialsGET /api/v1/connection/credential/Create credentialPOST /api/v1/connection/credential/Get credential detailGET /api/v1/connection/credential/{id}/Change credential detailPUT /api/v1/connection/credential/{id}/Patch credential detailPATCH /api/v1/connection/credential/{id}/Delete credentialDELETE /api/v1/connection/credential/{id}/List commands of a deviceGET /api/v1/controller/device/{id}/command/Execute a command a devicePOST /api/v1/controller/device/{id}/command/Get command detailsGET /api/v1/controller/device/{device_id}/command/{command_id}/List device groupsGET /api/v1/controller/group/Create device groupPOST /api/v1/controller/group/Get device group detailGET /api/v1/controller/group/{id}/Get device group from certificate common nameGET /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:GET /api/v1/controller/cert/{common_name}/group/?org={org1_slug},{org2_slug}Get device locationGET /api/v1/controller/device/{id}/location/Create device locationPUT /api/v1/controller/device/{id}/location/You can createDeviceLocationobject by using primary
keys of existingLocationandFloorPlanobjects as shown in
the example below.{"location":"f0cb5762-3711-4791-95b6-c2f6656249fa","floorplan":"dfeb6724-aab4-4533-aeab-f7feb6648acd","indoor":"-36,264"}Note:Theindoorfield 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 be0,0.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 relatedLocationandFloorPlanobjects for the
device directly from this endpoint.The following example demonstrates creating related location
object in a single request.{"location":{"name":"Via del Corso","address":"Via del Corso, Roma, Italia","geometry":{"type":"Point","coordinates":[12.512124,41.898903]},"type":"outdoor",}}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 thegeometryinWell-known text (WKT)format, like following:{"location":{"name":"Via del Corso","address":"Via del Corso, Roma, Italia","geometry":"POINT (12.512124 41.898903)","type":"outdoor",}}Similarly, you can createFloorplanobject with the same request.
But, note that aFloorPlancan be added toDeviceLocationonly
if the relatedLocationobject defines an indoor location. The example
below demonstrates creating bothLocationandFloorPlanobjects.// 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,
}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 usesmultipartcontent-typefor uploading floorplan image.You can also use an existingLocationobject and create a new
floorplan for that location using this endpoint.// 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
}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 locationPUT /api/v1/controller/device/{id}/location/Note:This endpoint can be used to update relatedLocationandFloorplanobjects. Referexamples of “Create device location”
section for information on payload format.Delete device locationDELETE /api/v1/controller/device/{id}/location/Get device coordinatesGET /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
devicekeyis passed asquery_parambecause the system
assumes that the device is updating it’s position.curl -X GET \
    'http://127.0.0.1:8000/api/v1/controller/device/8a85cc23-bad5-4c7e-b9f4-ffe298defb5c/coordinates/?key=10a0cb5a553c71099c0e4ef236435496'Update device coordinatesPUT /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
devicekeyis passed asquery_parambecause the system
assumes that the device is updating it’s position.{"type":"Feature","geometry":{"type":"Point","coordinates":[12.512124,41.898903]},}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 locationsGET /api/v1/controller/location/You can filter usingorganization_slugto get list locations that
belongs to an organization.GET /api/v1/controller/location/?organization_slug=<organization_slug>Create locationPOST /api/v1/controller/location/If you are creating anindoorlocation, you can use this endpoint
to create floorplan for the location.The following example demonstrates creating floorplan along with location
in a single request.{
    "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"
}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-fc6fcb4f3a05Note:You can also specify thegeometryinWell-known text (WKT)format, like following:{
    "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 detailsGET /api/v1/controller/location/{pk}/Change location detailsPUT /api/v1/controller/location/{pk}/Note: Only the first floorplan data present can be
edited or changed. Setting thetypeof location to
outdoor will remove all the floorplans associated with it.Referexamples of “Create location”
section for information on payload format.Delete locationDELETE /api/v1/controller/location/{pk}/List devices in a locationGET /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.GET /api/v1/controller/location/geojson/You can filter usingorganization_slugto get list location of
devices from that organization.GET /api/v1/controller/location/geojson/?organization_slug=<organization_slug>List floorplansGET /api/v1/controller/floorplan/You can filter usingorganization_slugto get list floorplans that
belongs to an organization.GET /api/v1/controller/floorplan/?organization_slug=<organization_slug>Create floorplanPOST /api/v1/controller/floorplan/Get floorplan detailsGET /api/v1/controller/floorplan/{pk}/Change floorplan detailsPUT /api/v1/controller/floorplan/{pk}/Delete floorplanDELETE /api/v1/controller/floorplan/{pk}/List templatesGET /api/v1/controller/template/Create templatePOST /api/v1/controller/template/Get template detailGET /api/v1/controller/template/{id}/Download template configurationGET /api/v1/controller/template/{id}/configuration/The above endpoint triggers the download of atar.gzfile
containing the generated configuration for that specific template.Change details of templatePUT /api/v1/controller/template/{id}/Patch details of templatePATCH /api/v1/controller/template/{id}/Delete templateDELETE /api/v1/controller/template/{id}/List VPNsGET /api/v1/controller/vpn/Create VPNPOST /api/v1/controller/vpn/Get VPN detailGET /api/v1/controller/vpn/{id}/Download VPN configurationGET /api/v1/controller/vpn/{id}/configuration/The above endpoint triggers the download of atar.gzfile
containing the generated configuration for that specific VPN.Change details of VPNPUT /api/v1/controller/vpn/{id}/Patch details of VPNPATCH /api/v1/controller/vpn/{id}/Delete VPNDELETE /api/v1/controller/vpn/{id}/List CAGET /api/v1/controller/ca/Create new CAPOST /api/v1/controller/ca/Import existing CAPOST /api/v1/controller/ca/Note: To import an existing CA, onlyname,certificateandprivate_keyfields have to be filled in theHTMLform or
included in theJSONformat.Get CA DetailGET /api/v1/controller/ca/{id}/Change details of CAPUT /api/v1/controller/ca/{id}/Patch details of CAPATCH /api/v1/controller/ca/{id}/Download CA(crl)GET /api/v1/controller/ca/{id}/crl/The above endpoint triggers the download of{id}.crlfile containing
up to date CRL of that specific CA.Delete CADELETE /api/v1/controller/ca/{id}/Renew CAPOST /api/v1/controller/ca/{id}/renew/List CertGET /api/v1/controller/cert/Create new CertPOST /api/v1/controller/cert/Import existing CertPOST /api/v1/controller/cert/Note: To import an existing Cert, onlyname,ca,certificateandprivate_keyfields have to be filled
in theHTMLform or included in theJSONformat.Get Cert DetailGET /api/v1/controller/cert/{id}/Change details of CertPUT /api/v1/controller/cert/{id}/Patch details of CertPATCH /api/v1/controller/cert/{id}/Delete CertDELETE /api/v1/controller/cert/{id}/Renew CertPOST /api/v1/controller/cert/{id}/renew/Revoke CertPOST /api/v1/controller/cert/{id}/revoke/SettingsYou can change the values for the following variables insettings.pyto configure your instance of openwisp-controller.OPENWISP_SSH_AUTH_TIMEOUTtype:intdefault:2unit:secondsConfigure timeout to wait for an authentication response when establishing a SSH connection.OPENWISP_SSH_BANNER_TIMEOUTtype:intdefault:60unit:secondsConfigure timeout to wait for the banner to be presented when establishing a SSH connection.OPENWISP_SSH_COMMAND_TIMEOUTtype:intdefault:30unit:secondsConfigure timeout on blocking read/write operations when executing a command in a SSH connection.OPENWISP_SSH_CONNECTION_TIMEOUTtype:intdefault:5unit:secondsConfigure timeout for the TCP connect when establishing a SSH connection.OPENWISP_CONNECTORStype:tupledefault:(('openwisp_controller.connection.connectors.ssh.Ssh','SSH'),)Available connector classes. Connectors are python classes that specify ways
in which OpenWISP can connect to devices in order to launch commands.OPENWISP_UPDATE_STRATEGIEStype:tupledefault:(('openwisp_controller.connection.connectors.openwrt.ssh.OpenWrt','OpenWRT SSH'),)Available update strategies. An update strategy is a subclass of a
connector class which defines anupdate_configmethod 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_MAPPINGtype:dictdefault:{'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_BACKENDStype:tupledefault:(('netjsonconfig.OpenWrt','OpenWRT'),('netjsonconfig.OpenWisp','OpenWISP'),)Available configuration backends. For more information, seenetjsonconfig backends.OPENWISP_CONTROLLER_VPN_BACKENDStype:tupledefault:(('openwisp_controller.vpn_backends.OpenVpn','OpenVPN'),('openwisp_controller.vpn_backends.Wireguard','WireGuard'),('openwisp_controller.vpn_backends.VxlanWireguard','VXLAN over WireGuard'),)Available VPN backends for VPN Server objects. For more information, seenetjsonconfig VPN backends.A VPN backend must follow some basic rules in order to be compatible withopenwisp-controller:it MUST allow at minimum and at maximum one VPN instancethe mainNetJSONproperty MUST match the lowercase version of the class name,
eg: when using theOpenVpnbackend, the system will look intoconfig['openvpn']it SHOULD focus on the server capabilities of the VPN software being usedOPENWISP_CONTROLLER_DEFAULT_BACKENDtype:strdefault:OPENWISP_CONTROLLER_BACKENDS[0][0]The preferred backend that will be used as initial value when adding newConfigorTemplateobjects in the admin.This setting defaults to the raw value of the first item in theOPENWISP_CONTROLLER_BACKENDSsetting,
which isnetjsonconfig.OpenWrt.Setting it toNonewill force the user to choose explicitly.OPENWISP_CONTROLLER_DEFAULT_VPN_BACKENDtype:strdefault:OPENWISP_CONTROLLER_VPN_BACKENDS[0][0]The preferred backend that will be used as initial value when adding newVpnobjects in the admin.This setting defaults to the raw value of the first item in theOPENWISP_CONTROLLER_VPN_BACKENDSsetting,
which isopenwisp_controller.vpn_backends.OpenVpn.Setting it toNonewill force the user to choose explicitly.OPENWISP_CONTROLLER_REGISTRATION_ENABLEDtype:booldefault:TrueWhether 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, seeopenwisp-config automatic
registrationfor more information.OPENWISP_CONTROLLER_CONSISTENT_REGISTRATIONtype:booldefault:TrueWhether 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, seeopenwisp-config
consistent key generationfor more information.OPENWISP_CONTROLLER_REGISTRATION_SELF_CREATIONtype:booldefault:TrueWhether 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_CONTEXTtype:dictdefault:{}Additional context that is passed to the default context of each device object.OPENWISP_CONTROLLER_CONTEXTcan be used to define system-wide configuration variables.For more information regarding how to use configuration variables in OpenWISP,
seeHow to use configuration variables.For technical information about how variables are handled in the lower levels
of OpenWISP, seenetjsonconfig context: configuration variables.OPENWISP_CONTROLLER_DEFAULT_AUTO_CERTtype:booldefault:TrueThe default value of theauto_certfield for newTemplateobjects.Theauto_certfield is valid only for templates which havetypeset toVPNand indicates whether configuration regarding the VPN tunnel is
provisioned automatically to each device using the template, eg:when using OpenVPN, newx509certificates
will be generated automatically using the same CA assigned to the related VPN objectwhen using WireGuard, new pair of private and public keys
(usingCurve25519) will be generated, as well as
an IP address of the subnet assigned to the related VPN objectwhen usingVXLANtunnels 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 objectAll 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_PATHtype:strdefault:/etc/x509The filesystem path where x509 certificate will be installed when
downloaded on routers whenauto_certis being used (enabled by default).OPENWISP_CONTROLLER_COMMON_NAME_FORMATtype:strdefault:{mac_address}-{name}Defines the format of thecommon_nameattribute of VPN client certificates
that are automatically created when using VPN templates which haveauto_certset toTrue. A unique slug generated usingshortuuidis appended to the common name to introduce uniqueness. Therefore, resulting
common names will have{OPENWISP_CONTROLLER_COMMON_NAME_FORMAT}-{unique-slug}format.Note:If thenameandmac addressof the device are equal,
thenameof the device will be omitted from the common name to avoid redundancy.OPENWISP_CONTROLLER_MANAGEMENT_IP_DEVICE_LISTtype:booldefault:TrueIn the device list page, the columnIPwill show themanagement_ipif
available, defaulting tolast_ipotherwise.If this setting is set toFalsethemanagement_ipwon’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 toFalseif for some reason the majority of your user
doesn’t care about the management ip address.OPENWISP_CONTROLLER_CONFIG_BACKEND_FIELD_SHOWNtype:booldefault:TrueThis setting toggles thebackendfields in add/edit pages in Device and Template configuration,
as well as thebackendfield/filter in Device list and Template list.If this setting is set toFalsethese items will be removed from the UI.Note: This setting affects only the configuration backend and NOT the VPN backend.OPENWISP_CONTROLLER_DEVICE_NAME_UNIQUEtype:booldefault:TrueThis 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_ENABLEDtype:booldefault:FalseThe fieldhardware_idcan be used to store a unique hardware id, for example a serial number.If this setting is set toTruethen 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_OPTIONStype:dictdefault:{'blank':notOPENWISP_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 fieldhardware_id.blank: wether the field is allowed to be blanknull: wether an empty value will be stored asNULLin the databasemax_length: maximum length of the fieldunique: wether the value of the field must be uniqueverbose_name: text for the human readable label of the fieldhelp_text: help text to be displayed with the fieldOPENWISP_CONTROLLER_HARDWARE_ID_AS_NAMEtype:booldefault:TrueWhen 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 toFalse.OPENWISP_CONTROLLER_DEVICE_VERBOSE_NAMEtype:tupledefault:('Device', 'Devices')Defines theverbose_nameattribute of theDevicemodel, 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:OPENWISP_CONTROLLER_DEVICE_VERBOSE_NAME=('Hotspot','Hotspots')OPENWISP_CONTROLLER_HIDE_AUTOMATICALLY_GENERATED_SUBNETS_AND_IPStype:booldefault:FalseSetting this toTruewill hide subnets and IPs generated usingsubnet division rulesfrom being displayed on the changelist view of Subnet and IP admin.OPENWISP_CONTROLLER_SUBNET_DIVISION_TYPEStype:tupledefault:(('openwisp_controller.subnet_division.rule_types.device.DeviceSubnetDivisionRuleType','Device'),('openwisp_controller.subnet_division.rule_types.vpn.VpnSubnetDivisionRuleType','VPN'),)Available types for Subject Division Ruleobjects.
For more information on how to write your own types, read“Custom Subnet Division Rule Types” section of this documentationOPENWISP_CONTROLLER_APItype:booldefault:TrueIndicates whether the API for Openwisp Controller is enabled or not.
To disable the API by default addOPENWISP_CONTROLLER_API = Falseinsettings.pyfile.OPENWISP_CONTROLLER_API_HOSTtype:strdefault:NoneAllows to specify backend URL for API requests, if the frontend is hosted separately.OPENWISP_CONTROLLER_USER_COMMANDStype:listdefault:[]Allows to specify alistof tuples for adding commands as described in‘How to define custom commands”section.OPENWISP_CONTROLLER_DEVICE_GROUP_SCHEMAtype:dictdefault:{'type': 'object', 'properties': {}}Allows specifying JSONSchema used for validating meta-data ofDevice Group.OPENWISP_CONTROLLER_SHARED_MANAGEMENT_IP_ADDRESS_SPACEtype:booldefault:TrueBy 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 toTrue, any device belonging to any
organization will never have the samemanagement_ipas 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 toFalseif every organization has its dedicated management
tunnel with a dedicated address space that is reachable by the OpenWISP server.OPENWISP_CONTROLLER_DSA_OS_MAPPINGtype:dictdefault:{}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 theosfield of theDeviceobject. 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 toOPENWISP_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 identifierMyCustomFirmware 2.0corresponds toOpenWrt 19.07, whileMyCustomFirmware 2.1corresponds toOpenWrt 21.02. Configuring this setting as indicated below will allow
OpenWISP to supply the right syntax automatically.Example: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_FALLBACKtype:booldefault:TrueThe value of this setting decides whether to use DSA syntax
(OpenWrt >=21 configuration syntax) if openwisp-controller fails
to make that decision automatically.Signalsconfig_modifiedPath:openwisp_controller.config.signals.config_modifiedArguments:instance: instance ofConfigwhich got itsconfigmodifiedprevious_status: indicates the status of the config object before the
signal was emittedaction: 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 ifConfig.statusis 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 whichconfig_modifiedis not emittedThis 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_clearm2m signal), this is necessary becausesortedm2m, 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 ignorepost_clearto 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_changedPath:openwisp_controller.config.signals.config_status_changedArguments:instance: instance ofConfigwhich got itsstatuschangedThis 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 onpost_addorpost_removeactions,post_clearis ignored for the same reason explained
in the previous section.checksum_requestedPath:openwisp_controller.config.signals.checksum_requestedArguments:instance: instance ofDevicefor which its configuration
checksum has been requestedrequest: the HTTP request objectThis 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_requestedPath:openwisp_controller.config.signals.config_download_requestedArguments:instance: instance ofDevicefor which its configuration has been
requested for downloadrequest: the HTTP request objectThis 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_changedPath:openwisp_controller.connection.signals.is_working_changedArguments:instance: instance ofDeviceConnectionis_working: value ofDeviceConnection.is_workingold_is_working: previous value ofDeviceConnection.is_working,
eitherNone(for new connections),TrueorFalsefailure_reason: error message explaining reason for failure in establishing connectionold_failure_reason: previous value ofDeviceConnection.failure_reasonThis signal is emitted every timeDeviceConnection.is_workingchanges.It is not triggered when the device is created for the first time.management_ip_changedPath:openwisp_controller.config.signals.management_ip_changedArguments:instance: instance ofDevicemanagement_ip: value ofDevice.management_ipold_management_ip: previous value ofDevice.management_ipThis signal is emitted every timeDevice.management_ipchanges.It is not triggered when the device is created for the first time.device_registeredPath:openwisp_controller.config.signals.device_registeredArguments:instance: instance ofDevicewhich got registered.is_new: boolean, will beTruewhen the device is new,Falsewhen 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_changedPath:openwisp_controller.config.signals.device_name_changedArguments:instance: instance ofDevice.The signal is emitted when the device name changes.It is not emitted when the device is created.device_group_changedPath:openwisp_controller.config.signals.device_group_changedArguments:instance: instance ofDevice.group_id: primary key ofDeviceGroupofDeviceold_group_id: primary key of previousDeviceGroupofDeviceThe signal is emitted when the device group changes.It is not emitted when the device is created.subnet_provisionedPath:openwisp_controller.subnet_division.signals.subnet_provisionedArguments:instance: instance ofVpnClient.provisioned: dictionary ofSubnetandIpAddressprovisioned,Noneif nothing is provisionedThe signal is emitted when subnets and IP addresses have been provisioned
for aVpnClientfor a VPN server with a subnet withsubnet division rule.vpn_server_modifiedPath:openwisp_controller.config.signals.vpn_server_modifiedArguments:instance: instance ofVpn.The signal is emitted when the VPN server is modified.vpn_peers_changedPath:openwisp_controller.config.signals.vpn_peers_changedArguments:instance: instance ofVpn.The signal is emitted when the peers of VPN server gets changed.It is only emitted forVpnobject withWireGuardorVXLAN over WireGuardbackend.Extending openwisp-controllerOne of the core values of the OpenWISP project isSoftware Reusability,
for this reasonopenwisp-controllerprovides a set of base classes
which can be imported, extended and reused to create derivative apps.In order to implement your custom version ofopenwisp-controller,
you need to perform the steps described in this section.When in doubt, the code in thetest projectwill serve you as source of truth: just replicate and adapt that code
to get a basic derivative ofopenwisp-controllerworking.If you want to add new users fields, please follow thetutorial to extend the
openwisp-users.
As an example, we have extendedopenwisp-userstosample_usersapp and
added a fieldsocial_security_numberin thesample_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 appsFirstly, to get started you need to create a django project:django-admin startproject mycontrollerNow, you need to do is to create some new django apps which will
contain your custom version ofopenwisp-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 apython package(a directory of python scripts), in the following examples we’ll call these django appsample_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_divisionKeep in mind that the command mentioned above must be called from a directory
which is available in yourPYTHON_PATHso 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 thedjango documentation.2. Installopenwisp-controllerInstall (and add to the requirement of your project) openwisp-controller:pip install openwisp-controller3. Add your apps in INSTALLED_APPSNow you need to addmycontroller.sample_config,mycontroller.sample_pki,mycontroller.sample_connection,mycontroller.sample_geo&mycontroller.sample_subnet_divisiontoINSTALLED_APPSin yoursettings.py, ensuring also thatopenwisp_controller.config,openwisp_controller.geo,openwisp_controller.pki,openwisp_controller.connnection&openwisp_controller.subnet_divisionhave been removed:# Remember: Order in INSTALLED_APPS is important.INSTALLED_APPS=[# other django installed apps'openwisp_utils.admin_theme',# 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',]Substitutemycontroller,sample_config,sample_pki,sample_connection,sample_geo&sample_subnet_divisionwith the name you chose in step 1.4. AddEXTENDED_APPSAdd the following to yoursettings.py:EXTENDED_APPS=('django_x509','django_loci','openwisp_controller.config','openwisp_controller.pki','openwisp_controller.geo','openwisp_controller.connection','openwisp_controller.subnet_division',)5. Addopenwisp_utils.staticfiles.DependencyFinderAddopenwisp_utils.staticfiles.DependencyFindertoSTATICFILES_FINDERSin yoursettings.py:STATICFILES_FINDERS=['django.contrib.staticfiles.finders.FileSystemFinder','django.contrib.staticfiles.finders.AppDirectoriesFinder','openwisp_utils.staticfiles.DependencyFinder',]6. Addopenwisp_utils.loaders.DependencyLoaderAddopenwisp_utils.loaders.DependencyLoadertoTEMPLATESin yoursettings.py, but ensure it comes beforedjango.template.loaders.app_directories.Loader: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 setupEnsure you are using one of the available geodjango backends, eg:DATABASES={'default':{'ENGINE':'django.contrib.gis.db.backends.spatialite','NAME':'openwisp-controller.db',}}For more information about GeoDjango, please refer to thegeodjango documentation.6. Django Channels SetupCreateasgi.pyin your project folder and add following lines in it:fromchannels.authimportAuthMiddlewareStackfromchannels.routingimportProtocolTypeRouter,URLRouterfromchannels.security.websocketimportAllowedHostsOriginValidatorfromdjango.core.asgiimportget_asgi_applicationfromopenwisp_controller.routingimportget_routes# You can also add your routes like thisfrommy_app.routingimportmy_routesapplication=ProtocolTypeRouter({"http":get_asgi_application(),'websocket':AllowedHostsOriginValidator(AuthMiddlewareStack(URLRouter(get_routes()+my_routes)))})7. Other SettingsAdd the following settings tosettings.py: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 theFORM_RENDERER documentation.
For more information about ASGI_APPLICATION setting, please refer to theASGI_APPLICATION documentation.
For more information about CHANNEL_LAYERS setting, please refer to theCHANNEL_LAYERS documentation.6. Inherit the AppConfig classPlease 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 ofAppConfigplease refer to
the“Applications” section in the django documentation.7. Create your custom modelsFor 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 modelssample_geo modelssample_pki modelssample_connection modelssample_subnet_divisionYou can add fields in a similar way in yourmodels.pyfile.Note: for doubts regarding how to use, extend or develop models please refer to
the“Models” section in the django documentation.8. Add swapper configurationsOnce you have created the models, add the following to yoursettings.py:# Setting models for swapper moduleCONFIG_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'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'Substitutesample_config,sample_pki,sample_connection,sample_geo&sample_subnet_divisionwith the name you chose in step 1.9. Create database migrationsCreate database migrations:./manage.py makemigrationsNow, to use the defaultadministratorandoperatoruser 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.pysample_geo/migrations/0002_default_group_permissions.pysample_pki/migrations/0002_default_group_permissions.pysample_connection/migrations/0002_default_group_permissions.pysample_subnet_division/migrations/0002_default_group_permissions.pyCreate database migrations:./manage.py migrateFor more information, refer to the“Migrations” section in the django documentation.10. Create the adminRefer to theadmin.pyfile 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 patchingIf the changes you need to add are relatively small, you can resort to monkey patching.For example:sample_configfromopenwisp_controller.config.adminimport(DeviceAdmin,DeviceGroupAdmin,TemplateAdmin,VpnAdmin,)# DeviceAdmin.fields += ['example'] <-- monkey patching examplesample_connectionfromopenwisp_controller.connection.adminimportCredentialsAdmin# CredentialsAdmin.fields += ['example'] <-- monkey patching examplesample_geofromopenwisp_controller.geo.adminimportFloorPlanAdmin,LocationAdmin# FloorPlanAdmin.fields += ['example'] <-- monkey patching examplesample_pkifromopenwisp_controller.pki.adminimportCaAdmin,CertAdmin# CaAdmin.fields += ['example'] <-- monkey patching examplesample_subnet_divisionfromopenwisp_controller.subnet_division.adminimportSubnetDivisionRuleInlineAdmin# SubnetDivisionRuleInlineAdmin.fields += ['example'] <-- monkey patching example2. Inheriting admin classesIf you need to introduce significant changes and/or you don’t want to resort to
monkey patching, you can proceed as follows:sample_configfromdjango.contribimportadminfromopenwisp_controller.config.adminimport(DeviceAdminasBaseDeviceAdmin,TemplateAdminasBaseTemplateAdmin,VpnAdminasBaseVpnAdmin,DeviceGroupAdminasBaseDeviceGroupAdmin,fromswapperimportload_modelVpn=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)classVpnAdmin(BaseVpnAdmin):# add your changes here@admin.register(Device)classDeviceAdmin(BaseDeviceAdmin):# add your changes here@admin.register(DeviceGroup)classDeviceGroupAdmin(BaseDeviceGroupAdmin):# add your changes here@admin.register(Template)classTemplateAdmin(BaseTemplateAdmin):# add your changes heresample_connectionfromopenwisp_controller.connection.adminimportCredentialsAdminasBaseCredentialsAdminfromdjango.contribimportadminfromswapperimportload_modelCredentials=load_model('openwisp_controller','Credentials')admin.site.unregister(Credentials)@admin.register(Device)classCredentialsAdmin(BaseCredentialsAdmin):# add your changes heresample_geofromopenwisp_controller.geo.adminimport(FloorPlanAdminasBaseFloorPlanAdmin,LocationAdminasBaseLocationAdmin)fromdjango.contribimportadminfromswapperimportload_modelLocation=load_model('openwisp_controller','Location')FloorPlan=load_model('openwisp_controller','FloorPlan')admin.site.unregister(FloorPlan)admin.site.unregister(Location)@admin.register(FloorPlan)classFloorPlanAdmin(BaseFloorPlanAdmin):# add your changes here@admin.register(Location)classLocationAdmin(BaseLocationAdmin):# add your changes heresample_pkifromopenwisp_controller.geo.adminimport(CaAdminasBaseCaAdmin,CertAdminasBaseCertAdmin)fromdjango.contribimportadminfromswapperimportload_modelCa=load_model('openwisp_controller','Ca')Cert=load_model('openwisp_controller','Cert')admin.site.unregister(Ca)admin.site.unregister(Cert)@admin.register(Ca)classCaAdmin(BaseCaAdmin):# add your changes here@admin.register(Cert)classCertAdmin(BaseCertAdmin):# add your changes heresample_subnet_divisionfromopenwisp_controller.subnet_division.adminimport(SubnetAdminasBaseSubnetAdmin,IpAddressAdminasBaseIpAddressAdmin,SubnetDivisionRuleInlineAdminasBaseSubnetDivisionRuleInlineAdmin,)fromdjango.contribimportadminfromswapperimportload_modelSubnet=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)classSubnetAdmin(BaseSubnetAdmin):# add your changes here@admin.register(IpAddress)classIpAddressAdmin(BaseIpAddressAdmin):# add your changes here@admin.register(SubnetDivisionRule)classSubnetDivisionRuleInlineAdmin(BaseSubnetDivisionRuleInlineAdmin):# add your changes here11. Create root URL configurationfromdjango.contribimportadminfromopenwisp_controller.config.utilsimportget_controller_urlsfromopenwisp_controller.geo.utilsimportget_geo_urls# from .sample_config import views as config_views# from .sample_geo import views as geo_viewsurlpatterns=[# ... 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 urlsurl(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 testsWhen 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 ofopenwisp-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.pysample_config tests.pysample_geo tests.pysample_geo pytest.pysample_pki tests.pysample_connection tests.pysample_subnet_division tests.pyFor running the tests, you need to copy fixtures as well:Changesample_configto your config app’s name insample_config fixturesand paste it in thesample_config/fixtures/directory.You can then run tests with:# the --parallel flag is optional
./manage.py test --parallel mycontrollerSubstitutemycontrollerwith 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 extendedThe following steps are not required and are intended for more advanced customization.1. Extending the Controller API ViewsExtending thesample_config/views.pyis required only when you want to make changes in the controller API,
Remember to changeconfig_viewslocation inurls.pyin point 11 for extending views.For more information about django views, please refer to theviews section in the django documentation.2. Extending the Geo API ViewsExtending thesample_geo/views.pyis required only when you want to make changes in the geo API,
Remember to changegeo_viewslocation inurls.pyin point 11 for extending views.For more information about django views, please refer to theviews section in the django documentation.Custom Subnet Division Rule TypesIt is possible to create your ownsubnet division rule types.
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 extendingopenwisp_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.# In mycontroller/sample_subnet_division/rules_types/custom.pyfromdjango.db.models.signalsimportpost_delete,post_savefromswapperimportload_modelfromopenwisp_controller.subnet_division.rule_types.baseimport(BaseSubnetDivisionRuleType,)Device=load_model('config','Device')classCustomRuleType(BaseSubnetDivisionRuleType):# The signal on which provisioning should be triggeredprovision_signal=post_save# The sender of the provision_signalprovision_sender=Device# Dispatch UID for connecting provision_signal to provision_receiverprovision_dispatch_uid='some_unique_identifier_string'# The signal on which deletion should be triggereddestroyer_signal=post_delete# The sender of the destroyer_signaldestroyer_sender=Device# Dispatch UID for connecting destroyer_signal to destroyer_receiverdestroyer_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 attributesubnet_path='subnet'# An intermediate method through which you can specify conditions for provisions@classmethoddefshould_create_subnets_ips(cls,instance,**kwargs):# Using "post_save" provision_signal, the rule should be only# triggered when a new object is created.returnkwargs['created']# You can define logic to trigger provisioning for existing objects# using following classmethod. By default, BaseSubnetDivisionRuleType# performs no operation for existing objects.@classmethoddefprovision_for_existing_objects(cls,rule_obj):fordeviceinDevice.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 setOPENWISP_CONTROLLER_SUBNET_DIVISION_TYPESsetting as follows: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 typesYou can define your own notification types usingregister_notification_typefunction from OpenWISP Notifications.For more information, see the relevantdocumentation 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-notificationsto send notifications for this type.ContributingPlease refer to theOpenWISP contributing guidelines.ChangelogSeeCHANGES.LicenseSeeLICENSE.SupportSeeOpenWISP Support Channels.