Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Expose the networking API over HTTP (#1064)
# Implementation PBI description --- In the previous PBI we documented a possible API and also created a POC but it just allowed to query some configuration, so we have extended the PR with the API implementation ## Implemented Network API <details> <summary>Resource <b>/network/state</b></summary> - GET: get the current network state and general information **Request example** ```json $ curl -s localhost:3000/api/network/state -H @headers.txt | j { "connectivity": true, "wireless_enabled": false, "networking_enabled": true } ``` - PUT: Update the current network state (currently only enabling wireless is supported) **Request example** ```json $ cat state.json { "connectivity": true, "wireless_enabled": true, "networking_enabled": true } $ curl -s -X POST localhost:3000/api/network/state -H @headers.txt -H "Content-Type: application/json" -d @state.json | jq { "connectivity": true, "wireless_enabled": true, "networking_enabled": true } ``` </details> <details> <summary>Resource <b>/network/devices</b></summary> - GET: list of known devices - Attributes - Name: String - Type: Enum **Request example** ```json $ curl -s localhost:3000/network/devices -H @headers.txt | jq [ { "name": "enp2s0f0", "type": "Ethernet" }, { "name": "wlp3s0", "type": "Wireless" }, { "name": "eth0", "type": "Ethernet" } ``` </details> <details> <summary>Resource <b>/network/connections</b></summary> - GET: list of managed / known connections **Request example** ```json $curl -s localhost:3000/api/network/connections -H @headers.txt | jq [ { "id": "lo", "method4": "manual", "method6": "manual", "addresses": [ "127.0.0.1/8", "::1" ], "interface": "lo" }, { "id": "Wired connection 1", "method4": "manual", "gateway4": "192.168.0.1", "method6": "auto", "addresses": [ "192.168.0.230/24" ], "nameservers": [ "192.168.0.1" ], "interface": "eth0" }, { "id": "AgamaNetwork", "method4": "disabled", "method6": "disabled", "wireless": { "password": "agama.test", "security": "wpa-psk", "ssid": "AgamaNetwork2", "mode": "infrastructure" } } ] ``` - POST: create a new connection ** Request example ** ```json $ curl -X POST localhost:3000/api/network/connections -H @headers.txt -H "Content-Type: application/json" -d @data_wifi.json "ce91e1b8-7da4-4bff-a286-91610a8cb762" ``` #### /network/connections/:id - GET: find a connection by its uuid or id **(not implemented)** - PATCH: update connection - DELETE: remove the connection The attributes for updating a connection or creating a new one could follow the same schema used for the profile: ```json "connections": { "description": "Network connections to be defined", "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "id": { "description": "Connection ID", "type": "string" }, "interface": { "description": "The name of the network interface bound to this connection", "type": "string" }, "mac-address": { "description": "Custom mac-address (can also be 'preserve', 'permanent', 'random' or 'stable')", "type": "string" }, "method4": { "description": "IPv4 configuration method (e.g., 'auto')", "type": "string", "enum": [ "auto", "manual", "link-local", "disabled" ] }, "method6": { "description": "IPv6 configuration method (e.g., 'auto')", "type": "string", "enum": [ "auto", "manual", "link-local", "disabled" ] }, "gateway4": { "description": "Connection gateway address (e.g., '192.168.122.1')", "type": "string" }, "gateway6": { "description": "Connection gateway address (e.g., '::ffff:c0a8:7a01')", "type": "string" }, "addresses": { "type": "array", "items": { "description": "Connection addresses", "type": "string", "additionalProperties": false } }, "nameservers": { "type": "array", "items": { "description": "Nameservers (IPv4 and/or IPv6 are allowed)", "type": "string", "additionalProperties": false } }, "wireless": { "type": "object", "description": "Wireless configuration", "additionalProperties": false, "properties": { "password": { "type": "string" }, "security": { "type": "string" }, "ssid": { "type": "string" }, "mode": { "type": "string", "enum": [ "infrastructure", "adhoc", "mesh", "ap" ] } } }, "bond": { "type": "object", "description": "Bonding configuration", "additionalProperties": false, "properties": { "mode": { "type": "string" }, "options": { "type": "string" }, "ports": { "type": "array", "items": { "description": "A list of the interfaces or connections to be bonded", "type": "string", "additionalProperties": false } } } }, "match": { "type": "object", "description": "Match settings", "additionalProperties": false, "properties": { "kernel": { "type": "array", "items": { "description": "A list of kernel command line arguments to match", "type": "string", "additionalProperties": false } }, "interface": { "type": "array", "items": { "description": "A list of interface names to match", "type": "string", "additionalProperties": false } }, "driver": { "type": "array", "items": { "description": "A list of driver names to match", "type": "string", "additionalProperties": false } }, "path": { "type": "array", "items": { "description": "A list of paths to match against the ID_PATH udev property of devices", "type": "string", "additionalProperties": false } } } } }, "required": [ "id" ] ``` Which means that for bringing up or down an specific connection / device could need an specific method or it could be handle as any other attribute. </details> <details> <summary>Resource <b>/network/wifi</b></summary> - GET: list of scanned WiFi networks **Request example** ```json $curl -s localhost:3000/api/network/wifi -H @headers.txt | jq [ "Agama", "Agama2" ] ``` </details> <details> <summary>Resource <b>/network/system/apply</b></summary> - PUT: Apply the changes to the system **Request example** ```json curl -s -X PUT localhost:3000/api/network/system/apply -H @headers.txt | jq null ``` </details> ## Testing <details> <summary><b>Tested manually</b></summary> ```bash suse@vikingo-laptop:~$ ./generate_headers.sh Obtained token 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MTEwMzU4MDV9.nifdrxlq3ZKK4LoVjY-2sPb9ZzXIq5M3Xxqudy8zqvE' suse@vikingo-laptop:~$ ./get_state.sh { "connectivity": true, "wireless_enabled": true, "networking_enabled": true } suse@vikingo-laptop:~$ ./get_connections.sh [ { "id": "lo", "method4": "manual", "method6": "manual", "addresses": [ "127.0.0.1/8", "::1" ], "interface": "lo" }, { "id": "Wired connection 1", "method4": "manual", "gateway4": "192.168.0.1", "method6": "auto", "addresses": [ "192.168.0.230/24" ], "nameservers": [ "192.168.0.1" ], "interface": "eth0" }, { "id": "AgamaNetwork", "method4": "disabled", "method6": "disabled", "wireless": { "password": "agama.test", "security": "wpa-psk", "ssid": "AgamaNetwork2", "mode": "infrastructure" } } ] suse@vikingo-laptop:~$ ./get_wifis.sh [ "AgamaNetwork", "AgamaNetwork2", ] <details> <summary>Resource <b>/network/system/apply</b></summary> - PUT: Appl list of scanned WiFi networks **Request example** curl -s -X PUT localhost:3000/api/network/system/apply -H @headers.txt | jq # Scripts used for obtain and modify the configuration suse@vikingo-laptop:~$ cat generate_headers.sh #!/bin/bash TOKEN=$(curl -s -X POST localhost:3000/api/auth -H 'Content-Type: application/json' -d '{"password": "agama.auth"}' | jq -r ".token") echo "Obtained token '${TOKEN}'" echo -n "Authorization: Bearer " >headers.txt echo $TOKEN >>headers.txt suse@vikingo-laptop:~$ cat get_connections.sh #!/bin/bash curl -s localhost:3000/api/network/connections -H @headers.txt | jq suse@vikingo-laptop:~$ cat get_wifis.sh #!/bin/bash curl -s localhost:3000/api/network/wifi -H @headers.txt | jq suse@vikingo-laptop:~$ cat get_state.sh #!/bin/bash curl -s localhost:3000/api/network/state -H @headers.txt | jq cat change_state.sh #!/bin/bash curl -X POST localhost:3000/api/network/state -H @headers.txt -H "Content-Type: application/json" -d @state.json ``` </details> # Documentation PBI description --- ## Problem We would like to expose the Networking API over http which is currently not available. ## Solution Document an HTTP alternative as well as provide a POC exposing it. ## API Design Our Rest API could expose the general state and configuration of the network as well as the devices and connections resources below the network namespace as shown below: <details> <summary>Resource <b>/network/state</b></summary> - GET: get the current network state and general information </details> <details> <summary>Resource <b>/network/config</b></summary> - GET: get the general configuration like wireless enabled or not - PATCH: update general configuration </details> <details> <summary>Resource <b>/network/devices</b></summary> - GET: list of known devices - Attributes - Name: String - Type: Enum #### /network/devices/:name - GET: find a device by its name **Request example** ```json $ curl -s localhost:3000/network/devices -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDkzOTEzMDN9.RYUSttHGhGiqmSDy01Kf3-fripqohA3Li0pIneb-t_Y' | jq [ { "name": "enp2s0f0", "type": "Ethernet" }, { "name": "wlp3s0", "type": "Wireless" }, { "name": "eth0", "type": "Ethernet" } ``` </details> <details> <summary>Resource <b>/network/connections</b></summary> - GET: list of managed / known connections - POST: create a new connection **Request example** ```json suse@vikingo-laptop:~$ curl -s localhost:3000/network/connections -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDkzOTEzMDN9.RYUSttHGhGiqmSDy01Kf3-fripqohA3Li0pIneb-t_Y' | jq [ { "id": "Wired connection 1", "uuid": "530d40c2-c580-43a7-b93e-c443702529a2", "mac_address": "", "ip_config": { "method4": "Manual", "method6": "Auto", "addresses": [ "192.168.0.230/24" ], "nameservers": [ "192.168.0.1" ], "gateway4": "192.168.0.1", "routes4": [ { "destination": "0.0.0.0/24", "next_hop": "192.168.0.1" } ], "routes6": [] }, "status": "Up", "interface": "eth0", "port_config": "None", "match_config": {}, "config": "Ethernet" }, { "id": "AgamaTest", "uuid": "e7c684d5-f8e9-43a0-b303-decfe2883e53", "mac_address": "", "ip_config": { "method4": "Auto", "method6": "Auto", "routes4": [], "routes6": [] }, "status": "Up", "interface": "wlp3s0", "port_config": "None", "match_config": {}, "config": { "Wireless": { "mode": "Infra", "ssid": "AgamaTest", "security": "WPA2", "wep_security": { "auth_alg": "Open", "wep_key_type": "Unknown", "wep_key_index": 0 }, "hidden": false } } }, { "id": "AgamaTest2", "uuid": "bc323ed0-18d8-4f85-b90e-5b7907e3b711", "mac_address": "", "ip_config": { "method4": "Auto", "method6": "Auto", "routes4": [], "routes6": [] }, "status": "Up", "interface": "wlp3s0", "port_config": "None", "match_config": {}, "config": { "Wireless": { "mode": "Infra", "ssid": "AgamaTest2", "security": "WPA2", "wep_security": { "auth_alg": "Open", "wep_key_type": "Unknown", "wep_key_index": 0 }, "hidden": false } } }, { "id": "lo", "uuid": "66d7c54a-735a-42c6-b412-572a1134efec", "mac_address": "", "ip_config": { "method4": "Manual", "method6": "Manual", "addresses": [ "127.0.0.1/8", "::1" ], "routes4": [], "routes6": [] }, "status": "Up", "interface": "lo", "port_config": "None", "match_config": {}, "config": "Loopback" } ] ``` #### /network/connections/:id - GET: find a connection by its uuid or id - PATCH: update connection - DELETE: remove the connection The attributes for updating a connection or creating a new one could follow the same schema used for the profile: ```json "connections": { "description": "Network connections to be defined", "type": "array", "items": { "type": "object", "additionalProperties": false, "properties": { "id": { "description": "Connection ID", "type": "string" }, "interface": { "description": "The name of the network interface bound to this connection", "type": "string" }, "mac-address": { "description": "Custom mac-address (can also be 'preserve', 'permanent', 'random' or 'stable')", "type": "string" }, "method4": { "description": "IPv4 configuration method (e.g., 'auto')", "type": "string", "enum": [ "auto", "manual", "link-local", "disabled" ] }, "method6": { "description": "IPv6 configuration method (e.g., 'auto')", "type": "string", "enum": [ "auto", "manual", "link-local", "disabled" ] }, "gateway4": { "description": "Connection gateway address (e.g., '192.168.122.1')", "type": "string" }, "gateway6": { "description": "Connection gateway address (e.g., '::ffff:c0a8:7a01')", "type": "string" }, "addresses": { "type": "array", "items": { "description": "Connection addresses", "type": "string", "additionalProperties": false } }, "nameservers": { "type": "array", "items": { "description": "Nameservers (IPv4 and/or IPv6 are allowed)", "type": "string", "additionalProperties": false } }, "wireless": { "type": "object", "description": "Wireless configuration", "additionalProperties": false, "properties": { "password": { "type": "string" }, "security": { "type": "string" }, "ssid": { "type": "string" }, "mode": { "type": "string", "enum": [ "infrastructure", "adhoc", "mesh", "ap" ] } } }, "bond": { "type": "object", "description": "Bonding configuration", "additionalProperties": false, "properties": { "mode": { "type": "string" }, "options": { "type": "string" }, "ports": { "type": "array", "items": { "description": "A list of the interfaces or connections to be bonded", "type": "string", "additionalProperties": false } } } }, "match": { "type": "object", "description": "Match settings", "additionalProperties": false, "properties": { "kernel": { "type": "array", "items": { "description": "A list of kernel command line arguments to match", "type": "string", "additionalProperties": false } }, "interface": { "type": "array", "items": { "description": "A list of interface names to match", "type": "string", "additionalProperties": false } }, "driver": { "type": "array", "items": { "description": "A list of driver names to match", "type": "string", "additionalProperties": false } }, "path": { "type": "array", "items": { "description": "A list of paths to match against the ID_PATH udev property of devices", "type": "string", "additionalProperties": false } } } } }, "required": [ "id" ] ``` Which means that for bringing up or down an specific connection / device could need an specific method or it could be handle as any other attribute. </details> <details> <summary>Resource <b>/network/wifi_networks</b></summary> - GET: list of scanned wifi networks </details> **Note:** Which attributes should be required and exposed needs to be discussed, and probably we should expose the persistent and running configuration maybe as part of the same resource or separately. <details> <summary>Example of the <b>OpenAPI</b> generated documentation based on the implemented <b>POC code</b></summary> ```json { "openapi": "3.0.3", "info": { "title": "agama-dbus-server", "description": "Agama web API description", "license": { "name": "" }, "version": "0.1.0" }, "paths": { "/network/connections": { "get": { "tags": [ "crate::network::web" ], "operationId": "connections", "responses": { "200": { "description": "List of known connections", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Connection" } } } } } } } }, "/network/devices": { "get": { "tags": [ "crate::network::web" ], "operationId": "devices", "responses": { "200": { "description": "List of devices", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Device" } } } } } } } } }, "components": { "schemas": { "Connection": { "type": "object", "description": "Represents a known network connection.", "required": [ "id", "uuid", "mac_address", "ip_config", "status", "port_config", "match_config", "config" ], "properties": { "config": { "$ref": "#/components/schemas/ConnectionConfig" }, "controller": { "allOf": [ { "$ref": "#/components/schemas/Uuid" } ], "nullable": true }, "id": { "type": "string" }, "interface": { "type": "string", "nullable": true }, "ip_config": { "$ref": "#/components/schemas/IpConfig" }, "mac_address": { "$ref": "#/components/schemas/MacAddress" }, "match_config": { "$ref": "#/components/schemas/MatchConfig" }, "port_config": { "$ref": "#/components/schemas/PortConfig" }, "status": { "$ref": "#/components/schemas/Status" }, "uuid": { "$ref": "#/components/schemas/Uuid" } } }, "Device": { "type": "object", "description": "Network device", "required": [ "name", "type" ], "properties": { "name": { "type": "string" }, "type": { "$ref": "#/components/schemas/DeviceType" } } }, "DeviceType": { "type": "string", "enum": [ "Loopback", "Ethernet", "Wireless", "Dummy", "Bond", "Vlan", "Bridge" ] }, "NetworkState": { "type": "object", "required": [ "devices", "connections" ], "properties": { "connections": { "type": "array", "items": { "$ref": "#/components/schemas/Connection" } }, "devices": { "type": "array", "items": { "$ref": "#/components/schemas/Device" } } } } } } } ``` </details> ### Signals / Websocket notifications With **websockets** we can carry more information than what is usually emitted over **DBUS** therefore we could subscribe to **DBUS** signals in the backend for the different resources submitting more information over the websocket, for example for any new device connected or any change in a connection property but have not gone over what to notify so far as we are not subscribing to signals in the backend at all and we are only sending when a connections has been added or removed. ## Testing <details> <summary><b>Tested manually</b></summary> ```bash $ curl -s -X POST localhost:3000/authenticate -H 'Content-Type: application/json' -d '{"password": "your_password"}' | jq -r ".token" eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDkzOTEzMDN9.RYUSttHGhGiqmSDy01Kf3-fripqohA3Li0pIneb-t_Y $ curl -s localhost:3000/network/devices -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDkzOTEzMDN9.RYUSttHGhGiqmSDy01Kf3-fripqohA3Li0pIneb-t_Y' | jq [ { "name": "enp2s0f0", "type": "Ethernet" }, { "name": "wlp3s0", "type": "Wireless" }, { "name": "eth0", "type": "Ethernet" } suse@vikingo-laptop:~$ curl -s localhost:3000/network/connections -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDkzOTEzMDN9.RYUSttHGhGiqmSDy01Kf3-fripqohA3Li0pIneb-t_Y' | jq [ { "id": "Wired connection 1", "uuid": "530d40c2-c580-43a7-b93e-c443702529a2", "mac_address": "", "ip_config": { "method4": "Manual", "method6": "Auto", "addresses": [ "192.168.0.230/24" ], "nameservers": [ "192.168.0.1" ], "gateway4": "192.168.0.1", "routes4": [ { "destination": "0.0.0.0/24", "next_hop": "192.168.0.1" } ], "routes6": [] }, "status": "Up", "interface": "eth0", "port_config": "None", "match_config": {}, "config": "Ethernet" }, { "id": "AgamaTest", "uuid": "e7c684d5-f8e9-43a0-b303-decfe2883e53", "mac_address": "", "ip_config": { "method4": "Auto", "method6": "Auto", "routes4": [], "routes6": [] }, "status": "Up", "interface": "wlp3s0", "port_config": "None", "match_config": {}, "config": { "Wireless": { "mode": "Infra", "ssid": "AgamaTest", "security": "WPA2", "wep_security": { "auth_alg": "Open", "wep_key_type": "Unknown", "wep_key_index": 0 }, "hidden": false } } }, { "id": "AgamaTest2", "uuid": "bc323ed0-18d8-4f85-b90e-5b7907e3b711", "mac_address": "", "ip_config": { "method4": "Auto", "method6": "Auto", "routes4": [], "routes6": [] }, "status": "Up", "interface": "wlp3s0", "port_config": "None", "match_config": {}, "config": { "Wireless": { "mode": "Infra", "ssid": "AgamaTest2", "security": "WPA2", "wep_security": { "auth_alg": "Open", "wep_key_type": "Unknown", "wep_key_index": 0 }, "hidden": false } } }, { "id": "lo", "uuid": "66d7c54a-735a-42c6-b412-572a1134efec", "mac_address": "", "ip_config": { "method4": "Manual", "method6": "Manual", "addresses": [ "127.0.0.1/8", "::1" ], "routes4": [], "routes6": [] }, "status": "Up", "interface": "lo", "port_config": "None", "match_config": {}, "config": "Loopback" } ] ``` </details>
- Loading branch information