Skip to content

Commit

Permalink
Fix auth on outgoing webhook delivery. Add logic monitor outgoing event
Browse files Browse the repository at this point in the history
  • Loading branch information
armiiller committed Oct 10, 2023
1 parent 72778df commit 01fe152
Show file tree
Hide file tree
Showing 9 changed files with 350 additions and 40 deletions.
158 changes: 156 additions & 2 deletions app/models/pager_tree/integrations/logic_monitor/v3.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
module PagerTree::Integrations
class LogicMonitor::V3 < Integration
OPTIONS = []
OPTIONS = [
{key: :alert_acknowledged, type: :boolean, default: false},
{key: :logic_monitor_account_name, type: :string, default: nil},
{key: :access_id, type: :string, default: nil},
{key: :access_key, type: :string, default: nil}
]
store_accessor :options, *OPTIONS.map { |x| x[:key] }.map(&:to_s), prefix: "option"

validates :option_alert_acknowledged, inclusion: {in: [true, false]}
validates :option_logic_monitor_account_name, presence: true, if: -> { option_alert_acknowledged == true }
validates :option_access_key, presence: true, if: -> { option_alert_acknowledged == true }
validates :option_access_id, presence: true, if: -> { option_alert_acknowledged == true }

after_initialize do
self.option_alert_acknowledged = false if option_alert_acknowledged.nil?
end

def adapter_supports_incoming?
true
end

def adapter_supports_outgoing?
false
true
end

def adapter_outgoing_interest?(event_name)
try("option_#{event_name}") || false
end

def adapter_show_outgoing_webhook_delivery?
true
end

def adapter_incoming_can_defer?
Expand Down Expand Up @@ -47,6 +66,15 @@ def adapter_process_create
)
end

def adapter_process_outgoing
event = adapter_outgoing_event.event_name.to_s

# only send sync to logic monitor if logic monitor is the source
if event == "alert_acknowledged" && options_alert_acknowledged == true && adapter_outgoing_event.alert.source == self
_on_acknowledge
end
end

private

def _title
Expand Down Expand Up @@ -75,5 +103,131 @@ def _additional_datums
AdditionalDatum.new(format: "text", label: "Service", value: adapter_incoming_request_params.dig("service"))
]
end

def _on_acknowledge
lm_alert_id = adapter_outgoing_event.alert.thirdparty_id
acknowledger = adapter_outgoing_event.account_user&.name || name || "someone"
resource_path = "/alert/alerts/#{lm_alert_id}/ack"
http_verb = "POST"
send_request_with_hmac(resource_path, http_verb, {ackComment: "Acknowledged by #{acknowledger}"})
end

# https://www.logicmonitor.com/support/rest-api-authentication
def send_request_with_hmac(resource_path, http_verb, data)
base_url = "https://#{option_logic_monitor_account_name}.logicmonitor.com/santaba/rest"
url = base_url + resource_path
timestamp_ms = Time.current.to_i * 1000
data_string = data.to_json
signature = Base64.strict_encode64(
OpenSSL::HMAC.digest(
OpenSSL::Digest.new("sha256"),
option_access_key,
(http_verb.upcase + timestamp_ms.to_s + data_string + resource_path)
)
)
headers = {
"Content-Type" => "application/json",
"Authorization" => "LMv1 #{option_access_id}:#{signature}:#{timestamp_ms}"
}

# note outgoing webhook delivery only supports the post method
outgoing_webhook_delivery = OutgoingWebhookDelivery.factory(
resource: self,
url: url,
body: data,
options: {headers: headers}
)
outgoing_webhook_delivery.save!
outgoing_webhook_delivery.deliver_later

outgoing_webhook_delivery
end
end
end

# https://www.logicmonitor.com/support/alerts/integrations/custom-http-delivery
# {
# "alertid": "LMS22",
# "alertstatus": "active",
# "datasource": "WinVolumeUsage-C:\",
# "datapoint": "PercentUsed",
# "date": "2014-05-02 14:21:40 PDT",
# "dsdesc": "Monitors space usage on logical volumes.",
# "dsidesc": null,
# "datapointdesc": "Percentage Used on the volume",
# "group": "group1,group2",
# "host": "opsgenie-test-server",
# "hostdesc": "Server used for testing OpsGenie integrations",
# "instance": "C:\",
# "level": "warning",
# "duration": "1465",
# "threshold": "10",
# "eventsource": "WinVolumeUsage-C:\",
# "eventlogfile": "Application",
# "eventtype": "information",
# "eventmsg": "Percentage used on the volume exceeded 80%",
# "eventlogmsg": "Remaining capacity(1456750MB) of volume C:\ is lower than 25%",
# "eventcode": "1847502394",
# "eventuser": "test-user",
# "value": "83",
# "batchdesc": "Monitors space usage on logical volumes everyday.",
# "hostips": "123.456.789.012",
# "hosturl": "https://opsgenie-test-server.net/",
# "service": "webservice",
# "alerttype": "error",
# "agent": "opsgenie-test-server",
# "checkpoint": "1879234",
# "hostinfo": null,
# "servicedetail": null,
# "serviceurl": "https://opsgenie-test-server.net/",
# "servicegroup": "Functional Testing",
# "clearvalue": "1"
# }

# {
# "service": "##SERVICE##",
# "alertid": "##ALERTID##",
# "alerttype": "##ALERTTYPE##",
# "alertstatus": "##ALERTSTATUS##",
# "level": "##LEVEL##",
# "host": "##HOST##",
# "datasource": "##DATASOURCE##",
# "eventsource": "##EVENTSOURCE##",
# "batchjob": "##BATCHJOB##",
# "group": "##GROUP##",
# "datapoint": "##DATAPOINT##",
# "start": "##START##",
# "finish": "##FINISH##",
# "duration": "##DURATION##",
# "value": "##VALUE##",
# "threshold": "##THRESHOLD##",
# "userdata": "##USERDATA##",
# "cmdline": "##CMDLINE##",
# "exitCode": "##EXITCODE##",
# "stdout": "##STDOUT##",
# "stderr": "##STDERR##",
# "agent": "##AGENT_DESCRIPTION##",
# "checkpoint": "##CHECKPOINT##",
# "datapointdesc": "##DPDESCRIPTION##",
# "hostdesc": "##HOSTDESCRIPTION##",
# "hostinfo": "##system.sysinfo##",
# "hostips": "##system.ips##",
# "hosturl": "##DEVICEURL##",
# "instance": "##INSTANCE##",
# "dsidesc": "##DSIDESCRIPTION##",
# "batchdesc": "##BJDESCRIPTION##",
# "dsdesc": "##DSDESCRIPTION##",
# "eventmsg": "##LIMITEDMESSAGE##",
# "eventlogmsg": "##MESSAGE##",
# "eventcode": "##EVENTCODE##",
# "eventtype": "##TYPE##",
# "eventuser": "##USER##",
# "eventlogfile": "##LOGFILE##",
# "eventsource": "##SOURCENAME##",
# "servicedetail": "##DETAIL##",
# "serviceurl": "##URL##",
# "servicegroup": "##SERVICEGROUP##",
# "date": "##DATE##",
# "clearvalue": "##CLEARVALUE##",
# "hostname": "##system.hostname##"
# }
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ class OutgoingWebhookDelivery < PagerTree::Integrations.outgoing_webhook_deliver
serialize :data, JSON
encrypts :data

store_accessor :data, *[:url, :body, :auth].map(&:to_s)
store_accessor :data, *[:url, :body, :auth, :options].map(&:to_s)

HTTP_OPTIONS = {
headers: {"Content-Type": "application/json"},
headers: {"Content-Type" => "application/json"},
timeout: 15
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,35 +31,7 @@ def deliver_later
def deliver
run_callbacks :deliver do
begin
hook_relay_options = {
headers: {
HR_TARGET_URL: url
}
}

pagertree_options = {
headers: {
Accept: "*/*",
"User-Agent": "pagertree outgoing webhook service; ref: #{resource&.id}; report: [email protected]",
"Content-Type": "application/json"
}
}

auth_options = {}
if auth.is_a?(Hash) && (username = auth.dig(:username)).present? && (password = auth.dig(:password)).present?
auth_options = {
headers: {
Authorization: Base64.strict_encode64("#{username}:#{password}")
}
}
end

options = OutgoingWebhookDelivery::HTTP_OPTIONS
.deep_merge(hook_relay_options)
.deep_merge(pagertree_options)
.deep_merge(auth_options)

response = HTTParty.post(hook_relay_hook_url, body: body.to_json, **options)
response = HTTParty.post(hook_relay_hook_url, body: body.to_json, **httparty_opts)

self.thirdparty_id = response["id"]
self.status = :sent
Expand All @@ -71,19 +43,53 @@ def deliver
end # run_callbacks
end

def httparty_opts
hook_relay_options = {
headers: {
"HR_TARGET_URL" => url
}
}

pagertree_options = {
headers: {
"Accept" => "*/*",
"User-Agent" => "pagertree outgoing webhook service; ref: #{resource&.id}; report: [email protected]",
"Content-Type" => "application/json"
}
}

auth_options = {}
if auth.is_a?(Hash)
auth_indifferent = auth.with_indifferent_access
if (username = auth_indifferent.dig(:username)).present? && (password = auth_indifferent.dig(:password)).present?
auth_options = {
headers: {
"Authorization" => Base64.strict_encode64("#{username}:#{password}")
}
}
end
end

OutgoingWebhookDelivery::HTTP_OPTIONS
.deep_merge(hook_relay_options)
.deep_merge(pagertree_options)
.deep_merge(auth_options)
.deep_merge((options&.symbolize_keys || {}))
end

def delivery
return @delivery if @delivery
return {} unless thirdparty_id

options = {
opts = {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer #{OutgoingWebhookDelivery::HookRelay.hook_relay_api_key}"
"Content-Type" => "application/json",
"Authorization" => "Bearer #{OutgoingWebhookDelivery::HookRelay.hook_relay_api_key}"
},
timeout: 15
}

response = ::HTTParty.get(hook_relay_delivery_url, **options)
response = ::HTTParty.get(hook_relay_delivery_url, **opts)
@delivery = response.parsed_response if response.success?

@delivery || {}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="form-group group">
<%= form.label :option_logic_monitor_account_name %>
<%= form.text_field :option_logic_monitor_account_name, class: "form-control" %>
<p class="form-hint"><%== t(".option_logic_monitor_account_name_hint_html") %></p>
</div>

<div class="form-group group">
<%= form.label :option_access_id %>
<%= form.text_field :option_access_id, class: "form-control" %>
<p class="form-hint"><%== t(".option_access_id_hint_html") %></p>
</div>

<div class="form-group group">
<%= form.label :option_access_key %>
<%= form.text_field :option_access_key, class: "form-control" %>
<p class="form-hint"><%== t(".option_access_key_hint_html") %></p>
</div>

<div class="form-group group">
<%= form.check_box :option_alert_acknowledged, class: "form-checkbox" %>
<%= form.label :option_alert_acknowledged, class: "inline-block" %>
<p class="form-hint md:inline-block"><%== t(".option_alert_acknowledged_hint_html") %></p>
</div>

</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<div class="sm:col-span-1">
<dt class="text-sm font-medium text-gray-500">
<%= t("activerecord.attributes.pager_tree/integrations/logic_monitor/v3.option_alert_acknowledged") %>
</dt>
<dd class="mt-1 text-sm text-gray-900">
<%= render partial: "shared/components/badge_enabled", locals: { enabled: integration.option_alert_acknowledged } %>
</dd>
</div>

<div class="sm:col-span-1">
<dt class="text-sm font-medium text-gray-500">
<%= t("activerecord.attributes.pager_tree/integrations/logic_monitor/v3.option_logic_monitor_account_name") %>
</dt>
<dd class="mt-1 text-sm text-gray-900">
<div class="flex items-center gap-2">
<p class="text-sm truncate">
<%= integration.option_logic_monitor_account_name %>
</p>
</div>
</dd>
</div>

<div class="sm:col-span-1">
<dt class="text-sm font-medium text-gray-500">
<%= t("activerecord.attributes.pager_tree/integrations/logic_monitor/v3.option_access_id") %>
</dt>
<dd class="mt-1 text-sm text-gray-900">
<div class="flex items-center gap-2">
<p class="text-sm truncate">
<%= mask integration.option_access_id %>
</p>
</div>
</dd>
</div>

<div class="sm:col-span-1">
<dt class="text-sm font-medium text-gray-500">
<%= t("activerecord.attributes.pager_tree/integrations/logic_monitor/v3.option_access_key") %>
</dt>
<dd class="mt-1 text-sm text-gray-900">
<div class="flex items-center gap-2">
<p class="text-sm truncate">
<%= mask integration.option_access_key %>
</p>
</div>
</dd>
</div>
12 changes: 12 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ en:
call_recording:
subject: "🎧 Alert #%{tiny_id} - New voicemail from %{from}"
body: "Alert #%{tiny_id} - New voicemail from %{from}"
logic_monitor:
v3:
form_options:
option_access_id_hint_html: "Logic Monitor Access <b>ID</b>"
option_access_key_hint_html: "Logic Monitor Access <b>Key</b>"
option_logic_monitor_account_name_hint_html: "Your Logic Monitor account name. (ex: https://<b>account_name</b>.logicmonitor.com)"
option_alert_acknowledged_hint_html: "Acknowledge the alert in Logic Monitor when the alert is acknowledged in PagerTree. (PagerTree => Logic Monitor)"
mattermost:
outgoing_webhook:
v3:
Expand Down Expand Up @@ -152,6 +159,11 @@ en:
option_force_input: "Force Caller Input"
option_record: "Voicemail"
option_record_emails_list: "Voicemail Emails"
"pager_tree/integrations/logic_monitor/v3":
option_access_id: "Logic Monitor Access ID"
option_access_key: "Logic Monitor Access Key"
option_logic_monitor_account_name: "Logic Monitor Account Name"
option_alert_acknowledged: "Acknowledge in Logic Monitor"
"pager_tree/integrations/slack/webhook/v3":
option_token: "Slack Token"
"pager_tree/integrations/server_guard24/v3":
Expand Down
Loading

0 comments on commit 01fe152

Please sign in to comment.