mirror of
https://github.com/ntop/ntopng.git
synced 2026-04-30 16:09:32 +00:00
alert store skeleton Alert database type changes Implement alert store for host alerts. All alert store skeletons. Fix class method access Enable tracing Implements simple queries for host alerts Implement flow alert store Fixes escaping of INSERT queries Flow alerts database schema fixes Adds escaping for alert JSON in flows and hosts Implements queries .select() for alerts store Adds limit and offset to perform paginated queries Adds new REST getter for flow alerts Name changes alert_severity to severity, alert_json to json Fixes alert message not shown Implement active monitoring alerts store Implements sort of queries in the new dataabase Changes alert_type to alert_id Implement mac alerts store Fixes flow alert messages Implement system alerts store Implement snmp alert store Add missing items to the flow alerts Add missing items to the host alerts Add missing items to the mac alerts Implements COUNT aplerts api Add device_name to snmp alerts add flow alerts templates updated gitignore fix for missing order field add families defined in `alert_store_schema.sql` Reworks and simplifies alert store subclasses Implements REST API to fetch alert timeseries Fixes date in flow REST api Host alert json fix Add get/host/alert/list.lua Move alert/list.lua to alert/past/list.lua Add alert/past/list.lua for all alert families Add entity_id to system table to identify the alert type based on <alert_id, entity_id> Add missing field Implements facilities to query engaged alerts via REST Handle both historical and past alerts in alert/list.lua Fix count Update params of select_historical Implement method to add family-specific filters Add alert/ts.lua for all alert families Implements facilities and REST endpoints to delete alerts Implements ordering of alert queries Fix add_order_by group_by Rest API tests update Tests output update Remove debug trace Use alert_id instead of type. Add more flow alert info. Update http lint Format obsolete tlv version alerts. Add more host info. Add row_id to list of alerts Fix selection of engaged alerts Add test for mac alerts (bcast domains) Removes attempt to format alerts as flow alerts Fixes interface selection for active monitoring Update test output with rest changes Add more fields to be ignores Set alert count to 1 for the time being add bar timeseries chart add apexcharts improvements on timeseries bar chart registered chart callbakcs working on alert page fix for date format Fixes acrive monitoring REST API Fixes alignment of grouped alert data Additional fix for alert histogram remove useless if formatting alerts page fixes on flows alert stats table rename local networks to device format host pagie in alert_stats add tag support for hosts and flow implemented single delete action add release modal Fixes format of threshold cross interface alerts Fixes wrong increase of dropped alerts Implements exclusion list for invalid dns queries Reworks exclusions lists for hosts and flows Addresses #5212 Addresses #5113 Adds host alert keys in host callbacks definitions Adds alert ids to flow callbacks fix for not working button (#5215) Fixes reported timeseries name removed any additional button inside chart's toolbar (#5200) Add tables for interfaces, networks, users to the schema. Skeleton alert_store classes fixed broken range picker layout in firefox (#5199) Alert insert fixes Add more info to network alerts Add rest endpoint for interface, network, user alerts Fix endpoint selection in alerts_stats Unifies columns between engaged and past alerts Fixes Missing mandatory 'alert_granularity' Minor fixes for missing alert_severity Fixes arithmetic on a nil value (field 'last_seen') Fixes get/system/alert/list.lua use tstamp for column names (#5221) Implements host alert formatter Add alerts_store format_record_common Use common format_record for am, system alerts Use common format_record for all alerts Fixes formatting of alerts of all types Fixes nil in function 'hostinfo2label' fixes on disable modal add pages for network, user and interface endpoint (#5224) Set alert_entity in all classes Unifies influxdb alerts into system alerts Addresses #5224 Unifies process alerts into system alerts Addresses #5224 Cleanup unused periodicActivityEntity Unifies category lists alerts into system alerts Addresses #5224 Aligns new alert enums Addresses #5224 Fixes alert page links Fixes insertion of interface alerts Implement filters for Host alerts Fixes active monitoring alerts not triggering Implement filters on flow alerts Fixes for internal alerts timestamp and subtype implements disable for the alerts formatted alert disable label Add address and device type to mac alert records fix for delete alert toggle Fix access to entity_val in alert_unexpected_new_device add mac address and device type inside table Fixes for new alert fields not handled Fixes alert_definitions to handle new fields Add ip/port to snmp alert records Implements deletion of stored flow alerts Add alert_name to all alert records via rest. Fix duration. fixes for snmp tab Implements delete of past host alerts Add name to snmp alert records fixes on system tab Fix Date column fixes link Update menu Fixes bad argument #3 to 'format' in snmp alerts updated interface link new alerts url for host (#5228) Fixes sort of engaged alert Minor cleanup Fixes data returned for local network alerts Fix duration for one shot. Note. Fix duration override Fixes interface selection for system alerts Move host alert page fixes for local network tab Minor fix Fix engaged host alerts fixes on user tab Fix alert_user_activity message
809 lines
29 KiB
Lua
809 lines
29 KiB
Lua
--
|
|
-- (C) 2013-21 - ntop.org
|
|
--
|
|
|
|
local dirs = ntop.getDirs()
|
|
package.path = dirs.installdir .. "/scripts/lua/modules/?.lua;" .. package.path
|
|
package.path = dirs.installdir .. "/scripts/lua/modules/pools/?.lua;" .. package.path
|
|
package.path = dirs.installdir .. "/scripts/lua/modules/notifications/?.lua;" .. package.path
|
|
|
|
|
|
local json = require("dkjson")
|
|
local alert_severities = require "alert_severities"
|
|
local alert_consts = require("alert_consts")
|
|
local os_utils = require("os_utils")
|
|
local recipients = require "recipients"
|
|
local do_trace = false
|
|
|
|
local alerts_api = {}
|
|
|
|
-- Just helpers
|
|
local str_2_periodicity = {
|
|
["min"] = 60,
|
|
["5mins"] = 300,
|
|
["hour"] = 3600,
|
|
["day"] = 86400,
|
|
}
|
|
|
|
local known_alerts = {}
|
|
local current_script
|
|
local current_configset -- The configset used for the generation of this alert
|
|
|
|
-- ##############################################
|
|
|
|
-- Returns a string which identifies an alert
|
|
function alerts_api.getAlertId(alert)
|
|
return(string.format("%s_%s_%s_%s_%s", alert.alert_type,
|
|
alert.subtype or "", alert.granularity or "",
|
|
alert.entity_id, alert.entity_val))
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- @brief Returns a key containing a hashed string of the `alert` to quickly identify the alert notification
|
|
-- @param alert A triggered/released alert table
|
|
-- @return The key as a string
|
|
local function get_notification_key(alert)
|
|
return string.format("ntopng.cache.alerts.notification.%s", ntop.md5(alerts_api.getAlertId(alert)))
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- @brief Checks whether the triggered `alert` has already been notified
|
|
-- @param alert A triggered alert table
|
|
-- @return True if the `alert` has already been notified, false otherwise
|
|
local function is_trigger_notified(alert)
|
|
local k = get_notification_key(alert)
|
|
local res = tonumber(ntop.getCache(k))
|
|
|
|
return res ~= nil
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- @brief Marks the triggered `alert` as notified to the recipients
|
|
-- @param alert A triggered alert table
|
|
-- @return nil
|
|
local function mark_trigger_notified(alert)
|
|
local k = get_notification_key(alert)
|
|
ntop.setCache(k, "1")
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- @brief Marks the released `alert` as notificed to the recipients
|
|
-- @param alert A released alert table
|
|
-- @return nil
|
|
local function mark_release_notified(alert)
|
|
local k = get_notification_key(alert)
|
|
ntop.delCache(k)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
local function alertErrorTraceback(msg)
|
|
traceError(TRACE_ERROR, TRACE_CONSOLE, msg)
|
|
traceError(TRACE_ERROR, TRACE_CONSOLE, debug.traceback())
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
local function get_alert_triggered_key(alert_id, subtype)
|
|
if not alert_id or not subtype then
|
|
if not subtype then
|
|
traceError(TRACE_ERROR, TRACE_CONSOLE, "subtype is nil")
|
|
end
|
|
if not alert_id then
|
|
traceError(TRACE_ERROR, TRACE_CONSOLE, "alert_id is nil")
|
|
end
|
|
traceError(TRACE_ERROR, TRACE_CONSOLE, debug.traceback())
|
|
end
|
|
|
|
local res = string.format("%d@%s", alert_id, subtype)
|
|
|
|
return res
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function alerts_api.addAlertGenerationInfo(alert_json, current_script)
|
|
if alert_json and current_script then
|
|
-- Add information about the script who generated this alert
|
|
alert_json.alert_generation = {
|
|
script_key = current_script.key,
|
|
subdir = current_script.subdir,
|
|
}
|
|
else
|
|
-- NOTE: there are currently some internally generated alerts which
|
|
-- do not use the user_scripts api (e.g. the ntopng startup)
|
|
--tprint(debug.traceback())
|
|
end
|
|
end
|
|
|
|
local function addAlertGenerationInfo(alert_json)
|
|
alerts_api.addAlertGenerationInfo(alert_json, current_script)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
--! @brief Adds pool information to the alert
|
|
--! @param entity_info data returned by one of the entity_info building functions
|
|
local function addAlertPoolInfo(entity_info, alert_json)
|
|
local pools_alert_utils = require "pools_alert_utils"
|
|
|
|
if alert_json then
|
|
local pool_id = pools_alert_utils.get_entity_pool_id(entity_info)
|
|
alert_json.pool_id = pool_id
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
--@brief Check if the `alert` belongs to an exclusion list
|
|
--! @param entity_info data returned by one of the entity_info building functions
|
|
--! @param type_info data returned by one of the type_info building functions
|
|
--@return True if the alert matches an exclusion list, false otherwise
|
|
local function matchExcludeFilter(entity_info, type_info)
|
|
local user_scripts = require "user_scripts"
|
|
|
|
-- Subdir equals the entity id, e.g., "host", "interface", etc.
|
|
local cur_subdir = alert_consts.alertEntityRaw(entity_info.alert_entity.entity_id)
|
|
|
|
-- Check if the alert has a filter and thus should not be generated
|
|
local cur_filters = user_scripts.getFilters(current_configset, cur_subdir)
|
|
|
|
-- Prepare the context with alert data
|
|
local context = {
|
|
alert_id = type_info.alert_type.alert_key,
|
|
subtype = type_info.subtype,
|
|
entity_id = entity_info.alert_entity.entity_id,
|
|
entity_val = entity_info.entity_val,
|
|
severity = type_info.severity.severity_id,
|
|
json = type_info.alert_type_params,
|
|
}
|
|
|
|
if current_script and current_script.key and cur_filters then
|
|
if user_scripts.matchExcludeFilter(cur_filters, current_script, cur_subdir, context) then
|
|
-- This alert is matching an exclusion filter. return, and do anything
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
--! @param entity_info data returned by one of the entity_info building functions
|
|
--! @param type_info data returned by one of the type_info building functions
|
|
--! @param when (optional) the time when the release event occurs
|
|
--! @return true if the alert was successfully stored, false otherwise
|
|
function alerts_api.store(entity_info, type_info, when)
|
|
if(not areAlertsEnabled()) then
|
|
return(false)
|
|
end
|
|
|
|
local force = false
|
|
local ifid = interface.getId()
|
|
local granularity_sec = type_info.granularity and type_info.granularity.granularity_seconds or 0
|
|
local granularity_id = type_info.granularity and type_info.granularity.granularity_id or -1
|
|
|
|
type_info.alert_type_params = type_info.alert_type_params or {}
|
|
addAlertGenerationInfo(type_info.alert_type_params)
|
|
|
|
local alert_json = json.encode(type_info.alert_type_params)
|
|
local subtype = type_info.subtype or ""
|
|
when = when or os.time()
|
|
|
|
-- Here the alert is considered stored. The actual store will be performed
|
|
-- asynchronously
|
|
|
|
-- NOTE: keep in sync with SQLite alert format in AlertsManager.cpp
|
|
local alert_to_store = {
|
|
ifid = ifid,
|
|
action = "store",
|
|
alert_id = type_info.alert_type.alert_key,
|
|
subtype = subtype,
|
|
granularity = granularity_sec,
|
|
entity_id = entity_info.alert_entity.entity_id,
|
|
entity_val = entity_info.entity_val,
|
|
severity = type_info.severity.severity_id,
|
|
tstamp = when,
|
|
tstamp_end = when,
|
|
json = alert_json,
|
|
}
|
|
|
|
addAlertPoolInfo(entity_info, alert_to_store)
|
|
|
|
if matchExcludeFilter(entity_info, type_info) then
|
|
-- This alert is matching an exclusion filter. return, and do anything
|
|
return false
|
|
end
|
|
|
|
recipients.dispatch_notification(alert_to_store, current_script)
|
|
|
|
return(true)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- @brief Determine whether the alert has already been triggered
|
|
-- @param candidate_severity the candidate alert severity
|
|
-- @param candidate_type the candidate alert type
|
|
-- @param candidate_granularity the candidate alert granularity
|
|
-- @param candidate_alert_subtype the candidate alert subtype
|
|
-- @param cur_alerts a table of currently triggered alerts
|
|
-- @return true on if the alert has already been triggered, false otherwise
|
|
--
|
|
-- @note Example of cur_alerts
|
|
-- cur_alerts table
|
|
-- cur_alerts.1 table
|
|
-- cur_alerts.1.alert_type number 2
|
|
-- cur_alerts.1.alert_subtype string min_bytes
|
|
-- cur_alerts.1.entity_val string 192.168.2.222@0
|
|
-- cur_alerts.1.alert_granularity number 60
|
|
-- cur_alerts.1.alert_severity number 2
|
|
-- cur_alerts.1.alert_json string {"metric":"bytes","threshold":1,"value":13727070,"operator":"gt"}
|
|
-- cur_alerts.1.alert_tstamp_end number 1571328097
|
|
-- cur_alerts.1.alert_tstamp number 1571327460
|
|
-- cur_alerts.1.alert_entity number 1
|
|
local function already_triggered(cur_alerts, candidate_severity, candidate_type,
|
|
candidate_granularity, candidate_alert_subtype, remove_from_cur_alerts)
|
|
for i = #cur_alerts, 1, -1 do
|
|
local cur_alert = cur_alerts[i]
|
|
|
|
if candidate_severity == cur_alert.severity
|
|
and candidate_type == cur_alert.alert_id
|
|
and candidate_granularity == cur_alert.granularity
|
|
and candidate_alert_subtype == cur_alert.subtype then
|
|
if remove_from_cur_alerts then
|
|
-- Remove from cur_alerts, this will save cycles for
|
|
-- subsequent calls of this method.
|
|
-- Using .remove is OK here as there won't unnecessarily move memory multiple times:
|
|
-- we return immeediately
|
|
-- NOTE: see un-removed alerts will be released by releaseEntityAlerts in interface.lua
|
|
table.remove(cur_alerts, i)
|
|
end
|
|
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
--! @brief Checks if a stateful alert should be triggered/released or if has already been triggered/released and there is nothing to do
|
|
--! @param cur_alerts a table containing triggered alerts for the current entity
|
|
--! @param candidate_severity the candidate alert severity
|
|
--! @param candidate_type the candidate alert type
|
|
--! @param candidate_granularity the candidate alert granularity
|
|
--! @param candidate_alert_subtype the candidate alert subtype
|
|
--! @param trigger The intention of the caller to trigger this alert (true) or to release this alert (false)
|
|
--! @return True if the alert should be processed, or false if there is nothing to do
|
|
function alerts_api.do_stateful_alert(cur_alerts, candidate_severity, candidate_type, candidate_granularity, candidate_alert_subtype, trigger)
|
|
if not cur_alerts then
|
|
return false
|
|
end
|
|
|
|
local severity_id = candidate_severity.severity_id
|
|
local alert_key = candidate_type.meta.alert_key
|
|
local granularity = alert_consts.alerts_granularities[candidate_granularity]
|
|
local granularity_sec = granularity and granularity.granularity_seconds or 0
|
|
|
|
if trigger then
|
|
-- True if the alert has not been already triggered
|
|
return not already_triggered(cur_alerts, severity_id, alert_key, granularity_sec, candidate_alert_subtype)
|
|
else --[[ release --]]
|
|
-- True if the alert was triggered
|
|
return already_triggered(cur_alerts, severity_id, alert_key, granularity_sec, candidate_alert_subtype)
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
--! @brief Trigger an alert of given type on the entity
|
|
--! @param entity_info data returned by one of the entity_info building functions
|
|
--! @param type_info data returned by one of the type_info building functions
|
|
--! @param when (optional) the time when the release event occurs
|
|
--! @param cur_alerts (optional) a table containing triggered alerts for the current entity
|
|
--! @return true on if the alert was triggered, false otherwise
|
|
--! @note The actual trigger is performed asynchronously
|
|
--! @note false is also returned if an existing alert is found and refreshed
|
|
function alerts_api.trigger(entity_info, type_info, when, cur_alerts)
|
|
if(not areAlertsEnabled()) then
|
|
return(false)
|
|
end
|
|
|
|
local ifid = interface.getId()
|
|
|
|
if(type_info.granularity == nil) then
|
|
alertErrorTraceback("Missing mandatory 'granularity'")
|
|
return(false)
|
|
end
|
|
|
|
-- Apply defaults
|
|
local granularity_sec = type_info.granularity and type_info.granularity.granularity_seconds or 0
|
|
local granularity_id = type_info.granularity and type_info.granularity.granularity_id or 0 --[[ 0 is aperiodic ]]
|
|
local subtype = type_info.subtype or ""
|
|
|
|
when = when or os.time()
|
|
|
|
type_info.alert_type_params = type_info.alert_type_params or {}
|
|
addAlertGenerationInfo(type_info.alert_type_params)
|
|
|
|
-- Check whether this alert is matching an exclusion filter
|
|
local match_exclude_filter = matchExcludeFilter(entity_info, type_info)
|
|
|
|
if(cur_alerts and already_triggered(cur_alerts, type_info.severity.severity_id,
|
|
type_info.alert_type.alert_key, granularity_sec, subtype, true) == true) then
|
|
-- If there, the alert was already engaged at the time this function was called. Hence, if the alert
|
|
-- is matching the exclusion filter, the alert must actually be RELEASED.
|
|
-- NOTE: release is called without `cur_alerts` as there is no need to use this cache. Release MUST be done.
|
|
if match_exclude_filter then
|
|
return alerts_api.release(entity_info, type_info, when, nil --[[ Don't pass cur_alerts, don't want to use this cache --]])
|
|
else
|
|
-- Alert does not belong to an exclusion filter and it is already triggered. There's nothing to do, just return.
|
|
return true
|
|
end
|
|
end
|
|
|
|
if match_exclude_filter then
|
|
-- This alert is matching an exclusion filter. Return, and do not perform any trigger action.
|
|
return false
|
|
end
|
|
|
|
local alert_json = json.encode(type_info.alert_type_params)
|
|
local triggered
|
|
local alert_key_name = get_alert_triggered_key(type_info.alert_type.alert_key, subtype)
|
|
|
|
local params = {
|
|
alert_key_name, granularity_id,
|
|
type_info.severity.severity_id, type_info.alert_type.alert_key,
|
|
subtype, alert_json,
|
|
}
|
|
|
|
if(entity_info.alert_entity.entity_id == alert_consts.alertEntity("interface")) then
|
|
interface.checkContext(entity_info.entity_val)
|
|
triggered = interface.storeTriggeredAlert(table.unpack(params))
|
|
elseif(entity_info.alert_entity.entity_id == alert_consts.alertEntity("network")) then
|
|
network.checkContext(entity_info.entity_val)
|
|
triggered = network.storeTriggeredAlert(table.unpack(params))
|
|
else
|
|
triggered = interface.triggerExternalAlert(entity_info.alert_entity.entity_id, entity_info.entity_val, table.unpack(params))
|
|
end
|
|
|
|
if(triggered == nil) then
|
|
if(do_trace) then print("[Don't Trigger alert (already triggered?) @ "..granularity_sec.."] "..
|
|
entity_info.entity_val .."@"..type_info.alert_type.i18n_title..":".. subtype .. "\n") end
|
|
return(false)
|
|
else
|
|
if(do_trace) then print("[TRIGGER alert @ "..granularity_sec.."] "..
|
|
entity_info.entity_val .."@"..type_info.alert_type.i18n_title..":".. subtype .. "\n") end
|
|
end
|
|
|
|
triggered.ifid = ifid
|
|
triggered.action = "engage"
|
|
|
|
addAlertPoolInfo(entity_info, triggered)
|
|
|
|
-- Emit the notification only if the notification hasn't already been emitted.
|
|
-- This is to avoid alert storms when ntopng is restarted. Indeeed,
|
|
-- if there are 100 alerts triggered when ntopng is switched off, chances are the
|
|
-- same 100 alerts will be triggered again as soon as ntopng is restarted, causing
|
|
-- 100 trigger notifications to be emitted twice. This check is to prevent such behavior.
|
|
if not is_trigger_notified(triggered) then
|
|
recipients.dispatch_notification(triggered, current_script)
|
|
mark_trigger_notified(triggered)
|
|
end
|
|
|
|
return(true)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
--! @brief Release an alert of given type on the entity
|
|
--! @param entity_info data returned by one of the entity_info building functions
|
|
--! @param type_info data returned by one of the type_info building functions
|
|
--! @param when (optional) the time when the release event occurs
|
|
--! @param cur_alerts (optional) a table containing triggered alerts for the current entity
|
|
--! @note The actual release is performed asynchronously
|
|
--! @return true on success, false otherwise
|
|
function alerts_api.release(entity_info, type_info, when, cur_alerts)
|
|
if(not areAlertsEnabled()) then
|
|
return(false)
|
|
end
|
|
|
|
-- Apply defaults
|
|
local granularity_sec = type_info.granularity and type_info.granularity.granularity_seconds or 0
|
|
local granularity_id = type_info.granularity and type_info.granularity.granularity_id or 0 --[[ 0 is aperiodic ]]
|
|
local subtype = type_info.subtype or ""
|
|
|
|
if(cur_alerts and (not already_triggered(cur_alerts, type_info.severity.severity_id,
|
|
type_info.alert_type.alert_key, granularity_sec, subtype, true))) then
|
|
return(true)
|
|
end
|
|
|
|
when = when or os.time()
|
|
local alert_key_name = get_alert_triggered_key(type_info.alert_type.alert_key, subtype)
|
|
local ifid = interface.getId()
|
|
local params = {alert_key_name, granularity_id, when}
|
|
local released = nil
|
|
|
|
if(type_info.severity == nil) then
|
|
alertErrorTraceback(string.format("Missing alert_severity [type=%s]", type_info.alert_type and type_info.alert_type.alert_key or ""))
|
|
return(false)
|
|
end
|
|
|
|
if(entity_info.alert_entity.entity_id == alert_consts.alertEntity("interface")) then
|
|
interface.checkContext(entity_info.entity_val)
|
|
released = interface.releaseTriggeredAlert(table.unpack(params))
|
|
elseif(entity_info.alert_entity.entity_id == alert_consts.alertEntity("network")) then
|
|
network.checkContext(entity_info.entity_val)
|
|
released = network.releaseTriggeredAlert(table.unpack(params))
|
|
else
|
|
released = interface.releaseExternalAlert(entity_info.alert_entity.entity_id, entity_info.entity_val, table.unpack(params))
|
|
end
|
|
|
|
if(released == nil) then
|
|
if(do_trace) then tprint("[Dont't Release alert (not triggered?) @ "..granularity_sec.."] "..
|
|
entity_info.entity_val .."@"..type_info.alert_type.i18n_title..":".. subtype .. "\n") end
|
|
return(false)
|
|
else
|
|
if(do_trace) then tprint("[RELEASE alert @ "..granularity_sec.."] "..
|
|
entity_info.entity_val .."@"..type_info.alert_type.i18n_title..":".. subtype .. "\n") end
|
|
end
|
|
|
|
released.ifid = ifid
|
|
released.action = "release"
|
|
|
|
addAlertPoolInfo(entity_info, released)
|
|
|
|
mark_release_notified(released)
|
|
|
|
if matchExcludeFilter(entity_info, type_info) then
|
|
-- This alert is matching an exclusion filter, return.
|
|
-- NOTE: this code is placed after the in-memory release (see <entity>.releaseTriggeredAlert calls above)
|
|
-- as we want to remove triggered alerts matching filters from memory. The only thing that
|
|
-- should be avoided is the generation of notifications causing the alert to be inserted into SQLite
|
|
-- and also sent to external endpoints
|
|
return false
|
|
end
|
|
|
|
recipients.dispatch_notification(released, current_script)
|
|
|
|
return(true)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Convenient method to release multiple alerts on an entity
|
|
function alerts_api.releaseEntityAlerts(entity_info, alerts)
|
|
if(alerts == nil) then
|
|
alerts = interface.getEngagedAlerts(entity_info.alert_entity.entity_id, entity_info.entity_val)
|
|
end
|
|
|
|
for _, cur_alert in pairs(alerts) do
|
|
-- NOTE: do not pass alerts here as a parameters as deleting items while
|
|
-- does not work in lua
|
|
|
|
local cur_alert_type = alert_consts.alert_types[alert_consts.getAlertType(cur_alert.alert_id)]
|
|
-- Instantiate the alert.
|
|
-- NOTE: No parameter is passed to :new() as parameters are NOT used when releasing alerts
|
|
-- This may change in the future.
|
|
local cur_alert_instance = cur_alert_type:new(--[[ empty, no parameters for the release --]])
|
|
|
|
-- Set alert params.
|
|
cur_alert_instance:set_severity(alert_severities[alert_consts.alertSeverityRaw(cur_alert.severity)])
|
|
cur_alert_instance:set_subtype(cur_alert.subtype)
|
|
cur_alert_instance:set_granularity(alert_consts.sec2granularity(cur_alert.granularity))
|
|
|
|
cur_alert_instance:release(entity_info)
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
-- entity_info building functions
|
|
-- ##############################################
|
|
|
|
function alerts_api.hostAlertEntity(hostip, hostvlan)
|
|
return {
|
|
alert_entity = alert_consts.alert_entities.host,
|
|
-- NOTE: keep in sync with C (Alertable::setEntityValue)
|
|
entity_val = hostinfo2hostkey({ip = hostip, vlan = hostvlan}, nil, true)
|
|
}
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function alerts_api.interfaceAlertEntity(ifid)
|
|
return {
|
|
alert_entity = alert_consts.alert_entities.interface,
|
|
-- NOTE: keep in sync with C (Alertable::setEntityValue)
|
|
entity_val = string.format("%d", ifid)
|
|
}
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function alerts_api.networkAlertEntity(network_cidr)
|
|
return {
|
|
alert_entity = alert_consts.alert_entities.network,
|
|
-- NOTE: keep in sync with C (Alertable::setEntityValue)
|
|
entity_val = network_cidr
|
|
}
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function alerts_api.snmpInterfaceEntity(snmp_device, snmp_interface)
|
|
return {
|
|
alert_entity = alert_consts.alert_entities.snmp_device,
|
|
entity_val = string.format("%s_ifidx%s", snmp_device, ""..snmp_interface)
|
|
}
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function alerts_api.snmpDeviceEntity(snmp_device)
|
|
return {
|
|
alert_entity = alert_consts.alert_entities.snmp_device,
|
|
entity_val = snmp_device
|
|
}
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function alerts_api.macEntity(mac)
|
|
return {
|
|
alert_entity = alert_consts.alert_entities.mac,
|
|
entity_val = mac
|
|
}
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function alerts_api.userEntity(user)
|
|
return {
|
|
alert_entity = alert_consts.alert_entities.user,
|
|
entity_val = user
|
|
}
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function alerts_api.hostPoolEntity(pool_id)
|
|
return {
|
|
alert_entity = alert_consts.alert_entities.host_pool,
|
|
entity_val = tostring(pool_id)
|
|
}
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function alerts_api.amThresholdCrossEntity(host)
|
|
return {
|
|
alert_entity = alert_consts.alert_entities.am_host,
|
|
entity_val = host
|
|
}
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function alerts_api.systemEntity(system_entity_name)
|
|
return {
|
|
alert_entity = alert_consts.alert_entities.system,
|
|
entity_val = system_entity_name or "system"
|
|
}
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function alerts_api.iec104Entity(flow)
|
|
return {
|
|
alert_entity = alert_consts.alert_entities.flow,
|
|
entity_val = "flow"
|
|
}
|
|
end
|
|
|
|
-- ##############################################
|
|
-- type_info building functions
|
|
-- ##############################################
|
|
|
|
function alerts_api.tooManyDropsType(drops, drop_perc, threshold)
|
|
return({
|
|
alert_id = alert_consts.alert_types.alert_too_many_drops,
|
|
severity = alert_severities.error,
|
|
granularity = alert_consts.alerts_granularities.min,
|
|
alert_type_params = {
|
|
drops = drops, drop_perc = drop_perc, edge = threshold,
|
|
},
|
|
})
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- TODO document
|
|
function alerts_api.checkThresholdAlert(params, alert_type, value, attacker, victim)
|
|
local user_scripts = require "user_scripts"
|
|
local script = params.user_script
|
|
local threshold_config = params.user_script_config
|
|
local alarmed = false
|
|
local threshold = threshold_config.threshold or threshold_config.default_contacts
|
|
|
|
-- Retrieve the function to be used for the threshold check.
|
|
-- The function depends on the operator, i.e., "gt", or "lt".
|
|
-- When there's no operator, the default "gt" function is taken from the available
|
|
-- operation functions
|
|
local op_fn = user_scripts.operator_functions[threshold_config.operator] or user_scripts.operator_functions.gt
|
|
if op_fn and op_fn(value, threshold) then alarmed = true end
|
|
|
|
-- tprint({params.cur_alerts, threshold_config.severity, alert_type.meta, params.granularity, script.key --[[ the subtype--]], alarmed})
|
|
|
|
-- Check if there is work to do before creating an instance of the alert and doing the actual trigger release
|
|
if not alerts_api.do_stateful_alert(params.cur_alerts, threshold_config.severity,
|
|
alert_type, params.granularity, script.key --[[ the subtype --]], alarmed) then
|
|
-- Nothing to do. Alert either already triggered or already released
|
|
return
|
|
end
|
|
|
|
local alert = alert_type.new(
|
|
params.user_script.key,
|
|
value,
|
|
threshold_config.operator,
|
|
threshold
|
|
)
|
|
|
|
alert:set_severity(threshold_config.severity)
|
|
alert:set_granularity(params.granularity)
|
|
alert:set_subtype(script.key)
|
|
|
|
if attacker ~= nil then
|
|
alert:set_attacker(attacker)
|
|
end
|
|
|
|
if victim ~= nil then
|
|
alert:set_victim(victim)
|
|
end
|
|
|
|
if(alarmed) then
|
|
alert:trigger(params.alert_entity, nil, params.cur_alerts)
|
|
else
|
|
alert:release(params.alert_entity, nil, params.cur_alerts)
|
|
end
|
|
end
|
|
|
|
-- #####################################
|
|
|
|
function alerts_api.handlerPeerBehaviour(params, stats, tot_anomalies, host_ip, threshold, behaviour_type, subtype)
|
|
local anomaly = stats["anomaly"]
|
|
local lower_bound = stats["lower_bound"]
|
|
local upper_bound = stats["upper_bound"]
|
|
local value = stats["value"]
|
|
local prediction = stats["prediction"]
|
|
|
|
local alert_unexpected_behaviour = behaviour_type.new(
|
|
value,
|
|
prediction,
|
|
upper_bound,
|
|
lower_bound
|
|
)
|
|
|
|
if threshold and tot_anomalies and tot_anomalies > threshold then
|
|
alert_unexpected_behaviour:set_severity(alert_severities.error)
|
|
else
|
|
alert_unexpected_behaviour:set_severity(alert_severities.warning)
|
|
end
|
|
|
|
alert_unexpected_behaviour:set_granularity(params.granularity)
|
|
|
|
if subtype then
|
|
alert_unexpected_behaviour:set_subtype(subtype)
|
|
end
|
|
|
|
if anomaly then
|
|
alert_unexpected_behaviour:trigger(params.alert_entity)
|
|
else
|
|
alert_unexpected_behaviour:release(params.alert_entity)
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- An alert check function which checks for anomalies.
|
|
-- The user_script key is the type of the anomaly to check.
|
|
-- The user_script must implement a anomaly_type_builder(anomaly_key) function
|
|
-- which returns a type_info for the given anomaly.
|
|
function alerts_api.anomaly_check_function(params)
|
|
local anomal_key = params.user_script.key
|
|
local type_info = params.user_script.anomaly_type_builder()
|
|
|
|
type_info:set_severity(alert_severities.error)
|
|
type_info:set_granularity(params.granularity)
|
|
type_info:set_subtype(anomal_key)
|
|
|
|
if params.entity_info.anomalies[anomal_key] then
|
|
type_info:trigger(params.alert_entity, nil, params.cur_alerts)
|
|
else
|
|
type_info:release(params.alert_entity, nil, params.cur_alerts)
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- @brief Performs a difference between the current metric value and
|
|
-- the previous saved value and saves the current value for next call.
|
|
-- @param reg lua C context pointer to the alertable entity storage
|
|
-- @param metric_name name of the metric to retrieve
|
|
-- @param granularity the granularity string
|
|
-- @param curr_val the current metric value
|
|
-- @param skip_first if true, 0 will be returned when no cached value is present
|
|
-- @return the difference between current and previous value
|
|
local function delta_val(reg, metric_name, granularity, curr_val, skip_first)
|
|
local granularity_num = alert_consts.granularity2id(granularity)
|
|
local key = string.format("%s:%s", metric_name, granularity_num)
|
|
|
|
-- Read cached value and purify it
|
|
local prev_val = tonumber(reg.getCachedAlertValue(key, granularity_num))
|
|
|
|
-- Save the value for the next round
|
|
reg.setCachedAlertValue(key, tostring(curr_val), granularity_num)
|
|
|
|
if((skip_first == true) and (prev_val == nil)) then
|
|
return(0)
|
|
else
|
|
return(curr_val - (prev_val or 0))
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function alerts_api.host_delta_val(metric_name, granularity, curr_val, skip_first)
|
|
return(delta_val(host --[[ the host Lua reg ]], metric_name, granularity, curr_val, skip_first))
|
|
end
|
|
|
|
function alerts_api.interface_delta_val(metric_name, granularity, curr_val, skip_first)
|
|
return(delta_val(interface --[[ the interface Lua reg ]], metric_name, granularity, curr_val, skip_first))
|
|
end
|
|
|
|
function alerts_api.network_delta_val(metric_name, granularity, curr_val, skip_first)
|
|
return(delta_val(network --[[ the network Lua reg ]], metric_name, granularity, curr_val, skip_first))
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function alerts_api.application_bytes(info, application_name)
|
|
local curr_val = 0
|
|
|
|
if info["ndpi"] and info["ndpi"][application_name] then
|
|
curr_val = info["ndpi"][application_name]["bytes.sent"] + info["ndpi"][application_name]["bytes.rcvd"]
|
|
end
|
|
|
|
return curr_val
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function alerts_api.category_bytes(info, category_name)
|
|
local curr_val = 0
|
|
|
|
if info["ndpi_categories"] and info["ndpi_categories"][category_name] then
|
|
curr_val = info["ndpi_categories"][category_name]["bytes.sent"] + info["ndpi_categories"][category_name]["bytes.rcvd"]
|
|
end
|
|
|
|
return curr_val
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function alerts_api.invokeScriptHook(user_script, configset, hook_fn, p1, p2, p3)
|
|
current_script = user_script
|
|
current_configset = configset
|
|
|
|
return(hook_fn(p1, p2, p3))
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
return(alerts_api)
|