mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-05-19 14:16:47 -04:00
First attempt at kea dhcp support
This commit is contained in:
70
front/plugins/kea_api/README.md
Executable file
70
front/plugins/kea_api/README.md
Executable file
@@ -0,0 +1,70 @@
|
||||
## Overview
|
||||
|
||||
A plugin allowing for importing devices from the Kea DHCP API.
|
||||
https://www.isc.org/kea/
|
||||
|
||||
And specifically:
|
||||
https://kea.readthedocs.io/en/kea-2.6.3/api.html#lease4-get-all
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
To enable the API, first you want to add something like this to your main kea configuration (this is for debian 13):
|
||||
|
||||
```json
|
||||
"control-socket": {
|
||||
"socket-type": "unix",
|
||||
"socket-name": "/run/kea/kea4-ctrl-socket"
|
||||
},
|
||||
|
||||
"hooks-libraries": [
|
||||
{
|
||||
"library": "/usr/lib/x86_64-linux-gnu/kea/hooks/libdhcp_lease_cmds.so"
|
||||
}
|
||||
],
|
||||
```
|
||||
|
||||
|
||||
And you need to install kea-ctrl-agent, with a config that looks something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"Control-agent": {
|
||||
"http-host": "127.0.0.1",
|
||||
"http-port": 8000,
|
||||
|
||||
"authentication": {
|
||||
"type": "basic",
|
||||
"realm": "Kea Control Agent",
|
||||
"directory": "/etc/kea",
|
||||
"clients": [
|
||||
{
|
||||
"user": "kea-api",
|
||||
"password-file": "kea-api-password"
|
||||
}
|
||||
]
|
||||
},
|
||||
"control-sockets": {
|
||||
"dhcp4": {
|
||||
"socket-type": "unix",
|
||||
"socket-name": "/run/kea/kea4-ctrl-socket"
|
||||
}
|
||||
},
|
||||
"loggers": [
|
||||
{
|
||||
"name": "kea-ctrl-agent",
|
||||
"output-options": [
|
||||
{
|
||||
"output": "stdout",
|
||||
"pattern": "%-5p %m\n"
|
||||
}
|
||||
],
|
||||
"severity": "INFO",
|
||||
"debuglevel": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You will need to configure the plugin with the URL to the API, and the username and password configured above (from kea-api-password file in the example)
|
||||
455
front/plugins/kea_api/config.json
Normal file
455
front/plugins/kea_api/config.json
Normal file
@@ -0,0 +1,455 @@
|
||||
{
|
||||
"code_name": "kea_api",
|
||||
"unique_prefix": "KEALSS",
|
||||
"plugin_type": "device_scanner",
|
||||
"execution_order" : "Layer_3",
|
||||
"enabled": true,
|
||||
"data_source": "script",
|
||||
"data_filters": [
|
||||
{
|
||||
"compare_column": "objectPrimaryId",
|
||||
"compare_operator": "==",
|
||||
"compare_field_id": "txtMacFilter",
|
||||
"compare_js_template": "'{value}'.toString()",
|
||||
"compare_use_quotes": true
|
||||
}
|
||||
],
|
||||
"show_ui": true,
|
||||
"localized": ["display_name", "description", "icon"],
|
||||
"mapped_to_table": "CurrentScan",
|
||||
"display_name": [{"language_code": "en_us", "string": "Kea DHCP API"}],
|
||||
"icon": [{"language_code": "en_us", "string": "<i class=\"fa-solid fa-hourglass-half\"></i>"}],
|
||||
"description": [{"language_code": "en_us", "string": "Imports leases via Kea Control Agent REST API"}],
|
||||
"database_column_definitions": [
|
||||
{
|
||||
"column": "index",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "none",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Index"}]
|
||||
},
|
||||
{
|
||||
"column": "objectPrimaryId",
|
||||
"mapped_to_column": "scanMac",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "device_mac",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "MAC address"}]
|
||||
},
|
||||
{
|
||||
"column": "objectSecondaryId",
|
||||
"mapped_to_column": "scanLastIP",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "device_ip",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "IP" }]
|
||||
},
|
||||
{
|
||||
"column": "dateTimeCreated",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Created"}]
|
||||
},
|
||||
{
|
||||
"column": "dateTimeChanged",
|
||||
"mapped_to_column": "scanLastConnection",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Changed"}]
|
||||
},
|
||||
{
|
||||
"column": "watchedValue1",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Is active"}]
|
||||
},
|
||||
{
|
||||
"column": "watchedValue2",
|
||||
"mapped_to_column": "scanName",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Hostname"}]
|
||||
},
|
||||
{
|
||||
"column": "watchedValue4",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": true,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "State"}]
|
||||
},
|
||||
{
|
||||
"column": "userData",
|
||||
"css_classes": "col-sm-2",
|
||||
"show": false,
|
||||
"type": "textbox_save",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Comments"}]
|
||||
},
|
||||
{
|
||||
"column": "Dummy",
|
||||
"mapped_to_column": "scanSourcePlugin",
|
||||
"mapped_to_column_data": {
|
||||
"value": "KEALSS"
|
||||
},
|
||||
"css_classes": "col-sm-2",
|
||||
"show": false,
|
||||
"type": "label",
|
||||
"default_value": "",
|
||||
"options": [],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Scan method"}]
|
||||
},
|
||||
{
|
||||
"column": "status",
|
||||
"css_classes": "col-sm-1",
|
||||
"show": true,
|
||||
"type": "replace",
|
||||
"default_value": "",
|
||||
"options": [
|
||||
{
|
||||
"equals": "watched-not-changed",
|
||||
"replacement": "<div style='text-align:center'><i class='fa-solid fa-square-check'></i><div></div>"
|
||||
},
|
||||
{
|
||||
"equals": "watched-changed",
|
||||
"replacement": "<div style='text-align:center'><i class='fa-solid fa-triangle-exclamation'></i></div>"
|
||||
},
|
||||
{
|
||||
"equals": "new",
|
||||
"replacement": "<div style='text-align:center'><i class='fa-solid fa-circle-plus'></i></div>"
|
||||
},
|
||||
{
|
||||
"equals": "missing-in-last-scan",
|
||||
"replacement": "<div style='text-align:center'><i class='fa-solid fa-question'></i></div>"
|
||||
}
|
||||
],
|
||||
"localized": ["name"],
|
||||
"name": [{"language_code": "en_us", "string": "Status"}]
|
||||
}
|
||||
],
|
||||
"settings": [
|
||||
{
|
||||
"function": "RUN",
|
||||
"events": ["run"],
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [{"elementType": "select", "elementOptions": [], "transformers": []}]
|
||||
},
|
||||
"default_value": "disabled",
|
||||
"options": [
|
||||
"disabled",
|
||||
"once",
|
||||
"schedule",
|
||||
"always_after_scan",
|
||||
"on_new_device"
|
||||
],
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "When to run"}],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Enable import of devices from <code>Kea API</code>. If you select <code>schedule</code> the scheduling settings from below are applied. If you select <code>once</code> the scan is run only once on start of the application (container) or after you update your settings. ⚠ Use the same schedule if you have multiple <i class=\"fa-solid fa-magnifying-glass-plus\"></i> Device scanners enabled."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "CMD",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{ "elementType": "input", "elementOptions": [], "transformers": [] }
|
||||
]
|
||||
},
|
||||
"default_value": "python3 /app/front/plugins/kea_api/script.py",
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "Command"}],
|
||||
"description": [{"language_code": "en_us", "string": "Command to run"}]
|
||||
},
|
||||
{
|
||||
"function": "URL",
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "API URL"}],
|
||||
"description": [{"language_code": "en_us", "string": "Kea Control Agent URL"}],
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "http://127.0.0.1:8000"
|
||||
},
|
||||
{
|
||||
"function": "USER",
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "API User"}],
|
||||
"description": [{"language_code": "en_us", "string": "Basic Auth Username"}],
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "kea-api"
|
||||
},
|
||||
{
|
||||
"function": "PASS",
|
||||
"localized": ["name", "description"],
|
||||
"name": [{"language_code": "en_us", "string": "API Password"}],
|
||||
"description": [{"language_code": "en_us", "string": "Basic Auth Password"}],
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [{"type": "password"}],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": ""
|
||||
},
|
||||
{
|
||||
"function": "RUN_SCHD",
|
||||
"type": {
|
||||
"dataType": "string",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "span",
|
||||
"elementOptions": [
|
||||
{
|
||||
"cssClasses": "input-group-addon validityCheck"
|
||||
},
|
||||
{
|
||||
"getStringKey": "Gen_ValidIcon"
|
||||
}
|
||||
],
|
||||
"transformers": []
|
||||
},
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [
|
||||
{
|
||||
"focusout": "validateRegex(this)"
|
||||
},
|
||||
{
|
||||
"base64Regex": "Xig/OlwqfCg/OlswLTldfFsxLTVdWzAtOV18WzAtOV0rLVswLTldK3xcKi9bMC05XSspKVxzKyg/OlwqfCg/OlswLTldfDFbMC05XXwyWzAtM118WzAtOV0rLVswLTldK3xcKi9bMC05XSspKVxzKyg/OlwqfCg/OlsxLTldfFsxMl1bMC05XXwzWzAxXXxbMC05XSstWzAtOV0rfFwqL1swLTldKykpXHMrKD86XCp8KD86WzEtOV18MVswLTJdfFswLTldKy1bMC05XSt8XCovWzAtOV0rKSlccysoPzpcKnwoPzpbMC02XXxbMC02XS1bMC02XXxcKi9bMC05XSspKSQ="
|
||||
}
|
||||
],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": "0 2 * * *",
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Schedule"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Only enabled if you select <code>schedule</code> in the <a href=\"#KEALSS_RUN\"><code>KEALSS_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format (e.g. validate at <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). For example entering <code>0 4 * * *</code> will run the scan after 4 am in the <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</code> you set above</a>. Will be run NEXT time the time passes. <br/> It's recommended to use the same schedule interval for all plugins responsible for discovering new devices."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "RUN_TIMEOUT",
|
||||
"type": {
|
||||
"dataType": "integer",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "input",
|
||||
"elementOptions": [{ "type": "number" }],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": 5,
|
||||
"options": [],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Run timeout"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "SET_ALWAYS",
|
||||
"type": {
|
||||
"dataType": "array",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "select",
|
||||
"elementOptions": [{ "multiple": "true", "orderable": "true"}],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": ["devMac", "devLastIP"],
|
||||
"options": [
|
||||
"devMac",
|
||||
"devLastIP"
|
||||
],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Set always columns"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "These columns are treated as authoritative and will overwrite existing values, including those set by other plugins, unless the current value was explicitly set by the user (<code>Source = USER</code> or <code>Source = LOCKED</code>)."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "SET_EMPTY",
|
||||
"type": {
|
||||
"dataType": "array",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "select",
|
||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": [],
|
||||
"options": [
|
||||
"devMac",
|
||||
"devLastIP",
|
||||
"devName",
|
||||
"devSourcePlugin"
|
||||
],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Set empty columns"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "These columns are only overwritten if they are empty (<code>NULL</code> / empty string) or if their Source is set to <code>NEWDEV</code>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "WATCH",
|
||||
"type": {
|
||||
"dataType": "array",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "select",
|
||||
"elementOptions": [{ "multiple": "true", "orderable": "true"}],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": ["watchedValue1", "watchedValue4"],
|
||||
"options": [
|
||||
"watchedValue1",
|
||||
"watchedValue2",
|
||||
"watchedValue4"
|
||||
],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Watched"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Send a notification if selected values change. Use <code>CTRL + Click</code> to select/deselect. <ul> <li><code>watchedValue1</code> is Active </li><li><code>watchedValue2</code> is Hostname </li><li><code>watchedValue4</code> is State</li></ul>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"function": "REPORT_ON",
|
||||
"type": {
|
||||
"dataType": "array",
|
||||
"elements": [
|
||||
{
|
||||
"elementType": "select",
|
||||
"elementOptions": [{ "multiple": "true", "orderable": "true"}],
|
||||
"transformers": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_value": ["new", "watched-changed"],
|
||||
"options": [
|
||||
"new",
|
||||
"watched-changed",
|
||||
"watched-not-changed",
|
||||
"missing-in-last-scan"
|
||||
],
|
||||
"localized": ["name", "description"],
|
||||
"name": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Report on"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"language_code": "en_us",
|
||||
"string": "Send a notification only on these statuses. <code>new</code> means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. <code>watched-changed</code> means that selected <code>watchedValueN</code> columns changed."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
70
front/plugins/kea_api/script.py
Normal file
70
front/plugins/kea_api/script.py
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import requests
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../server'))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../plugins'))
|
||||
|
||||
from plugin_helper import Plugin_Objects, mylog, handleEmpty, is_mac
|
||||
from helper import get_setting_value
|
||||
from const import logPath
|
||||
|
||||
pluginName = "KEALSS"
|
||||
LOG_PATH = logPath + "/plugins"
|
||||
LOG_FILE = os.path.join(LOG_PATH, f'script.{pluginName}.log')
|
||||
RESULT_FILE = os.path.join(LOG_PATH, f'last_result.{pluginName}.log')
|
||||
|
||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
url = get_setting_value(f'{pluginName}_URL')
|
||||
user = get_setting_value(f'{pluginName}_USER')
|
||||
password = get_setting_value(f'{pluginName}_PASS')
|
||||
|
||||
mylog('verbose', [f'[{pluginName}] Querying Kea API at {url}'])
|
||||
|
||||
payload = {'command': 'lease4-get-all', 'service': ['dhcp4']}
|
||||
|
||||
response = requests.post(url, json=payload, auth=(user, password), timeout=10)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
count = 0
|
||||
for entry in data:
|
||||
# Result: 0 (success), 1 (error), or 3 (empty).
|
||||
if entry.get("result") == 0:
|
||||
leases = entry["arguments"].get("leases", [])
|
||||
for l in leases:
|
||||
mac = l['hw-address']
|
||||
state = l['state']
|
||||
if is_mac(mac):
|
||||
plugin_objects.add_object(
|
||||
primaryId = mac,
|
||||
secondaryId = l['ip-address'],
|
||||
# Active or not, similar to watched 1 of DHCPLSS plugin
|
||||
watched1 = state == 0,
|
||||
watched2 = l['hostname'],
|
||||
watched3 = None,
|
||||
# Default (or assigned) (0), declined (1), expired-reclaimed (2), released (3), and registered (4)).
|
||||
watched4 = state,
|
||||
extra = None,
|
||||
foreignKey = mac
|
||||
)
|
||||
count += 1
|
||||
elif entry.get("result") == 1:
|
||||
mylog('none', [f'[{pluginName}] ⚠ ERROR: Kea API indicated error'])
|
||||
|
||||
plugin_objects.write_result_file()
|
||||
|
||||
mylog('verbose', [f'[{pluginName}] Successfully imported {count} devices reported by Kea API'])
|
||||
|
||||
except Exception as e:
|
||||
mylog('none', [f'[{pluginName}] ⚠ ERROR: {str(e)}'])
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user