mirror of
https://github.com/ntop/ntopng.git
synced 2026-04-28 23:19:33 +00:00
Scaffolding code of the host scripts
Scaffolding code for host alerts
Adds host_callbacks/ for .cpp files
Implements all classes for host callbacks
Removes pro/enterprise host callbacks
Adds typedefs with callback deltas
Compilation fix
Creates instances of host callbacks in loader
Link fix
Removes redundant/non-necessary host alerts
Merges Scan and Flood callbacks together
Removes outdated API files
Refactors alert keys into entity|id
Refactors all flow alert_{...} into flow_alert_{...}
Refactors C++ flow alert_{...} into flow_alert_{...}
Reworks alert ids to include an entity type
Cleanup and merge alertTypeRaw with getAlertType
Minor fix
Refactors alert definitions and keys into sub directories
Implement host alert callback execution and trigger/release logic
Update callbacks API
Adds base CallbacksLoader for {Host,Flow}CallbacksLoader
Implements load of host user scripts with periodicities
Implements runtime reload of host callbacks
Add logic for periodic callbacks
Add 'expired' flag to host alerts
Implements execution of host callbacks and SYN flood checks
Adds triggerAlertAsync calls to SYNFlood
Implements JSON host alert generation info
Handle callback getPeriod. Optimize callback lookup.
Implements host recipients in C++
Add callback status
Define destructor
Iterator fixes
Cleanup host Lua calls (now performed in C++)
Changes to show new host alerts in SQLite
Adds release/engage action on alert JSON
Move AlertableEntity to OtherAlertableEntity, inheriting from a new AlertableEntity. Add HostAlertableEntity.
Implements SYN Flood Attacker with params
Uses parametrized thresholds to trigger syn flood alerts
Implements build alert of both attacker and victim
Implement HostAlertableEntity
Implements SYN scan attacker/victim alerts
Implements flow flood attacker/victim alerts
Removes a debug flag
Add virtual allocStatus
Add HostAlert disableAutoRelease()
Add Ãexplicit releaseAlert()
Implements SYNFloodHostCallbackStatus
Implements SYNScanHostCallbackStatus
Implements FlowFloodHostCallbackStatus
Change trigger API to handle cli/src score
Implements {DNS,SMTP,NTP}ServerContactsAlert
Reworks ServerContacts host alerts
Implement exclusion bitmaps for host alerts
Implements {SMTP,DNS,NTP}ServerContactsAlert
Adds host_info to the generated alert JSON
Minor cleanup
Optimize access to callback status
Move RepliesRequestsRatio to pro
Rework triggerAlert on host to avoid multiple call and unneeded status data
Compilation fix
Reworked host alerts API (wip)
Cleanup unused host callbacks
Compilation fixes
Finishes backend implementation of host alerts exclusions
Rework host callbacks executor
Implements disable of host alerts
Port SYNFlood to the new api
Cleanup
Reduce duplicated code
Comments
Port ServerContacts to the new api
Clenaup
Adds parsing of configuration for host callbacks
Port SYNScanAlert to the new API
Port FlowFlood to the new API
Cleanup unused HostAlert getName
Reworks DNSRequestsErrorsRatioAlert
Adds JSON for DNSTrafficAlert
Adds FlowsAlert
Adds P2PTrafficAlert
Add RepliesRequestsRatioAlert
Adds ScoreAlert
Adds ThroughputAlert
Adds TrafficAlert
Fixes for scan/flood alerts
DNS ratio alert support
Add HTTP stats getters
Implements deltas for many host callbacks
Host score inc
Adds missing Alert params to host alerts
Release all host alerts on idle
Refactors score classes
Implements class Score to contain scores for hosts, flows, etc
Adds scores to VLANs, Networks, ASes and Countries
Host callbacks can trigger a single alert now
FlowFlood, SYNFloo, SYNScan now inherit from FlowHits
Move severity and score to constructor
Add else branch to hits callbacks
Alert definition update for flows_flood, syn_flood, syn_scan
Update field name
Engaged alert init
Adds score incs/decs for AS, VLAN, country, os and network
Restore network scripts
Restored other alert definitions for floods
Handle decreasing alert score
Removes include
Rename flows_flood to flow_flood for consistency
Restored alert_tcp_syn_flood_victim alert_tcp_syn_scan_victim definitions
Fixes for non-host engaged/release alerts
Cleanup LuaEngineFlow and LuaEngineHost classes
Fixes old calls to host lua during shutdown
Removes AlertCheckLuaEngine instance
Fixes purging of flows
Fixes alerts release upon shutdown causing wrong uses
Removes a debug flag
Fix getNumEngagedAlerts
Cleanup unnecessary host callbacks
Removes array of callback statuses inside host
Bitmap fixes
Refactors Bitmap into Bitmap128
Implements 16-bits bitmaps for host alerts
Adds class HostCallbacksStatus
Moves callback status p2p and DNS inside HostCallbacksStatus
Removes unused callbacks in typedefs
Minor cleanup
Adds trigger/release for DNS/p2p alerts
Reworks UI of hosts user scripts
Rename HostCallbackType to HostCallbackID, getType to getID, others
2389 lines
84 KiB
Lua
2389 lines
84 KiB
Lua
--
|
|
-- (C) 2014-21 - ntop.org
|
|
--
|
|
|
|
local dirs = ntop.getDirs()
|
|
package.path = dirs.installdir .. "/scripts/lua/modules/pools/?.lua;" .. package.path
|
|
|
|
-- This file contains the description of all functions
|
|
-- used to trigger host alerts
|
|
local verbose = ntop.getCache("ntopng.prefs.alerts.debug") == "1"
|
|
local callback_utils = require "callback_utils"
|
|
local template = require "template_utils"
|
|
local json = require("dkjson")
|
|
local host_pools = require "host_pools"
|
|
local recovery_utils = require "recovery_utils"
|
|
local alert_severities = require "alert_severities"
|
|
local alert_entities = require "alert_entities"
|
|
local alert_consts = require "alert_consts"
|
|
local format_utils = require "format_utils"
|
|
local telemetry_utils = require "telemetry_utils"
|
|
local tracker = require "tracker"
|
|
local alerts_api = require "alerts_api"
|
|
local icmp_utils = require "icmp_utils"
|
|
local user_scripts = require "user_scripts"
|
|
|
|
local shaper_utils = nil
|
|
|
|
if(ntop.isnEdge()) then
|
|
package.path = dirs.installdir .. "/pro/scripts/lua/modules/?.lua;" .. package.path
|
|
shaper_utils = require("shaper_utils")
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
local alert_utils = {}
|
|
|
|
-- ##############################################
|
|
|
|
if ntop.isEnterpriseM() then
|
|
local dirs = ntop.getDirs()
|
|
package.path = dirs.installdir .. "/pro/scripts/lua/enterprise/modules/?.lua;" .. package.path
|
|
-- add enterprise utils to this module
|
|
alert_utils = require "enterprise_alert_utils"
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
local function alertTypeDescription(v, alert_entity_id)
|
|
local alert_key = alert_consts.getAlertType(v, alert_entity_id)
|
|
|
|
if(alert_key) then
|
|
if alert_consts.alert_types[alert_key].format then
|
|
-- New API
|
|
return alert_consts.alert_types[alert_key].format
|
|
else
|
|
-- TODO: Possible removed once migration is done
|
|
return(alert_consts.alert_types[alert_key].i18n_description)
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
local function get_make_room_keys(ifId)
|
|
return {flows="ntopng.cache.alerts.ifid_"..ifId..".make_room_flow_alerts",
|
|
entities="ntopng.cache.alerts.ifid_"..ifId..".make_room_closed_alerts"}
|
|
end
|
|
|
|
-- #################################
|
|
|
|
-- This function maps the SQLite table names to the conventional table
|
|
-- names used in this script
|
|
local function luaTableName(sqlite_table_name)
|
|
--~ ALERTS_MANAGER_FLOWS_TABLE_NAME "flows_alerts"
|
|
if(sqlite_table_name == "flows_alerts") then
|
|
return("historical-flows")
|
|
else
|
|
return("historical")
|
|
end
|
|
end
|
|
|
|
-- #################################
|
|
|
|
local function performAlertsQuery(statement, what, opts, force_query, group_by)
|
|
local wargs = {"1=1"}
|
|
local oargs = {}
|
|
|
|
if(group_by ~= nil) then
|
|
group_by = " GROUP BY " .. group_by
|
|
else
|
|
group_by = ""
|
|
end
|
|
|
|
if tonumber(opts.row_id) ~= nil then
|
|
wargs[#wargs+1] = 'AND rowid = '..(opts.row_id)
|
|
end
|
|
|
|
if (not isEmptyString(opts.entity)) and (not isEmptyString(opts.entity_val)) then
|
|
if(what == "historical-flows") then
|
|
if(tonumber(opts.entity) ~= alert_consts.alertEntity("host")) then
|
|
return({})
|
|
else
|
|
-- need to handle differently for flows table
|
|
local info = hostkey2hostinfo(opts.entity_val)
|
|
wargs[#wargs+1] = 'AND (cli_addr="'..(info.host)..'" OR srv_addr="'..(info.host)..'")'
|
|
wargs[#wargs+1] = 'AND vlan_id='..(info.vlan)
|
|
end
|
|
else
|
|
wargs[#wargs+1] = 'AND alert_entity = "'..(opts.entity)..'"'
|
|
wargs[#wargs+1] = 'AND alert_entity_val = "'..(opts.entity_val)..'"'
|
|
end
|
|
elseif (what ~= "historical-flows") then
|
|
if (not isEmptyString(opts.entity)) then
|
|
wargs[#wargs+1] = 'AND alert_entity = "'..(opts.entity)..'"'
|
|
end
|
|
end
|
|
|
|
if not isEmptyString(opts.origin) then
|
|
local info = hostkey2hostinfo(opts.origin)
|
|
wargs[#wargs+1] = 'AND cli_addr="'..(info.host)..'"'
|
|
wargs[#wargs+1] = 'AND vlan_id='..(info.vlan)
|
|
end
|
|
|
|
if not isEmptyString(opts.target) then
|
|
local info = hostkey2hostinfo(opts.target)
|
|
wargs[#wargs+1] = 'AND srv_addr="'..(info.host)..'"'
|
|
wargs[#wargs+1] = 'AND vlan_id='..(info.vlan)
|
|
end
|
|
|
|
if tonumber(opts.epoch_begin) ~= nil then
|
|
wargs[#wargs+1] = 'AND alert_tstamp >= '..(opts.epoch_begin)
|
|
end
|
|
|
|
if tonumber(opts.epoch_end) ~= nil then
|
|
wargs[#wargs+1] = 'AND alert_tstamp <= '..(opts.epoch_end)
|
|
end
|
|
|
|
if not isEmptyString(opts.flowhosts_type) then
|
|
if opts.flowhosts_type ~= "all_hosts" then
|
|
local cli_local, srv_local = 0, 0
|
|
|
|
if opts.flowhosts_type == "local_only" then cli_local, srv_local = 1, 1
|
|
elseif opts.flowhosts_type == "remote_only" then cli_local, srv_local = 0, 0
|
|
elseif opts.flowhosts_type == "local_origin_remote_target" then cli_local, srv_local = 1, 0
|
|
elseif opts.flowhosts_type == "remote_origin_local_target" then cli_local, srv_local = 0, 1
|
|
end
|
|
|
|
if what == "historical-flows" then
|
|
wargs[#wargs+1] = "AND cli_localhost = "..cli_local
|
|
wargs[#wargs+1] = "AND srv_localhost = "..srv_local
|
|
end
|
|
-- TODO cannot apply it to other tables right now
|
|
end
|
|
end
|
|
|
|
if tonumber(opts.alert_type) ~= nil then
|
|
wargs[#wargs+1] = "AND alert_type = "..(opts.alert_type)
|
|
end
|
|
|
|
if tonumber(opts.alert_severity) ~= nil then
|
|
wargs[#wargs+1] = "AND alert_severity = "..(opts.alert_severity)
|
|
end
|
|
|
|
if what == "historical-flows" then
|
|
if tonumber(opts.alert_l7_proto) ~= nil then
|
|
wargs[#wargs+1] = "AND l7_proto = "..(opts.alert_l7_proto)
|
|
end
|
|
end
|
|
|
|
if((not isEmptyString(opts.sortColumn)) and (not isEmptyString(opts.sortOrder))) then
|
|
local order_by
|
|
|
|
if opts.sortColumn == "column_date" then
|
|
order_by = "alert_tstamp"
|
|
elseif opts.sortColumn == "column_key" then
|
|
order_by = "rowid"
|
|
elseif opts.sortColumn == "column_severity" then
|
|
order_by = "alert_severity"
|
|
elseif opts.sortColumn == "column_type" then
|
|
order_by = "alert_type"
|
|
elseif opts.sortColumn == "column_count" and what ~= "engaged" then
|
|
order_by = "alert_counter"
|
|
elseif opts.sortColumn == "column_score" and what ~= "engaged" then
|
|
order_by = "score"
|
|
elseif((opts.sortColumn == "column_duration") and (what == "historical")) then
|
|
order_by = "(alert_tstamp_end - alert_tstamp)"
|
|
else
|
|
-- default
|
|
order_by = "alert_tstamp"
|
|
end
|
|
|
|
oargs[#oargs+1] = "ORDER BY "..order_by
|
|
oargs[#oargs+1] = string.upper(opts.sortOrder)
|
|
end
|
|
|
|
-- pagination
|
|
if((tonumber(opts.perPage) ~= nil) and (tonumber(opts.currentPage) ~= nil)) then
|
|
local to_skip = (tonumber(opts.currentPage)-1) * tonumber(opts.perPage)
|
|
oargs[#oargs+1] = "LIMIT"
|
|
oargs[#oargs+1] = to_skip..","..(opts.perPage)
|
|
end
|
|
|
|
local query = table.concat(wargs, " ")
|
|
group_by = table.concat(oargs, " ") .. group_by
|
|
local res
|
|
|
|
-- Uncomment to debug the queries
|
|
-- tprint(statement.." (from "..what..") WHERE "..query .. " ".. group_by)
|
|
|
|
|
|
if((what == "engaged") or (what == "historical")) then
|
|
res = interface.queryAlertsRaw(statement, query, group_by, force_query)
|
|
elseif what == "historical-flows" then
|
|
res = interface.queryFlowAlertsRaw(statement, query, group_by, force_query)
|
|
else
|
|
error("Invalid alert subject: "..what)
|
|
end
|
|
|
|
return res
|
|
end
|
|
|
|
-- #################################
|
|
|
|
local function getNumEngagedAlerts(options)
|
|
local entity_type_filter = tonumber(options.entity)
|
|
local entity_value_filter = options.entity_val
|
|
|
|
local res = interface.getEngagedAlertsCount(entity_type_filter, entity_value_filter)
|
|
|
|
if(res ~= nil) then
|
|
return(res.num_alerts)
|
|
end
|
|
|
|
return(0)
|
|
end
|
|
|
|
-- #################################
|
|
|
|
-- Remove pagination options from the options
|
|
local function getUnpagedAlertOptions(options)
|
|
local res = {}
|
|
|
|
local paged_option = { currentPage=1, perPage=1, sortColumn=1, sortOrder=1 }
|
|
|
|
for k,v in pairs(options) do
|
|
if not paged_option[k] then
|
|
res[k] = v
|
|
end
|
|
end
|
|
|
|
return res
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.getNumAlerts(what, options)
|
|
local num = 0
|
|
|
|
if(what == "engaged") then
|
|
num = getNumEngagedAlerts(options)
|
|
else
|
|
local opts = getUnpagedAlertOptions(options or {})
|
|
local res = performAlertsQuery("SELECT COUNT(*) AS count", what, opts)
|
|
if((res ~= nil) and (#res == 1) and (res[1].count ~= nil)) then num = tonumber(res[1].count) end
|
|
end
|
|
|
|
return num
|
|
end
|
|
|
|
-- #################################
|
|
|
|
-- Faster than of getNumAlerts
|
|
function alert_utils.hasAlerts(what, options)
|
|
if(what == "engaged") then
|
|
return(getNumEngagedAlerts(options) > 0)
|
|
end
|
|
|
|
local opts = getUnpagedAlertOptions(options or {})
|
|
-- limit 1
|
|
opts.perPage = 1
|
|
opts.currentPage = 1
|
|
local res = performAlertsQuery("SELECT rowid", what, opts)
|
|
|
|
if((res ~= nil) and (#res == 1)) then
|
|
return(true)
|
|
else
|
|
return(false)
|
|
end
|
|
end
|
|
|
|
-- #################################
|
|
|
|
local function engagedAlertsQuery(params)
|
|
local type_filter = tonumber(params.alert_type)
|
|
local severity_filter = tonumber(params.alert_severity)
|
|
local entity_type_filter = tonumber(params.entity)
|
|
local entity_value_filter = params.entity_val
|
|
|
|
local perPage = tonumber(params.perPage or 10)
|
|
local sortColumn = params.sortColumn or "column_"
|
|
local sortOrder = params.sortOrder or "desc"
|
|
local sOrder = ternary(sortOrder == "desc", rev_insensitive, asc_insensitive)
|
|
local currentPage = tonumber(params.currentPage or 1)
|
|
local totalRows = 0
|
|
|
|
--~ tprint(string.format("type=%s sev=%s entity=%s val=%s", type_filter, severity_filter, entity_type_filter, entity_value_filter))
|
|
local alerts = interface.getEngagedAlerts(entity_type_filter, entity_value_filter, type_filter, severity_filter)
|
|
local sort_2_col = {}
|
|
|
|
-- Sort
|
|
for idx, alert in pairs(alerts) do
|
|
if sortColumn == "column_type" then
|
|
sort_2_col[idx] = alert.alert_type
|
|
elseif sortColumn == "column_severity" then
|
|
sort_2_col[idx] = alert.alert_severity
|
|
elseif sortColumn == "column_duration" then
|
|
sort_2_col[idx] = os.time() - alert.alert_tstamp
|
|
else -- column_date
|
|
sort_2_col[idx] = alert.alert_tstamp
|
|
end
|
|
|
|
totalRows = totalRows + 1
|
|
end
|
|
|
|
-- Pagination
|
|
local to_skip = (currentPage-1) * perPage
|
|
local totalRows = #alerts
|
|
local res = {}
|
|
local i = 0
|
|
|
|
for idx in pairsByValues(sort_2_col, sOrder) do
|
|
if i >= to_skip + perPage then
|
|
break
|
|
end
|
|
|
|
if (i >= to_skip) then
|
|
res[#res + 1] = alerts[idx]
|
|
end
|
|
|
|
i = i + 1
|
|
end
|
|
|
|
return res, totalRows
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.getAlerts(what, options, with_counters)
|
|
local alerts, num_alerts
|
|
|
|
if what == "engaged" then
|
|
alerts, num_alerts = engagedAlertsQuery(options)
|
|
|
|
if not with_counters then
|
|
num_alerts = nil
|
|
end
|
|
else
|
|
alerts = performAlertsQuery("SELECT rowid, *", what, options)
|
|
|
|
if with_counters then
|
|
num_alerts = alert_utils.getNumAlerts(what, options)
|
|
end
|
|
end
|
|
|
|
return alerts, num_alerts
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.getNumAlertsPerHour(what, epoch_begin, epoch_end, alert_type, alert_severity, host_info)
|
|
local opts = {
|
|
epoch_begin = epoch_begin,
|
|
epoch_end = epoch_end,
|
|
alert_type = alert_type,
|
|
alert_severity = alert_severity,
|
|
}
|
|
|
|
if host_info and host_info.host then
|
|
local entity_info = alerts_api.hostAlertEntity(host_info.host, host_info.vlan)
|
|
opts.entity = entity_info.alert_entity.entity_id
|
|
opts.entity_val = entity_info.alert_entity_val
|
|
end
|
|
|
|
return performAlertsQuery("select (alert_tstamp - alert_tstamp % 3600) as hour, count(*) count", what, opts, nil, "hour")
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.getNumAlertsPerType(what, epoch_begin, epoch_end)
|
|
local opts = {
|
|
epoch_begin = epoch_begin,
|
|
epoch_end = epoch_end,
|
|
}
|
|
|
|
return performAlertsQuery("select alert_type id, count(*) count", what, opts, nil, "alert_type" --[[ group by ]])
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.getNumAlertsPerSeverity(what, epoch_begin, epoch_end)
|
|
local opts = {
|
|
epoch_begin = epoch_begin,
|
|
epoch_end = epoch_end,
|
|
}
|
|
|
|
return performAlertsQuery("select alert_severity severity, count(*) count", what, opts, nil, "alert_severity" --[[ group by ]])
|
|
end
|
|
|
|
-- #################################
|
|
|
|
local function refreshAlerts(ifid)
|
|
ntop.delCache(string.format("ntopng.cache.alerts.ifid_%d.has_alerts", ifid))
|
|
ntop.delCache("ntopng.cache.update_alerts_stats_time")
|
|
end
|
|
|
|
-- #################################
|
|
|
|
local function deleteAlerts(what, options)
|
|
local opts = getUnpagedAlertOptions(options or {})
|
|
performAlertsQuery("DELETE", what, opts)
|
|
end
|
|
|
|
-- #################################
|
|
|
|
--@brief Deletes all stored alerts matching a user script `filter`
|
|
-- @param subdir the modules subdir
|
|
-- @param user_script The string script identifier
|
|
-- @param filter An already validated user script filter
|
|
-- @return nil
|
|
function alert_utils.deleteAlertsMatchingUserScriptFilter(subdir, user_script, filter)
|
|
local res = {}
|
|
local statement = "DELETE " -- TODO: change to DELETE
|
|
|
|
local query = user_scripts.prepareFilterSQLiteWhere(subdir, user_script, filter)
|
|
|
|
if subdir ~= "flow" then
|
|
res = interface.queryAlertsRaw(statement, query, group_by, true)
|
|
else
|
|
res = interface.queryFlowAlertsRaw(statement, query, group_by, true)
|
|
end
|
|
end
|
|
|
|
-- #################################
|
|
|
|
--@brief Deletes all stored alerts matching an host and an IP
|
|
-- @return nil
|
|
function alert_utils.deleteFlowAlertsMatching(host_ip, alert_key)
|
|
local res = {}
|
|
local statement = "DELETE "
|
|
|
|
-- This is to match elements inside the alert_json
|
|
local where = {
|
|
string.format("(cli_addr = '%s' OR srv_addr = '%s')", host_ip, host_ip),
|
|
string.format("alert_type = %u", alert_key),
|
|
}
|
|
|
|
where = table.concat(where, " AND ")
|
|
|
|
res = interface.queryFlowAlertsRaw(statement, where, nil, true)
|
|
end
|
|
|
|
-- #################################
|
|
|
|
--@brief Deletes all stored alerts matching an host and an IP
|
|
-- @return nil
|
|
function alert_utils.deleteHostAlertsMatching(host_ip, alert_key)
|
|
local res = {}
|
|
local statement = "DELETE "
|
|
|
|
-- This is to match elements inside the alert_json
|
|
local where = {
|
|
string.format("alert_entity = %u", alert_entities.host.entity_id),
|
|
string.format("alert_entity_val LIKE '%s%%'", host_ip), -- Use like to disregard any @<VLAN> after the IP address
|
|
string.format("alert_type = %u", alert_key),
|
|
}
|
|
|
|
where = table.concat(where, " AND ")
|
|
|
|
res = interface.queryAlertsRaw(statement, where, nil, true)
|
|
end
|
|
|
|
-- #################################
|
|
|
|
-- this function returns an object with parameters specific for one tab
|
|
function alert_utils.getTabParameters(_get, what)
|
|
local opts = {}
|
|
for k,v in pairs(_get) do opts[k] = v end
|
|
|
|
-- these options are contextual to the current tab (status)
|
|
if _get.status ~= what then
|
|
opts.alert_type = nil
|
|
opts.alert_severity = nil
|
|
end
|
|
if not isEmptyString(what) then opts.status = what end
|
|
opts.ifid = interface.getId()
|
|
return opts
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.checkDeleteStoredAlerts()
|
|
_GET["status"] = _GET["status"] or _POST["status"]
|
|
|
|
if((_POST["id_to_delete"] ~= nil) and (_GET["status"] ~= nil)) then
|
|
if(_POST["id_to_delete"] ~= "__all__") then
|
|
_GET["row_id"] = tonumber(_POST["id_to_delete"])
|
|
end
|
|
|
|
deleteAlerts(_GET["status"], _GET)
|
|
|
|
-- TRACKER HOOK
|
|
tracker.log("checkDeleteStoredAlerts", {_GET["status"], _POST["id_to_delete"]})
|
|
|
|
-- to avoid performing the delete again
|
|
_POST["id_to_delete"] = nil
|
|
-- to avoid filtering by id
|
|
_GET["row_id"] = nil
|
|
-- in case of delete "older than" button, resets the time period after the delete took place
|
|
if isEmptyString(_GET["epoch_begin"]) then _GET["epoch_end"] = nil end
|
|
|
|
local has_alerts = alert_utils.hasAlerts(_GET["status"], _GET)
|
|
if(not has_alerts) then
|
|
-- reset the filter to avoid hiding the tab
|
|
_GET["alert_severity"] = nil
|
|
_GET["alert_type"] = nil
|
|
end
|
|
end
|
|
|
|
if(_POST["action"] == "release_alert") then
|
|
local entity_info = {
|
|
alert_entity = alert_consts.alert_entities[alert_consts.alertEntityRaw(_POST["entity"])],
|
|
alert_entity_val = _POST["entity_val"],
|
|
}
|
|
|
|
local type_info = {
|
|
alert_type = (alert_consts.alert_types[alert_consts.getAlertType(_POST["alert_type"])]).meta,
|
|
alert_severity = alert_severities[alert_consts.alertSeverityRaw(_POST["alert_severity"])],
|
|
alert_subtype = _POST["alert_subtype"],
|
|
alert_granularity = alert_consts.alerts_granularities[alert_consts.sec2granularity(_POST["alert_granularity"])],
|
|
}
|
|
|
|
alerts_api.release(entity_info, type_info)
|
|
end
|
|
end
|
|
|
|
-- #################################
|
|
|
|
-- Return more information for the flow alert description
|
|
local function getAlertTypeInfo(record, alert_info)
|
|
local res = ""
|
|
|
|
local l7proto_name = interface.getnDPIProtoName(tonumber(record["l7_proto"]) or 0)
|
|
|
|
if l7proto_name == "ICMP" then -- is ICMPv4
|
|
-- TODO: old format - remove when the all the flow alers will be generated in lua
|
|
local type_code = {type = alert_info["icmp.icmp_type"], code = alert_info["icmp.icmp_code"]}
|
|
|
|
if table.empty(type_code) and alert_info["icmp"] then
|
|
-- This is the new format created when setting the alert from lua
|
|
type_code = {type = alert_info["icmp"]["type"], code = alert_info["icmp"]["code"]}
|
|
end
|
|
|
|
if alert_info["icmp.unreach.src_ip"] then -- TODO: old format to be removed
|
|
res = string.format("[%s]", i18n("icmp_page.icmp_port_unreachable_extra", {unreach_host=alert_info["icmp.unreach.dst_ip"], unreach_port=alert_info["icmp.unreach.dst_port"], unreach_protocol = l4_proto_to_string(alert_info["icmp.unreach.protocol"])}))
|
|
elseif alert_info["icmp"] and alert_info["icmp"]["unreach"] then -- New format
|
|
res = string.format("[%s]", i18n("icmp_page.icmp_port_unreachable_extra", {unreach_host=alert_info["icmp"]["unreach"]["dst_ip"], unreach_port=alert_info["icmp"]["unreach"]["dst_port"], unreach_protocol = l4_proto_to_string(alert_info["icmp"]["unreach"]["protocol"])}))
|
|
else
|
|
res = string.format("[%s]", icmp_utils.get_icmp_label(4 --[[ ipv4 --]], type_code["type"], type_code["code"]))
|
|
end
|
|
end
|
|
|
|
return string.format(" %s", res)
|
|
end
|
|
|
|
-- #################################
|
|
|
|
-- This function formats flows in alerts
|
|
local function formatRawFlow(ifid, alert, alert_json)
|
|
require "flow_utils"
|
|
local time_bounds
|
|
local add_links = (not skip_add_links)
|
|
|
|
if interfaceHasNindexSupport() and not skip_add_links then
|
|
-- only add links if nindex is present
|
|
add_links = true
|
|
time_bounds = {getAlertTimeBounds(alert)}
|
|
end
|
|
|
|
-- TODO: adapter just to be compatible with old alerts, can be removed at some point
|
|
if alert_json["alert_info"] then
|
|
alert_json = json.decode(alert_json["alert_info"])
|
|
end
|
|
|
|
-- active flow lookup
|
|
if not interface.isView() and alert_json and alert_json["ntopng.key"] and alert_json["hash_entry_id"] and alert["alert_tstamp"] then
|
|
-- attempt a lookup on the active flows
|
|
local active_flow = interface.findFlowByKeyAndHashId(alert_json["ntopng.key"], alert_json["hash_entry_id"])
|
|
|
|
if active_flow and active_flow["seen.first"] < tonumber(alert["alert_tstamp"]) then
|
|
return string.format("<i class=\"fas fa-stream\"></i> %s <A class='btn-sx' HREF='%s/lua/flow_details.lua?flow_key=%u&flow_hash_id=%u'><i class='fas fa-search-plus'></i></A> %s",
|
|
'',
|
|
ntop.getHttpPrefix(), active_flow["ntopng.key"], active_flow["hash_entry_id"],
|
|
getFlowLabel(active_flow, true, true))
|
|
end
|
|
end
|
|
|
|
-- pretend alert is a flow to reuse getFlowLabel
|
|
local flow = {
|
|
["cli.ip"] = alert["cli_addr"], ["cli.port"] = tonumber(alert["cli_port"]),
|
|
["cli.blacklisted"] = tostring(alert["cli_blacklisted"]) == "1",
|
|
["cli.localhost"] = tostring(alert["cli_localhost"]) == "1",
|
|
["srv.ip"] = alert["srv_addr"], ["srv.port"] = tonumber(alert["srv_port"]),
|
|
["srv.blacklisted"] = tostring(alert["srv_blacklisted"]) == "1",
|
|
["srv.localhost"] = tostring(alert["srv_localhost"]) == "1",
|
|
["vlan"] = alert["vlan_id"]}
|
|
|
|
flow = "[ <i class=\"fas fa-stream\"></i> "..(getFlowLabel(flow, false, add_links, time_bounds, {page = "alerts"}) or "").."] "
|
|
local l4_proto_label = l4_proto_to_string(alert["proto"] or 0) or ""
|
|
|
|
if not isEmptyString(l4_proto_label) then
|
|
flow = flow.."[" .. l4_proto_label .. "] "
|
|
end
|
|
|
|
if alert_json ~= nil then
|
|
-- render the json
|
|
local msg = ""
|
|
|
|
if not isEmptyString(flow) then
|
|
msg = msg..flow.." "
|
|
end
|
|
|
|
if not isEmptyString(alert_json["info"]) then
|
|
local lb = ""
|
|
local info
|
|
|
|
if string.len(alert_json["info"]) > 24 then
|
|
info = "<abbr title=\"".. alert_json["info"] .."\">".. shortenString(alert_json["info"], 24)
|
|
else
|
|
info = alert_json["info"]
|
|
end
|
|
msg = msg.."[" .. info ..lb.."] "
|
|
end
|
|
|
|
flow = msg
|
|
end
|
|
|
|
if alert_json then
|
|
flow = flow..getAlertTypeInfo(alert, alert_json)
|
|
end
|
|
|
|
return flow
|
|
end
|
|
|
|
-- #################################
|
|
|
|
local function getMenuEntries(status, selection_name, get_params)
|
|
local actual_entries = {}
|
|
|
|
-- table.clone needed to modify some parameters while keeping the original unchanged
|
|
local params = table.clone(get_params)
|
|
|
|
-- Remove previous filters
|
|
params.alert_severity = nil
|
|
params.alert_type = nil
|
|
params.l7_proto = nil
|
|
|
|
if selection_name == "severity" then
|
|
actual_entries = performAlertsQuery("select alert_severity id, count(*) count", status, params, nil, "alert_severity" --[[ group by ]])
|
|
elseif selection_name == "type" then
|
|
actual_entries = performAlertsQuery("select alert_type id, count(*) count", status, params, nil, "alert_type" --[[ group by ]])
|
|
elseif selection_name == "l7_proto" then
|
|
actual_entries = performAlertsQuery("select l7_proto id, count(*) count", status, params, nil, "l7_proto" --[[ group by ]])
|
|
end
|
|
|
|
return(actual_entries)
|
|
end
|
|
|
|
-- #################################
|
|
|
|
local function dropdownUrlParams(get_params)
|
|
local buttons = ""
|
|
|
|
for param, val in pairs(get_params) do
|
|
-- NOTE: exclude the ifid parameter to avoid interface selection issues with system interface alerts
|
|
if((param ~= "alert_severity") and (param ~= "alert_type") and (param ~= "status") and (param ~= "ifid")) then
|
|
buttons = buttons.."&"..param.."="..val
|
|
end
|
|
end
|
|
|
|
return(buttons)
|
|
end
|
|
|
|
-- #################################
|
|
|
|
local function drawDropdown(status, selection_name, active_entry, entries_table, button_label, get_params, actual_entries)
|
|
-- alert_consts.alert_severity_keys and alert_consts.alert_type_keys are defined in lua_utils
|
|
local id_to_label
|
|
if selection_name == "severity" then
|
|
id_to_label = alert_consts.alertSeverityLabel
|
|
elseif selection_name == "type" then
|
|
id_to_label = alert_consts.alertTypeLabel
|
|
elseif selection_name == "l7_proto" then
|
|
id_to_label = interface.getnDPIProtoName
|
|
end
|
|
|
|
actual_entries = actual_entries or getMenuEntries(status, selection_name, get_params)
|
|
|
|
local buttons = '<div class="btn-group">'
|
|
|
|
button_label = button_label or firstToUpper(selection_name)
|
|
if active_entry ~= nil and active_entry ~= "" then
|
|
if selection_name == "l7_proto" then
|
|
button_label = firstToUpper(interface.getnDPIProtoName(active_entry))..'<span class="fas fa-filter"></span>'
|
|
else
|
|
button_label = firstToUpper(active_entry)..'<span class="fas fa-filter"></span>'
|
|
end
|
|
end
|
|
|
|
buttons = buttons..'<button class="btn btn-link dropdown-toggle" data-toggle="dropdown">'..button_label
|
|
buttons = buttons..'<span class="caret"></span></button>'
|
|
|
|
buttons = buttons..'<ul class="dropdown-menu dropdown-menu-right" role="menu">'
|
|
|
|
local class_active = ""
|
|
|
|
if active_entry == nil then class_active = 'active' end
|
|
buttons = buttons..'<li><a class="dropdown-item '..class_active..'" href="?status='..status..'">All</a></i>'
|
|
|
|
-- add a label to each entry
|
|
for _, entry in pairs(actual_entries) do
|
|
local id = tonumber(entry["id"])
|
|
entry.label = firstToUpper(id_to_label(id, true))
|
|
end
|
|
|
|
for _, entry in pairsByField(actual_entries, 'label', asc) do
|
|
local id = tonumber(entry["id"])
|
|
local count = entry["count"]
|
|
|
|
if(id >= 0) then
|
|
local label = entry.label
|
|
|
|
class_active = ""
|
|
if label == active_entry then class_active = 'active' end
|
|
-- buttons = buttons..'<li'..class_active..'><a class="dropdown-item" href="'..ntop.getHttpPrefix()..'/lua/show_alerts.lua?status='..status
|
|
buttons = buttons..'<li><a class="dropdown-item '..class_active..'" href="?status='..status
|
|
buttons = buttons..dropdownUrlParams(get_params)
|
|
buttons = buttons..'&alert_'..selection_name..'='..id..'">'
|
|
buttons = buttons..firstToUpper(label)..' ('..count..')</a></li>'
|
|
end
|
|
end
|
|
|
|
buttons = buttons..'</ul></div>'
|
|
|
|
return buttons
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.printAlertTables(entity_type, alert_source, page_name, page_params, alt_name, options)
|
|
local has_engaged_alerts, has_past_alerts, has_flow_alerts = false,false,false
|
|
local tab = _GET["tab"]
|
|
local have_nedge = ntop.isnEdge()
|
|
options = options or {}
|
|
|
|
local function printTab(tab, content, sel_tab)
|
|
if(tab == sel_tab) then print("\t<li class='nav-item active show'>") else print("\t<li class='nav-item'>") end
|
|
print("<a class='nav-link' href=\""..ntop.getHttpPrefix().."/lua/"..page_name.."?page=alerts&tab="..tab)
|
|
for param, value in pairs(page_params) do
|
|
print("&"..param.."="..value)
|
|
end
|
|
print("\">"..content.."</a></li>\n")
|
|
end
|
|
|
|
-- these fields will be used to perform queries
|
|
_GET["entity"] = alert_consts.alertEntity(entity_type)
|
|
_GET["entity_val"] = alert_source
|
|
|
|
-- possibly process pending delete arguments
|
|
alert_utils.checkDeleteStoredAlerts()
|
|
|
|
-- possibly add a tab if there are alerts configured for the host
|
|
has_engaged_alerts = alert_utils.hasAlerts("engaged", alert_utils.getTabParameters(_GET, "engaged"))
|
|
has_past_alerts = alert_utils.hasAlerts("historical", alert_utils.getTabParameters(_GET, "historical"))
|
|
has_flow_alerts = alert_utils.hasAlerts("historical-flows", alert_utils.getTabParameters(_GET, "historical-flows"))
|
|
|
|
if(has_engaged_alerts or has_past_alerts or has_flow_alerts) then
|
|
print("<div class='card'>")
|
|
print("<div class='card-header'>")
|
|
print('<ul class="nav nav-tabs card-header-tabs">')
|
|
|
|
if(has_engaged_alerts) then
|
|
tab = tab or "alert_list"
|
|
printTab("alert_list", i18n("show_alerts.engaged_alerts"), tab)
|
|
end
|
|
if(has_past_alerts) then
|
|
tab = tab or "past_alert_list"
|
|
printTab("past_alert_list", i18n("show_alerts.past_alerts"), tab)
|
|
end
|
|
if(has_flow_alerts) then
|
|
tab = tab or "flow_alert_list"
|
|
printTab("flow_alert_list", i18n("show_alerts.flow_alerts"), tab)
|
|
end
|
|
else
|
|
-- if there are no alerts, we show a message
|
|
print("<div class=\"alert alert alert-info\"><i class=\"fas fa-info-circle fa-lg\" aria-hidden=\"true\"></i>" .. " " .. i18n("show_alerts.no_recorded_alerts_message").."</div>")
|
|
return
|
|
end
|
|
|
|
print('</ul>')
|
|
print("</div>")
|
|
|
|
alert_utils.drawAlertTables(has_past_alerts, has_engaged_alerts, has_flow_alerts, false, _GET, true, nil, { dont_nest_alerts = true })
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.optimizeAlerts()
|
|
if(not areAlertsEnabled()) then
|
|
return
|
|
end
|
|
|
|
interface.optimizeAlerts()
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.housekeepingAlertsMakeRoom(ifId)
|
|
local prefs = ntop.getPrefs()
|
|
local max_num_alerts_per_entity = prefs.max_num_alerts_per_entity
|
|
local max_num_flow_alerts = prefs.max_num_flow_alerts
|
|
|
|
local k = get_make_room_keys(ifId)
|
|
|
|
if ntop.getCache(k["entities"]) == "1" then
|
|
ntop.delCache(k["entities"])
|
|
local res = interface.queryAlertsRaw(
|
|
"SELECT alert_entity, alert_entity_val, count(*) count", "",
|
|
"GROUP BY alert_entity, alert_entity_val HAVING COUNT >= "..max_num_alerts_per_entity) or {}
|
|
|
|
for _, e in pairs(res) do
|
|
local to_keep = (max_num_alerts_per_entity * 0.8) -- deletes 20% more alerts than the maximum number
|
|
to_keep = round(to_keep, 0)
|
|
-- tprint({e=e, total=e.count, to_keep=to_keep, to_delete=to_delete, to_delete_not_discounted=(e.count - max_num_alerts_per_entity)})
|
|
local cleanup = interface.queryAlertsRaw(
|
|
"DELETE",
|
|
"alert_entity="..e.alert_entity.." AND alert_entity_val=\""..e.alert_entity_val.."\" "
|
|
.." AND rowid NOT IN (SELECT rowid FROM alerts WHERE alert_entity="..e.alert_entity.." AND alert_entity_val=\""..e.alert_entity_val.."\" "
|
|
,"ORDER BY alert_tstamp DESC LIMIT "..to_keep..")", false)
|
|
end
|
|
end
|
|
|
|
if ntop.getCache(k["flows"]) == "1" then
|
|
ntop.delCache(k["flows"])
|
|
local res = interface.queryFlowAlertsRaw("SELECT count(*) count") or {}
|
|
local count = nil
|
|
|
|
-- res can be an empty table, so a check is needed
|
|
if table.len(res) >= 2 then
|
|
count = tonumber(res[1].count)
|
|
end
|
|
|
|
if count ~= nil and count >= max_num_flow_alerts then
|
|
local to_keep = (max_num_flow_alerts * 0.8)
|
|
to_keep = round(to_keep, 0)
|
|
local cleanup = interface.queryFlowAlertsRaw("DELETE",
|
|
"rowid NOT IN (SELECT rowid FROM flows_alerts ORDER BY alert_tstamp DESC LIMIT "..to_keep..")")
|
|
-- tprint({total=count, to_delete=to_delete, cleanup=cleanup})
|
|
-- tprint(cleanup)
|
|
-- TODO: possibly raise a too many flow alerts
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
-- #################################
|
|
|
|
local function menuEntriesToDbFormat(entries)
|
|
local res = {}
|
|
|
|
for entry_id, entry_val in pairs(entries) do
|
|
res[#res + 1] = {
|
|
id = tostring(entry_id),
|
|
count = tostring(entry_val),
|
|
}
|
|
end
|
|
|
|
return(res)
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.drawAlertPCAPDownloadDialog(ifid)
|
|
local modalID = "pcapDownloadModal"
|
|
|
|
print[[
|
|
<script>
|
|
function bpfValidator(filter_field) {
|
|
// no pre validation required as the user is not
|
|
// supposed to edit the filter here
|
|
return true;
|
|
}
|
|
|
|
function pcapDownload(item) {
|
|
var modalID = "]] print(modalID) print [[";
|
|
var bpf_filter = item.getAttribute('data-filter');
|
|
var epoch_begin = item.getAttribute('data-epoch-begin');
|
|
var epoch_end = item.getAttribute('data-epoch-end');
|
|
var date_begin = new Date(epoch_begin * 1000);
|
|
var date_end = new Date(epoch_begin * 1000);
|
|
var epoch_begin_formatted = $.datepicker.formatDate('M dd, yy ', date_begin)+date_begin.getHours()
|
|
+":"+date_begin.getMinutes()+":"+date_begin.getSeconds();
|
|
var epoch_end_formatted = $.datepicker.formatDate('M dd, yy ', date_end)
|
|
+date_end.getHours()+":"+date_end.getMinutes()+":"+date_end.getSeconds();
|
|
|
|
$('#'+modalID+'_ifid').val(]] print(ifid) print [[);
|
|
$('#'+modalID+'_epoch_begin').val(epoch_begin);
|
|
$('#'+modalID+'_epoch_end').val(epoch_end);
|
|
$('#'+modalID+'_begin').text(epoch_begin_formatted);
|
|
$('#'+modalID+'_end').text(epoch_end_formatted);
|
|
$('#'+modalID+'_query_items').html("");
|
|
$('#'+modalID+'_chart_link').val("");
|
|
|
|
$('#'+modalID+'_bpf_filter').val(bpf_filter);
|
|
$('#'+modalID).modal('show');
|
|
|
|
$("#]] print(modalID) print [[ form:data(bs.validator)").each(function(){
|
|
$(this).data("bs.validator").validate();
|
|
});
|
|
}
|
|
|
|
function submitPcapDownload(form) {
|
|
var frm = $('#'+form.id);
|
|
window.open(']] print(ntop.getHttpPrefix()) print [[/lua/rest/v1/get/pcap/live_extraction.lua?' + frm.serialize(), '_self', false);
|
|
$('#]] print(modalID) print [[').modal('hide');
|
|
return false;
|
|
}
|
|
|
|
</script>
|
|
]]
|
|
|
|
print(template.gen("traffic_extraction_dialog.html", { dialog = {
|
|
id = modalID,
|
|
title = i18n("traffic_recording.pcap_download"),
|
|
message = i18n("traffic_recording.about_to_download_flow", {date_begin = '<span id="'.. modalID ..'_begin">', date_end = '<span id="'.. modalID ..'_end">'}),
|
|
submit = i18n("traffic_recording.download"),
|
|
form_method = "post",
|
|
validator_options = "{ custom: { bpf: bpfValidator }, errors: { bpf: '"..i18n("traffic_recording.invalid_bpf").."' } }",
|
|
form_action = ntop.getHttpPrefix().."/lua/traffic_extraction.lua",
|
|
form_onsubmit = "submitPcapDownload",
|
|
advanced_class = "d-none",
|
|
extract_now_class = "d-none", -- direct download only
|
|
}}))
|
|
|
|
print(template.gen("modal_confirm_dialog.html", { dialog = {
|
|
id = "no-recording-data",
|
|
title = i18n("traffic_recording.pcap_download"),
|
|
message = "<span id='no-recording-data-message'></span>",
|
|
}}))
|
|
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.drawAlertTables(has_past_alerts, has_engaged_alerts, has_flow_alerts, has_disabled_alerts, get_params, hide_extended_title, alt_nav_tabs, options, disable_delete)
|
|
local alert_items = {}
|
|
local url_params = {}
|
|
local options = options or {}
|
|
local ifid = interface.getId()
|
|
local additional_params = {}
|
|
local err = ""
|
|
|
|
-- this paramater is used to print out a card container for the table
|
|
local is_standalone = options.is_standalone or false
|
|
|
|
print(
|
|
template.gen("modal_confirm_dialog.html", {
|
|
dialog={
|
|
id = "delete_alert_dialog",
|
|
action = "deleteAlertById(delete_alert_id)",
|
|
title = i18n("show_alerts.delete_alert"),
|
|
message = i18n("show_alerts.confirm_delete_alert"),
|
|
confirm = i18n("delete"),
|
|
confirm_button = "btn-danger",
|
|
}
|
|
})
|
|
)
|
|
|
|
print(
|
|
template.gen("modal_alert_filter_dialog.html", {
|
|
dialog={
|
|
id = "filter_alert_dialog",
|
|
action = "filterAlertByFilters(subdir, script_key, alert_key)",
|
|
title = i18n("show_alerts.filter_alert"),
|
|
message = i18n("show_alerts.confirm_filter_alert"),
|
|
delete_message = i18n("show_alerts.confirm_delete_filtered_alerts"),
|
|
delete_alerts = i18n("delete_disabled_alerts"),
|
|
alert_filter = "default_filter",
|
|
confirm = i18n("filter"),
|
|
confirm_button = "btn-warning",
|
|
}
|
|
})
|
|
)
|
|
|
|
-- Filtering for flow alerts
|
|
print(
|
|
template.gen("modal_flow_alerts_filter_dialog.html", {
|
|
dialog={
|
|
id = "flow_alerts_filter_dialog",
|
|
action = "filterFlowAlerts(alert_key)",
|
|
title = i18n("show_alerts.filter_alert"),
|
|
message = i18n("show_alerts.confirm_filter_alert"),
|
|
delete_message = i18n("show_alerts.confirm_delete_filtered_alerts"),
|
|
delete_alerts = i18n("delete_disabled_alerts"),
|
|
alert_filter = "default_filter",
|
|
confirm = i18n("filter"),
|
|
confirm_button = "btn-warning",
|
|
}
|
|
})
|
|
)
|
|
|
|
-- Filtering for host alerts
|
|
print(
|
|
template.gen("modal_host_alerts_filter_dialog.html", {
|
|
dialog={
|
|
id = "host_alerts_filter_dialog",
|
|
action = "filterHostAlerts(alert_key)",
|
|
title = i18n("show_alerts.filter_alert"),
|
|
message = i18n("show_alerts.confirm_filter_alert"),
|
|
delete_message = i18n("show_alerts.confirm_delete_filtered_alerts"),
|
|
delete_alerts = i18n("delete_disabled_alerts"),
|
|
alert_filter = "default_filter",
|
|
confirm = i18n("filter"),
|
|
confirm_button = "btn-warning",
|
|
}
|
|
})
|
|
)
|
|
|
|
|
|
print(
|
|
template.gen("modal_confirm_dialog.html", {
|
|
dialog={
|
|
id = "release_single_alert",
|
|
action = "releaseAlert(alert_to_release)",
|
|
title = i18n("show_alerts.release_alert"),
|
|
message = i18n("show_alerts.confirm_release_alert"),
|
|
confirm = i18n("show_alerts.release_alert_action"),
|
|
confirm_button = "btn-primary",
|
|
}
|
|
})
|
|
)
|
|
|
|
print(
|
|
template.gen("modal_confirm_dialog.html", {
|
|
dialog={
|
|
id = "myModal",
|
|
action = "checkModalDelete()",
|
|
title = i18n("show_alerts.purge_all_alerts"),
|
|
confirm_button = "btn-danger",
|
|
custom_alert_class = "alert alert-danger",
|
|
message = i18n("show_alerts.purge_subj_alerts_confirm", {subj = '<span id="modalDeleteContext"></span><span id="modalDeleteAlertsMsg"></span>'}),
|
|
confirm = i18n("show_alerts.purge_num_alerts", {
|
|
num_alerts = '<img id="alerts-summary-wait" src="'..ntop.getHttpPrefix()..'/img/loading.gif"/><span id="alerts-summary-body"></span>'
|
|
}),
|
|
}
|
|
})
|
|
)
|
|
|
|
if is_standalone then
|
|
print("<div class='card'>")
|
|
print("<div class='card-header'>")
|
|
end
|
|
|
|
for k,v in pairs(get_params) do if k ~= "csrf" then url_params[k] = v end end
|
|
if not alt_nav_tabs then
|
|
print[[
|
|
<ul class="nav nav-tabs card-header-tabs card-header-pills" role="tablist" id="alert-tabs" style="]] print(ternary(options.dont_nest_alerts, 'display:none', '')) print[[">
|
|
<!-- will be populated later with javascript -->
|
|
</ul>
|
|
]]
|
|
nav_tab_id = "alert-tabs"
|
|
else
|
|
nav_tab_id = alt_nav_tabs
|
|
end
|
|
|
|
if is_standalone then
|
|
print("</div>")
|
|
end
|
|
|
|
print[[
|
|
<script>
|
|
|
|
function checkAlertActionsPanel() {
|
|
/* check if this tab is handled by this script */
|
|
if(getCurrentStatus() == "" || getCurrentStatus() == "engaged")
|
|
$("#alertsActionsPanel").css("display", "none");
|
|
else
|
|
$("#alertsActionsPanel").css("display", "");
|
|
}
|
|
|
|
function setActiveHashTab(hash) {
|
|
$('#]] print(nav_tab_id) --[[ see "clicked" below for the other part of this logic ]] print[[ a[href="' + hash + '"]').tab('show');
|
|
}
|
|
|
|
/* Handle the current tab */
|
|
$(function() {
|
|
$("ul.nav-tabs > li > a").on("shown.bs.tab", function(e) {
|
|
var id = $(e.target).attr("href").substr(1);
|
|
history.replaceState(null, null, "#"+id);
|
|
updateDeleteLabel(id);
|
|
checkAlertActionsPanel();
|
|
});
|
|
|
|
var hash = window.location.hash;
|
|
if (! hash && ]] if(isEmptyString(status) and not isEmptyString(_GET["tab"])) then print("true") else print("false") end print[[)
|
|
hash = "#]] print(_GET["tab"] or "") print[[";
|
|
|
|
if (hash)
|
|
setActiveHashTab(hash)
|
|
|
|
$(function() { checkAlertActionsPanel(); });
|
|
});
|
|
|
|
function getActiveTabId() {
|
|
return $("#]] print(nav_tab_id) print[[ > li > a.active").attr('href').substr(1);
|
|
}
|
|
|
|
function updateDeleteLabel(tabid) {
|
|
var label = $("#purgeBtnLabel");
|
|
var prefix = "]]
|
|
if not isEmptyString(_GET["entity"]) then print(alert_consts.alertEntityLabel(_GET["entity"], true).." ") end
|
|
print [[";
|
|
var val = "";
|
|
|
|
if (tabid == "tab-table-engaged-alerts")
|
|
val = "]] print(i18n("show_alerts.engaged")) print[[ ";
|
|
else if (tabid == "tab-table-alerts-history")
|
|
val = "]] print(i18n("show_alerts.past")) print[[ ";
|
|
else if (tabid == "tab-table-flow-alerts-history")
|
|
val = "]] print(i18n("show_alerts.past_flow")) print[[ ";
|
|
|
|
label.html(prefix + val);
|
|
}
|
|
|
|
function getCurrentStatus() {
|
|
var tabid = getActiveTabId();
|
|
|
|
if (tabid == "tab-table-engaged-alerts")
|
|
val = "engaged";
|
|
else if (tabid == "tab-table-alerts-history")
|
|
val = "historical";
|
|
else if (tabid == "tab-table-flow-alerts-history")
|
|
val = "historical-flows";
|
|
else
|
|
val = "";
|
|
|
|
return val;
|
|
}
|
|
|
|
function deleteAlertById(alert_key) {
|
|
var params = {};
|
|
params.id_to_delete = alert_key;
|
|
params.status = getCurrentStatus();
|
|
params.csrf = "]] print(ntop.getRandomCSRFValue()) print[[";
|
|
|
|
var form = NtopUtils.paramsToForm('<form method="post"></form>', params);
|
|
form.appendTo('body').submit();
|
|
}
|
|
|
|
function filterAlertByFilters(subdir, script_key, alert_key) {
|
|
$.ajax({
|
|
type: 'POST',
|
|
contentType: "application/json",
|
|
dataType: "json",
|
|
url: `${http_prefix}/lua/rest/v1/edit/user_script/filter.lua`, /* TODO: Change */
|
|
data: JSON.stringify({
|
|
filters: document.getElementById("name_input").value,
|
|
subdir: subdir,
|
|
script_key: script_key,
|
|
alert_key: alert_key,
|
|
status: getCurrentStatus(),
|
|
delete_alerts: $('#delete_alert_switch').prop('checked'),
|
|
csrf: "]] print(ntop.getRandomCSRFValue()) print[[",
|
|
}),
|
|
success: function(rsp) {
|
|
let get_params = NtopUtils.paramsExtend(]] print(tableToJsObject(alert_utils.getTabParameters(url_params, nil))) print[[, {status:getCurrentStatus()});
|
|
get_params.csrf = "]] print(ntop.getRandomCSRFValue()) print[[";
|
|
let form = NtopUtils.paramsToForm('<form method="post"></form>', get_params);
|
|
form.appendTo('body').submit();
|
|
},
|
|
error: function(rsp) {
|
|
$("#filter_alert_dialog_error").text(rsp.responseJSON.rsp).show();
|
|
},
|
|
});
|
|
}
|
|
|
|
function filterFlowAlerts(alert_key) {
|
|
$.ajax({
|
|
type: 'POST',
|
|
contentType: "application/json",
|
|
dataType: "json",
|
|
url: `${http_prefix}/lua/rest/v1/edit/user_script/filter.lua`,
|
|
data: JSON.stringify({
|
|
alert_addr: $("input[name='alert_addr']:checked").val(),
|
|
subdir: "flow",
|
|
alert_key: alert_key,
|
|
delete_alerts: $('#delete_flow_alerts_switch').prop('checked'),
|
|
csrf: "]] print(ntop.getRandomCSRFValue()) print[[",
|
|
}),
|
|
success: function(rsp) {
|
|
let get_params = NtopUtils.paramsExtend(]] print(tableToJsObject(alert_utils.getTabParameters(url_params, nil))) print[[, {status:getCurrentStatus()});
|
|
get_params.csrf = "]] print(ntop.getRandomCSRFValue()) print[[";
|
|
let form = NtopUtils.paramsToForm('<form method="post"></form>', get_params);
|
|
form.appendTo('body').submit();
|
|
},
|
|
error: function(rsp) {
|
|
$("#filter_alert_dialog_error").text(rsp.responseJSON.rsp).show();
|
|
},
|
|
});
|
|
}
|
|
|
|
function filterHostAlerts(alert_key) {
|
|
$.ajax({
|
|
type: 'POST',
|
|
contentType: "application/json",
|
|
dataType: "json",
|
|
url: `${http_prefix}/lua/rest/v1/edit/user_script/filter.lua`,
|
|
data: JSON.stringify({
|
|
alert_addr: $("#alert_addr").html(),
|
|
subdir: "host",
|
|
alert_key: alert_key,
|
|
delete_alerts: $('#delete_host_alerts_switch').prop('checked'),
|
|
csrf: "]] print(ntop.getRandomCSRFValue()) print[[",
|
|
}),
|
|
success: function(rsp) {
|
|
let get_params = NtopUtils.paramsExtend(]] print(tableToJsObject(alert_utils.getTabParameters(url_params, nil))) print[[, {status:getCurrentStatus()});
|
|
get_params.csrf = "]] print(ntop.getRandomCSRFValue()) print[[";
|
|
let form = NtopUtils.paramsToForm('<form method="post"></form>', get_params);
|
|
form.appendTo('body').submit();
|
|
},
|
|
error: function(rsp) {
|
|
$("#filter_alert_dialog_error").text(rsp.responseJSON.rsp).show();
|
|
},
|
|
});
|
|
}
|
|
|
|
var alert_to_toggle = null;
|
|
var alert_to_release = null;
|
|
|
|
function releaseAlert(idx) {
|
|
var table_data = $("#table-engaged-alerts").data("datatable").resultset.data;
|
|
var row = table_data[idx];
|
|
|
|
var params = {
|
|
"action": "release_alert",
|
|
"entity": row.column_entity_id,
|
|
"entity_val": row.column_entity_val,
|
|
"alert_type": row.column_type_id,
|
|
"alert_severity": row.column_severity_id,
|
|
"alert_subtype": row.column_subtype,
|
|
"alert_granularity": row.column_granularity,
|
|
"csrf": "]] print(ntop.getRandomCSRFValue()) print[[",
|
|
};
|
|
|
|
var form = NtopUtils.paramsToForm('<form method="post"></form>', params);
|
|
form.appendTo('body').submit();
|
|
}
|
|
</script>
|
|
]]
|
|
|
|
if not alt_nav_tabs then
|
|
print [[<div class='card-body'>]]
|
|
print [[<div class="tab-content">]]
|
|
end
|
|
|
|
local status = _GET["status"]
|
|
if(status == nil) then
|
|
local tab = _GET["tab"]
|
|
|
|
if(tab == "past_alert_list") then
|
|
status = "historical"
|
|
elseif(tab == "flow_alert_list") then
|
|
status = "historical-flows"
|
|
end
|
|
end
|
|
|
|
local status_reset = (status == nil)
|
|
|
|
if(has_engaged_alerts) then
|
|
alert_items[#alert_items + 1] = {
|
|
["label"] = i18n("show_alerts.engaged_alerts"),
|
|
["chart"] = ternary(areInterfaceTimeseriesEnabled(ifid), "iface:alerts_stats", ""),
|
|
["div-id"] = "table-engaged-alerts", ["status"] = "engaged"}
|
|
elseif status == "engaged" then
|
|
status = nil; status_reset = 1
|
|
end
|
|
|
|
if(has_past_alerts) then
|
|
alert_items[#alert_items +1] = {
|
|
["label"] = i18n("show_alerts.past_alerts"),
|
|
["chart"] = "",
|
|
["div-id"] = "table-alerts-history", ["status"] = "historical"}
|
|
elseif status == "historical" then
|
|
status = nil; status_reset = 1
|
|
end
|
|
|
|
if(has_flow_alerts) then
|
|
alert_items[#alert_items +1] = {
|
|
["label"] = i18n("show_alerts.flow_alerts"),
|
|
["chart"] = "",
|
|
["div-id"] = "table-flow-alerts-history", ["status"] = "historical-flows"}
|
|
elseif status == "historical-flows" then
|
|
status = nil; status_reset = 1
|
|
end
|
|
|
|
for k, t in ipairs(alert_items) do
|
|
local clicked = "0"
|
|
if((not alt_nav_tabs) and ((k == 1 and status == nil) or (status ~= nil and status == t["status"]))) then
|
|
clicked = "1"
|
|
end
|
|
|
|
print [[
|
|
<div class="tab-pane in" id="tab-]] print(t["div-id"]) print[[">
|
|
<!-- Table to render --->
|
|
<div id="]] print(t["div-id"]) print[["></div>
|
|
</div>
|
|
|
|
<script type="text/javascript">
|
|
$("#]] print(nav_tab_id) print[[").append('<li class="nav-item ]] print(ternary(options.dont_nest_alerts, 'hidden', '')) print[["><a class="nav-link" href="#tab-]] print(t["div-id"]) print[[" clicked="]] print(clicked) print[[" role="tab" data-toggle="tab">]] print(t["label"]) print[[</a></li>')
|
|
</script>
|
|
]]
|
|
|
|
print[[
|
|
<script type="text/javascript">
|
|
$('a[href="#tab-]] print(t["div-id"]) print[["]').on('shown.bs.tab', function (e) {
|
|
// append the li to the tabs
|
|
|
|
$("#]] print(t["div-id"]) print[[").datatable({
|
|
url: "]] print(ntop.getHttpPrefix()) print [[/lua/get_alerts_table_data.lua?" + $.param(]] print(tableToJsObject(alert_utils.getTabParameters(url_params, t["status"]))) print [[),
|
|
showFilter: true,
|
|
showPagination: true,
|
|
buttons: [']]
|
|
|
|
local title = t["label"]
|
|
|
|
|
|
if(t["chart"] ~= "") then
|
|
local base_url
|
|
|
|
if interface.getId() == tonumber(getSystemInterfaceId()) then
|
|
base_url = "/lua/system_stats.lua"
|
|
else
|
|
base_url = "/lua/if_stats.lua"
|
|
end
|
|
|
|
title = title .. " <small><A HREF='"..ntop.getHttpPrefix().. base_url .. "?ifid="..string.format("%d", ifid).."&page=historical&ts_schema="..t["chart"].."'><i class='fas fa-chart-area fa-sm'></i></A></small>"
|
|
end
|
|
|
|
if(not options.hide_filters) then
|
|
-- alert_consts.alert_severity_keys and alert_consts.alert_type_keys are defined in lua_utils
|
|
local alert_severities = {}
|
|
for s, _ in pairs(alert_severities) do alert_severities[#alert_severities +1 ] = s end
|
|
local alert_types = {}
|
|
for s, _ in pairs(alert_consts.alert_types) do alert_types[#alert_types +1 ] = s end
|
|
local l7_proto = {}
|
|
local type_menu_entries = nil
|
|
local sev_menu_entries = nil
|
|
local l7_proto_entries = nil
|
|
|
|
local a_type, a_severity, a_l7_proto = nil, nil, nil
|
|
if clicked == "1" then
|
|
if tonumber(_GET["alert_type"]) ~= nil then a_type = alert_consts.alertTypeLabel(_GET["alert_type"], true) end
|
|
if tonumber(_GET["alert_l7_proto"]) ~= nil then a_l7_proto = tonumber(_GET["alert_l7_proto"]) end
|
|
if tonumber(_GET["alert_severity"]) ~= nil then a_severity = alert_consts.alertSeverityLabel(_GET["alert_severity"], true) end
|
|
end
|
|
|
|
if t["status"] == "engaged" then
|
|
local res = interface.getEngagedAlertsCount(tonumber(_GET["entity"]), _GET["entity_val"])
|
|
|
|
if(res ~= nil) then
|
|
type_menu_entries = menuEntriesToDbFormat(res.type)
|
|
sev_menu_entries = menuEntriesToDbFormat(res.severities)
|
|
--l7_proto_entries = menuEntriesToDbFormat(res.l7_proto)
|
|
end
|
|
end
|
|
|
|
print(drawDropdown(t["status"], "type", a_type, alert_types, i18n("alerts_dashboard.alert_type"), get_params, type_menu_entries))
|
|
if t["status"] == "historical-flows" then
|
|
print(drawDropdown(t["status"], "l7_proto", a_l7_proto, l7_proto, i18n("application"), get_params, l7_proto_entries))
|
|
end
|
|
print(drawDropdown(t["status"], "severity", a_severity, alert_severities, i18n("alerts_dashboard.alert_severity"), get_params, sev_menu_entries))
|
|
elseif((not isEmptyString(_GET["entity_val"])) and (not hide_extended_title)) then
|
|
if entity == "host" then
|
|
title = title .. " - " .. firstToUpper(alert_consts.formatAlertEntity(getInterfaceId(ifname), entity, _GET["entity_val"], nil))
|
|
end
|
|
end
|
|
|
|
if options.dont_nest_alerts then
|
|
title = ""
|
|
end
|
|
|
|
print[['],
|
|
/*
|
|
buttons: ['<div class="btn-group"><button class="btn btn-link dropdown-toggle" data-toggle="dropdown">Severity<span class="caret"></span></button><ul class="dropdown-menu scrollable-dropdown" role="menu"><li>test severity</li></ul></div><div class="btn-group"><button class="btn btn-link dropdown-toggle" data-toggle="dropdown">Type<span class="caret"></span></button><ul class="dropdown-menu scrollable-dropdown" role="menu"><li>test type</li></ul></div>'],
|
|
*/
|
|
]]
|
|
|
|
if(_GET["currentPage"] ~= nil and _GET["status"] == t["status"]) then
|
|
print("currentPage: ".._GET["currentPage"]..",\n")
|
|
end
|
|
if(_GET["perPage"] ~= nil and _GET["status"] == t["status"]) then
|
|
print("perPage: ".._GET["perPage"]..",\n")
|
|
end
|
|
print ('sort: [ ["' .. getDefaultTableSort("alerts") ..'","' .. getDefaultTableSortOrder("alerts").. '"] ],\n')
|
|
print [[
|
|
title: "",
|
|
columns: [
|
|
{
|
|
title: "]]print(i18n("show_alerts.alert_datetime"))print[[",
|
|
field: "column_date",
|
|
sortable: true,
|
|
css: {
|
|
textAlign: 'center',
|
|
width: '10%',
|
|
}
|
|
},
|
|
|
|
{
|
|
title: "]]print(i18n("show_alerts.alert_duration"))print[[",
|
|
field: "column_duration",
|
|
sortable: true,
|
|
css: {
|
|
textAlign: 'center',
|
|
width: '5%',
|
|
}
|
|
},
|
|
|
|
{
|
|
title: "]]print(i18n("show_alerts.alert_count"))print[[",
|
|
field: "column_count",
|
|
hidden: ]] print(ternary(t["status"] ~= "historical-flows" and t["status"] ~= "historical", "true", "false")) print[[,
|
|
sortable: true,
|
|
css: {
|
|
textAlign: 'center'
|
|
}
|
|
},
|
|
|
|
{
|
|
title: "]]print(i18n("show_alerts.alert_severity"))print[[",
|
|
field: "column_severity",
|
|
sortable: true,
|
|
css: {
|
|
textAlign: 'center',
|
|
width: '2%',
|
|
}
|
|
},
|
|
|
|
{
|
|
title: "]]print(i18n("show_alerts.alert_type"))print[[",
|
|
field: "column_type",
|
|
sortable: true,
|
|
css: {
|
|
textAlign: 'center',
|
|
width: '10%',
|
|
}
|
|
},
|
|
|
|
{
|
|
title: "]]print(i18n("score"))print[[",
|
|
field: "column_score",
|
|
hidden: ]] print(ternary(t["status"] ~= "historical-flows", "true", "false")) print[[,
|
|
sortable: true,
|
|
css: {
|
|
textAlign: 'center'
|
|
}
|
|
},
|
|
|
|
{
|
|
title: "]]print(i18n("application"))print[[",
|
|
field: "column_ndpi",
|
|
sortable: false,
|
|
hidden: ]] print(ternary(t["status"] ~= "historical-flows", "true", "false")) print[[,
|
|
css: {
|
|
textAlign: 'center',
|
|
width: '5%',
|
|
}
|
|
},
|
|
|
|
{
|
|
title: "]]print(i18n("show_alerts.alert_description"))print[[",
|
|
field: "column_msg",
|
|
css: {
|
|
textAlign: 'left',
|
|
}
|
|
},
|
|
|
|
{
|
|
field: "column_key",
|
|
hidden: true
|
|
},
|
|
{
|
|
title: "]]print(i18n("show_alerts.alert_actions")) print[[",
|
|
css: {
|
|
textAlign: 'center',
|
|
minWidth: "12rem",
|
|
}
|
|
},
|
|
|
|
], tableCallback: function() {
|
|
var table_data = $("#]] print(t["div-id"]) print[[").data("datatable").resultset.data;
|
|
|
|
datatableForEachRow("#]] print(t["div-id"]) print[[", function(row_id) {
|
|
var alert_key = $("td:nth(8)", this).html().split("|");
|
|
var alert_key = alert_key[0];
|
|
var data = table_data[row_id];
|
|
var explorer_url = data["column_explorer"];
|
|
|
|
if(data["column_filter"] && data["column_subdir"] == "flow") {
|
|
/* Extract client and server address that come into column_filter concatenated with a pipe */
|
|
const cli_srv_addr = data["column_filter"].split('|'), cli_addr = cli_srv_addr[0], srv_addr = cli_srv_addr[1];
|
|
|
|
/* Populate client and server radio buttons */
|
|
const srv_radio = " $('#srv_radio').attr('value', '" + srv_addr + "'); $('#srv_addr').html('" + srv_addr + "'); ";
|
|
const cli_radio = " $('#cli_radio').attr('value', '" + cli_addr + "'); $('#cli_addr').html('" + cli_addr + "'); ";
|
|
const alert_label = "$('.alert_label').html('" + data["column_type_str"] + "'); ";
|
|
console.log(alert_label);
|
|
datatableAddFilterButtonCallback.bind(this)(10, "alert_key = '" + data["column_type_id"] + "'; " + alert_label + srv_radio + cli_radio + " $('#flow_alerts_filter_dialog').modal('show');", "<i class='fas fa-bell-slash'></i>", "]] print(i18n("filter")) print[[");
|
|
} else if(data["column_filter"] && data["column_subdir"] == "host") {
|
|
/* Populate modal data */
|
|
const alert_label = "$('.alert_label').html('" + data["column_type_str"] + "'); ";
|
|
const alert_addr = "$('#alert_addr').html('" + data["column_filter"] + "'); ";
|
|
|
|
datatableAddFilterButtonCallback.bind(this)(10, "alert_key = '" + data["column_type_id"] + "'; " + alert_label + alert_addr + " $('#host_alerts_filter_dialog').modal('show');", "<i class='fas fa-bell-slash'></i>", "]] print(i18n("filter")) print[[");
|
|
} else if(data["column_filter"]) {
|
|
datatableAddFilterButtonCallback.bind(this)(10, "alert_key = '" + data["column_type_id"] + "'; alert_label = $('.alert_label').html('" + data["column_type_str"] + "'); subdir = '" + data["column_subdir"] + "'; script_key = '" + data["column_script_key"] + "'; $('#name_input').attr('value', '" + data["column_filter"] + "'); $('#filter_alert_dialog').modal('show');", "<i class='fas fa-bell-slash'></i>", "]] print(i18n("filter")) print[[");
|
|
} else if(data["column_filter_disabled"]) {
|
|
datatableAddFilterButtonCallback.bind(this)(10, "subdir = ''; script_key = '';", "<i class='fas fa-bell-slash'></i>", "]] print(i18n("filter")) print[[", false); }
|
|
|
|
if(data["column_drilldown"]) {
|
|
datatableAddLinkButtonCallback.bind(this)(10, data["column_drilldown"], "<i class='fas fa-search-plus drilldown-icon'></i>", "]] print(i18n("show_alerts.expand_action")) print[[");
|
|
}
|
|
|
|
if(explorer_url) {
|
|
datatableAddLinkButtonCallback.bind(this)(10, explorer_url, "<i class='fab fa-wpexplorer'></i>", "]] print(i18n("show_alerts.explorer")) print[[");
|
|
disable_alerts_dialog = "#disable_flows_alerts";
|
|
}
|
|
|
|
if(]] print(ternary(t["status"] == "engaged", "true", "false")) print[[)
|
|
datatableAddActionButtonCallback.bind(this)(10, "alert_to_release = "+ row_id +"; $('#release_single_alert').modal('show');", "<i class='fas fa-unlock'></i>", true, "]] print(i18n("show_alerts.release_alert_action")) print[[");
|
|
|
|
if(]] print(ternary(t["status"] ~= "engaged", "true", "false")) print[[) {
|
|
datatableAddDeleteButtonCallback.bind(this)(10, "delete_alert_id ='" + alert_key + "'; $('#delete_alert_dialog').modal('show');", "<i class='fas fa-trash'></i>");
|
|
}
|
|
|
|
$("form", this).submit(function() {
|
|
// add "status" parameter to the form
|
|
var get_params = NtopUtils.paramsExtend(]] print(tableToJsObject(alert_utils.getTabParameters(url_params, nil))) print[[, {status:getCurrentStatus()});
|
|
$(this).attr("action", "?" + $.param(get_params));
|
|
|
|
return true;
|
|
});
|
|
|
|
$(`a[title]`).tooltip();
|
|
});
|
|
}
|
|
});
|
|
});
|
|
]]
|
|
if (clicked == "1") then
|
|
print[[
|
|
// must wait for modalDeleteAlertsStatus to be created
|
|
$(function() {
|
|
var status_reset = ]] print(tostring(status_reset)) --[[ this is necessary because of status parameter inconsistency after tab switch ]] print[[;
|
|
var tabid;
|
|
|
|
if ((status_reset) || (getCurrentStatus() == "")) {
|
|
tabid = "]] print("tab-"..t["div-id"]) print[[";
|
|
history.replaceState(null, null, "#"+tabid);
|
|
} else {
|
|
tabid = getActiveTabId();
|
|
}
|
|
|
|
updateDeleteLabel(tabid);
|
|
});
|
|
]]
|
|
end
|
|
print[[
|
|
</script>
|
|
]]
|
|
|
|
::next_menu_item::
|
|
end
|
|
|
|
local zoom_vals = {
|
|
{ i18n("show_alerts.5_min"), 5*60*1, i18n("show_alerts.older_5_minutes_ago") },
|
|
{ i18n("show_alerts.30_min"), 30*60*1, i18n("show_alerts.older_30_minutes_ago") },
|
|
{ i18n("show_alerts.1_hour"), 60*60*1, i18n("show_alerts.older_1_hour_ago") },
|
|
{ i18n("show_alerts.1_day"), 60*60*24, i18n("show_alerts.older_1_day_ago") },
|
|
{ i18n("show_alerts.1_week"), 60*60*24*7, i18n("show_alerts.older_1_week_ago") },
|
|
{ i18n("show_alerts.1_month"), 60*60*24*31, i18n("show_alerts.older_1_month_ago") },
|
|
{ i18n("show_alerts.6_months"), 60*60*24*31*6, i18n("show_alerts.older_6_months_ago") },
|
|
{ i18n("show_alerts.1_year"), 60*60*24*366 , i18n("show_alerts.older_1_year_ago") }
|
|
}
|
|
|
|
if(has_engaged_alerts or has_past_alerts or has_flow_alerts) then
|
|
-- trigger the click on the right tab to force table load
|
|
print[[
|
|
<script type="text/javascript">
|
|
$("[clicked=1]").trigger("click");
|
|
</script>
|
|
]]
|
|
|
|
if not alt_nav_tabs then
|
|
print [[</div> <!-- closes tab-content -->]]
|
|
print [[</div> <!-- Close Card body -->]]
|
|
end
|
|
local has_fixed_period = ((not isEmptyString(_GET["epoch_begin"])) or (not isEmptyString(_GET["epoch_end"])))
|
|
|
|
-- the dont_print_footer option is used to skip the card footer printing
|
|
if not options.dont_print_footer then print([[<div class='card-footer'>]]) end
|
|
|
|
local purge_label
|
|
if (_GET['alert_type']) then
|
|
purge_label = i18n("show_alerts.alerts_to_purge_x", { filter = "<b>" .. alert_consts.alertTypeLabel(_GET["alert_type"], true) .. "</b>"})
|
|
elseif (_GET['alert_severity']) then
|
|
purge_label = i18n("show_alerts.alerts_to_purge_x", { filter = "<b>" .. alert_consts.alertSeverityLabel(_GET["alert_severity"], true) .. "</b>"})
|
|
elseif (not isEmptyString(_GET['alert_l7_proto'])) then
|
|
purge_label = i18n("show_alerts.alerts_to_purge_x", { filter = "<b>" .. interface.getnDPIProtoName(tonumber(_GET["alert_l7_proto"])) .. "</b>"})
|
|
else
|
|
purge_label = i18n("show_alerts.alerts_to_purge")
|
|
end
|
|
|
|
print('<div id="alertsActionsPanel">')
|
|
if not disable_delete then
|
|
print(purge_label .. ': ')
|
|
end
|
|
print[[<select id="deleteZoomSelector" class="form-control" style="display:]] if has_fixed_period then print("none") else print("inline") end print[[; width:14em; margin:0 1em;">]]
|
|
local all_msg = ""
|
|
|
|
if not has_fixed_period then
|
|
print('<optgroup label="' .. i18n("show_alerts.older_than") .. '">')
|
|
for k,v in ipairs(zoom_vals) do
|
|
print('<option data-older="'..(os.time() - zoom_vals[k][2])..'" data-msg="'.." "..zoom_vals[k][3].. '">'..zoom_vals[k][1]..'</option>\n')
|
|
end
|
|
print('</optgroup>')
|
|
else
|
|
all_msg = " " .. i18n("show_alerts.in_the_selected_time_frame")
|
|
end
|
|
|
|
|
|
print('<option selected="selected" data-older="0" data-msg="') print(all_msg) print('">' .. i18n("all") .. '</option>\n')
|
|
|
|
|
|
print[[</select>
|
|
<form id="modalDeleteForm" class="form-inline" style="display:none;" method="post" onsubmit="return checkModalDelete();">
|
|
<input type="hidden" id="modalDeleteAlertsOlderThan" value="-1" />
|
|
<input id="csrf" name="csrf" type="hidden" value="]] print(ntop.getRandomCSRFValue()) print[[" />
|
|
</form>
|
|
]]
|
|
|
|
-- we need to dynamically modify parameters at js-time because we switch tab
|
|
local delete_params = alert_utils.getTabParameters(url_params, nil)
|
|
delete_params.epoch_end = -1
|
|
print[[<button id="buttonOpenDeleteModal" data-toggle="modal" data-target="#myModal" class="btn btn-danger"]]
|
|
if(disable_delete == true) then
|
|
print[[ style="display:none;"]]
|
|
end
|
|
print[[> <span id="purgeBtnMessage">]] print(i18n("show_alerts.purge_subj_alerts", {subj='<span id="purgeBtnLabel"></span>'}))
|
|
print[[</span></button>
|
|
|
|
<a href="#" class="btn btn-primary" role="button" aria-disabled="true" onclick="downloadAlerts();"><i class="fas fa-download"></i></a>
|
|
</div> <!-- closes alertsActionsPanel -->]]
|
|
|
|
if not options.dont_print_footer then print([[</div> <!-- card-footer -->]]) end
|
|
|
|
print[[
|
|
</div> <!-- closes card -->
|
|
|
|
<script>
|
|
|
|
NtopUtils.paramsToForm('#modalDeleteForm', ]] print(tableToJsObject(delete_params)) print[[);
|
|
|
|
function getTabSpecificParams() {
|
|
var tab_specific = {status:getCurrentStatus()};
|
|
var period_end = $('#modalDeleteAlertsOlderThan').val();
|
|
if (parseInt(period_end) > 0)
|
|
tab_specific.epoch_end = period_end;
|
|
|
|
if (tab_specific.status == "]] print(_GET["status"]) print[[") {
|
|
tab_specific.alert_severity = ]] if tonumber(_GET["alert_severity"]) ~= nil then print(_GET["alert_severity"]) else print('""') end print[[;
|
|
tab_specific.alert_l7_proto = ]] if tonumber(_GET["alert_l7_proto"]) ~= nil then print(_GET["alert_l7_proto"]) else print('""') end print[[;
|
|
tab_specific.alert_type = ]] if tonumber(_GET["alert_type"]) ~= nil then print(_GET["alert_type"]) else print('""') end print[[;
|
|
}
|
|
|
|
// merge the general parameters to the tab specific ones
|
|
return NtopUtils.paramsExtend(]] print(tableToJsObject(alert_utils.getTabParameters(url_params, nil))) print[[, tab_specific);
|
|
}
|
|
|
|
function checkModalDelete() {
|
|
var get_params = getTabSpecificParams();
|
|
var post_params = {};
|
|
post_params.csrf = "]] print(ntop.getRandomCSRFValue()) print[[";
|
|
post_params.id_to_delete = "__all__";
|
|
|
|
// this actually performs the request
|
|
var form = NtopUtils.paramsToForm('<form method="post"></form>', post_params);
|
|
form.attr("action", "?" + $.param(get_params));
|
|
form.appendTo('body').submit();
|
|
return false;
|
|
}
|
|
|
|
function downloadAlerts() {
|
|
$.ajax({
|
|
type: 'POST',
|
|
contentType: "application/json",
|
|
dataType: "json",
|
|
url: `${http_prefix}/lua/rest/v1/get/alert/data.lua`,
|
|
data: JSON.stringify(getTabSpecificParams()),
|
|
success: function(rsp) {
|
|
// Convert the Byte Data to BLOB object.
|
|
// Source: https://www.aspsnippets.com/Articles/Download-File-in-AJAX-Response-Success-using-jQuery.aspx
|
|
|
|
var blob = new Blob([JSON.stringify(rsp.rsp)], { type: "application/octetstream" });
|
|
|
|
//Check the Browser type and download the File.
|
|
var isIE = false || !!document.documentMode;
|
|
if (isIE) {
|
|
window.navigator.msSaveBlob(blob, fileName);
|
|
} else {
|
|
var url = window.URL || window.webkitURL;
|
|
var link = url.createObjectURL(blob);
|
|
var a = $("<a />");
|
|
a.attr("download", "alerts.json");
|
|
a.attr("href", link);
|
|
$("body").append(a);
|
|
a[0].click();
|
|
$("body").remove(a);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
var cur_alert_num_req = null;
|
|
|
|
/* This acts before shown.bs.modal event, avoiding visual fields substitution glitch */
|
|
$('#buttonOpenDeleteModal').on('click', function() {
|
|
var lb = $("#purgeBtnLabel");
|
|
var zoomsel = $("#deleteZoomSelector").find(":selected");
|
|
$("#myModal h3").html($("#purgeBtnMessage").html());
|
|
|
|
$(".modal-body #modalDeleteAlertsMsg").html(zoomsel.data('msg') + ']]
|
|
if tonumber(_GET["alert_severity"]) ~= nil then
|
|
print(' with severity "'..alert_consts.alertSeverityLabel(_GET["alert_severity"], true)..'" ')
|
|
elseif tonumber(_GET["alert_type"]) ~= nil then
|
|
print(' with type "'..alert_consts.alertTypeLabel(_GET["alert_type"], true)..'" ')
|
|
elseif tonumber(_GET["alert_l7_proto"]) ~= nil then
|
|
print(' with type "'..interface.getnDPIProtoName(tonumber(_GET["alert_l7_proto"]))..'" ')
|
|
end
|
|
print[[');
|
|
if (lb.length == 1)
|
|
$(".modal-body #modalDeleteContext").html(" " + lb.html());
|
|
|
|
$('#modalDeleteAlertsOlderThan').val(zoomsel.data('older'));
|
|
|
|
cur_alert_num_req = $.ajax({
|
|
type: 'GET',
|
|
]] print("url: '"..ntop.getHttpPrefix().."/lua/get_num_alerts.lua'") print[[,
|
|
data: $.extend(getTabSpecificParams(), {ifid: ]] print(string.format("%d", ifid)) print[[}),
|
|
complete: function() {
|
|
$("#alerts-summary-wait").hide();
|
|
}, error: function() {
|
|
$("#alerts-summary-body").html("?");
|
|
}, success: function(count){
|
|
$("#alerts-summary-body").html(count);
|
|
if (count == 0)
|
|
$('#myModal button[type="submit"]').attr("disabled", "disabled");
|
|
}
|
|
});
|
|
});
|
|
|
|
$('#myModal').on('hidden.bs.modal', function () {
|
|
if(cur_alert_num_req) {
|
|
cur_alert_num_req.abort();
|
|
cur_alert_num_req = null;
|
|
}
|
|
|
|
$("#alerts-summary-wait").show();
|
|
$("#alerts-summary-body").html("");
|
|
$('#myModal button[type="submit"]').removeAttr("disabled");
|
|
})
|
|
</script>]]
|
|
end
|
|
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.drawAlerts(options)
|
|
local has_engaged_alerts = alert_utils.hasAlerts("engaged", alert_utils.getTabParameters(_GET, "engaged"))
|
|
local has_past_alerts = alert_utils.hasAlerts("historical", alert_utils.getTabParameters(_GET, "historical"))
|
|
local has_flow_alerts = false
|
|
|
|
if _GET["entity"] == nil then
|
|
has_flow_alerts = alert_utils.hasAlerts("historical-flows", alert_utils.getTabParameters(_GET, "historical-flows"))
|
|
end
|
|
|
|
alert_utils.checkDeleteStoredAlerts()
|
|
return alert_utils.drawAlertTables(has_past_alerts, has_engaged_alerts, num_flow_alerts, false, _GET, true, nil, options)
|
|
end
|
|
|
|
-- #################################
|
|
|
|
-- A redis set with mac addresses as keys
|
|
function alert_utils.getActiveDevicesHashKey(ifid)
|
|
return "ntopng.cache.active_devices.ifid_" .. ifid
|
|
end
|
|
|
|
function alert_utils.deleteActiveDevicesKey(ifid)
|
|
ntop.delCache(alert_utils.getActiveDevicesHashKey(ifid))
|
|
end
|
|
|
|
-- #################################
|
|
|
|
-- A redis set with host pools as keys
|
|
local function getActivePoolsHashKey(ifid)
|
|
return "ntopng.cache.active_pools.ifid_" .. ifid
|
|
end
|
|
|
|
function alert_utils.deleteActivePoolsKey(ifid)
|
|
ntop.delCache(getActivePoolsHashKey(ifid))
|
|
end
|
|
|
|
-- #################################
|
|
|
|
-- Redis hashe with key=pool and value=list of quota_exceed_items, separated by |
|
|
local function getPoolsQuotaExceededItemsKey(ifid)
|
|
return "ntopng.cache.quota_exceeded_pools.ifid_" .. ifid
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.check_host_pools_alerts(params, ifid, alert_pool_connection_enabled, alerts_on_quota_exceeded)
|
|
local active_pools_set = getActivePoolsHashKey(ifid)
|
|
local prev_active_pools = swapKeysValues(ntop.getMembersCache(active_pools_set)) or {}
|
|
local pools_stats = interface.getHostPoolsStats()
|
|
local quota_exceeded_pools_key = getPoolsQuotaExceededItemsKey(ifid)
|
|
local quota_exceeded_pools_values = ntop.getHashAllCache(quota_exceeded_pools_key) or {}
|
|
local quota_exceeded_pools = {}
|
|
local now_active_pools = {}
|
|
|
|
-- Deserialize quota_exceeded_pools
|
|
for pool, v in pairs(quota_exceeded_pools_values) do
|
|
quota_exceeded_pools[pool] = {}
|
|
|
|
for _, group in pairs(split(quota_exceeded_pools_values[pool], "|")) do
|
|
local parts = split(group, "=")
|
|
|
|
if #parts == 2 then
|
|
local proto = parts[1]
|
|
local quota = parts[2]
|
|
|
|
local parts = split(quota, ",")
|
|
quota_exceeded_pools[pool][proto] = {toboolean(parts[1]), toboolean(parts[2])}
|
|
end
|
|
end
|
|
-- quota_exceeded_pools[pool] is like {Youtube={true, false}}, where true is bytes_exceeded, false is time_exceeded
|
|
end
|
|
|
|
local pools = interface.getHostPoolsInfo()
|
|
if(pools ~= nil) and (pools_stats ~= nil) then
|
|
for pool, info in pairs(pools.num_members_per_pool) do
|
|
local pool_stats = pools_stats[tonumber(pool)]
|
|
local pool_exceeded_quotas = quota_exceeded_pools[pool] or {}
|
|
|
|
-- Pool quota
|
|
if((pool_stats ~= nil) and (shaper_utils ~= nil)) then
|
|
local quotas_info = shaper_utils.getQuotasInfo(ifid, pool, pool_stats)
|
|
|
|
for proto, info in pairs(quotas_info) do
|
|
local prev_exceeded = pool_exceeded_quotas[proto] or {false,false}
|
|
|
|
if alerts_on_quota_exceeded then
|
|
if info.bytes_exceeded and not prev_exceeded[1] then
|
|
local alert = alert_consts.alert_types.alert_quota_exceeded.new(
|
|
"traffic_quota",
|
|
pool,
|
|
proto,
|
|
info.bytes_value,
|
|
info.bytes_quota
|
|
)
|
|
|
|
alert:set_severity(params.user_script_config.severity)
|
|
alert:store(alerts_api.hostPoolEntity(pool))
|
|
end
|
|
|
|
if info.time_exceeded and not prev_exceeded[2] then
|
|
local alert = alert_consts.alert_types.alert_quota_exceeded.new(
|
|
"time_quota",
|
|
pool,
|
|
proto,
|
|
info.time_value,
|
|
info.time_quota
|
|
)
|
|
|
|
alert:set_severity(alert_severities.warning)
|
|
alert:store(alerts_api.hostPoolEntity(pool))
|
|
end
|
|
end
|
|
|
|
if not info.bytes_exceeded and not info.time_exceeded then
|
|
-- delete as no quota is left
|
|
pool_exceeded_quotas[proto] = nil
|
|
else
|
|
-- update/add serialized
|
|
pool_exceeded_quotas[proto] = {info.bytes_exceeded, info.time_exceeded}
|
|
end
|
|
end
|
|
|
|
if table.empty(pool_exceeded_quotas) then
|
|
ntop.delHashCache(quota_exceeded_pools_key, pool)
|
|
else
|
|
-- Serialize the new quota information for the pool
|
|
for proto, value in pairs(pool_exceeded_quotas) do
|
|
pool_exceeded_quotas[proto] = table.concat({tostring(value[1]), tostring(value[2])}, ",")
|
|
end
|
|
|
|
ntop.setHashCache(quota_exceeded_pools_key, pool, table.tconcat(pool_exceeded_quotas, "=", "|"))
|
|
end
|
|
end
|
|
|
|
-- Pool presence
|
|
if (pool ~= host_pools.DEFAULT_POOL_ID) and (info.num_hosts > 0) then
|
|
now_active_pools[pool] = 1
|
|
|
|
if not prev_active_pools[pool] then
|
|
-- Pool connection
|
|
ntop.setMembersCache(active_pools_set, pool)
|
|
|
|
if alert_pool_connection_enabled then
|
|
local alert = alert_consts.alert_types.alert_host_pool_connection.new(
|
|
pool
|
|
)
|
|
|
|
alert:set_severity(alert_severities.notice)
|
|
alert:store(alerts_api.hostPoolEntity(pool))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Pool presence
|
|
for pool in pairs(prev_active_pools) do
|
|
if not now_active_pools[pool] then
|
|
-- Pool disconnection
|
|
ntop.delMembersCache(active_pools_set, pool)
|
|
|
|
if alert_pool_connection_enabled then
|
|
local alert = alert_consts.alert_types.alert_host_pool_disconnection.new(
|
|
pool
|
|
)
|
|
|
|
alert:set_severity(alert_severities.notice)
|
|
alert:store(alerts_api.hostPoolEntity(pool))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.disableAlertsGeneration()
|
|
if not haveAdminPrivileges() then
|
|
return
|
|
end
|
|
|
|
-- Ensure we do not conflict with others
|
|
ntop.setPref("ntopng.prefs.disable_alerts_generation", "1")
|
|
if(verbose) then io.write("[Alerts] Disable done\n") end
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.flushAlertsData()
|
|
if not haveAdminPrivileges() then
|
|
return
|
|
end
|
|
|
|
local selected_interface = ifname
|
|
local ifnames = interface.getIfNames()
|
|
local force_query = true
|
|
local generation_toggle_backup = ntop.getPref("ntopng.prefs.disable_alerts_generation")
|
|
|
|
if(verbose) then io.write("[Alerts] Temporary disabling alerts generation...\n") end
|
|
ntop.setAlertsTemporaryDisabled(true);
|
|
ntop.msleep(3000)
|
|
|
|
callback_utils.foreachInterface(ifnames, nil, function(ifname, ifstats)
|
|
if(verbose) then io.write("[Alerts] Processing interface "..ifname.."...\n") end
|
|
|
|
if(verbose) then io.write("[Alerts] Flushing SQLite configuration...\n") end
|
|
performAlertsQuery("DELETE", "engaged", {}, force_query)
|
|
performAlertsQuery("DELETE", "historical", {}, force_query)
|
|
performAlertsQuery("DELETE", "historical-flows", {}, force_query)
|
|
end)
|
|
|
|
if(verbose) then io.write("[Alerts] Flushing Redis configuration...\n") end
|
|
deleteCachePattern("ntopng.alerts.*")
|
|
deleteCachePattern("ntopng.prefs.alerts.*")
|
|
|
|
-- Avoid using 'alert' instead of 'alerts' if we do not want to touch user scripts configurations
|
|
-- as it also deletes ntopng.prefs.plugins_consts_utils.assigned_ids.const_type_alert and others
|
|
deleteCachePattern("ntopng.prefs.*alerts*")
|
|
|
|
for _, key in pairs(get_make_room_keys("*")) do deleteCachePattern(key) end
|
|
|
|
if(verbose) then io.write("[Alerts] Enabling alerts generation...\n") end
|
|
ntop.setAlertsTemporaryDisabled(false);
|
|
|
|
ntop.setPref("ntopng.prefs.disable_alerts_generation", generation_toggle_backup)
|
|
refreshAlerts(interface.getId())
|
|
|
|
if(verbose) then io.write("[Alerts] Flush done\n") end
|
|
interface.select(selected_interface)
|
|
end
|
|
|
|
-- #################################
|
|
|
|
local function alertNotificationActionToLabel(action, use_emoji)
|
|
local label = "["
|
|
|
|
if action == "engage" then
|
|
if(use_emoji) then label = label .."\xE2\x9D\x97 " end
|
|
label = label .. "Engaged]"
|
|
elseif action == "release" then
|
|
if(use_emoji) then label = label .."\xE2\x9C\x94 " end
|
|
label = label .. "Released]"
|
|
end
|
|
|
|
return label
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.getConfigsetAlertLink(alert_json)
|
|
local info = alert_json.alert_generation or (alert_json.alert_info and alert_json.alert_info.alert_generation)
|
|
|
|
if(info and isAdministrator()) then
|
|
return(' <a href="'.. ntop.getHttpPrefix() ..'/lua/admin/edit_configset.lua?'..
|
|
'subdir='.. info.subdir ..'&user_script='.. info.script_key ..'#all">'..
|
|
'<i class="fas fa-cog" title="'.. i18n("edit_configuration") ..'"></i></a>')
|
|
end
|
|
|
|
return('')
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.getAlertInfo(alert)
|
|
local alert_json = alert["alert_json"]
|
|
|
|
if isEmptyString(alert_json) then
|
|
alert_json = {}
|
|
elseif(string.sub(alert_json, 1, 1) == "{") then
|
|
alert_json = json.decode(alert_json) or {}
|
|
end
|
|
|
|
return alert_json
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.formatAlertMessage(ifid, alert, alert_json, skip_live_data)
|
|
local msg
|
|
|
|
if(alert_json == nil) then
|
|
alert_json = alert_utils.getAlertInfo(alert)
|
|
end
|
|
|
|
msg = alert_json
|
|
local description = alertTypeDescription(alert.alert_type, alert.alert_entity)
|
|
|
|
if(type(description) == "string") then
|
|
-- localization string
|
|
msg = i18n(description, msg)
|
|
elseif(type(description) == "function") then
|
|
msg = description(ifid, alert, msg)
|
|
end
|
|
|
|
if(type(msg) == "table") then
|
|
return("")
|
|
end
|
|
|
|
-- Append flow information to the alert message
|
|
if(alert.alert_entity == alert_consts.alertEntity("flow") or not alert.alert_entity) and not skip_live_data then
|
|
if msg == nil then
|
|
msg = formatRawFlow(ifid, alert, alert_json, true --[[ skip alert description, description already set --]])
|
|
else
|
|
msg = msg.. " "..formatRawFlow(ifid, alert, alert_json, true --[[ skip alert description, description already set --]])
|
|
end
|
|
end
|
|
|
|
if(msg) then
|
|
if(alert_consts.getAlertType(alert.alert_type, alert.alert_entity) == "alert_am_threshold_cross") then
|
|
local plugins_utils = require "plugins_utils"
|
|
local active_monitoring_utils = plugins_utils.loadModule("active_monitoring", "am_utils")
|
|
local host = active_monitoring_utils.key2host(alert.alert_entity_val)
|
|
|
|
if host and host.measurement and not host.is_infrastructure then
|
|
msg = msg .. ' <a href="'.. ntop.getHttpPrefix() ..'/plugins/active_monitoring_stats.lua?am_host='
|
|
.. host.host .. '&measurement='.. host.measurement ..'&page=overview"><i class="fas fa-cog" title="'.. i18n("edit_configuration") ..'"></i></a>'
|
|
end
|
|
else
|
|
msg = msg .. alert_utils.getConfigsetAlertLink(alert_json)
|
|
end
|
|
end
|
|
|
|
return(msg or "")
|
|
end
|
|
|
|
-- #################################
|
|
|
|
function alert_utils.notification_timestamp_rev(a, b)
|
|
return (a.alert_tstamp > b.alert_tstamp)
|
|
end
|
|
|
|
-- Returns a summary of the alert as readable text
|
|
function alert_utils.formatAlertNotification(notif, options)
|
|
local defaults = {
|
|
nohtml = false,
|
|
show_severity = true,
|
|
}
|
|
options = table.merge(defaults, options)
|
|
|
|
local ifname
|
|
local severity
|
|
local when
|
|
|
|
if(notif.ifid ~= -1) then
|
|
ifname = string.format(" [%s]", getInterfaceName(notif.ifid))
|
|
else
|
|
ifname = ""
|
|
end
|
|
|
|
if(options.show_severity == false) then
|
|
severity = ""
|
|
else
|
|
severity = " [" .. alert_consts.alertSeverityLabel(notif.alert_severity, options.nohtml, options.emoji) .. "]"
|
|
end
|
|
|
|
if(options.nodate == true) then
|
|
when = ""
|
|
else
|
|
when = formatEpoch(notif.alert_tstamp_end or notif.alert_tstamp or 0)
|
|
|
|
if(not options.no_bracket_around_date) then
|
|
when = "[" .. when .. "]"
|
|
end
|
|
|
|
when = when .. " "
|
|
end
|
|
|
|
local msg = string.format("%s%s%s [%s]",
|
|
when, ifname, severity,
|
|
alert_consts.alertTypeLabel(notif.alert_type, options.nohtml))
|
|
|
|
-- entity can be hidden for example when one is OK with just the message
|
|
if options.show_entity then
|
|
msg = msg.."["..alert_consts.alertEntityLabel(notif.alert_entity).."]"
|
|
|
|
if notif.alert_entity ~= "flow" then
|
|
local ev = notif.alert_entity_val
|
|
if notif.alert_entity == "host" then
|
|
-- suppresses @0 when the vlan is zero
|
|
ev = hostinfo2hostkey(hostkey2hostinfo(notif.alert_entity_val))
|
|
end
|
|
|
|
msg = msg.."["..(ev or '').."]"
|
|
end
|
|
end
|
|
|
|
-- add the label, that is, engaged or released
|
|
msg = msg .. " " .. alertNotificationActionToLabel(notif.action, options.emoji).. " "
|
|
local alert_message = alert_utils.formatAlertMessage(notif.ifid, notif)
|
|
|
|
if(options.add_cr) then
|
|
msg = msg .. "\n"
|
|
end
|
|
|
|
if options.nohtml then
|
|
msg = msg .. noHtml(alert_message)
|
|
else
|
|
msg = msg .. alert_message
|
|
end
|
|
|
|
return msg
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- Processes queued alerts and returns the information necessary to store them.
|
|
-- Alerts are only enqueued by AlertsQueue in C. From lua, the alerts_api
|
|
-- can be called directly as slow operations will be postponed
|
|
local function processStoreAlertFromQueue(alert)
|
|
local entity_info = nil
|
|
local type_info = nil
|
|
|
|
interface.select(tostring(alert.ifid))
|
|
|
|
if(alert.alert_type == "misconfigured_dhcp_range") then
|
|
local router_info = {host = alert.router_ip, vlan = alert.vlan_id}
|
|
entity_info = alerts_api.hostAlertEntity(alert.client_ip, alert.vlan_id)
|
|
type_info = alert_consts.alert_types.alert_ip_outsite_dhcp_range.new(
|
|
router_info,
|
|
alert.mac_address,
|
|
alert.client_mac,
|
|
alert.sender_mac
|
|
)
|
|
type_info:set_severity(alert_severities.warning)
|
|
type_info:set_subtype(string.format("%s_%s_%s", hostinfo2hostkey(router_info), alert.client_mac, alert.sender_mac))
|
|
elseif(alert.alert_type == "mac_ip_association_change") then
|
|
local name = getDeviceName(alert.new_mac)
|
|
entity_info = alerts_api.macEntity(alert.new_mac)
|
|
type_info = alert_consts.alert_types.alert_mac_ip_association_change.new(
|
|
name,
|
|
alert.ip,
|
|
alert.old_mac,
|
|
alert.new_mac
|
|
)
|
|
type_info:set_severity(alert_severities.warning)
|
|
type_info:set_subtype(string.format("%s_%s_%s", alert.ip, alert.old_mac, alert.new_mac))
|
|
elseif(alert.alert_type == "login_failed") then
|
|
entity_info = alerts_api.userEntity(alert.user)
|
|
type_info = alert_consts.alert_types.alert_login_failed.new()
|
|
type_info:set_severity(alert_severities.warning)
|
|
elseif(alert.alert_type == "broadcast_domain_too_large") then
|
|
entity_info = alerts_api.macEntity(alert.src_mac)
|
|
type_info = alert_consts.alert_types.alert_broadcast_domain_too_large.new(alert.src_mac, alert.dst_mac, alert.vlan_id, alert.spa, alert.tpa)
|
|
type_info:set_severity(alert_severities.warning)
|
|
type_info:set_subtype(string.format("%u_%s_%s_%s_%s", alert.vlan_id, alert.src_mac, alert.spa, alert.dst_mac, alert.tpa))
|
|
elseif((alert.alert_type == "user_activity") and (alert.scope == "login")) then
|
|
entity_info = alerts_api.userEntity(alert.user)
|
|
type_info = alert_consts.alert_types.alert_user_activity.new(
|
|
"login",
|
|
nil,
|
|
nil,
|
|
nil,
|
|
"authorized"
|
|
)
|
|
type_info:set_severity(alert_severities.notice)
|
|
type_info:set_subtype("login//")
|
|
elseif(alert.alert_type == "nfq_flushed") then
|
|
entity_info = alerts_api.interfaceAlertEntity(alert.ifid)
|
|
type_info = alert_consts.alert_types.alert_nfq_flushed.new(
|
|
getInterfaceName(alert.ifid),
|
|
alert.pct,
|
|
alert.tot,
|
|
alert.dropped
|
|
)
|
|
|
|
type_info:set_severity(alert_severities.error)
|
|
else
|
|
traceError(TRACE_ERROR, TRACE_CONSOLE, "Unknown alert type " .. (alert.alert_type or ""))
|
|
end
|
|
|
|
return entity_info, type_info
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
-- @brief Process notifications arriving from the internal C queue
|
|
-- Such notifications are transformed into stored alerts
|
|
function alert_utils.process_notifications_from_c_queue()
|
|
local budget = 1024 -- maximum 1024 alerts per call
|
|
local budget_used = 0
|
|
|
|
-- Check for alerts pushed by the datapath to an internal queue (from C)
|
|
-- and store them (push them to the SQLite and Notification queues).
|
|
-- NOTE: this is executed in a system VM, with no interfaces references
|
|
while budget_used <= budget do
|
|
local alert = ntop.popInternalAlerts()
|
|
|
|
if alert == nil then
|
|
break
|
|
end
|
|
|
|
if(verbose) then tprint(alert) end
|
|
|
|
local entity_info, type_info = processStoreAlertFromQueue(alert)
|
|
|
|
if type_info and entity_info then
|
|
type_info:store(entity_info)
|
|
end
|
|
|
|
|
|
budget_used = budget_used + 1
|
|
end
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
local function notify_ntopng_status(started)
|
|
local info = ntop.getInfo()
|
|
local severity = alert_consts.alertSeverity("info")
|
|
local msg
|
|
local msg_details = string.format("%s v.%s (%s) [OS: %s][pid: %s][options: %s]", info.product, info.version, info.revision, info.OS, info.pid, info.command_line)
|
|
local anomalous = false
|
|
local event
|
|
|
|
if(started) then
|
|
|
|
-- reading current version and last version to check if it has been updated
|
|
local last_version_key = "ntopng.updates.last_version"
|
|
local last_version = ntop.getCache(last_version_key)
|
|
local curr_version = info["version"].."-"..info["revision"]
|
|
ntop.setCache(last_version_key, curr_version)
|
|
|
|
-- let's check if we are restarting from an anomalous termination
|
|
-- e.g., from a crash
|
|
if not recovery_utils.check_clean_shutdown() then
|
|
-- anomalous termination
|
|
msg = string.format("%s %s", i18n("alert_messages.ntopng_anomalous_termination", {url="https://www.ntop.org/support/need-help-2/need-help/"}), msg_details)
|
|
severity = alert_consts.alertSeverity("error")
|
|
anomalous = true
|
|
event = "anomalous_termination"
|
|
elseif not isEmptyString(last_version) and last_version ~= curr_version then
|
|
-- software update
|
|
msg = string.format("%s %s", i18n("alert_messages.ntopng_update"), msg_details)
|
|
event = "update"
|
|
else
|
|
-- normal termination
|
|
msg = string.format("%s %s", i18n("alert_messages.ntopng_start"), msg_details)
|
|
event = "start"
|
|
end
|
|
else
|
|
msg = string.format("%s %s", i18n("alert_messages.ntopng_stop"), msg_details)
|
|
event = "stop"
|
|
end
|
|
|
|
local entity_value = "ntopng"
|
|
|
|
obj = {
|
|
entity_type = alert_consts.alertEntity("process"), entity_value=entity_value,
|
|
type = alert_consts.alertType("alert_process_notification"),
|
|
severity = severity,
|
|
message = msg,
|
|
when = os.time() }
|
|
|
|
if anomalous then
|
|
telemetry_utils.notify(obj)
|
|
end
|
|
|
|
local entity_info = alerts_api.processEntity(entity_value)
|
|
local type_info = alert_consts.alert_types.alert_process_notification.new(
|
|
event,
|
|
msg_details
|
|
)
|
|
|
|
type_info:set_severity(alert_severities[alert_consts.alertSeverityRaw(severity)])
|
|
|
|
interface.select(getSystemInterfaceId())
|
|
return(type_info:store(entity_info))
|
|
end
|
|
|
|
function alert_utils.notify_ntopng_start()
|
|
return(notify_ntopng_status(true))
|
|
end
|
|
|
|
function alert_utils.notify_ntopng_stop()
|
|
return(notify_ntopng_status(false))
|
|
end
|
|
|
|
-- A redis set with mac addresses as keys
|
|
function alert_utils.deleteOldData(interface_id, epoch_end)
|
|
local opts = {}
|
|
|
|
opts["ifid"] = interface_id
|
|
opts["epoch_end"] = tostring(epoch_end)
|
|
opts["status"] = "historical"
|
|
|
|
deleteAlerts("historical", opts)
|
|
|
|
opts["status"] = "historical-flows"
|
|
deleteAlerts("historical-flows", opts)
|
|
end
|
|
|
|
|
|
return alert_utils
|