-- -- (C) 2021 - ntop.org -- require "lua_utils" local json = require "dkjson" local alert_utils = require "alert_utils" local webhook = { name = "Webhook", endpoint_params = { { param_name = "webhook_url" }, { param_name = "webhook_sharedsecret", optional = true }, { param_name = "webhook_username", optional = true }, { param_name = "webhook_password", optional = true }, -- TODO: configure severity (Errors, Errors and Warnings, All) }, endpoint_template = { script_key = "webhook", template_name = "webhook_endpoint.template" }, recipient_params = { }, recipient_template = { script_key = "webhook", template_name = "webhook_recipient.template" }, } webhook.EXPORT_FREQUENCY = 5 webhook.API_VERSION = "0.2" webhook.REQUEST_TIMEOUT = 10 webhook.ITERATION_TIMEOUT = 3 webhook.prio = 400 local MAX_ALERTS_PER_REQUEST = 10 -- ############################################## -- @brief Returns the desided formatted output for recipient params function webhook.format_recipient_params(recipient_params) return string.format("(%s)", webhook.name) end -- ############################################## local function recipient2sendMessageSettings(recipient) local settings = { url = recipient.endpoint_conf.webhook_url, sharedsecret = recipient.endpoint_conf.webhook_sharedsecret, username = recipient.endpoint_conf.webhook_username, password = recipient.endpoint_conf.webhook_password, } return settings end -- ############################################## function webhook.sendMessage(alerts, settings) if isEmptyString(settings.url) then return false end local message = { version = webhook.API_VERSION, timestamp = os.time(), sharedsecret = settings.sharedsecret, alerts = alerts, } -- Use dkjson with specific formatting options for consistency local json_message = json.encode(message, { indent = true, -- Pretty print keyorder = { -- Consistent key ordering "version", "timestamp", "sharedsecret", "alerts" } }) local rc = false local retry_attempts = 3 while retry_attempts > 0 do if ntop.postHTTPJsonData(settings.username, settings.password, settings.url, json_message, webhook.REQUEST_TIMEOUT) then rc = true break end retry_attempts = retry_attempts - 1 end return rc end -- ############################################## local function formatAlertMsg(alert) local decoded_alert = json.decode(alert) if decoded_alert and decoded_alert.json then local json_decoded = json.decode(decoded_alert.json) -- Decode json (old format was string) if json_decoded and json_decoded.flow_risk_info and type(json_decoded.flow_risk_info) == "string" then json_decoded.flow_risk_info = json.decode(json_decoded.flow_risk_info) end if json_decoded and json_decoded.alert_generation and json_decoded.alert_generation.flow_risk_info and type(json_decoded.alert_generation.flow_risk_info) == "string" then json_decoded.alert_generation.flow_risk_info = json.decode(json_decoded.alert_generation.flow_risk_info) end decoded_alert.json = json_decoded decoded_alert.metadata = {} end return decoded_alert end -- ############################################## function webhook.dequeueRecipientAlerts(recipient, budget) local start_time = os.time() local sent = 0 local budget_used = 0 local num_messages_dequeued = 0 local debugme = false local settings = recipient2sendMessageSettings(recipient) local more_available = true local success = true local error_message = nil local delivered = 0 local discarded = 0 local failures = 0 -- Dequeue alerts up to budget x MAX_ALERTS_PER_REQUEST -- Note: in this case budget is the number of webhook messages to send while budget_used <= budget and more_available do local diff = os.time() - start_time if diff >= webhook.ITERATION_TIMEOUT then break end -- Dequeue MAX_ALERTS_PER_REQUEST notifications local notifications = {} local i = 0 while i < MAX_ALERTS_PER_REQUEST do local notification = ntop.recipient_dequeue(recipient.recipient_id) if notification then if alert_utils.filter_notification(notification, recipient.recipient_id) then notifications[#notifications + 1] = notification.alert i = i + 1 else discarded = discarded + 1 end else break end end if not notifications or #notifications == 0 then more_available = false break end local alerts = {} for _, json_message in ipairs(notifications) do local alert = formatAlertMsg(json_message) if(debugme) then tprint("[ALERT] "..json_message) end table.insert(alerts, alert) end num_messages_dequeued = num_messages_dequeued + #notifications if(debugme) then tprint("[PARTIAL] Sending ".. #notifications .." messages out of "..i.." messages dequeued") end if not webhook.sendMessage(alerts, settings) then if(debugme) then tprint("[FAILURE] Message delivery failed") end success = false error_message = "Unable to send alerts to the webhook" failures = failures + #notifications goto done else if(debugme) then tprint("[OK] Message sent correctly") end delivered = delivered + #notifications end -- Remove the processed messages from the queue budget_used = budget_used + #notifications sent = sent + 1 end if(debugme) then tprint("[END] Sent "..num_messages_dequeued.." messages") end ::done:: return { success = success, error_message = error_message, delivered = delivered, discarded = discarded, failures = failures, more_available = more_available, } end -- ############################################## function webhook.runTest(recipient) local message_info local settings = recipient2sendMessageSettings(recipient) local success = webhook.sendMessage({}, settings) if success then message_info = i18n("prefs.webhook_sent_successfully") else message_info = i18n("prefs.webhook_send_error") end return success, message_info end -- ############################################## return webhook