Custom Check - Filter SNMP results

I’m working on a custom check using SNMP to determine certificate expiration on an F5. When I perform an SNMP walk on .1.3.6.1.4.1.3375.2.1.15.1.2.1, I get the cert name, expiration date in a string and expiration date in epoch. I would like to be able to filter out the default certificates that come with the device as part of my custom check. Below are examples of the walk, with full OIDs:

iso.3.6.1.4.1.3375.2.1.15.1.2.1.1.16.47.67.111.109.109.111.110.47.99.114.105.116.99.101.114.116 = STRING: “/Common/critcert”
iso.3.6.1.4.1.3375.2.1.15.1.2.1.1.16.47.67.111.109.109.111.110.47.103.111.111.100.99.101.114.116 = STRING: “/Common/goodcert”
iso.3.6.1.4.1.3375.2.1.15.1.2.1.1.16.47.67.111.109.109.111.110.47.119.97.114.110.99.101.114.116 = STRING: “/Common/warncert”
iso.3.6.1.4.1.3375.2.1.15.1.2.1.1.19.47.67.111.109.109.111.110.47.100.101.102.97.117.108.116.46.99.114.116 = STRING: “/Common/default.crt”
iso.3.6.1.4.1.3375.2.1.15.1.2.1.1.20.47.67.111.109.109.111.110.47.102.53.45.105.114.117.108.101.46.99.114.116 = STRING: “/Common/f5-irule.crt”
iso.3.6.1.4.1.3375.2.1.15.1.2.1.1.21.47.67.111.109.109.111.110.47.99.97.45.98.117.110.100.108.101.46.99.114.116 = STRING: “/Common/ca-bundle.crt”
iso.3.6.1.4.1.3375.2.1.15.1.2.1.1.24.47.67.111.109.109.111.110.47.102.53.45.99.97.45.98.117.110.100.108.101.46.99.114.116 = STRING: “/Common/f5-ca-bundle.crt”

I would like to be able to exclude default, f5-irule, etc and the OID strings all appear to end with 101.46.99.114.116 for these.

#inventory all certificates installed on the F5
def inventory_f5_bigip_certs(info):
for certname, fulldate, epochdate in info:
yield certname, “cert_thresholds”

#checkdata to pull matching SNMP strings
check_info[“f5_bigip_certs”] = {
“check_function” : check_f5_bigip_certs,
“inventory_function” : inventory_f5_bigip_certs,
“service_description” : “Certificate Expiration”,
“snmp_info” : ( “.1.3.6.1.4.1.3375.2.1.15.1.2.1”, [ 1,4,5 ] )

Can’t you just skip them in your inventory function?

Btw, your service description lacks a %s for the item name.

This is my first custom check, so I’m not 100% sure how to skip those in the inventory function. Thanks for the %s tip.

What you could do in your inventory functions is:

# inventory all certificates installed on the F5
def inventory_f5_bigip_certs(info):
    ignore_list = set(['/Common/default.crt', 
                       '/Common/f5-irule.crt',
                       '/Common/ca-bundle.crt',
                       '/Common/f5-ca-bundle.crt'])
    for certname, fulldate, epochdate in info:
        if certname not in ignore_list:
            yield certname, "cert_thresholds"

Or, if you have a parse function, filter them out there (in the same way). Then the inventory and check functions won’t even see them. I don’t think you can filter them out by OID.

Working like a champ! Thank you for the assist.

Adding my full check for anyone that might find it useful.

#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2017             mk@mathias-kettner.de |
# +------------------------------------------------------------------+
#
# This file is part of Check_MK.
# The official homepage is at http://mathias-kettner.de/check_mk.
#
# check_mk 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 in version 2.  check_mk is  distributed
# in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
# out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
# PARTICULAR PURPOSE. See the  GNU General Public License for more de-
# tails. You should have  received  a copy of the  GNU  General Public
# License along with GNU Make; see the file  COPYING.  If  not,  write
# to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
# Boston, MA 02110-1301 USA.

## Custom check for F5 certificate expiration
## Author: Shaun Pillé
## Contact: shaun.pille@gmail.com
## Version 0.2


#define current date in epoch time
currdate = int(time.time())
cert_thresholds = []
#define warning and critical thresholds in days
custom_warn=30
custom_crit=10
#convert custom warning thresholds to epoch time
cert_thresholds = [(custom_warn*86400),(custom_crit*86400)]

#inventory all certificates installed on the F5
def inventory_f5_bigip_certs(info):
    ignore_list = set (['/Common/default.crt','/Common/f5-irule.crt','/Common/ca- 
    bundle.crt','/Common/f5-ca-bundle.crt'])
    for certname, fulldate, epochdate in info:
        if certname not in ignore_list:
            yield certname, "cert_thresholds"

#check the expiration dates and return crit, warn, ok based on defined thresholds
def check_f5_bigip_certs(item, params, info):
    cert_warn, cert_crit = params
    state=0
    for certname, fulldate, epochdate in info:
        if certname == item:
            expires=(int(epochdate) - currdate)/86400
            if int(epochdate) - currdate < cert_crit:
                state=2
            elif int(epochdate) - currdate >= cert_crit and int(epochdate) - currdate <= cert_warn:
                state=1
            else:
                state=0

        infotext = "Valid for %d days" % expires

        if certname:
            infotext = ": ".join([infotext])

        if state > 0:
            infotext += " (warn/crit below %s/%s)" % (custom_warn, custom_crit)

        yield state, infotext, [("daysleft", expires, cert_warn, cert_crit)]

#checkdata to pull matching SNMP strings
check_info["f5_bigip_certs"] = {
    "check_function"        : check_f5_bigip_certs,
    "inventory_function"    : inventory_f5_bigip_certs,
    "service_description"   : "Certificate Expiration %s",
    "snmp_info"             : ( ".1.3.6.1.4.1.3375.2.1.15.1.2.1", [ 1,4,5 ] )
}
2 Likes

Only one small hint.
The variable definitions at the start should/must be inside the functions. If you have another plugin with also the variable “custom_warn” defined the same way then only one will win :slight_smile:
All definitions outside of functions are global in the CMK environment.

2 Likes

In addition to @andreas-doehler’s tip I suggest adding a snmp_scan_function. I’m not sure why it works without one. I can only guess that checkmk then tries to request the data defined by snmp_info from every device it encounters, which probably slows things down.

check_info["f5_bigip_certs"] = {
    "check_function"        : check_f5_bigip_certs,
    "inventory_function"    : inventory_f5_bigip_certs,
    "service_description"   : "Certificate Expiration %s",
    "snmp_info"             : ( ".1.3.6.1.4.1.3375.2.1.15.1.2.1", [ 1,4,5 ] )
    "snmp_scan_function"    : lambda oid: ".1.3.6.1.4.1.3375.2" in oid(".1.3.6.1.2.1.1.2.0") \
                                and "big-ip" in oid(".1.3.6.1.4.1.3375.2.1.4.1.0").lower(),
}

In pseudo code the snmp_scan_function translates to:

device_specific_oid = snmpget(".1.3.6.1.2.1.1.2.0")
if device_specific_oid contains ".1.3.6.1.4.1.3375.2":
    some_identifier = snmpget(".1.3.6.1.4.1.3375.2.1.4.1.0")
    if some_identifier contains "big-ip":
        return True
return False

checkmk queries all “things” enclosed in oid(…) with snmpget-requests. sysObjectID is the OID that contains the OID of device specific data. Then it checks whether the F5 specific OID (…3375…) is contained in the returned OID. If so, a second (device specific) OID is queried to check if that value contains “big-ip”. Only then we know this is an F5 device and can go ahead and query whatever is defined by “snmp_info”. Else it is not worth querying “your” table.

I made some more modifications to make it more 1.6 / 1.7 check style like. :slight_smile:
It is possible that there are 1-2 errors inside as i had no chance to test it.

With the release of 1.7 you can also remove a little bit of code. As there is then a “f5_bigip.include” file with common functions for this devices like a scan function.

1 Like

Great! Very close to the check I wrote (but didn’t publish). I often use get_relative_date_human_readable(epochdate) which is like get_age_human_readable but automatically returns something like “10 y ago” or “in 5 d”.

Btw, I’m very grateful to @ShaunNeutron for letting me know that F5s have a MIB tree for certificates :slight_smile: . I wasn’t aware of that but can make use of it. Until now I thought F5s can only send traps when a certificate expires. The docs don’t mention the MIB tree.

I’ve updated my check to work with CheckMK 2.0+. A year later, I’m still bad a Python so I welcome any feedback.

1 Like