mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Re-implement NAC as a class based module (#9573)
* Less sql queries for nac module * Re-implement NAC as a class based module * Update comments * update module capture order * Fix style issues
This commit is contained in:
56
LibreNMS/Interfaces/Module.php
Normal file
56
LibreNMS/Interfaces/Module.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* Module.php
|
||||
*
|
||||
* LibreNMS Discovery/Poller Module
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Interfaces;
|
||||
|
||||
use LibreNMS\OS;
|
||||
|
||||
interface Module
|
||||
{
|
||||
/**
|
||||
* Discover this module. Heavier processes can be run here
|
||||
* Run infrequently (default 4 times a day)
|
||||
*
|
||||
* @param OS $os
|
||||
*/
|
||||
public function discover(OS $os);
|
||||
|
||||
/**
|
||||
* Poll data for this module and update the DB / RRD.
|
||||
* Try to keep this efficient and only run if discovery has indicated there is a reason to run.
|
||||
* Run frequently (default every 5 minutes)
|
||||
*
|
||||
* @param OS $os
|
||||
*/
|
||||
public function poll(OS $os);
|
||||
|
||||
/**
|
||||
* Remove all DB data for this module.
|
||||
* This will be run when the module is disabled.
|
||||
*
|
||||
* @param OS $os
|
||||
*/
|
||||
public function cleanup(OS $os);
|
||||
}
|
36
LibreNMS/Interfaces/Polling/NacPolling.php
Normal file
36
LibreNMS/Interfaces/Polling/NacPolling.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* NacPolling.php
|
||||
*
|
||||
* Nac Polling interface
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Interfaces\Polling;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
interface NacPolling
|
||||
{
|
||||
/**
|
||||
* @return Collection PortNac objects
|
||||
*/
|
||||
public function pollNac();
|
||||
}
|
93
LibreNMS/Modules/Nac.php
Normal file
93
LibreNMS/Modules/Nac.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* Nac.php
|
||||
*
|
||||
* network access controls module
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace LibreNMS\Modules;
|
||||
|
||||
use App\Models\PortsNac;
|
||||
use LibreNMS\Interfaces\Module;
|
||||
use LibreNMS\Interfaces\Polling\NacPolling;
|
||||
use LibreNMS\OS;
|
||||
use LibreNMS\Util\ModuleModelObserver;
|
||||
|
||||
class Nac implements Module
|
||||
{
|
||||
/**
|
||||
* Discover this module. Heavier processes can be run here
|
||||
* Run infrequently (default 4 times a day)
|
||||
*
|
||||
* @param OS $os
|
||||
*/
|
||||
public function discover(OS $os)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll data for this module and update the DB / RRD.
|
||||
* Try to keep this efficient and only run if discovery has indicated there is a reason to run.
|
||||
* Run frequently (default every 5 minutes)
|
||||
*
|
||||
* @param OS $os
|
||||
*/
|
||||
public function poll(OS $os)
|
||||
{
|
||||
if ($os instanceof NacPolling) {
|
||||
// discovery output (but don't install it twice (testing can can do this)
|
||||
if (!PortsNac::getEventDispatcher()->hasListeners('eloquent.created: App\Models\PortsNac')) {
|
||||
PortsNac::observe(new ModuleModelObserver());
|
||||
}
|
||||
|
||||
$nac_entries = $os->pollNac()->keyBy('mac_address');
|
||||
$existing_entries = $os->getDeviceModel()->portsNac->keyBy('mac_address');
|
||||
|
||||
// update existing models
|
||||
foreach ($nac_entries as $nac_entry) {
|
||||
if ($existing = $existing_entries->get($nac_entry->mac_address)) {
|
||||
$nac_entries->put($nac_entry->mac_address, $existing->fill($nac_entry->attributesToArray()));
|
||||
}
|
||||
}
|
||||
|
||||
// persist to DB
|
||||
$os->getDeviceModel()->portsNac()->saveMany($nac_entries);
|
||||
|
||||
$delete = $existing_entries->diffKeys($nac_entries)->pluck('ports_nac_id');
|
||||
if ($delete->isNotEmpty()) {
|
||||
$count = PortsNac::query()->whereIn('ports_nac_id', $delete)->delete();
|
||||
d_echo('Deleted ' . $count, str_repeat('-', $count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all DB data for this module.
|
||||
* This will be run when the module is disabled.
|
||||
*
|
||||
* @param OS $os
|
||||
*/
|
||||
public function cleanup(OS $os)
|
||||
{
|
||||
$os->getDeviceModel()->portsNac()->delete();
|
||||
}
|
||||
}
|
@@ -25,6 +25,7 @@
|
||||
|
||||
namespace LibreNMS;
|
||||
|
||||
use App\Models\Device;
|
||||
use LibreNMS\Device\WirelessSensor;
|
||||
use LibreNMS\Device\YamlDiscovery;
|
||||
use LibreNMS\Interfaces\Discovery\ProcessorDiscovery;
|
||||
@@ -42,6 +43,7 @@ class OS implements ProcessorDiscovery
|
||||
}
|
||||
|
||||
private $device; // annoying use of references to make sure this is in sync with global $device variable
|
||||
private $device_model;
|
||||
private $cache; // data cache
|
||||
private $pre_cache; // pre-fetch data cache
|
||||
|
||||
@@ -74,6 +76,20 @@ class OS implements ProcessorDiscovery
|
||||
return (int)$this->device['device_id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Eloquent Device Model for the current device
|
||||
*
|
||||
* @return Device
|
||||
*/
|
||||
public function getDeviceModel()
|
||||
{
|
||||
if (is_null($this->device_model)) {
|
||||
$this->device_model = Device::find($this->getDeviceId());
|
||||
}
|
||||
|
||||
return $this->device_model;
|
||||
}
|
||||
|
||||
public function preCache()
|
||||
{
|
||||
if (is_null($this->pre_cache)) {
|
||||
|
@@ -21,15 +21,19 @@
|
||||
* @link http://librenms.org
|
||||
* @copyright 2018 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
* @copyright 2018 Jose Augusto Cardoso
|
||||
*/
|
||||
|
||||
namespace LibreNMS\OS\Shared;
|
||||
|
||||
use App\Models\PortsNac;
|
||||
use LibreNMS\Device\Processor;
|
||||
use LibreNMS\Interfaces\Discovery\ProcessorDiscovery;
|
||||
use LibreNMS\Interfaces\Polling\NacPolling;
|
||||
use LibreNMS\OS;
|
||||
use LibreNMS\Util\IP;
|
||||
|
||||
class Cisco extends OS implements ProcessorDiscovery
|
||||
class Cisco extends OS implements ProcessorDiscovery, NacPolling
|
||||
{
|
||||
/**
|
||||
* Discover processors.
|
||||
@@ -112,4 +116,46 @@ class Cisco extends OS implements ProcessorDiscovery
|
||||
|
||||
return $processors;
|
||||
}
|
||||
|
||||
public function pollNac()
|
||||
{
|
||||
$nac = collect();
|
||||
|
||||
$portAuthSessionEntry = snmpwalk_cache_oid($this->getDevice(), 'cafSessionEntry', [], 'CISCO-AUTH-FRAMEWORK-MIB');
|
||||
if (!empty($portAuthSessionEntry)) {
|
||||
$cafSessionMethodsInfoEntry = collect(snmpwalk_cache_oid($this->getDevice(), 'cafSessionMethodsInfoEntry', [], 'CISCO-AUTH-FRAMEWORK-MIB'))->mapWithKeys(function ($item, $key) {
|
||||
$key_parts = explode('.', $key);
|
||||
$key = implode('.', array_slice($key_parts, 0, 2)); // remove the auth method
|
||||
return [$key => ['method' => $key_parts[2], 'authc_status' => $item['cafSessionMethodState']]];
|
||||
});
|
||||
|
||||
// cache port ifIndex -> port_id map
|
||||
$ifIndex_map = $this->getDeviceModel()->ports()->pluck('port_id', 'ifIndex');
|
||||
|
||||
// update the DB
|
||||
foreach ($portAuthSessionEntry as $index => $portAuthSessionEntryParameters) {
|
||||
list($ifIndex, $auth_id) = explode('.', str_replace("'", '', $index));
|
||||
$session_info = $cafSessionMethodsInfoEntry->get($ifIndex . '.' . $auth_id);
|
||||
$mac_address = strtolower(implode(array_map('zeropad', explode(':', $portAuthSessionEntryParameters['cafSessionClientMacAddress']))));
|
||||
|
||||
$nac->put($mac_address, new PortsNac([
|
||||
'port_id' => $ifIndex_map->get($ifIndex, 0),
|
||||
'mac_address' => $mac_address,
|
||||
'auth_id' => $auth_id,
|
||||
'domain' => $portAuthSessionEntryParameters['cafSessionDomain'],
|
||||
'username' => $portAuthSessionEntryParameters['cafSessionAuthUserName'],
|
||||
'ip_address' => (string)IP::fromHexString($portAuthSessionEntryParameters['cafSessionClientAddress'], true),
|
||||
'host_mode' => $portAuthSessionEntryParameters['cafSessionAuthHostMode'],
|
||||
'authz_status' => $portAuthSessionEntryParameters['cafSessionStatus'],
|
||||
'authz_by' => $portAuthSessionEntryParameters['cafSessionAuthorizedBy'],
|
||||
'timeout' => $portAuthSessionEntryParameters['cafSessionTimeout'],
|
||||
'time_left' => $portAuthSessionEntryParameters['cafSessionTimeLeft'],
|
||||
'authc_status' => $session_info['authc_status'],
|
||||
'method' => $session_info['method'],
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
return $nac;
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ namespace LibreNMS\Util;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model as Eloquent;
|
||||
|
||||
class DiscoveryModelObserver
|
||||
class ModuleModelObserver
|
||||
{
|
||||
public function saving(Eloquent $model)
|
||||
{
|
@@ -499,6 +499,11 @@ class Device extends BaseModel
|
||||
return $this->hasMany('App\Models\Port', 'device_id', 'device_id');
|
||||
}
|
||||
|
||||
public function portsNac()
|
||||
{
|
||||
return $this->hasMany('App\Models\PortsNac', 'device_id', 'device_id');
|
||||
}
|
||||
|
||||
public function processors()
|
||||
{
|
||||
return $this->hasMany('App\Models\Processor', 'device_id');
|
||||
|
@@ -32,19 +32,19 @@ class PortsNac extends BaseModel
|
||||
public $timestamps = false;
|
||||
protected $fillable = [
|
||||
'auth_id',
|
||||
'port_id',
|
||||
'device_id',
|
||||
'port_id',
|
||||
'domain',
|
||||
'username',
|
||||
'mac_address',
|
||||
'ip_address',
|
||||
'authz_status',
|
||||
'domain',
|
||||
'host_mode',
|
||||
'username',
|
||||
'authz_status',
|
||||
'authz_by',
|
||||
'timeout',
|
||||
'time_left',
|
||||
'authc_status',
|
||||
'method',
|
||||
'timeout',
|
||||
'time_left',
|
||||
];
|
||||
|
||||
|
||||
|
@@ -24,65 +24,9 @@
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
use App\Models\Port;
|
||||
use App\Models\PortsNac;
|
||||
use LibreNMS\Util\DiscoveryModelObserver;
|
||||
use LibreNMS\Util\IP;
|
||||
use LibreNMS\OS;
|
||||
|
||||
echo "\nCisco-NAC\n";
|
||||
|
||||
// cache port ifIndex -> port_id map
|
||||
$ports_map = Port::where('device_id', $device['device_id'])->pluck('port_id', 'ifIndex');
|
||||
$port_nac_ids = [];
|
||||
|
||||
// discovery output (but don't install it twice (testing can can do this)
|
||||
if (!PortsNac::getEventDispatcher()->hasListeners('eloquent.created: App\Models\PortsNac')) {
|
||||
PortsNac::observe(new DiscoveryModelObserver());
|
||||
if (!$os instanceof OS) {
|
||||
$os = OS::make($device);
|
||||
}
|
||||
|
||||
// collect data via snmp and reorganize the session method entry a bit
|
||||
$portAuthSessionEntry = snmpwalk_cache_oid($device, 'cafSessionEntry', [], 'CISCO-AUTH-FRAMEWORK-MIB');
|
||||
if (!empty($portAuthSessionEntry)) {
|
||||
$cafSessionMethodsInfoEntry = collect(snmpwalk_cache_oid($device, 'cafSessionMethodsInfoEntry', [], 'CISCO-AUTH-FRAMEWORK-MIB'))->mapWithKeys(function ($item, $key) {
|
||||
$key_parts = explode('.', $key);
|
||||
$key = implode('.', array_slice($key_parts, 0, 2)); // remove the auth method
|
||||
return [$key => ['method' => $key_parts[2], 'authc_status' => $item['cafSessionMethodState']]];
|
||||
});
|
||||
}
|
||||
|
||||
// update the DB
|
||||
foreach ($portAuthSessionEntry as $index => $portAuthSessionEntryParameters) {
|
||||
$auth_id = trim(strstr($index, "'"), "'");
|
||||
$ifIndex = substr($index, 0, strpos($index, "."));
|
||||
$session_info = $cafSessionMethodsInfoEntry->get($ifIndex . '.' . $auth_id);
|
||||
|
||||
$port_nac = PortsNac::updateOrCreate([
|
||||
'port_id' => $ports_map->get($ifIndex, 0),
|
||||
'mac_address' => strtolower(implode(array_map('zeropad', explode(':', $portAuthSessionEntryParameters['cafSessionClientMacAddress'])))),
|
||||
], [
|
||||
'auth_id' => $auth_id,
|
||||
'device_id' => $device['device_id'],
|
||||
'domain' => $portAuthSessionEntryParameters['cafSessionDomain'],
|
||||
'username' => $portAuthSessionEntryParameters['cafSessionAuthUserName'],
|
||||
'ip_address' => (string)IP::fromHexString($portAuthSessionEntryParameters['cafSessionClientAddress'], true),
|
||||
'host_mode' => $portAuthSessionEntryParameters['cafSessionAuthHostMode'],
|
||||
'authz_status' => $portAuthSessionEntryParameters['cafSessionStatus'],
|
||||
'authz_by' => $portAuthSessionEntryParameters['cafSessionAuthorizedBy'],
|
||||
'authc_status' => $session_info['authc_status'],
|
||||
'timeout' => $portAuthSessionEntryParameters['cafSessionTimeout'],
|
||||
'time_left' => $portAuthSessionEntryParameters['cafSessionTimeLeft'],
|
||||
'method' => $session_info['method'],
|
||||
]);
|
||||
|
||||
// save valid ids
|
||||
$port_nac_ids[] = $port_nac->ports_nac_id;
|
||||
}
|
||||
|
||||
|
||||
// delete old entries
|
||||
$count = \LibreNMS\DB\Eloquent::DB()->table('ports_nac')->whereNotIn('ports_nac_id', $port_nac_ids)->delete();
|
||||
d_echo('Deleted ' . $count, str_repeat('-', $count));
|
||||
// \App\Models\PortsNac::whereNotIn('ports_nac_id', $port_nac_ids)->get()->each->delete(); // alternate delete to trigger model events
|
||||
|
||||
|
||||
unset($port_nac_ids, $ports_map, $portAuthSessionEntry, $cafSessionMethodsInfoEntry, $port_nac);
|
||||
(new \LibreNMS\Modules\Nac())->poll($os);
|
||||
|
@@ -2577,21 +2577,6 @@
|
||||
"time_left": "23",
|
||||
"ifIndex": 10002
|
||||
},
|
||||
{
|
||||
"auth_id": "000000000000000405F13EAB",
|
||||
"domain": "data",
|
||||
"username": "username4",
|
||||
"mac_address": "788a207f0e95",
|
||||
"ip_address": "0.0.0.0",
|
||||
"host_mode": "multiAuth",
|
||||
"authz_status": "authorizationSuccess",
|
||||
"authz_by": "Authentication Server",
|
||||
"authc_status": "authcSuccess",
|
||||
"method": "other",
|
||||
"timeout": "5",
|
||||
"time_left": "12",
|
||||
"ifIndex": 10003
|
||||
},
|
||||
{
|
||||
"auth_id": "000000000000000505F17F8C",
|
||||
"domain": "data",
|
||||
@@ -2606,6 +2591,21 @@
|
||||
"timeout": "6",
|
||||
"time_left": "0",
|
||||
"ifIndex": 10003
|
||||
},
|
||||
{
|
||||
"auth_id": "000000000000000405F13EAB",
|
||||
"domain": "data",
|
||||
"username": "username4",
|
||||
"mac_address": "788a207f0e95",
|
||||
"ip_address": "0.0.0.0",
|
||||
"host_mode": "multiAuth",
|
||||
"authz_status": "authorizationSuccess",
|
||||
"authz_by": "Authentication Server",
|
||||
"authc_status": "authcSuccess",
|
||||
"method": "other",
|
||||
"timeout": "5",
|
||||
"time_left": "12",
|
||||
"ifIndex": 10003
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ nac:
|
||||
excluded_fields: [ports_nac_id, device_id, port_id]
|
||||
joins:
|
||||
- { left: ports_nac.port_id, right: ports.port_id, select: [ifIndex] }
|
||||
order_by: ports.ifIndex, domain
|
||||
order_by: ports.ifIndex, mac_address
|
||||
os:
|
||||
devices:
|
||||
included_fields: [sysName, sysObjectID, sysDescr, sysContact, version, hardware, features, location, os, type, serial, icon]
|
||||
|
Reference in New Issue
Block a user