You are here: Home / Blogs / Zenoss - emails into Events

Zenoss - emails into Events

by Alan Milligan — last modified Nov 12, 2013 05:39 PM

How to get email messages/mailboxes content into your Zenoss NOC/Console.

I have seem a number of posts and pleas for help with regard to support mail boxes etc and how to empty their contents into Zenoss. 

I've also seen a range of bizarre solution proposals around doing this - bizarre in the sense that there seems to be little understanding of email and how it works.  A solution that falls into this category is a daemon/process that creates an IMAP client to read the mail periodically...

What needs to be done, is that a mailbox designated to send messages to your Zentinel/ZEP should have a custom SMTP delivery instructuon that rather than write a message to the mailbox, remotely connects to the Zentinel and delivers it as an Event.  Most real MTA's (Sendmail, Postfix, ...) have features which allow you to do this.  One of my long-time favourites is maildrop, a part of the Courier-MTA suite, and used quite extensively by other MTA's.  Maildrop has some compelling features as exemplified below.

This example uses Courier as the MTA, but you'll do something similar for Postfix (heh - and write some m4 macros for Sendmail).  You'll also tweak your Zentinel parameters etc as per your setup.

In the $HOME of your mail user ([email protected] say), place the following (customised for your setup) in the .courier and .courier-default files:

| /usr/bin/zenemailevent -u admin -p password -s http://zenoss.domain.com:8080


If you direct mail to [email protected], it will be forwarded to your Zentinel with a default severity of Info.  If you send to [email protected], it'll have a Critical severity.  The default event class is /Status/Update.

The component and the device are calculated from the Sender/From header as component@device.  As it is trivially easy to 'spoof' these, you have considerable flexibility in assuring email events are bound to their associated device(s).

A real-world configuration of Monit's email alerting is as simple as (in /etc/monitrc):

set mail-format 
  { from: [email protected]
    subject: $SERVICE $EVENT
    message: $ACTION: $DESCRIPTION } 
set alert [email protected]  # Send alert to system admin on any event


This will register Monit emails as info events on the /Device mydevice.domain.com.  Note that because we've also removed $DATE markers from the message, monit events will roll-up.

We use our OpsCode/Chef infrastructure for large-scale client system deployment.  It is quite simple to insert these email addresses into all sorts of processing which delivers to mailboxes - cron/users, postmaster accounts, logwatch, anything ...

The solution is available on BastionLinux here but you're more than welcome to paste the stub below into your own /usr/bin/zenemailevent ....

 

#!/usr/bin/env python
#
#    Copyright (C) 2013  Last Bastion Network Pty Ltd. All rights Reserved.
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
#    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
#    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
#    GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
#    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
#    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
#    OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##############################################################################

# zenossemailevent     Script to launch from .courier-default to
#                      send an email message to Zenoss Event Console.
#                      Severity is controlled by <mbox>-critical@<domain.com>,
#                      the from-header in the email is auto-elected as the
#                      component @ device as this is easily 'spoofed' into
#                      matching your systems on Zenoss.
#
#                      see dot-courier (5) for further explanation
#
##############################################################################

import cookielib, email, json, os, sys, urllib, urllib2, urlparse
from optparse import OptionParser

SEVERITY = {'critical':5, 'error':4, 'warning':3, 'info':2, 'debug':1, 'clear':0,}

class zeneventconn:
    """ manage connection, authentication, json to Zeninel """

    # executable name ...
    user_agent = sys.argv[0]

    def __init__(self, url, user, pwd, debuglevel=1):
        cj = cookielib.CookieJar()
        opener = self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj), 
                                                    urllib2.HTTPHandler(debuglevel=debuglevel))
        request = urllib2.Request('%s/zport/acl_users/cookieAuthHelper/login' % url,
                                  urllib.urlencode({'__ac_name':user,
                                                    '__ac_password':pwd,
                                                    'submitted':'true',
                                                    'came_from':'%s/zport/dmd' % url}))
        response = opener.open(request)
        self.url = url
        self.rec_count = 1

    def request(self, summary, device, component, severity, evclass):
        request = urllib2.Request('%s/zport/dmd/evconsole_router' % self.url,
                                  json.dumps(dict(action='EventsRouter',
                                                  method='add_event',
                                                  data=[{'summary':summary,
                                                         'device':device,
                                                         'component':component,
                                                         'severity':severity,
                                                         'evclasskey':evclass,
                                                         'evclass':evclass}],
                                                  type='rpc',
                                                  tid=self.rec_count)),
                                  {'Content-type': 'application/json; charset=utf-8'})

               response = self.opener.open(request)
        self.rec_count += 1
        return json.load(response)

if __name__ == '__main__':
    parser = OptionParser(usage="usage: %prog [options]")
    parser.add_option('-u', '--user', help="Zenoss User Name")
    parser.add_option('-p', '--password', help="Zenoss Password")
    parser.add_option('-s', '--server', help="Zenoss Zentinel Url (just to base - no /zport/dmd)")
    parser.add_option('-d', '--debug', action='store_true', help="extra debug/logging")
    parser.add_option('-e', '--eventclass', help="EventClass", default="/Status/Update")

    (options, args) = parser.parse_args()

    if not (options.server and options.user and options.password):
        parser.error('some arguments not set (try -h to see available options)')
        sys.exit(70)

    message = email.message_from_file(sys.stdin)
    conn = zeneventconn(options.server, options.user, options.password, options.debug and 1 or 0)

    # device (the 'from' domain name - easily spoofable), component, severity, eventclass ...
    component, device = email.Utils.parseaddr(message['From'] or os.environ['SENDER'])[1].split('@')
    response = conn.request('%s\n%s' % (message['Subject'], message.get_payload(decode=1) or ""),
                            device.lower(),
                            component.capitalize(),
                            SEVERITY[os.environ.get('EXT', '').lower() or 'info'],
                            options.eventclass)

    if response['result']['success']:
        sys.exit(0)

    # undelivered, no further MTA action ...
    sys.exit(70)

 

Tag Cloud
Weblog Authors

Alan Milligan

Alan Milligan

Alan Milligan

Location: Sydney, Australia
Alan Milligan
Alan is the principal technical architect of Last Bastion Network solutions in Australia. Alan's background is in application development with a number of global titans of retail and investment banking. Alan also has a history of CIO roles for a number of start ups where he delivers business value with open source solutions. Talk to Alan about how you can deliver critical infrastructure while mitigating risk and managing your existing vendor relationships.