Monitoring Notifications via Telegram
(updated ) Admin Checkmk Monitoring Telegram
After a self-inflicted downtime of my server that also happens to handle my email I decided that it’s time to have monitoring notifications delivered out-of-band instead of foolishly trying to send them via the most important resource I monitor.
Since I recently started using Telegram I decided to give it a try for monitoring notifications.
Please note that normal Telegram message contents are unencrypted so they can possibly be read on the Telegram server side!
Telegram Bot
The first step is to create a bot that can send notifications by following the
Telegram Bot instructions.
By using a Telegram bot you don’t have to use a real Telegram client or reuse
your Telegram account. Instead all that has to be done is triggering a REST
interface via HTTPS including an authorization token that you receive when
creating said bot (all following snippets will use ${TOKEN}
as a placeholder
for the actual token).
To do all the initial HTTP requests for setup and testing I’ll be using curl. In addition to make the answers coming from the Telegram Bot API a bit more readable for this blog post they will be piped through jq. The latter is of course totally optional.
Where to send?
To send messages to a Telegram contact, the contact first needs to start a chat
with the bot. Clicking on the bot link after creation should be enough, it
will automatically send a message of /start
to the bot.
Afterwards the bot can be queried for updates which will, among other information, provide us with a chat id which is later used to send messages to. To fetch the updates a request to the getUpdates method has to be made.
$ curl --silent "https://api.telegram.org/bot${TOKEN}/getUpdates" | jq
{
"ok": true,
"result": [
{
"update_id": 909090909,
"message": {
"message_id": 39,
"from": {
"id": 424242424,
"first_name": "User"
},
"chat": {
"id": 424242424,
"first_name": "User",
"type": "private"
},
"date": 1452179894,
"text": "test"
}
}
]
}
In this example the chat id to look out for is 424242424
.
How to send?
Sending messages to the chat id fetched before works very similar to fetching updates. This time a request to the sendMessage method has to be made. Additionally this request needs to contain the information where to send the message to as well as the actual message text. These should be passed in the request as form data (very similar to the request a browser makes when submitting a standard HTML form). The following call to curl does exactly that.
$ curl --silent \
--data-urlencode "chat_id=424242424" \
--data-urlencode "text=Message" \
"https://api.telegram.org/bot${TOKEN}/sendMessage" | jq
{
"ok": true,
"result": {
"message_id": 41,
"from": {
"id": 101010101,
"first_name": "Name of created bot",
"username": "name_of_created_bot"
},
"chat": {
"id": 424242424,
"first_name": "User",
"type": "private"
},
"date": 1452181242
"text": "Message"
}
}
And this is actually all there is to sending messages via Telegram, at least as far as commandline access goes.
Telegram notifications in Icinga 2
I’ve been experimenting with monitoring using Icinga 2 for a few days and therefore wanted to try out Telegram notifications for that system as well.
Since the example configuration already contains a flexible setup for sending emails I decided to copy that approach and adapt it to Telegram.
There are several parts to receive Icinga 2 notifications via Telegram:
- Shell scripts to send messages for host/service notifications
- NotificationCommand entries that call the shell scripts
- Notification templates for global properties applied to the above commands
- Apply rules to instantiate Notification objects for users or groups the have a Telegram Chat ID set
Icinga 2 shell scripts
The shell scripts are basically a modified version of
email-host-notification.sh
and email-service-notification.sh
that ship with
Icinga 2. Instead of calling sendmail
they rather call curl
and instead of
the contact email address they expect the Telegram bot token and the chat id as
environment variables.
Here is telegram-host-notification.sh for sending host notifications.
#!/bin/sh
template=$(cat <<TEMPLATE
*$NOTIFICATIONTYPE - $HOSTDISPLAYNAME is $HOSTSTATE*
Notification Type: $NOTIFICATIONTYPE
Host: $HOSTALIAS
Address: $HOSTADDRESS
State: $HOSTSTATE
Date/Time: $LONGDATETIME
Additional Info:
\`$HOSTOUTPUT\`
Comment: [$NOTIFICATIONAUTHORNAME] $NOTIFICATIONCOMMENT
TEMPLATE
)
/usr/bin/curl --silent --output /dev/null \
--data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
--data-urlencode "text=${template}" \
--data-urlencode "parse_mode=Markdown" \
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage"
And telegram-service-notification.sh is of course for sending notifications about service problems.
#!/bin/sh
template=$(cat <<TEMPLATE
*$NOTIFICATIONTYPE - $HOSTDISPLAYNAME - $SERVICEDISPLAYNAME is $SERVICESTATE*
Notification Type: $NOTIFICATIONTYPE
Service: $SERVICEDESC
Host: $HOSTALIAS
Address: $HOSTADDRESS
State: $SERVICESTATE
Date/Time: $LONGDATETIME
Additional Info:
\`$SERVICEOUTPUT\`
Comment: [$NOTIFICATIONAUTHORNAME] $NOTIFICATIONCOMMENT
TEMPLATE
)
/usr/bin/curl --silent --output /dev/null \
--data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
--data-urlencode "text=${template}" \
--data-urlencode "parse_mode=Markdown" \
"https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage"
Icinga 2 Configuration
Since the Telegram bot token is a secret that should not be duplicated or
be openly visible, let’s declare a constant which can later be forwarded
as an environment variable to the notification shell scripts (replace ${TOKEN}
with
the one received on bot registration)
const TelegramBotToken = "${TOKEN}"
Next up is the pair of NotificationCommand
objects for calling the shell
scripts and preparing a suitable environment.
object NotificationCommand "telegram-host-notification" {
import "plugin-notification-command"
command = [ SysconfDir + "/icinga2/scripts/telegram-host-notification.sh" ]
env = {
NOTIFICATIONTYPE = "$notification.type$"
HOSTALIAS = "$host.display_name$"
HOSTADDRESS = "$address$"
HOSTSTATE = "$host.state$"
LONGDATETIME = "$icinga.long_date_time$"
HOSTOUTPUT = "$host.output$"
NOTIFICATIONAUTHORNAME = "$notification.author$"
NOTIFICATIONCOMMENT = "$notification.comment$"
HOSTDISPLAYNAME = "$host.display_name$"
TELEGRAM_BOT_TOKEN = TelegramBotToken
TELEGRAM_CHAT_ID = "$user.vars.telegram_chat_id$"
}
}
object NotificationCommand "telegram-service-notification" {
import "plugin-notification-command"
command = [ SysconfDir + "/icinga2/scripts/telegram-service-notification.sh" ]
env = {
NOTIFICATIONTYPE = "$notification.type$"
SERVICEDESC = "$service.name$"
HOSTALIAS = "$host.display_name$"
HOSTADDRESS = "$address$"
SERVICESTATE = "$service.state$"
LONGDATETIME = "$icinga.long_date_time$"
SERVICEOUTPUT = "$service.output$"
NOTIFICATIONAUTHORNAME = "$notification.author$"
NOTIFICATIONCOMMENT = "$notification.comment$"
HOSTDISPLAYNAME = "$host.display_name$"
SERVICEDISPLAYNAME = "$service.display_name$"
TELEGRAM_BOT_TOKEN = TelegramBotToken
TELEGRAM_CHAT_ID = "$user.vars.telegram_chat_id$"
}
}
As you can see here, the TelegramBotToken
constant is passed as the
TELEGRAM_BOT_TOKEN
environment variable. That way the token is not openly
visible in the process list while sending a notification.
The Telegram chat id in turn is taken from the user that is being notified, more on that later.
Next is to link these NotificationCommand
objects to actual Notification
objects which are in turn attached to certain hosts.
template Notification "telegram-host-notification" {
command = "telegram-host-notification"
period = "24x7"
}
template Notification "telegram-service-notification" {
command = "telegram-service-notification"
period = "24x7"
}
apply Notification "telegram-notification" to Host {
import "telegram-host-notification"
user_groups = host.vars.notification.telegram.groups
users = host.vars.notification.telegram.users
assign where host.vars.notification.telegram
}
apply Notification "telegram-notification" to Service {
import "telegram-service-notification"
user_groups = host.vars.notification.telegram.groups
users = host.vars.notification.telegram.users
assign where host.vars.notification.telegram
}
Again this is an almost 100% copy of the logic used for email notifications, only the object names and variables have been renamed.
Finally the contacts and hosts that should get notified via Telegram need
to be defined. For the hosts I opted to do this in the generic-host
template
to make it apply to all of my hosts to keep the config DRY:
template Host "generic-host" {
[...]
vars.notification["telegram"] = {
users = [ "icingaadmin" ]
}
}
And for the individual users it’s just a matter of setting another property
object User "icingaadmin" {
[...]
vars.telegram_chat_id = "101010101"
}
Seeing it in action
After reloading the Icinga 2 configuration and manually triggering one of the services from Icinga Web 2, the Telegram client greeted me with a new message coming from the bot.
Telegram Notifications in Checkmk
Update 2021-01-06: Checkmk Telegram Notifications is now an actual project available as Checkmk package.
After having tried Icinga 2 I decided to stick to my previous setup of Checkmk which I have been using for the last few years. Icinga 2 has a very powerful and flexible configuration system that is indeed a big improvement over the Nagios configuration but having to write all these rules manually is not really an option for me since for the few hosts and services I monitor I’d have to write all of it manually.
So here we go again, another setup for sending notifications using Telegram, this time for Checkmk. Most of it is configured via the great Multisite and WATO web frontend that comes with Checkmk so less typing but a bit more clicking.
For Checkmk there are a few steps less to end up with Telegram notifications. Also, apart from the first step everything can/should be done in the WATO web frontend, no need to edit configuration files.
- Put a notification script into the Checkmk notifications directory
- Add a custom attribute for users to set the Telegram chat id
- Set attribute for users that should receive notifications
- Add a notification rule
Checkmk Notification Script
The notification script is very similar to those used in a standard Nagios or Icinga installation. The environment passed to it may be a bit different but in the end they are very much alike. The basics are even documented.
Since Checkmk is mostly written in Python I opted to do the HTTP requests
using the urllib2 Python
module, there is no need for curl
to send notifications in this case. The
script is based on the asciimail
notification script that ships with
Checkmk, the message formatting and text template are very close to what I
wanted to have.
The telegram notification script can be put into
PREFIX/share/check_mk/notifications/
(with PREFIX being wherever Checkmk got
installed to) and will automatically be available as a notification method in
the WATO Notification configuration.
NOTE: The Telegram bot token currently needs to be set at the top of the file, the above linked version only contains a placeholder. Once I have figured out a good place where to configure the token I will probably create a proper Checkmk Package out of it.
Checkmk Custom Attribute
Next up is adding a custom attribute in WATO so the Telegram chat id can be set
for every user that should get notified. I chose TELEGRAM_CHAT_ID
for the
name since that fits in very well with the other environment variables passed
to the notification script.
The value of this field will then end up as NOTIFY_CONTACT_TELEGRAM_CHAT_ID
in the environment when checking the “Add as Custom Macro” setting and when the
Nagios/Icinga core exports custom variables to the environment (this may be
disabled by default on an OMD setup!).
Also make sure to check “Editable by Users”, at least my installation did not show any text input at all unless that was checked (maybe a bug?).
The complete User Attribute configuration is visible in the following screenshot.
Checkmk Notification Rule
To get notified one has to add a rule that triggers the notification script. The actual circumstances when to trigger and whom to actually send a notification is of course different for everybody, since my installation only has one user to notify a rule that triggers for everything and everybody was just fine for me. I simply copied the default email rule and changed the “Notification Method” to “Telegram” and disabled the email rule temporarily.
Seeing it in action (again)
After applying the configuration changes in WATO and manually triggering as service notification from the web frontend, the Telegram client again rang, due to the slightly different text formatting the result of course looks a bit different (plus I dropped the “Perfdata” line later on, some checks return lots of performance data, making the message unnecessarily long).