add json_app_get and convert fail2ban over to JSON (#8571)

* add json_app_get function

* add numeric testing and version support

* now use json_app_get

* remove some unneeded code

* update the docs for json_app_get some more

* make the format checker happy

* add in min version support and now take extend name instead of the partial OID

* hmm... don't make min version optional

* add Exception usage for this all make min version actually work

* minor formatting cleanup

* minor style cleanup

* update json_app_get with $throw_me setting

* Use exceptions to fully handle errors.
Always update the application.  Include error message for use in UI.
Move data to data key for easier parsing.
Add test data

* make a few changes to the lovely changes from @murrant

* style cleanup

* now attempt parsing it the old way if a error of -5 is returned

* add new exceptions and rework them all

* add new exceptions and min version 0 no longer bypasses the key checks

* redo the error codes a bit and improve the comment about it all

* fix a a bit of formatting

* added JsonAppException and make the other JsonApp stuff a sub of it

* note JsonAppException

* fix class creation

* JsonAppBlank now extends JsonApp

* doh! add <?php

* update the poller to properly use the new exceptions

* no longer check for error twice and make sure the data key is present

* cleanup processing of legacy scripts

* tweak this a bit

* white space fix

* fix the tests for fail2ban
This commit is contained in:
VVelox
2018-05-25 21:16:16 -05:00
committed by Tony Murray
parent c2f990bf71
commit c3007b483a
13 changed files with 392 additions and 32 deletions

View File

@@ -0,0 +1,21 @@
<?php
namespace LibreNMS\Exceptions;
use Throwable;
class JsonAppBlankJsonException extends JsonAppException
{
private $output;
public function __construct($message, $output, $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->output = $output;
}
public function getOutput()
{
return $this->output;
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace LibreNMS\Exceptions;
class JsonAppException extends \Exception
{
}

View File

@@ -0,0 +1,28 @@
<?php
namespace LibreNMS\Exceptions;
use Throwable;
class JsonAppExtendErroredException extends JsonAppException
{
private $output;
private $parsed_json;
public function __construct($message, $output, $parsed_json = [], $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->output = $output;
$this->parsed_json = $parsed_json;
}
public function getOutput()
{
return $this->output;
}
public function getParsedJson()
{
return $this->parsed_json;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace LibreNMS\Exceptions;
use Throwable;
class JsonAppMissingKeysException extends JsonAppException
{
private $output;
private $parsed_json;
public function __construct($message, $output, $parsed_json = [], $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->output = $output;
$this->parsed_json = $parsed_json;
}
public function getOutput()
{
return $this->output;
}
public function getParsedJson()
{
return $this->parsed_json;
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace LibreNMS\Exceptions;
use Throwable;
class JsonAppParsingFailedException extends JsonAppException
{
private $output;
public function __construct($message, $output, $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->output = $output;
}
public function getOutput()
{
return $this->output;
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace LibreNMS\Exceptions;
class JsonAppPollingFailedException extends JsonAppException
{
}

View File

@@ -0,0 +1,28 @@
<?php
namespace LibreNMS\Exceptions;
use Throwable;
class JsonAppWrongVersionException extends JsonAppException
{
private $output;
private $parsed_json;
public function __construct($message, $output, $parsed_json = [], $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->output = $output;
$this->parsed_json = $parsed_json;
}
public function getOutput()
{
return $this->output;
}
public function getParsedJson()
{
return $this->parsed_json;
}
}

View File

@@ -1,21 +1,41 @@
<?php
use LibreNMS\Exceptions\JsonAppParsingFailedException;
use LibreNMS\Exceptions\JsonAppException;
use LibreNMS\RRD\RrdDefinition;
echo "fail2ban";
$name = 'fail2ban';
$app_id = $app['app_id'];
$options = '-O qv';
$oid = '.1.3.6.1.4.1.8072.1.3.2.3.1.2.8.102.97.105.108.50.98.97.110';
$f2b = snmp_walk($device, $oid, $options);
$f2b = trim($f2b, '"');
echo $name;
$metrics = array();
$bannedStuff = explode("\n", $f2b);
try {
$f2b = json_app_get($device, $name);
} catch (JsonAppParsingFailedException $e) {
// Legacy script, build compatible array
$legacy = explode("\n", $e->getOutput());
$f2b = [
'data' => [
'total' => array_shift($legacy), // total was first line in legacy app
'jails' => []
]
];
$total_banned=$bannedStuff[0];
foreach ($legacy as $jail_data) {
list($jail, $banned) = explode(" ", $jail_data);
if (isset($jail) && isset($banned)) {
$f2b['data']['jails'][$jail] = $banned;
}
}
} catch (JsonAppException $e) {
echo PHP_EOL . $name . ':' .$e->getCode().':'. $e->getMessage() . PHP_EOL;
update_application($app, $e->getCode().':'.$e->getMessage(), []); // Set empty metrics and error message
return;
}
$f2b = $f2b[data];
$metrics = [];
$rrd_name = array('app', $name, $app_id);
$rrd_def = RrdDefinition::make()
@@ -24,41 +44,30 @@ $rrd_def = RrdDefinition::make()
$fields = array(
'banned' =>$total_banned,
'firewalled'=>'U',
'banned' => $f2b['total'],
'firewalled'=>'U', // legacy ds
);
$metrics['total'] = $fields;
$tags = array('name' => $name, 'app_id' => $app_id, 'rrd_def' => $rrd_def, 'rrd_name' => $rrd_name);
data_update($device, 'app', $tags, $fields);
$int=1;
$jails=array();
foreach ($f2b['jails'] as $jail => $banned) {
$rrd_name = array('app', $name, $app_id, $jail);
$rrd_def = RrdDefinition::make()->addDataset('banned', 'GAUGE', 0);
$fields = array('banned' => $banned);
while (isset($bannedStuff[$int])) {
list($jail, $banned) = explode(" ", $bannedStuff[$int]);
if (isset($jail) && isset($banned)) {
$jails[] = $jail;
$rrd_name = array('app', $name, $app_id, $jail);
$rrd_def = RrdDefinition::make()->addDataset('banned', 'GAUGE', 0);
$fields = array('banned' => $banned);
$metrics["jail_$jail"] = $fields;
$tags = array('name' => $name, 'app_id' => $app_id, 'rrd_def' => $rrd_def, 'rrd_name' => $rrd_name);
data_update($device, 'app', $tags, $fields);
}
$int++;
$metrics["jail_$jail"] = $fields;
$tags = array('name' => $name, 'app_id' => $app_id, 'rrd_def' => $rrd_def, 'rrd_name' => $rrd_name);
data_update($device, 'app', $tags, $fields);
}
update_application($app, $f2b, $metrics);
update_application($app, 'ok', $metrics);
//
// component processing for fail2ban
//
$device_id=$device['device_id'];
$device_id = $device['device_id'];
$options=array(
'filter' => array(
@@ -85,7 +94,7 @@ if (empty($jails)) {
$id = $component->getFirstComponentID($f2bc);
$f2bc[$id]['label'] = 'Fail2ban Jails';
$f2bc[$id]['jails'] = json_encode($jails);
$f2bc[$id]['jails'] = json_encode(array_keys($f2b['jails']));
$component->setComponentPrefs($device_id, $f2bc);
}

View File

@@ -1,6 +1,13 @@
<?php
use LibreNMS\RRD\RrdDefinition;
use LibreNMS\Exceptions\JsonAppException;
use LibreNMS\Exceptions\JsonAppPollingFailedException;
use LibreNMS\Exceptions\JsonAppParsingFailedException;
use LibreNMS\Exceptions\JsonAppBlankJsonException;
use LibreNMS\Exceptions\JsonAppMissingKeysException;
use LibreNMS\Exceptions\JsonAppWrongVersionException;
use LibreNMS\Exceptions\JsonAppExtendErroredException;
function bulk_sensor_snmpget($device, $sensors)
{
@@ -690,3 +697,85 @@ function convert_to_celsius($value)
$value = ($value - 32) / 1.8;
return sprintf('%.02f', $value);
}
/**
* This is to make it easier polling apps. Also to help standardize around JSON.
*
* The required keys for the returned JSON are as below.
* version - The version of the snmp extend script. Should be numeric and at least 1.
* error - Error code from the snmp extend script. Should be > 0 (0 will be ignored and negatives are reserved)
* errorString - Text to describe the error.
* data - An key with an array with the data to be used.
*
* If the app returns an error, an exception will be raised.
* Positive numbers will be errors returned by the extend script.
*
* Possible parsing related errors:
* -2 : Failed to fetch data from the device
* -3 : Could not decode the JSON.
* -4 : Empty JSON parsed, meaning blank JSON was returned.
* -5 : Valid json, but missing required keys
* -6 : Returned version is less than the min version.
*
* Error checking may also be done via checking the exceptions listed below.
* JsonAppPollingFailedException, -2 : Empty return from SNMP.
* JsonAppParsingFailedException, -3 : Could not parse the JSON.
* JsonAppBlankJsonException, -4 : Blank JSON.
* JsonAppMissingKeysException, -5 : Missing required keys.
* JsonAppWrongVersionException , -6 : Older version than supported.
* JsonAppExtendErroredException : Polling and parsing was good, but the returned data has an error set.
* This may be checked via $e->getParsedJson() and then checking the
* keys error and errorString.
* The error value can be accessed via $e->getCode()
* The output can be accessed via $->getOutput() Only returned for code -3 or lower.
* The parsed JSON can be access via $e->getParsedJson()
*
* All of the exceptions extend JsonAppException.
*
* If the error is less than -1, you can assume it is a legacy snmp extend script.
*
* @param array $device
* @param string $extend the extend name. For example, if 'zfs' is passed it will be converted to 'nsExtendOutputFull.3.122.102.115'.
* @param integer $min_version the minimum version to accept for the returned JSON. default: 1
*
* @return array The json output data parsed into an array
* @throws JsonAppPollingFailedException
*/
function json_app_get($device, $extend, $min_version = 1)
{
$output = snmp_get($device, 'nsExtendOutputFull.'.string_to_oid($extend), '-Oqv', 'NET-SNMP-EXTEND-MIB');
// make sure we actually get something back
if (empty($output)) {
throw new JsonAppPollingFailedException("Empty return from snmp_get.", -2);
}
// turn the JSON into a array
$parsed_json = json_decode(stripslashes($output), true);
// improper JSON or something else was returned. Populate the variable with an error.
if (json_last_error() !== JSON_ERROR_NONE) {
throw new JsonAppParsingFailedException("Invalid JSON", $output, -3);
}
// There no keys in the array, meaning '{}' was was returned
if (empty($parsed_json)) {
throw new JsonAppBlankJsonException("Blank JSON returned.", $output, -4);
}
// It is a legacy JSON app extend, meaning these are not set
if (!isset($parsed_json['error'], $parsed_json['data'], $parsed_json['errorString'], $parsed_json['version'])) {
throw new JsonAppMissingKeysException("Legacy script or extend error, missing one or more required keys.", $output, $parsed_json, -5);
}
if ($parsed_json['version'] < $min_version) {
throw new JsonAppWrongVersionException("Script,'".$parsed_json['version']."', older than required version of '$min_version'", $output, $parsed_json, -6);
}
if ($parsed_json['error'] != 0) {
throw new JsonAppExtendErroredException("Script returned exception: {$parsed_json['errorString']}", $output, $parsed_json, $parsed_json['error']);
}
return $parsed_json;
}

View File

@@ -0,0 +1,49 @@
{
"applications": {
"discovery": {
"applications": [
{
"app_type": "fail2ban",
"app_state": "UNKNOWN",
"discovered": "1",
"app_state_prev": null,
"app_status": "",
"app_instance": ""
}
],
"application_metrics": []
},
"poller": {
"applications": [
{
"app_type": "fail2ban",
"app_state": "OK",
"discovered": "1",
"app_state_prev": "UNKNOWN",
"app_status": "",
"app_instance": ""
}
],
"application_metrics": [
{
"metric": "jail_dovecot",
"value": "0",
"value_prev": null,
"app_type": "fail2ban"
},
{
"metric": "jail_sshd",
"value": "0",
"value_prev": null,
"app_type": "fail2ban"
},
{
"metric": "total",
"value": "0",
"value_prev": null,
"app_type": "fail2ban"
},
]
}
}
}

View File

@@ -0,0 +1,49 @@
{
"applications": {
"discovery": {
"applications": [
{
"app_type": "fail2ban",
"app_state": "UNKNOWN",
"discovered": "1",
"app_state_prev": null,
"app_status": "",
"app_instance": ""
}
],
"application_metrics": []
},
"poller": {
"applications": [
{
"app_type": "fail2ban",
"app_state": "OK",
"discovered": "1",
"app_state_prev": "UNKNOWN",
"app_status": "",
"app_instance": ""
}
],
"application_metrics": [
{
"metric": "jail_dovecot",
"value": "0",
"value_prev": null,
"app_type": "fail2ban"
},
{
"metric": "jail_sshd",
"value": "0",
"value_prev": null,
"app_type": "fail2ban"
},
{
"metric": "total",
"value": "0",
"value_prev": null,
"app_type": "fail2ban"
},
]
}
}
}

View File

@@ -0,0 +1,11 @@
1.3.6.1.2.1.1.1.0|4|Linux server 3.10.0-693.5.2.el7.x86_64 #1 SMP Fri Oct 20 20:32:50 UTC 2017 x86_64
1.3.6.1.2.1.1.2.0|6|1.3.6.1.4.1.8072.3.2.10
1.3.6.1.2.1.1.3.0|67|77550514
1.3.6.1.2.1.1.4.0|4|<private>
1.3.6.1.2.1.1.5.0|4|<private>
1.3.6.1.2.1.1.6.0|4|<private>
1.3.6.1.2.1.25.1.1.0|67|77552962
1.3.6.1.4.1.8072.1.3.2.2.1.21.6.100.105.115.116.114.111|2|1
1.3.6.1.4.1.8072.1.3.2.2.1.21.8.102.97.105.108.50.98.97.110|2|1
1.3.6.1.4.1.8072.1.3.2.3.1.2.8.102.97.105.108.50.98.97.110|4x|300a646f7665636f7420300a7373686420300a
1.3.6.1.6.3.10.2.1.3.0|2|775505

View File

@@ -0,0 +1,11 @@
1.3.6.1.2.1.1.1.0|4|Linux server 3.10.0-693.5.2.el7.x86_64 #1 SMP Fri Oct 20 20:32:50 UTC 2017 x86_64
1.3.6.1.2.1.1.2.0|6|1.3.6.1.4.1.8072.3.2.10
1.3.6.1.2.1.1.3.0|67|77550514
1.3.6.1.2.1.1.4.0|4|<private>
1.3.6.1.2.1.1.5.0|4|<private>
1.3.6.1.2.1.1.6.0|4|<private>
1.3.6.1.2.1.25.1.1.0|67|77552962
1.3.6.1.4.1.8072.1.3.2.2.1.21.6.100.105.115.116.114.111|2|1
1.3.6.1.4.1.8072.1.3.2.2.1.21.8.102.97.105.108.50.98.97.110|2|1
1.3.6.1.4.1.8072.1.3.2.3.1.2.8.102.97.105.108.50.98.97.110|4x|7b2264617461223a7b226a61696c73223a7b2273736864223a2230222c22646f7665636f74223a2230227d2c22746f74616c223a307d2c226572726f72223a2230222c2276657273696f6e223a312c226572726f72537472696e67223a226661696c3262616e2d636c69656e742065786974656420776974682030227d0a
1.3.6.1.6.3.10.2.1.3.0|2|775505