openwifi
table of contents
A management tool for OpenWrt/LEDE devices.
Getting Started
Getting started using docker
There are docker images that will help you to getting started with OpenWifi. First of all clone the repo
git clone https://github.com/berlin-open-wireless-lab/wrtmgmt.git
If you want to use the webviews clone them into the Plugin folder
cd Plugins
git clone https://github.com/berlin-open-wireless-lab/OpenWifiWeb.git
cd ..
Then there are scripts that help downloading and starting the docker image:
cd Docker
./pull_image_and_run.sh
When the scripts are finished you can use OpenWifi on localhost with port 6543.
The images mount the git repo - so you can easily make changes and try them out. The ini-file
for the image is development_listen_global.ini
in the repository root directory.
Getting started manually
sudo apt-get install rabbitmq-server python3-pip git redis-server
git clone https://github.com/berlin-open-wireless-lab/wrtmgmt.git
cd wrtmgmt
pip3 install virtualenv
virtualenv venv
. venv/bin/activate
pip install -r requirements.txt
python setup.py develop
initialize_openwifi_db development.ini
echo development is ready now
pserve development.ini &
celery -A openwifi.jobserver.tasks worker --loglevel=info
celery -A openwifi.jobserver.tasks beat
Dependencies:
- rabbitmq <- for states and gearman jobs
- redis <- storing real-time information about nodes
API
This is a short overview about the rest-style API. It uses JSON as a serialization format.
Nodes
To list the UUIDs of all nodes just do a get
to /nodes
curl localhost:6543/nodes
["0a7b5cad-7435-95fb-1ba0-0242ac110003", "0a7b5cad-7435-95fb-1ba0-0242ac110004"]
To get all infos about a node do a get
to /nodes/UUID
curl localhost:6543/nodes/0a7b5cad-7435-95fb-1ba0-0242ac110003 | python -m json.tool
{
"distribution": "LEDE",
"name": "LEDE",
"uuid": "0a7b5cad-7435-95fb-1ba0-0242ac110003",
"configuration": { ... },
"address": "172.17.0.3 ",
"password": "59543c74cd26bbea",
"login": "root",
"configured": true,
"version": "17.01-SNAPSHOT"
}
You can add a new node by doing a post
to /nodes. The UUID
-key is optional. Name, address, distribution, version, login and password are mandatory fields. The return code is the UUID of the new node.
curl -H 'content-type: application/json' -X POST -d '{"name":"test","address":"127.0.0.1", "distribution":"LEDE", "version":"some version name", "login":"root", "password":"some password"}' localhost:6543/nodes
"79926581-2cd0-52f6-bb8d-4e8ed33271b9"
In the same way you can change node parameters with doing a post
to /nodes/UUID
. Furthermore you can delete a node by sending delete
to /nodes/UUID
.
You can display the changelog (how the configuration has been changed during syncing) by doing a get
to /nodes/UUID/diff
.
User management and access control
To use the user management and access control it has to be enabled in the ini file
. The key is called "openwifi.useAuth"
and must be set to "true"
. By default if no other user exists an admin user with the credentials admin:admin is created.
In order to change user data you have to login as an admin user:
curl -c curl_cookies localhost:6543/login -d '{"login":"admin","password":"admin"}' -X POST -H 'content-type: application/json'
Now you can list all users:
curl -b curl_cookies localhost:6543/users
{"admin": "465EWO"}
It lists the logins as keys with the user id as a value. You get more information about a user on /users/USER_ID
:
curl -b curl_cookies localhost:6543/users/465EWO
{"admin": true, "login": "admin"}
You can also modify a user by doing a POST
to /users/USER_ID
. The following keys are valid (and all of them are optional):
Key name | description |
---|---|
login |
change the login to this string value |
password |
change the password to this string value |
admin |
change admin status to this bool value |
Access to nodes is granted by access objects. You can create new ones by doing a post
to /access
. In the post you need to provide a JSON-Object with the following optional fields:
Key name | description |
---|---|
userid |
id of the user to add to the access object |
apikeyid |
id of the apikey to add to the access object |
data |
actual access data - more on that below |
access_all_nodes |
a boolean indicating that access is allowed to all nodes |
nodes |
list of UUIDs that should be added to this access |
The data field is a list of JSON-objects with the following fields:
Key name | description |
---|---|
type |
either “pathstring” or “query” |
access |
required if type is “pathstring” values are rw, ro or none |
string |
required if type is “pathstring” - the actual path |
query |
required if type is “query” - the query (see below) that is allowed |
An access can be modified by providing the same keys with a POST
to /access/ACCESSID
.
If a user is logged in he or she can change the password with a POST
to /password
. It takes a JSON-object with the single password
key.
Services
Another program can subscribe to OpenWifi to automatically change configuration options if the node matches certain criteria. To do so it has to provide a shell script that is run on the node and the output (stdout) is compared to a match string. If both match the node gets the name of the service as a capability. If the node has the name of the service as a capability the list of queries is run on the node.
You can add a service by doing a post
to /service
with the following options:
Key name | description |
---|---|
name |
the name of the service and the capability |
queries |
a list of queries (see below) |
capability_script |
the shell script that is run on the node (commands) |
capabaility_match |
the string the shell script has to match |
As usual you get a list of registered services by doing a get
to /service
and can modify and delete services with a post
or a delete
to /service/SERVICE_ID
.
Master Configurations
Master configurations are the representation of the graph configuration model. A master configuration is associated with a node and contains all configurations and links between the configuration. To list all master configurations do a get
to /masterConfig
- it will return a list of JSON-Objects with the id
and its associated nodes (assoc
) as keys.
If you do a get
to /masterConfig/ID
it will return a JSON-Object with a list of the vertices (key is nodes
) and edges (key is edges
) of the graph representation. If you do a delete
it will delete the master configuration.
A get
to /masterConfig/ID/json
will return a UCI compatible JSON-Object.
Config Database Interaction
DB-Queries
Queries are the way to display or change values from the configuration database. The endpoint is /masterConfig/ID/query
. It has the following keys:
Key | Value |
---|---|
package |
optional package name |
name |
optional config name |
type |
optional config type |
matchOptions |
optional dict of option-value pairs to match, dot is possible like in option, use null if you just want to check if the option exists |
option |
optional option name, it is possible to go though a link with dots like: linkname.option |
set |
optional set option to this value |
add_config |
add config, type and package are mandatory for new configs, use either “new” for a new config, “new-nonexistent” to just create if no other exists, or a node-id to add a config |
add_options |
optional dict of key-value pairs that should be added to found configs |
del_options |
optional list of options to remove |
A query works in the way that it first filters all configurations according to a criteria (like package
, name
, type
, matchOptions
). If you add option
the option value is returned or you can change it’s value with set
. You can also add options (add_options
) or delete (del_options
) them. You can also add a configuration with the given matching criteria (add_config
).
Node info
You can get information about a node by doing a GET
to /masterConfig/Node/NODEID
. This information contains the id, if it is a link or a config node and in case of a config node the config and the path to this config. In case of a link it also contains the link data (which is currently its name).
Connect two config nodes
You can connect two config nodes by doing a POST
to /masterConfig/Node/FROM_NODE/link/TO_NODE
. The data should contain the data that is associated with the link (currently used for its name).
Plugins
Plugins are realized by having special named entry points. There is an example plugin that demonstrates the possible functionality.
To use plugins with the docker images: just clone the repository into the Plugins
folder and restart the image.
Available Plugins
OpenWifiExamplePlugin
This Plugin demonstrates how to implement a plugin.
OpenWifiWeb
This Plugin adds web views to the OpenWifiCore application.
OpenWifiLocation
This Plugin implements a geolocation lookup via GoogleGeo API with the help of a scan of nearby SSIDs.
OpenWifiIcinga
This Plugin registers a node to icinga for monitoring.
OpenWifiTemplates
This adds the old templating system to OpenWifi.
Architecture
As stated previously the plugins are realized by special entry points. The entry point group is OpenWifi.plugin
. See also the setup.py of the example plugin. The following subsections explain the entry points and what they do in more details.
Routes and global views
If you want to register new routes in your plugin you have to point the entry point addPluginRoutes
to a method that gets the pyramid config
object as a parameter.
Example from the example Plugin:
def addPluginRoutes(config):
config.add_route('testplugin', '/testplugin')
config.add_route('testplugin_assign', '/testplugin/add/{uuid}')
return "Testplugin"
The return string is used for logging purposes. If you want to register views to the main menu you need the globalPluginViews
entry point. This points to a list of views you want to register. The views you want to register are made up of a list containing the route name and the displayed name in the menu.
Example from the example Plugin:
globalTestpluginViews = [['testplugin', 'Testplugin']]
Tasks
A plugin might register new tasks to the job server. To do so use the addJobserverTasks
entry point. You get the celery app as a parameter. Read more about registering tasks in the celery documentation.
Example from the example Plugin:
def addJobserverTasks(app):
@app.task
def testPluginTask():
print("testplugin task")
Database Models
You can add new models to the database. Use the models
entry point and point to a string containing the module with the new models. Have a look at the example plugin models.
Action on device registration
You can perform actions on registration of a new node. To do that use the onDeviceRegister
entry point. You get the UUID as a parameter.
Here is the example from the example Plugin:
def testOnDeviceRegister(uuid):
from openwifi.models import OpenWrt, DBSession
device = DBSession.query(OpenWrt).get(uuid)
print("testplugin " + str(device))
Communication
Communication uses a abstract class to define class methods that are invoked when OpenWifi tries to communicate with the node. This is the abstract class:
class OpenWifiCommunication(metaclass=ABCMeta):
@ClassProperty
@classmethod
def string_identifier_list(self): pass
@abstractclassmethod
def get_config(self, device, DBSession): pass
@abstractclassmethod
def update_config(self, device, DBSession): pass
@abstractclassmethod
def update_status(self, device, redisDB): pass
@abstractclassmethod
def update_sshkeys(self, device, DBSession): pass
@abstractclassmethod
def exec_on_device(self, device, DBSession, cmd, prms): pass
The string identifier acts as a way to determine if this class should use for the node for communication and must therefore match the node’s communication_protocol
property. The rest of the methods should be quiet self explanatory.
To register a new communication class use the communication
entry point and point it to the new class.