Package: django-netjsonconfig
Configuration manager for embedded devices, implemented as a reusable django-app.Based on theNetJSONformat and thenetjsonconfiglibrary.Table of Contents:Current featuresProject goalsDeploy it in productionDependenciesInstall stable version from pypiInstall development versionSetup (integrate in an existing django project)Installing for developmentHow to use configuration variablesPredefined device variablesUser defined device variablesTemplate default valuesGlobal variablesExample usage of variablesSignalsconfig_modifiedconfig_status_changedchecksum_requestedconfig_download_requestedSettingsNETJSONCONFIG_BACKENDSNETJSONCONFIG_VPN_BACKENDSNETJSONCONFIG_DEFAULT_BACKENDNETJSONCONFIG_DEFAULT_VPN_BACKENDNETJSONCONFIG_REGISTRATION_ENABLEDNETJSONCONFIG_CONSISTENT_REGISTRATIONNETJSONCONFIG_REGISTRATION_SELF_CREATIONNETJSONCONFIG_SHARED_SECRETNETJSONCONFIG_CONTEXTNETJSONCONFIG_DEFAULT_AUTO_CERTNETJSONCONFIG_CERT_PATHNETJSONCONFIG_COMMON_NAME_FORMATNETJSONCONFIG_MANAGEMENT_IP_DEVICE_LISTNETJSONCONFIG_BACKEND_DEVICE_LISTNETJSONCONFIG_HARDWARE_ID_ENABLEDNETJSONCONFIG_HARDWARE_ID_OPTIONSNETJSONCONFIG_HARDWARE_ID_AS_NAMEExtending django-netjsonconfig1. AddEXTENDED_APPS2. Addopenwisp_utils.staticfiles.DependencyFinder3. Addopenwisp_utils.loaders.DependencyLoaderExtending modelsExtending the adminExtending controller viewsController URLsExtending AppConfigReal world extensions of django-netjsonconfigScreenshotsContributingChangelogLicenseSupportCurrent featuresconfiguration 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 minimumconfiguration variables: reference
ansible-like variables in the configuration and templatestemplate tags: tag templates to automate different types of auto-configurations (eg: mesh, WDS, 4G)simple HTTP resources: allow devices to automatically download configuration updatesVPN management: easily create VPN servers and clientsProject goalsautomate configuration management for embedded devicesallow to minimize repetition by using templatesprovide base logic that can be extended by third-party apps (seeExtending django-netjsonconfig)provide ways to support more firmwares by adding custom backendskeep the core as simple as possibleDeploy it in productionAn automated installer is available atansible-openwisp2.DependenciesPython >=3.6OpenSSLInstall stable version from pypiInstall from pypi:pipinstalldjango-netjsonconfigInstall development versionInstall tarball:pipinstallhttps://github.com/openwisp/django-netjsonconfig/tarball/masterAlternatively you can install via pip using git:pipinstall-egit+git://github.com/openwisp/django-netjsonconfig#egg=django-netjsonconfigIf you want to contribute, install your cloned fork:gitclonegit@github.com:<your_fork>/django-netjsonconfig.gitcddjango-netjsonconfigpythonsetup.pydevelopSetup (integrate in an existing django project)Adddjango_netjsonconfig,django.contrib.admin,sortedm2mandreversiontoINSTALLED_APPSin the following order:INSTALLED_APPS=[# other apps'openwisp_utils.admin_theme','django_netjsonconfig',# ensure the django admin comes after django-netjsonconfig'django.contrib.admin','sortedm2m','reversion'# optional, can be removed if not needed# ...]Add the controller URLs to your mainurls.py:urlpatterns=[# ... other urls in your project ...# controller URLs# used by devices to download/update their configuration# keep the namespace argument unchangedurl(r'^',include('django_netjsonconfig.controller.urls',namespace='controller')),# common URLs# shared among django-netjsonconfig components# keep the namespace argument unchangedurl(r'^',include('django_netjsonconfig.urls',namespace='netjsonconfig')),]Then run:./manage.pymigrateInstalling for developmentInstall sqlite:sudoapt-getinstallsqlite3libsqlite3-devopenssllibssl-devInstall your forked repo:gitclonegit://github.com/<your_fork>/django-netjsonconfigcddjango-netjsonconfig/pythonsetup.pydevelopInstall test requirements:pipinstall-rrequirements-test.txtCreate database:cdtests/./manage.pymigrate./manage.pycreatesuperuserLaunch development server:./manage.pyrunserverYou can access the admin interface athttp://127.0.0.1:8000/admin/.Run tests with:./runtests.pyHow 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 access the context
field by clicking on “Advanced Options (show)”.Then you can define the variables as a key, value dictionary (JSON formatted)
as shown 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.To do this, click on “Advanced Options (show)” in the edit template page:Then you can define the default values of the variables:Global variablesVariables can also be defined globally using theNETJSONCONFIG_CONTEXTsetting.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"}Signalsconfig_modifiedPath:django_netjsonconfig.signals.config_modifiedArguments:instance: instance ofConfigwhich got itsconfigmodifiedThis 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.It is not triggered when the device is created for the first time.This signal is used to trigger the update of the configuration on devices,
when the push feature is enabled (requires Device credentials).config_status_changedPath:django_netjsonconfig.signals.config_status_changedArguments:instance: instance ofConfigwhich got itsstatuschangedThis signal is emitted only when the configuration status of a device has changed.checksum_requestedPath:django_netjsonconfig.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:django_netjsonconfig.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.SettingsNETJSONCONFIG_BACKENDStype:tupledefault:(('netjsonconfig.OpenWrt','OpenWRT'),('netjsonconfig.OpenWisp','OpenWISP'),)Available configuration backends. For more information, seenetjsonconfig backends.NETJSONCONFIG_VPN_BACKENDStype:tupledefault:(('django_netjsonconfig.vpn_backends.OpenVpn','OpenVPN'),)Available VPN backends for VPN Server objects. For more information, seeOpenVPN netjsonconfig backend.A VPN backend must follow some basic rules in order to be compatible withdjango-netjsonconfig: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 usedNETJSONCONFIG_DEFAULT_BACKENDtype:strdefault:NETJSONCONFIG_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 theNETJSONCONFIG_BACKENDSsetting,
which isnetjsonconfig.OpenWrt.Setting it toNonewill force the user to choose explicitly.NETJSONCONFIG_DEFAULT_VPN_BACKENDtype:strdefault:NETJSONCONFIG_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 theNETJSONCONFIG_VPN_BACKENDSsetting,
which isdjango_netjsonconfig.vpn_backends.OpenVpn.Setting it toNonewill force the user to choose explicitly.NETJSONCONFIG_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.NETJSONCONFIG_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.NETJSONCONFIG_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.NETJSONCONFIG_SHARED_SECRETtype:strdefault:""A secret key which must be used by devices to performautomatic registration.This key MUST be explicitly set in production (ifsettings.DEBUG is False), otherwise
anImproperlyConfiguredexception will be raised on startup.NETJSONCONFIG_CONTEXTtype:dictdefault:{}Additional context that is passed to the default context of each device object.NETJSONCONFIG_CONTEXTcan be used to define system-wide configuration variables.For technical information about how variables are handled in the lower levels
of OpenWISP, seenetjsonconfig context: configuration variables.NETJSONCONFIG_DEFAULT_AUTO_CERTtype:booldefault:TrueThe default value of theauto_certfield for newTemplateobjects.Theauto_certfield is valid only for templates which havetypeset toVPNand indicates whether a new x509 certificate should be created
automatically for each configuration using that template.The automatically created certificates will also be removed when they are not
needed anymore (eg: when the VPN template is removed from a configuration object).NETJSONCONFIG_CERT_PATHtype:strdefault:/etc/x509The filesystem path where x509 certificate will be installed when
downloaded on routers whenauto_certis being used (enabled by default).NETJSONCONFIG_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.NETJSONCONFIG_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.NETJSONCONFIG_BACKEND_DEVICE_LISTtype:booldefault:TrueIn the device list page, the columnbackendand the backend filter are
shown by default.If this setting is set toFalsethese items will be removed from the UI.You may set this toFalseif you are using only one configuration backend
and having this UI element doesn’t add any value to your users.NETJSONCONFIG_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.NETJSONCONFIG_HARDWARE_ID_OPTIONStype:dictdefault:{'blank':notNETJSONCONFIG_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 fieldNETJSONCONFIG_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.Extending django-netjsonconfigdjango-netjsonconfigprovides a set of models, admin classes and generic views which can be imported,
extended and reused by third party apps.To extenddjango-netjsonconfig,you MUST NOTadd it tosettings.INSTALLED_APPS,
but you must create your own app (which goes intosettings.INSTALLED_APPS), import the
base classes from django-netjsonconfig and add your customizations.In order to help django find the static files and templates ofdjango-netjsonconfig,
you need to perform the steps described below.1. AddEXTENDED_APPSAdd the following to yoursettings.py:EXTENDED_APPS=('django_netjsonconfig','django_x509',)2. 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',]3. Addopenwisp_utils.loaders.DependencyLoaderAddopenwisp_utils.loaders.DependencyLoadertoTEMPLATESin yoursettings.py:TEMPLATES=[{'BACKEND':'django.template.backends.django.DjangoTemplates','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',],},}]Extending modelsThis example provides an example of how to extend the base models ofdjango-netjsonconfigby adding a relation to another django model namedOrganization.# models.py of your custom ``config`` appfromdjango.dbimportmodelsfromsortedm2m.fieldsimportSortedManyToManyFieldfromtaggit.managersimportTaggableManagerfromdjango_netjsonconfig.base.configimportAbstractConfig,TemplatesVpnMixinfromdjango_netjsonconfig.base.tagimportAbstractTaggedTemplate,AbstractTemplateTagfromdjango_netjsonconfig.base.templateimportAbstractTemplatefromdjango_netjsonconfig.base.vpnimportAbstractVpn,AbstractVpnClient# the model ``organizations.Organization`` is omitted for brevity# if you are curious to see a real implementation, check out django-organizations# https://github.com/bennylope/django-organizationsclassOrganizationMixin(models.Model):organization=models.ForeignKey('organizations.Organization')classMeta:abstract=TrueclassConfig(OrganizationMixin,TemplatesVpnMixin,AbstractConfig):templates=SortedManyToManyField('config.Template',related_name='config_relations',blank=True)vpn=models.ManyToManyField('config.Vpn',through='config.VpnClient',related_name='vpn_relations',blank=True)defclean(self):# your own validation logic here...passclassMeta(AbstractConfig.Meta):abstract=FalseclassTemplateTag(AbstractTemplateTag):classMeta(AbstractTemplateTag.Meta):abstract=FalseclassTaggedTemplate(AbstractTaggedTemplate):tag=models.ForeignKey('config.TemplateTag',related_name='%(app_label)s_%(class)s_items',on_delete=models.CASCADE)classMeta(AbstractTaggedTemplate.Meta):abstract=FalseclassTemplate(OrganizationMixin,AbstractTemplate):tags=TaggableManager(through='config.TaggedTemplate',blank=True)vpn=models.ForeignKey('config.Vpn',blank=True,null=True)defclean(self):# your own validation logic here...passclassMeta(AbstractTemplate.Meta):abstract=FalseclassVpn(OrganizationMixin,AbstractVpn):classMeta(AbstractVpn.Meta):abstract=FalseclassVpnClient(AbstractVpnClient):config=models.ForeignKey('config.Config',on_delete=models.CASCADE)vpn=models.ForeignKey('config.Vpn',on_delete=models.CASCADE)cert=models.OneToOneField('django_x509.Cert',on_delete=models.CASCADE,blank=True,null=True)classMeta(AbstractVpnClient.Meta):abstract=FalseExtending the adminFollowing the previousOrganizationexample, you can avoid duplicating the admin
code by importing the base admin classes and registering your models with.# admin.py of your app# these are your custom models, they must be imported before the abstract admin classesfrom.modelsimportConfig,Template,Vpnfromdjango.contribimportadminfromdjango_netjsonconfig.base.adminimport(AbstractConfigAdmin,AbstractConfigForm,AbstractTemplateAdmin,AbstractVpnAdmin,AbstractVpnForm,BaseForm)classConfigForm(AbstractConfigForm):classMeta(AbstractConfigForm.Meta):model=ConfigclassConfigAdmin(AbstractConfigAdmin):form=ConfigFormclassTemplateForm(BaseForm):classMeta(BaseForm.Meta):model=TemplateclassTemplateAdmin(AbstractTemplateAdmin):form=TemplateFormclassVpnForm(AbstractVpnForm):classMeta(AbstractVpnForm.Meta):model=VpnclassVpnAdmin(AbstractVpnAdmin):form=VpnFormadmin.site.register(Config,ConfigAdmin)admin.site.register(Template,TemplateAdmin)admin.site.register(Vpn,VpnAdmin)Extending controller viewsIf your use case doesn’t vary a lot from the base one, you may also want
to try to reuse the controller views:# your_config_app.controller.viewsfrom..modelsimportDevice,Vpnfromdjango_netjsonconfig.controller.genericsimport(BaseDeviceChecksumView,BaseDeviceDownloadConfigView,BaseDeviceRegisterView,BaseDeviceReportStatusView,BaseVpnChecksumView,BaseVpnDownloadConfigView)classDeviceChecksumView(BaseDeviceChecksumView):model=DeviceclassDeviceDownloadConfigView(BaseDeviceDownloadConfigView):model=DeviceclassDeviceReportStatusView(BaseDeviceReportStatusView):model=DeviceclassDeviceRegisterView(BaseDeviceRegisterView):model=DeviceclassVpnChecksumView(BaseVpnChecksumView):model=VpnclassVpnDownloadConfigView(BaseVpnDownloadConfigView):model=Vpndevice_checksum=DeviceChecksumView.as_view()device_download_config=DeviceDownloadConfigView.as_view()device_report_status=DeviceReportStatusView.as_view()device_register=DeviceRegisterView.as_view()vpn_checksum=VpnChecksumView.as_view()vpn_download_config=VpnDownloadConfigView.as_view()Controller URLsIf you are not making drastic changes to the controller views, you can avoid duplicating the URL
logic by using theget_controller_urlsfunction. Put this in your controllerurls.py:# your_config_app.controller.urlsfromdjango_netjsonconfig.utilsimportget_controller_urlsfrom.importviewsurlpatterns=get_controller_urls(views)Extending AppConfigYou may want to reuse theAppConfigclass ofdjango-netjsonconfigtoo:fromdjango_netjsonconfig.appsimportDjangoNetjsonconfigAppclassMyOwnConfig(DjangoNetjsonconfigApp):name='yourapp.config'label='config'def__setmodels__(self):from.modelsimportConfig,VpnClient# these are your custom modelsself.config_model=Configself.vpnclient_model=VpnClientReal world extensions of django-netjsonconfigFor full working examples of django proejcts which extenddjango-netjsonconfig, see:openwisp/openwisp-controllerinnovationgarage/extendnetjson_projectScreenshotsContributingAnnounce your intentions in theOpenWISP Mailing ListFork this repo and install itFollowPEP8, Style Guide for Python CodeWrite codeWrite tests for your codeEnsure all tests passEnsure test coverage does not decreaseDocument your changesSend pull requestChangelogSeeCHANGES.LicenseSeeLICENSE.SupportSeeOpenWISP Support Channels.