From 8542d05f663755a53875c5c793d2b04f62405cd8 Mon Sep 17 00:00:00 2001 From: Jokob-sk Date: Sat, 8 Apr 2023 13:45:15 +1000 Subject: [PATCH] SNMPDSC plugin 0.1 + PLUG README updates --- Dockerfile | 2 +- docker-compose.yml | 4 +- front/plugins/README.md | 11 +- front/plugins/snmp_discovery/README.md | 15 ++ front/plugins/snmp_discovery/config.json | 328 +++++++++++++++++++++++ front/plugins/snmp_discovery/script.py | 173 ++++++++++++ front/plugins/website_monitor/script.py | 2 +- 7 files changed, 526 insertions(+), 9 deletions(-) create mode 100755 front/plugins/snmp_discovery/README.md create mode 100755 front/plugins/snmp_discovery/config.json create mode 100755 front/plugins/snmp_discovery/script.py diff --git a/Dockerfile b/Dockerfile index 13fab0d2..43259427 100755 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ ENV USER=pi USER_ID=1000 USER_GID=1000 TZ=Europe/London PORT=20211 # Todo, do we still need all these packages? I can already see sudo which isn't needed RUN apt-get update \ - && apt-get install --no-install-recommends tini ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo nginx-light php php-cgi php-fpm php-sqlite3 php-curl sqlite3 dnsutils net-tools python3 iproute2 nmap python3-pip zip -y \ + && apt-get install --no-install-recommends tini snmp ca-certificates curl libwww-perl arp-scan perl apt-utils cron sudo nginx-light php php-cgi php-fpm php-sqlite3 php-curl sqlite3 dnsutils net-tools python3 iproute2 nmap python3-pip zip -y \ && pip3 install requests paho-mqtt scapy cron-converter pytz json2table dhcp-leases pyunifi \ && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 \ && apt-get clean autoclean \ diff --git a/docker-compose.yml b/docker-compose.yml index 83359199..fd148df4 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,9 +7,9 @@ services: network_mode: "host" restart: unless-stopped volumes: - - ${APP_DATA_LOCATION}/pialert/config:/home/pi/pialert/config + - ${APP_DATA_LOCATION}/pialert2/config:/home/pi/pialert/config # - ${APP_DATA_LOCATION}/pialert/db/pialert.db:/home/pi/pialert/db/pialert.db - - ${APP_DATA_LOCATION}/pialert/db:/home/pi/pialert/db + - ${APP_DATA_LOCATION}/pialert2/db:/home/pi/pialert/db # (optional) useful for debugging if you have issues setting up the container - ${LOGS_LOCATION}:/home/pi/pialert/front/log # DELETE START anyone trying to use this file: comment out / delete BELOW lines, they are only for development purposes diff --git a/front/plugins/README.md b/front/plugins/README.md index 652d5c86..956fc9e9 100755 --- a/front/plugins/README.md +++ b/front/plugins/README.md @@ -21,7 +21,7 @@ Again, please read the below carefully if you'd like to contribute with a plugin ## ⚠ Disclaimer -Highly experimental feature. Follow the below very carefully and check example plugin(s). Plugin UI is not my priority right now, happy to approve PRs if you are interested in extending/improvintg the UI experience. Example improvements for the taking: +Experimental feature used also to speed up development and to make the app more maintainable. Follow the below very carefully and check example plugin(s) if you'd like to write one yourself. Plugin UI is not my priority right now, happy to approve PRs if you are interested in extending/improvintg the UI experience. Example improvements for the taking: * Making the tables sortable/filterable * Using the same approach to display table data as in the Devices section (solves above) @@ -360,15 +360,16 @@ Example: The UI will adjust how columns are displayed in the UI based on the definition of the `database_column_definitions` object. Thease are the supported form controls and related functionality: - Only columns with `"show": true` and also with at least an English translation will be shown in the UI. -- Supported types: `label`, `text`, `threshold`, `replace` +- Supported types: `label`, `text`, `threshold`, `replace`, `deviceip`, `devicemac`, `url`. Check for details below, how columns behave based on the type. - `label` makes a column display only - - `text` makes a column editable + - `text` makes a column editable and a save icon is displayed next to it. - See below for information on `threshold`, `replace` - The `options` property is used in conjunction with these types: - `threshold` - The `options` array contains objects from lowest `maximum` to highest with corresponding `hexColor` used for the value background color if it's less than the specified `maximum`, but more than the previous one in the `options` array - `replace` - The `options` array contains objects with an `equals` property, that is compared to the "value" and if the values are the same, the string in `replacement` is displayed in the UI instead of the actual "value" - - `devicemac` - The value is considered to be a mac adress and a link pointing to the device with the given mac address is generated. - - `url` - The value is considered to be a url so a link is generated. +- `devicemac` - The value is considered to be a mac address and a link pointing to the device with the given mac address is generated. +- `deviceip` - The value is considered to be an IP address and a link pointing to the device with the given IP is generated. The IP is cheked against the last detected IP addresses and translated into a mac address that is then used for the link itself. +- `url` - The value is considered to be a url so a link is generated. ```json diff --git a/front/plugins/snmp_discovery/README.md b/front/plugins/snmp_discovery/README.md new file mode 100755 index 00000000..fc960e77 --- /dev/null +++ b/front/plugins/snmp_discovery/README.md @@ -0,0 +1,15 @@ +## Overview + +A plugin for importing devices from an SNMP enabled router or switch. + +### Usage + +Specify the following settings in the Settings section of PiAlert: + +- `SNMPDSC_routers` - A list of IP addresses of roputers/switches with SNMP turned on. + +### Notes + +- Executing the `snmpwalk -v 2c -c public -OXsq .1.3.6.1.2.1.3.1.1.2` command. +- Only IPv4 supported. +- Expected output in format `iso.3.6.1.2.1.3.1.1.2.3.1.192.168.1.2 "6C 6C 6C 6C 6C 6C "`. \ No newline at end of file diff --git a/front/plugins/snmp_discovery/config.json b/front/plugins/snmp_discovery/config.json new file mode 100755 index 00000000..56a3ad77 --- /dev/null +++ b/front/plugins/snmp_discovery/config.json @@ -0,0 +1,328 @@ +{ + "code_name": "snmp_discovery", + "unique_prefix": "SNMPDSC", + "enabled": true, + "data_source": "python-script", + "localized": ["display_name", "description", "icon"], + "mapped_to_table": "DHCP_Leases", + "display_name" : [{ + "language_code":"en_us", + "string" : "SNMP discovery" + }], + "icon":[{ + "language_code":"en_us", + "string" : "" + }], + "description": [{ + "language_code":"en_us", + "string" : "This plugin is used to discover devices via the arp table(s) of a RFC1213 compliant router or switch." + }], + "params" : [ + { + "name" : "routers", + "type" : "setting", + "value" : "SNMPDSC_routers" + } + ], + "database_column_definitions": + [ + { + "column": "Index", + "css_classes": "col-sm-2", + "show": false, + "type": "label", + "default_value":"", + "options": [], + "localized": ["name"], + "name":[{ + "language_code":"en_us", + "string" : "N/A" + }] + } , + { + "column": "Plugin", + "css_classes": "col-sm-2", + "show": false, + "type": "label", + "default_value":"", + "options": [], + "localized": ["name"], + "name":[{ + "language_code":"en_us", + "string" : "N/A" + }] + }, + { + "column": "Object_PrimaryID", + "mapped_to_column": "DHCP_MAC", + "css_classes": "col-sm-2", + "show": true, + "type": "devicemac", + "default_value":"", + "options": [], + "localized": ["name"], + "name":[{ + "language_code":"en_us", + "string" : "MAC address" + }] + }, + { + "column": "Object_SecondaryID", + "mapped_to_column": "DHCP_IP", + "css_classes": "col-sm-2", + "show": true, + "type": "deviceip", + "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": "DHCP_DateTime", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value":"", + "options": [], + "localized": ["name"], + "name":[{ + "language_code":"en_us", + "string" : "Changed" + }] + }, + { + "column": "Watched_Value1", + "mapped_to_column": "DHCP_Name", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value":"(unknown)", + "options": [], + "localized": ["name"], + "name":[{ + "language_code":"en_us", + "string" : "Hostname" + }] + }, + { + "column": "Watched_Value2", + "css_classes": "col-sm-2", + "show": true, + "type": "label", + "default_value":"", + "options": [], + "localized": ["name"], + "name":[{ + "language_code":"en_us", + "string" : "Router IP" + }] + }, + { + "column": "Watched_Value3", + "css_classes": "col-sm-2", + "show": false, + "type": "label", + "default_value":"", + "options": [], + "localized": ["name"], + "name":[{ + "language_code":"en_us", + "string" : "Type" + }] + } , + { + "column": "Watched_Value4", + "css_classes": "col-sm-2", + "show": false, + "type": "label", + "default_value":"", + "options": [], + "localized": ["name"], + "name":[{ + "language_code":"en_us", + "string" : "Network" + }] + } , + { + "column": "UserData", + "css_classes": "col-sm-2", + "show": false, + "type": "textboxsave", + "default_value":"", + "options": [], + "localized": ["name"], + "name":[{ + "language_code":"en_us", + "string" : "Comments" + }] + }, + { + "column": "Extra", + "css_classes": "col-sm-3", + "show": true, + "type": "label", + "default_value":"", + "options": [], + "localized": ["name"], + "name":[{ + "language_code":"en_us", + "string" : "RAW output" + }] + }, + { + "column": "Status", + "css_classes": "col-sm-1", + "show": true, + "type": "replace", + "default_value":"", + "options": [ + { + "equals": "watched-not-changed", + "replacement": "
" + }, + { + "equals": "watched-changed", + "replacement": "
" + }, + { + "equals": "new", + "replacement": "
" + } + ], + "localized": ["name"], + "name":[{ + "language_code":"en_us", + "string" : "Status" + }] + } + ], + "settings":[ + { + "function": "RUN", + "type": "selecttext", + "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 a SNMP enabled device. If you select schedule the scheduling settings from below are applied. If you select once the scan is run only once on start of the application (container) or after you update your settings." + }] + }, + { + "function": "CMD", + "type": "text", + "default_value":"python3 /home/pi/pialert/front/plugins/snmp_discovery/script.py routers={routers} ", + "options": [], + "localized": ["name", "description"], + "name" : [{ + "language_code":"en_us", + "string" : "Command" + }], + "description": [{ + "language_code":"en_us", + "string" : "Command to run. Not recommended to change." + }] + }, + { + "function": "routers", + "type": "list", + "default_value":["192.168.1.1"], + "options": [], + "localized": ["name", "description"], + "name" : [{ + "language_code":"en_us", + "string" : "Routers" + }], + "description": [{ + "language_code":"en_us", + "string" : "The router IP addresses you want to connect to. SNMP has to be enabled on the target devices." + }] + }, + { + "function": "RUN_SCHD", + "type": "text", + "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 schedule in the DHCPLSS_RUN setting. Make sure you enter the schedule in the correct cron-like format (e.g. validate at crontab.guru). For example entering 0 4 * * * will run the scan after 4 am in the TIMEZONE you set above. Will be run NEXT time the time passes." + }] + }, + { + "function": "RUN_TIMEOUT", + "type": "integer", + "default_value":5, + "options": [], + "localized": ["name", "description"], + "name" : [{ + "language_code":"en_us", + "string" : "Run timeout" + }, + { + "language_code":"de_de", + "string" : "Wartezeit" + }], + "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": "WATCH", + "type": "multiselect", + "default_value":["Watched_Value1"], + "options": ["Watched_Value1","Watched_Value2","Watched_Value3","Watched_Value4"], + "localized": ["name", "description"], + "name" :[{ + "language_code":"en_us", + "string" : "Watched" + }] , + "description":[{ + "language_code":"en_us", + "string" : "Send a notification if selected values change. Use CTRL + Click to select/deselect.
  • Watched_Value1 is Hostname (not discoverable)
  • Watched_Value2 is Router IP
  • Watched_Value3 is not used
  • Watched_Value4 is not used
" + }] + }, + { + "function": "REPORT_ON", + "type": "multiselect", + "default_value":["new","watched-changed"], + "options": ["new","watched-changed","watched-not-changed"], + "localized": ["name", "description"], + "name" :[{ + "language_code":"en_us", + "string" : "Report on" + }] , + "description":[{ + "language_code":"en_us", + "string" : "Send a notification only on these statuses. new means a new unique (unique combination of PrimaryId and SecondaryId) object was discovered. watched-changed means that selected Watched_ValueN columns changed." + }] + } + ] +} + diff --git a/front/plugins/snmp_discovery/script.py b/front/plugins/snmp_discovery/script.py new file mode 100755 index 00000000..09ce968d --- /dev/null +++ b/front/plugins/snmp_discovery/script.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python + +# Example call +# python3 /home/pi/pialert/front/plugins/snmp_discovery/script.py routers=192.168.1.1 + +from __future__ import unicode_literals +from time import sleep, time, strftime +import requests +from requests import Request, Session, packages +import pathlib +import threading +import subprocess +import socket +import json +import argparse +import io +import sys +from requests.packages.urllib3.exceptions import InsecureRequestWarning +import pwd +import os + + +curPath = str(pathlib.Path(__file__).parent.resolve()) +log_file = curPath + '/script.log' +last_run = curPath + '/last_result.log' + +# Workflow + +def main(): + + # init global variables + global ROUTERS + + last_run_logfile = open(last_run, 'a') + + # empty file + last_run_logfile.write("") + + parser = argparse.ArgumentParser(description='This plugin is used to discover devices via the arp table(s) of a RFC1213 compliant router or switch.') + + parser.add_argument('routers', action="store", help="IP(s) of routers, separated by comma (,) if passing multiple") + + values = parser.parse_args() + + # parse output + newEntries = [] + + if values.routers: + + ROUTERS = values.routers.split('=')[1] + + newEntries = get_entries(newEntries) + + for e in newEntries: + # Insert list into the log + service_monitoring_log(e.primaryId, e.secondaryId, e.created, e.watched1, e.watched2, e.watched3, e.watched4, e.extra, e.foreignKey ) + +# ----------------------------------------------------------------------------- +def get_entries(newEntries): + + routers = [] + + if ',' in ROUTERS: + # multiple + routers = ROUTERS.split(',') + + else: + # only one + routers.append(ROUTERS) + + for router in routers: + # snmpwalk -v 2c -c public -OXsq 192.168.1.1 .1.3.6.1.2.1.3.1.1.2 + + timeoutSec = 10 + + snmpwalkArgs = ['snmpwalk', '-v', '2c', '-c', 'public', '-OXsq', router, '.1.3.6.1.2.1.3.1.1.2'] + + # Execute N probes and insert in list + probes = 1 # N probes + newLines = [] + for _ in range(probes): + output = subprocess.check_output (snmpwalkArgs, universal_newlines=True, stderr=subprocess.STDOUT, timeout=(timeoutSec )) + newLines = newLines + output.split("\n") + + # Process outputs + # Sample: iso.3.6.1.2.1.3.1.1.2.3.1.192.168.1.2 "6C 6C 6C 6C 6C 6C " + + with open(log_file, 'a') as run_logfile: + for line in newLines: + # debug + run_logfile.write(line) + + # print(line) + + tmpSplt = line.split('"') + + if len(tmpSplt) == 3: + + ipStr = tmpSplt[0].split('.') # contains IP + + # print(len(tmpSplt)) + # print(tmpSplt[0]) + # print(tmpSplt[1]) + + macStr = tmpSplt[1].split(' ') # contains MAC + + if 'iso.' in line and len(ipStr) == 16: + tmpEntry = plugin_object_class( + f'{macStr[0]}:{macStr[1]}:{macStr[2]}:{macStr[3]}:{macStr[4]}:{macStr[5]}', + f'{ipStr[12]}.{ipStr[13]}.{ipStr[14]}.{ipStr[15]}'.strip(), + watched1='(unknown)', + watched2=router, + extra=line + ) + newEntries.append(tmpEntry) + + return newEntries + + +# ------------------------------------------------------------------- +class plugin_object_class: + def __init__(self, primaryId = '',secondaryId = '', watched1 = '',watched2 = '',watched3 = '',watched4 = '',extra = '',foreignKey = ''): + self.pluginPref = '' + self.primaryId = primaryId + self.secondaryId = secondaryId + self.created = strftime("%Y-%m-%d %H:%M:%S") + self.changed = '' + self.watched1 = watched1 + self.watched2 = watched2 + self.watched3 = watched3 + self.watched4 = watched4 + self.status = '' + self.extra = extra + self.userData = '' + self.foreignKey = foreignKey + +# ----------------------------------------------------------------------------- +def service_monitoring_log(primaryId, secondaryId, created, watched1, watched2 = 'null', watched3 = 'null', watched4 = 'null', extra ='null', foreignKey ='null' ): + + if watched1 == '': + watched1 = 'null' + if watched2 == '': + watched2 = 'null' + if watched3 == '': + watched3 = 'null' + if watched4 == '': + watched4 = 'null' + if extra == '': + extra = 'null' + if foreignKey == '': + foreignKey = 'null' + + with open(last_run, 'a') as last_run_logfile: + last_run_logfile.write("{}|{}|{}|{}|{}|{}|{}|{}|{}\n".format( + primaryId, + secondaryId, + created, + watched1, + watched2, + watched3, + watched4, + extra, + foreignKey + ) + ) + + +#=============================================================================== +# BEGIN +#=============================================================================== +if __name__ == '__main__': + main() + diff --git a/front/plugins/website_monitor/script.py b/front/plugins/website_monitor/script.py index 67a33622..7c0d76b6 100755 --- a/front/plugins/website_monitor/script.py +++ b/front/plugins/website_monitor/script.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # Based on the work of https://github.com/leiweibau/Pi.Alert -# /home/pi/pialert/front/plugins/website_monitor/script.py urls=http://google.com,http://bing.com +# python3 /home/pi/pialert/front/plugins/website_monitor/script.py urls=http://google.com,http://bing.com from __future__ import unicode_literals from time import sleep, time, strftime import requests