[VS] Fix notification sending at the end of periodic scan or scan all. (#7937)

This commit is contained in:
Nicolo Maio 2023-10-23 15:24:17 +02:00
parent 9facf5bb79
commit 181f8a8178
3 changed files with 286 additions and 36 deletions

View file

@ -39,12 +39,23 @@ package.path = dirs.installdir .. "/scripts/lua/modules/recipients/?.lua;" .. pa
require "lua_utils" -- used by tprint (debug)
local host_to_scan_key = "ntopng.prefs.host_to_scan"
local host_to_scan_periodicity_key = "ntopng.prefs.host_to_scan.periodicity_scan"
local host_scannned_count_key = "ntopng.prefs.host_to_scan.count_scanned"
local host_scan_queue_key = "ntopng.vs_scan_queue"
local scanned_hosts_changes_key = "ntopng.alerts.scanned_hosts_changes"
local host_in_scanning_hash_key = "ntopng.prefs.host_to_scan.in_scanning"
local host_to_scan_key = "ntopng.prefs.host_to_scan"
local host_to_scan_periodicity_key = "ntopng.prefs.host_to_scan.periodicity_scan"
local host_to_scan_all_key = "ntopng.prefs.host_to_scan.scan_all"
local host_scannned_count_key = "ntopng.prefs.host_to_scan.count_scanned"
local host_scan_queue_key = "ntopng.vs_scan_queue"
local scanned_hosts_changes_key = "ntopng.alerts.scanned_hosts_changes"
local host_in_scanning_hash_key = "ntopng.prefs.host_to_scan.in_scanning"
-- redis keys for periodic scan info
local host_periodic_scan_cve_num_key = "ntopng.prefs.host_to_scan.periodicity_scan.info.cve_num"
local host_periodic_scan_udp_ports_key = "ntopng.prefs.host_to_scan.periodicity_scan.info.udp_ports"
local host_periodic_scan_tcp_ports_key = "ntopng.prefs.host_to_scan.periodicity_scan.info.tcp_ports"
-- redis keys for scan all info
local host_scan_all_cve_num_key = "ntopng.prefs.host_to_scan.scan_all.info.cve_num"
local host_scan_all_udp_ports_key = "ntopng.prefs.host_to_scan.scan_all.info.udp_ports"
local host_scan_all_tcp_ports_key = "ntopng.prefs.host_to_scan.scan_all.info.tcp_ports"
local json = require("dkjson")
local format_utils = require("format_utils")
@ -614,6 +625,59 @@ local function save_scanning_host(scan_info)
ntop.setHashCache(host_in_scanning_hash_key, host_to_scan_hash_key, json.encode(scan_info))
end
-- **********************************************************
-- Function to select correctly redis keys on periodic or scan all
local function get_counter_periodic_all_scan_keys(is_periodic)
if (is_periodic) then
return host_periodic_scan_cve_num_key,host_periodic_scan_udp_ports_key,host_periodic_scan_tcp_ports_key
else
return host_scan_all_cve_num_key,host_scan_all_udp_ports_key,host_scan_all_tcp_ports_key
end
end
-- **********************************************************
-- Function to update counters of periodically scan or scan all
-- @param is_periodic (true -> is a periodic scan, false -> is a scan all)
local function update_periodicity_or_all_scan_info(is_periodic, new_item)
-- select correctly redis keys
local cve_num_redis_key, udp_ports_redis_key, tcp_ports_redis_key = get_counter_periodic_all_scan_keys(is_periodic)
if (new_item.num_vulnerabilities_found ~= nil) then
local cve_num_string = ntop.getCache(cve_num_redis_key)
local old_saved_cve_num = tonumber(cve_num_string or 0)
local new_cve_num = new_item.num_vulnerabilities_found + old_saved_cve_num
if (new_cve_num ~= old_saved_cve_num) then
ntop.setCache(cve_num_redis_key, tostring(new_cve_num))
end
end
if (new_item.udp_ports ~= nil) then
local udp_ports_num_string = ntop.getCache(udp_ports_redis_key)
local old_udp_ports_num = tonumber(udp_ports_num_string or 0)
local new_udp_ports_num = old_udp_ports_num + new_item.udp_ports
if (old_udp_ports_num ~= new_udp_ports_num) then
ntop.setCache(udp_ports_redis_key, tostring(new_udp_ports_num))
end
end
if (new_item.tcp_ports ~= nil) then
local tcp_ports_num_string = ntop.getCache(tcp_ports_redis_key)
local old_tcp_ports_num = tonumber(tcp_ports_num_string or 0)
local new_tcp_ports_num = old_tcp_ports_num + new_item.tcp_ports
if (old_tcp_ports_num ~= new_tcp_ports_num) then
ntop.setCache(tcp_ports_redis_key, tostring(new_tcp_ports_num))
end
end
end
-- **********************************************************
-- Function to restore scanning host
@ -632,7 +696,9 @@ function vs_utils.restore_host_to_scan()
ntop.lpushCache(host_scan_queue_key, hash_value_string)
-- set status to scheduled
vs_utils.set_status_scan(host_info_to_restore.scan_type, host_info_to_restore.host, host_info_to_restore.ports, host_info_to_restore.id, nil, vs_utils.scan_status.scheduled)
vs_utils.set_status_scan( host_info_to_restore.scan_type, host_info_to_restore.host, host_info_to_restore.ports,
host_info_to_restore.id, host_info_to_restore.is_periodicity, host_info_to_restore.is_all,
vs_utils.scan_status.scheduled)
end
end
end
@ -766,6 +832,15 @@ function vs_utils.save_host_to_scan(scan_type, host, scan_result, last_scan_time
new_item.scan_frequency = old_data.scan_frequency
end
-- the is_periodicity param and is_all param are set outside the save_host_to_scan into the set_status method
if (old_data and old_data.is_periodicity ~= nil) then
new_item.is_periodicity = old_data.is_periodicity
end
if (old_data and old_data.is_all ~= nil) then
new_item.is_all = old_data.is_all
end
if(scan_result ~= nil) then
local handle = io.open(get_report_path(scan_type, host), "w")
local result = handle:write(scan_result)
@ -795,7 +870,14 @@ function vs_utils.save_host_to_scan(scan_type, host, scan_result, last_scan_time
local counts = vs_utils.update_ts_counters()
vs_utils.notify_end_periodicity()
--vs_utils.notify_end_periodicity()
if (new_item.is_periodicity) then
update_periodicity_or_all_scan_info(true, new_item)
end
if (new_item.is_all) then
update_periodicity_or_all_scan_info(false, new_item)
end
remove_scanning_host({host=host, scan_type=scan_type, ports=ports})
@ -838,19 +920,111 @@ function vs_utils.update_ts_counters()
return response
end
-- **********************************************************
-- Function to format num for emails
-- @param case: 0 - cve, 1 - udp, 2 - tcp
local function format_num_for_email(num, case)
if (case == 0) then
-- cve
if (num == 0) then
return(i18n("hosts_stats.page_scan_hosts.email.no_cves"))
else
return(i18n("hosts_stats.page_scan_hosts.email.num_cves", {num = num}))
end
elseif (case == 1) then
-- udp
if (num == 0) then
return(i18n("hosts_stats.page_scan_hosts.email.no_udp"))
else
return(i18n("hosts_stats.page_scan_hosts.email.num_udp", {num = num}))
end
elseif (case == 2) then
-- tcp
if (num == 0) then
return(i18n("hosts_stats.page_scan_hosts.email.no_tcp"))
else
return(i18n("hosts_stats.page_scan_hosts.email.num_tcp", {num = num}))
end
end
end
-- **********************************************************
-- Function to send notification after periodicity scan
function vs_utils.notify_end_periodicity()
-- @param is periodic (true -> is a periodic scan message, false -> is an all scan message)
-- @param periodicity (can be nil in case of scan all)
function vs_utils.notify_end_periodicity_or_all_scan(is_periodic, periodicity)
local notification_message = ""
local cve_num_redis_key, udp_ports_redis_key, tcp_ports_redis_key = get_counter_periodic_all_scan_keys(is_periodic)
local cve_num_string = ntop.getCache(cve_num_redis_key)
local cve_num = tonumber(cve_num_string) or 0
local udp_ports_string = ntop.getCache(udp_ports_redis_key)
local udp_ports = tonumber(udp_ports_string) or 0
local tcp_ports_string = ntop.getCache(tcp_ports_redis_key)
local tcp_ports = tonumber(tcp_ports_string) or 0
local title = i18n("hosts_stats.page_scan_hosts.email.vulnerability_scan_report_title",{host = getHttpHost()})
if (periodicity and periodicity == "1day") then
notification_message = i18n("hosts_stats.page_scan_hosts.email.periodicity_scan_1_day_ended", {
cves = format_num_for_email(cve_num,0),
udp_ports = format_num_for_email(udp_ports,1),
tcp_ports = format_num_for_email(tcp_ports,2),
url = getHttpHost() .. ntop.getHttpPrefix() .. "/lua/pro/reportng.lua?report_template=vs_result"
})
elseif (periodicity and periodicity == "1week") then
notification_message = i18n("hosts_stats.page_scan_hosts.email.periodicity_scan_1_week_ended", {
cves = format_num_for_email(cve_num,0),
udp_ports = format_num_for_email(udp_ports,1),
tcp_ports = format_num_for_email(tcp_ports,2),
url = getHttpHost() .. ntop.getHttpPrefix() .. "/lua/pro/reportng.lua?report_template=vs_result"
})
else
-- scan all case
notification_message = i18n("hosts_stats.page_scan_hosts.email.scan_all_ended", {
cves = format_num_for_email(cve_num,0),
udp_ports = format_num_for_email(udp_ports,1),
tcp_ports = format_num_for_email(tcp_ports,2),
url = getHttpHost() .. ntop.getHttpPrefix() .. "/lua/pro/reportng.lua?report_template=vs_result"
})
end
recipients.sendMessageByNotificationType({periodicity = periodicity, success=true, message = notification_message, title = title}, "vulnerability_scans")
ntop.setCache(cve_num_redis_key,"0")
ntop.setCache(udp_ports_redis_key,"0")
ntop.setCache(tcp_ports_redis_key,"0")
end
-- **********************************************************
-- Function to verify if periodic scan is ended
function vs_utils.is_periodic_scan_over()
local periodicity_scan_in_progress = ntop.getCache(host_to_scan_periodicity_key) == "1"
if (periodicity_scan_in_progress) then
local hosts_details = vs_utils.retrieve_hosts_to_scan()
for _,item in ipairs(hosts_details) do
if(item.is_periodicity and item.is_ok_last_scan == vs_utils.scan_status.scheduled) then
return
-- verify status of in progress periodic scanning
if(item.is_periodicity and (item.is_ok_last_scan == vs_utils.scan_status.scheduled or item.is_ok_last_scan == vs_utils.scan_status.scanning)) then
return false
end
end
@ -858,7 +1032,6 @@ function vs_utils.notify_end_periodicity()
local periodicity = ntop.getCache(host_to_scan_periodicity_key.."type")
for _,item in ipairs(hosts_details) do
local host_hash_key = vs_utils.get_host_hash_key(item.host, item.scan_type)
local host_hash_value_string = ntop.getHashCache(host_to_scan_key, host_hash_key)
@ -872,15 +1045,58 @@ function vs_utils.notify_end_periodicity()
end
end
local notification_message = ""
if (periodicity == "1day") then
notification_message = i18n("hosts_stats.page_scan_hosts.periodicity_scan_1_day_ended")
elseif (periodicity == "1week") then
notification_message = i18n("hosts_stats.page_scan_hosts.periodicity_scan_1_week_ended")
end
recipients.sendMessageByNotificationType({periodicity = periodicity, success=true, message = notification_message}, "vulnerability_scans")
return true, periodicity
end
return false
end
-- **********************************************************
-- Function to verify if scan all is ended
function vs_utils.is_scan_all_over()
local scan_all_in_progress = ntop.getCache(host_to_scan_all_key) == "1"
if (scan_all_in_progress) then
local hosts_details = vs_utils.retrieve_hosts_to_scan()
for _,item in ipairs(hosts_details) do
-- verify status of in progress periodic scanning
if(item.is_all and (item.is_ok_last_scan == vs_utils.scan_status.scheduled or item.is_ok_last_scan == vs_utils.scan_status.scanning)) then
return false
end
end
ntop.setCache(host_to_scan_all_key, "0")
for _,item in ipairs(hosts_details) do
local host_hash_key = vs_utils.get_host_hash_key(item.host, item.scan_type)
local host_hash_value_string = ntop.getHashCache(host_to_scan_key, host_hash_key)
if(not isEmptyString(host_hash_value_string)) then
local host_hash_value = json.decode(host_hash_value_string)
host_hash_value.is_all = false
ntop.setHashCache(host_to_scan_key, host_hash_key, json.encode(host_hash_value))
end
end
return true
end
return false
end
-- **********************************************************
-- Function to enable periodic scan end check on callbacks
function vs_utils.is_periodic_scan_running()
return ntop.getCache(host_to_scan_periodicity_key) == "1"
end
-- **********************************************************
@ -1110,7 +1326,7 @@ function vs_utils.scan_host(scan_type, host, ports, scan_id, use_coroutines)
end
end
vs_utils.set_status_scan(scan_type, host, ports_scan_param, id, nil, vs_utils.scan_status.scanning)
vs_utils.set_status_scan(scan_type, host, ports_scan_param, id, nil,nil, vs_utils.scan_status.scanning)
-- Save on redis the scanning host to avoid inconsistent state on ntopng restarts
local scanning_host = {scan_type = scan_type, host = host, ports = ports_scan_param, id = scan_id}
@ -1146,7 +1362,7 @@ end
-- **********************************************************
-- Function to update single host status
function vs_utils.set_status_scan(scan_type, host, ports, id, is_periodicity, status)
function vs_utils.set_status_scan(scan_type, host, ports, id, is_periodicity, is_all, status)
local host_hash_key = vs_utils.get_host_hash_key(host, scan_type)
local host_hash_value_string = ntop.getHashCache(host_to_scan_key, host_hash_key)
@ -1159,6 +1375,10 @@ function vs_utils.set_status_scan(scan_type, host, ports, id, is_periodicity, st
host_hash_value.is_periodicity = is_periodicity
end
if (is_all ~= nil) then
host_hash_value.is_all = is_all
end
ntop.setHashCache(host_to_scan_key, host_hash_key, json.encode(host_hash_value))
end
@ -1167,9 +1387,9 @@ end
-- **********************************************************
function vs_utils.schedule_host_scan(scan_type, host, ports, scan_id, is_periodicity)
function vs_utils.schedule_host_scan(scan_type, host, ports, scan_id, is_periodicity, is_all)
local scan = { scan_type = scan_type, host = host, ports = ports, id= scan_id}
vs_utils.set_status_scan(scan_type, host, ports, scan_id, is_periodicity, vs_utils.scan_status.scheduled)
vs_utils.set_status_scan(scan_type, host, ports, scan_id, is_periodicity, is_all, vs_utils.scan_status.scheduled)
ntop.rpushCache(host_scan_queue_key, json.encode(scan))
@ -1180,13 +1400,23 @@ end
function vs_utils.schedule_all_hosts_scan()
local host_to_scan_list = vs_utils.retrieve_hosts_to_scan()
local is_scanning_almost_one = false
if #host_to_scan_list > 0 then
for _,scan_info in ipairs(host_to_scan_list) do
vs_utils.schedule_host_scan(scan_info.scan_type, scan_info.host, scan_info.ports, scan_info.id, false)
vs_utils.schedule_host_scan(scan_info.scan_type, scan_info.host, scan_info.ports, scan_info.id, false, true)
is_scanning_almost_one = true
end
end
if (is_scanning_almost_one) then
ntop.setCache(host_to_scan_all_key , "1")
end
ntop.setCache(host_scan_all_cve_num_key,"0")
ntop.setCache(host_scan_all_udp_ports_key,"0")
ntop.setCache(host_scan_all_tcp_ports_key,"0")
return true
end
@ -1207,7 +1437,7 @@ function vs_utils.schedule_periodic_scan(periodicity)
local frequency = scan_info.scan_frequency
if(frequency == periodicity) then
vs_utils.schedule_host_scan(scan_info.scan_type, scan_info.host, scan_info.ports, scan_info.id, true)
vs_utils.schedule_host_scan(scan_info.scan_type, scan_info.host, scan_info.ports, scan_info.id, true, false)
is_scanning_almost_one = true
end
end
@ -1217,14 +1447,11 @@ function vs_utils.schedule_periodic_scan(periodicity)
ntop.setCache(host_to_scan_periodicity_key , "1")
ntop.setCache(host_to_scan_periodicity_key.."type", periodicity)
local notification_message = ""
if (periodicity == "1day") then
notification_message = i18n("hosts_stats.page_scan_hosts.periodicity_scan_1_day_started")
elseif (periodicity == "1week") then
notification_message = i18n("hosts_stats.page_scan_hosts.periodicity_scan_1_week_started")
end
ntop.setCache(host_periodic_scan_cve_num_key , "0")
ntop.setCache(host_periodic_scan_udp_ports_key , "0")
ntop.setCache(host_periodic_scan_tcp_ports_key , "0")
recipients.sendMessageByNotificationType({periodicity = periodicity, success=true, message = notification_message}, "vulnerability_scans")
end
end
end