bin/handler-mailer.rb
#!/usr/bin/env ruby
#
# Sensu Handler: mailer
#
# This handler formats alerts as mails and sends them off to a pre-defined recipient.
#
# Copyright 2012 Pal-Kristian Hamre (https://github.com/pkhamre | http://twitter.com/pkhamre)
#
# Released under the same terms as Sensu (the MIT license); see LICENSE
# for details.
# Note: The default mailer config is fetched from the predefined json config file which is "mailer.json" or any other
# file defined using the "json_config" command line option. The mailing list could also be configured on a per client basis
# by defining the "mail_to" attribute in the client config file. This will override the default mailing list where the
# alerts are being routed to for that particular client.
require 'sensu-handler'
require 'mail'
require 'timeout'
require 'erubis'
require 'set'
require 'ntlm/smtp'
# patch to fix Exim delivery_method: https://github.com/mikel/mail/pull/546
# #YELLOW
module ::Mail # rubocop:disable Style/ClassAndModuleChildren
class Exim < Sendmail
def self.call(path, arguments, _destinations, encoded_message)
popen "#{path} #{arguments}" do |io|
io.puts encoded_message.to_lf
io.flush
end
end
end
end
class Mailer < Sensu::Handler
option :json_config_name,
description: 'Name of the JSON Configuration block in Sensu (default: mailer)',
short: '-j JsonConfig',
long: '--json_config JsonConfig',
required: false,
default: 'mailer'
option :template,
description: 'Message template to use',
short: '-t TemplateFile',
long: '--template TemplateFile',
required: false
option :subject_template,
description: 'Subject template to use',
short: '-T TemplateFile',
long: '--subject_template TemplateFile',
required: false
option :content_type,
description: 'Content-type of message',
short: '-c ContentType',
long: '--content_type ContentType',
required: false
option :subject_prefix,
description: 'Prefix message subjects with this string',
short: '-s prefix',
long: '--subject_prefix prefix',
required: false
# JSON configuration settings typically defined in the handler
# file for mailer. JSON Config Name defaultly looks for a block
# named 'mailer', as seen in the Installation step of the README
#
# @example
#
# ```json
# {
# "admin_gui": "http://localhost:3000",
# "mail_from": "from@email.com",
# "mail_to": "to@email.com",
# "delivery_method": "smtp",
# "smtp_address": "localhost",
# "smtp_port": "25",
# "smtp_domain": "localhost.local_domain",
# "smtp_enable_starttls_auto": "true",
# "smtp_username": "username",
# "smtp_password": "XXXXXXXX"
# }
# ```
#
# @return [Hash]
def json_config_settings
settings[config[:json_config_name]]
end
def short_name
@event['client']['name'] + '/' + @event['check']['name']
end
def action_to_string
@event['action'].eql?('resolve') ? 'RESOLVED' : 'ALERT'
end
def prefix_subject
if config[:subject_prefix]
config[:subject_prefix] + ' '
elsif json_config_settings['subject_prefix']
json_config_settings['subject_prefix'] + ' '
else
''
end
end
def status_to_string
case @event['check']['status']
when 0
'OK'
when 1
'WARNING'
when 2
'CRITICAL'
else
'UNKNOWN'
end
end
def parse_content_type
use = if config[:content_type]
config[:content_type]
elsif @event['check']['content_type']
@event['check']['content_type']
elsif json_config_settings['content_type']
json_config_settings['content_type']
else
'plain'
end
if use.casecmp('html').zero?
'text/html; charset=UTF-8'
else
'text/plain; charset=ISO-8859-1'
end
end
def raise_contact_is_array(src)
msg = "you passed #{src} which is meant to be a string, "
msg += 'if you need more than one contact you can use `contacts` to '
msg += 'configure multiple recipients'
p msg
exit 3 # unknown
end
def build_mail_to_list
mail_to = Set.new
mail_to.add(@event['client']['mail_to'] || json_config_settings['mail_to'])
if json_config_settings.key?('subscriptions') && @event['check']['subscribers']
@event['check']['subscribers'].each do |sub|
if json_config_settings['subscriptions'].key?(sub)
mail_to.add(json_config_settings['subscriptions'][sub]['mail_to'].to_s)
end
end
end
if settings.key?('contact') && settings['contact'].is_a?(Array)
raise_contact_is_array(settings['contact'])
elsif settings.key?('contacts')
all_contacts = []
%w(check client).each do |field|
if @event[field].key?('contact') && @event[field]['contact'].is_a?(Array)
raise_contact_is_array(@event[field]['contact'])
elsif @event[field].key?('contact')
all_contacts << @event[field]['contact']
end
if @event[field].key?('contacts')
all_contacts += @event[field]['contacts']
end
end
all_contacts.each do |sub|
if settings['contacts'].key?(sub)
mail_to.add(settings['contacts'][sub]['email']['to'].to_s)
end
end
end
mail_to.to_a.join(', ')
end
def subject_template
return config[:subject_template] if config[:subject_template]
return @event['check']['subject_template'] if @event['check']['subject_template']
return json_config_settings['subject_template'] if json_config_settings['subject_template']
nil
end
def build_subject
template = if subject_template && File.readable?(subject_template)
File.read(subject_template)
else
<<-SUBJECT.gsub(/^\s+/, '')
<%= action_to_string %> - <%= short_name %>: <% if (@event['check']['notification'] != nil) then %><%= @event['check']['notification'] %><% else %><%= status_to_string %><% end %>
SUBJECT
end
eruby = Erubis::Eruby.new(template)
eruby.result(binding)
end
def message_template
return config[:template] if config[:template]
return @event['check']['template'] if @event['check']['template']
return json_config_settings['template'] if json_config_settings['template']
nil
end
def build_body
admin_gui = json_config_settings['admin_gui'] || 'http://localhost:8080/'
# try to redact passwords from output and command
output = (@event['check']['output']).to_s.gsub(/(\s-p|\s-P|\s--password)(\s*\S+)/, '\1 <password omitted>')
command = (@event['check']['command']).to_s.gsub(/(\s-p|\s-P|\s--password)(\s*\S+)/, '\1 <password omitted>')
playbook = "Playbook: #{@event['check']['playbook']}" if @event['check']['playbook']
template = if message_template && File.readable?(message_template)
File.read(message_template)
else
<<-BODY.gsub(/^\s+/, '')
<%= output %>
Admin GUI: <%= admin_gui %>
Host: <%= @event['client']['name'] %>
Timestamp: <%= Time.at(@event['check']['issued']) %>
Address: <%= @event['client']['address'] %>
Check Name: <%= @event['check']['name'] %>
Command: <%= command %>
Status: <%= status_to_string %>
Occurrences: <%= @event['occurrences'] %>
<%= playbook %>
BODY
end
eruby = Erubis::Eruby.new(template)
eruby.result(binding)
end
def handle
body = build_body
subject = "#{prefix_subject}#{build_subject}"
content_type = parse_content_type
mail_to = build_mail_to_list
mail_from = json_config_settings['mail_from']
reply_to = json_config_settings['reply_to'] || mail_from
delivery_method = json_config_settings['delivery_method'] || 'smtp'
smtp_address = json_config_settings['smtp_address'] || 'localhost'
smtp_port = json_config_settings['smtp_port'] || '25'
smtp_domain = json_config_settings['smtp_domain'] || 'localhost.localdomain'
smtp_username = json_config_settings['smtp_username'] || nil
smtp_password = json_config_settings['smtp_password'] || nil
smtp_authentication = json_config_settings['smtp_authentication'] || :plain
smtp_use_tls = json_config_settings['smtp_use_tls'] || nil
smtp_enable_starttls_auto = json_config_settings['smtp_enable_starttls_auto'] == 'false' ? false : true
timeout_interval = json_config_settings['timeout'] || 10
headers = {
'X-Sensu-Host' => (@event['client']['name']).to_s,
'X-Sensu-Timestamp' => Time.at(@event['check']['issued']).to_s,
'X-Sensu-Address' => (@event['client']['address']).to_s,
'X-Sensu-Check-Name' => (@event['check']['name']).to_s,
'X-Sensu-Status' => status_to_string.to_s,
'X-Sensu-Occurrences' => (@event['occurrences']).to_s
}
Mail.defaults do
delivery_options = {
address: smtp_address,
port: smtp_port,
domain: smtp_domain,
openssl_verify_mode: 'none',
tls: smtp_use_tls,
enable_starttls_auto: smtp_enable_starttls_auto
}
unless smtp_username.nil?
auth_options = {
user_name: smtp_username,
password: smtp_password,
authentication: smtp_authentication
}
delivery_options.merge! auth_options
end
delivery_method delivery_method.intern, delivery_options
end
begin
Timeout.timeout timeout_interval do
Mail.deliver do
to mail_to
from mail_from
reply_to reply_to
subject subject
body body
headers headers
content_type content_type
end
puts 'mail -- sent alert for ' + short_name + ' to ' + mail_to.to_s
end
rescue Timeout::Error
puts 'mail -- timed out while attempting to ' + @event['action'] + ' an incident -- ' + short_name
end
end
end