ntopng/scripts/lua/modules/control_groups.lua
2024-10-04 16:58:38 +02:00

441 lines
13 KiB
Lua

--
-- (C) 2017-24 - ntop.org
--
-- Module to keep things in common across control_groups of various type
local dirs = ntop.getDirs()
package.path = dirs.installdir .. "/scripts/lua/modules/?.lua;" .. package.path
require "lua_utils"
local json = require "dkjson"
-- ##############################################
local control_groups = {}
-- ##############################################
-- This is the minimum control_group id which will be used to create new control_groups
control_groups.MIN_ASSIGNED_CONTROL_GROUP_ID = 0
-- ##############################################
local function _get_control_groups_prefix_key()
local key = string.format("ntopng.prefs.control_groups")
return key
end
-- ##############################################
local function _get_control_group_ids_key()
local key = string.format("%s.control_group_ids", _get_control_groups_prefix_key())
return key
end
-- ##############################################
local function _get_control_group_lock_key()
local key = string.format("ntopng.cache.control_groups.control_group_lock")
return key
end
-- ##############################################
local function _get_control_group_details_key(control_group_id)
if not control_group_id then
-- A control_group id is always needed
return nil
end
local key = string.format("%s.control_group_id_%d.details", _get_control_groups_prefix_key(), control_group_id)
return key
end
-- ##############################################
-- @brief Returns an array with all the currently assigned control_group ids
local function _get_assigned_control_group_ids()
local res = {}
local cur_control_group_ids = ntop.getMembersCache(_get_control_group_ids_key())
for _, cur_control_group_id in pairs(cur_control_group_ids) do
cur_control_group_id = tonumber(cur_control_group_id)
res[#res + 1] = cur_control_group_id
end
return res
end
-- ##############################################
local function _assign_control_group_id()
-- OVERRIDE
-- To stay consistent with the old implementation control_groups_nedge.lua
-- control_group_ids are re-used. This means reading the set of currently used control_group
-- ids, and chosing the minimum not available control_group id
-- This method is called from functions which perform locks so
-- there's no risk to assign the same id multiple times
local cur_control_group_ids = _get_assigned_control_group_ids()
local next_control_group_id = control_groups.MIN_ASSIGNED_CONTROL_GROUP_ID
-- Find the first available control_group id which is not in the set
for _, control_group_id in pairsByValues(cur_control_group_ids, asc) do
if control_group_id > next_control_group_id then break end
next_control_group_id = math.max(control_group_id + 1, next_control_group_id)
end
ntop.setMembersCache(_get_control_group_ids_key(), string.format("%d", next_control_group_id))
return next_control_group_id
end
-- ##############################################
local function _lock()
local max_lock_duration = 5 -- seconds
local max_lock_attempts = 5 -- give up after at most this number of attempts
local lock_key = _get_control_group_lock_key()
for i = 1, max_lock_attempts do
local value_set = ntop.setnxCache(lock_key, "1", max_lock_duration)
if ntop.getCache(lock_key) == "1" then
return true -- lock acquired
end
ntop.msleep(1000)
end
return false -- lock not acquired
end
-- ##############################################
local function _unlock()
ntop.delCache(_get_control_group_lock_key())
end
-- ##############################################
-- @brief Persist control_group details to disk. Possibly assign a control_group id
-- @param control_group_id The control_group_id of the control_group which needs to be persisted. If nil, a new control_group id is assigned
local function _persist(control_group_id, name, members, disabled_alerts)
local control_group_details_key = _get_control_group_details_key(control_group_id)
local control_group_details = {
name = name,
members = members or {},
disabled_alerts = disabled_alerts or {}
}
ntop.setCache(control_group_details_key, json.encode(control_group_details))
ntop.reloadControlGroups()
-- Return the assigned control_group_id
return control_group_id
end
-- ##############################################
function control_groups.add_control_group(name, members)
local locked = _lock()
if locked then
if name and members then
local checks_ok = true
-- Check if duplicate names exist
local same_name_control_group = control_groups.get_control_group_by_name(name)
if same_name_control_group then
checks_ok = false
end
-- Check if members are valid
if check_ok and not control_groups.are_valid_members(members) then
checks_ok = false
end
if checks_ok then
-- All the checks have succeeded
-- Now that everything is ok, the id can be assigned and the control_group can be persisted with the assigned id
control_group_id = _assign_control_group_id()
_persist(control_group_id, name, members)
end
end
_unlock()
end
return control_group_id
end
-- ##############################################
function control_groups.edit_control_group(control_group_id, new_name, new_members)
local ret = false
local locked = _lock()
-- If here, control_group_id has been found
if locked then
-- Make sure the control_group exists
local cur_details = control_groups.get_control_group(control_group_id)
if cur_details and new_name then
local checks_ok = true
if not new_members then
-- In case members have not been sumbitted, new_members
-- are assumed to be the existing members
new_members = cur_details["members"]
end
-- Check if new_name is not the name of any other existing control_group
local same_name_control_group = control_groups.get_control_group_by_name(new_name)
if same_name_control_group and same_name_control_group.control_group_id ~= control_group_id then
checks_ok = false
end
-- Check if members are valid
if checks_ok and not control_groups.are_valid_members(new_members) then
checks_ok = false
end
if checks_ok then
-- If here, all checks are valid and the control_group can be edited
_persist(control_group_id, new_name, new_members, cur_details["disabled_alerts"])
-- Control_Group edited successfully
ret = true
end
end
_unlock()
end
return ret
end
-- ##############################################
--@brief Marks an alert as disabled for a given control group identified with `control_group_id`
--@return True, if alert is disabled with success, false otherwise
function control_groups.disable_control_group_flow_alert(control_group_id, alert_key)
local ret = false
local locked = _lock()
-- If here, control_group_id has been found
if locked then
-- Make sure the control_group exists
local cur_details = control_groups.get_control_group(control_group_id)
if cur_details then
local checks_ok = true
-- Check if alert_key is already disabled
for _, disabled_alert in pairs(cur_details["disabled_alerts"]) do
if tonumber(alert_key) == disabled_alert then
checks_ok = false -- Already present, nothing to do
break
end
end
if checks_ok then
-- Disable the alert
cur_details["disabled_alerts"][#cur_details["disabled_alerts"] + 1] = tonumber(alert_key)
-- If here, all checks are valid and the control_group can be edited
_persist(control_group_id, cur_details["name"], cur_details["members"], cur_details["disabled_alerts"])
-- Control_Group edited successfully
ret = true
end
end
_unlock()
end
return ret
end
-- ##############################################
--@brief Marks an alert as disabled for a given control group identified with `control_group_id`
--@return True, if alert is disabled with success, false otherwise
function control_groups.enable_control_group_flow_alert(control_group_id, alert_key)
local ret = false
local locked = _lock()
-- If here, control_group_id has been found
if locked then
-- Make sure the control_group exists
local cur_details = control_groups.get_control_group(control_group_id)
if cur_details then
local new_disabled_alerts = {}
local checks_ok = false
-- Check if alert_key is among disabled alerts
for _, disabled_alert in pairs(cur_details["disabled_alerts"]) do
if tonumber(alert_key) == disabled_alert then
checks_ok = true -- Present among the disabled alerts, can remove it
-- Don't break, finish the loop to prepare `new_disabled_alerts`
else
new_disabled_alerts[#new_disabled_alerts + 1] = disabled_alert
end
end
if checks_ok then
-- If here, all checks are valid and the control_group can be edited
_persist(control_group_id, cur_details["name"], cur_details["members"], new_disabled_alerts)
-- Control_Group edited successfully
ret = true
end
end
_unlock()
end
return ret
end
-- ##############################################
function control_groups.delete_control_group(control_group_id)
local ret = false
local locked = _lock()
if locked then
-- Make sure the control_group exists
local cur_details = control_groups.get_control_group(control_group_id)
if cur_details then
-- Remove the key with all the control_group details (e.g., with members)
ntop.delCache(_get_control_group_details_key(control_group_id))
-- Remove the control_group_id from the set of all currently existing control_group ids
ntop.delMembersCache(_get_control_group_ids_key(), string.format("%d", control_group_id))
-- Tell the core to reload control groups
ntop.reloadControlGroups()
ret = true
end
_unlock()
end
return ret
end
-- ##############################################
-- @brief Returns all the defined control_groups. Control_Groups are returned in a lua table with control_group ids as keys
function control_groups.get_all_control_groups()
local cur_control_group_ids = _get_assigned_control_group_ids()
local res = {}
for _, control_group_id in pairs(cur_control_group_ids) do
local control_group_details = control_groups.get_control_group(control_group_id)
if control_group_details then res[#res + 1] = control_group_details end
end
return res
end
-- ##############################################
-- @brief Returns the number of currently defined control_group ids
function control_groups.get_num_control_groups()
local cur_control_group_ids = _get_assigned_control_group_ids()
return #cur_control_group_ids
end
-- ##############################################
function control_groups.get_control_group(control_group_id, recipient_details)
local recipient_details = recipient_details or true
local control_group_details
local control_group_details_key = _get_control_group_details_key(control_group_id)
-- Attempt at retrieving the control_group details key and at decoding it from JSON
if control_group_details_key then
local control_group_details_str = ntop.getCache(control_group_details_key)
control_group_details = json.decode(control_group_details_str)
if control_group_details then
-- Add the integer control_group id
control_group_details["control_group_id"] = tonumber(control_group_id)
end
end
-- Upon success, control_group details are returned, otherwise nil
return control_group_details
end
-- ##############################################
-- @brief Delete all control_groups
function control_groups.cleanup()
-- Delete control_group details
local cur_control_group_ids = _get_assigned_control_group_ids()
for _, control_group_id in pairs(cur_control_group_ids) do
control_groups.delete_control_group(control_group_id)
end
local locked = _lock()
if locked then
-- Delete control_group ids
ntop.delCache(_get_control_group_ids_key())
_unlock()
end
end
-- ##############################################
-- @brief Returns a boolean indicating whether the member is a valid control_group member
function control_groups.is_valid_member(member)
return isIPv4Network(member)
end
-- ##############################################
-- @brief Returns a boolean indicating whether the array of members passed contains all valid members
function control_groups.are_valid_members(members)
for _, member in pairs(members) do
if not control_groups.is_valid_member(member) then
return false
end
end
return true
end
-- ##############################################
function control_groups.get_control_group_by_name(name)
local cur_control_group_ids = _get_assigned_control_group_ids()
for _, control_group_id in pairs(cur_control_group_ids) do
local control_group_details = control_groups.get_control_group(control_group_id)
if control_group_details and control_group_details["name"] and control_group_details["name"] == name then
return control_group_details
end
end
return nil
end
-- ##############################################
return control_groups