Talking to the the Zabbix JSON API

Hey there, I recently tried to get some info out of my Zabbix instance to use in another context and therefore had a look at Zabbix’ API.
Turns out, it is quite simple to use and works with JSON messages.

Communication flow is pretty simple:

  1. Send username and password to the API
  2. Retrieve Auth-Token from API
  3. Send your actual query to the API and append the Auth-Token
  4. Retrieve queried data from API

To test communication, I wrote a simple PHP script to fiddle around with the possibilities of the API:


<?php

/* 
          _     _     _      
 ______ _| |__ | |__ (_)_  __
|_  / _` | '_ \| '_ \| \ \/ /
 / / (_| | |_) | |_) | |>  < 
/___\__,_|_.__/|_.__/|_/_/\_\  - API PoC

2012, looke

*/

$uri = "https://zabbix.foo.bar/api_jsonrpc.php";
$username = "testuser";
$password = "xyz";

function expand_arr($array) {	
	foreach ($array as $key => $value) {
		if (is_array($value)) {			
			echo "<i>".$key."</i>:<br>";
			expand_arr($value);
			echo "<br>\n";
		} else {			
			echo "<i>".$key."</i>: ".$value."<br>\n";
		}		
	}
}

function json_request($uri, $data) {
	$json_data = json_encode($data);	
	$c = curl_init();
	curl_setopt($c, CURLOPT_URL, $uri);
	curl_setopt($c, CURLOPT_CUSTOMREQUEST, "POST");                                                  
	curl_setopt($c, CURLOPT_RETURNTRANSFER, true); 
	curl_setopt($c, CURLOPT_POST, $json_data);
	curl_setopt($c, CURLOPT_POSTFIELDS, $json_data);
	curl_setopt($c, CURLOPT_HTTPHEADER, array(                                                                          
		'Content-Type: application/json',                                                                                
		'Content-Length: ' . strlen($json_data))                                                                       
	);
	curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);	
	$result = curl_exec($c);
	
	/* Uncomment to see some debug info
	echo "<b>JSON Request:</b><br>\n";
	echo $json_data."<br><br>\n";

	echo "<b>JSON Answer:</b><br>\n";
	echo $result."<br><br>\n";

	echo "<b>CURL Debug Info:</b><br>\n";
	$debug = curl_getinfo($c);
	echo expand_arr($debug)."<br><hr>\n";
	*/

	return json_decode($result, true);
}

function zabbix_auth($uri, $username, $password) {
	$data = array(
		'jsonrpc' => "2.0",
		'method' => "user.authenticate",
		'params' => array(
			'user' => $username,
			'password' => $password
		),
		'id' => "1"
	);	
	$response = json_request($uri, $data);	
	return $response['result'];
}

function zabbix_get_hostgroups($uri, $authtoken) {
	$data = array(
		'jsonrpc' => "2.0",
		'method' => "hostgroup.get",
		'params' => array(
			'output' => "extend",
			'sortfield' => "name"
		),
		'id' => "2",
		'auth' => $authtoken
	);	
	$response = json_request($uri, $data);	
	return $response['result'];
}

$authtoken = zabbix_auth($uri, $username, $password);
expand_arr(zabbix_get_hostgroups($uri, $authtoken));

?>

If everything worked, the scripts output should look something like this:


0:
groupid: 5
name: Discovered Hosts
internal: 1

1:
groupid: 2
name: Linux Servers
internal: 0

2:
groupid: 7
name: NAS
internal: 0

3:
groupid: 6
name: Routers
internal: 0

4:
groupid: 3
name: Windows Servers
internal: 0

5:
groupid: 4
name: Zabbix Servers
internal: 0

Important
Authentication method is user.authenticate and NOT user.login as mentioned in the manual.

Setting up a Zabbix user with API access

Additional info
http://www.zabbix.com/documentation/1.8/api/getting_started

Monitoring SSL Certificate Expiration with Zabbix

If you run some websites/webservices that run over HTTPS, you might be interested in getting some notice before your SSL Certificate is about to expire. If you already use Zabbix, here is a possible way to do so.

Place this script somewhere accessible for the “zabbix” agent-user on the system to monitor:


#!/bin/bash

# checkcert.sh
# 2012, Looke

# Checks whether a SSL x509 Certificate expires within a specified amount of seconds.
# Takes two arguments: 
# 1. Certificate
# 2. Time Until Expiration in Seconds

OPENSSL=/usr/bin/openssl

if [ -f "$1" ] && [ "$(file -b $1)" == "PEM certificate" ] && [ -n $2 ] && [ $2 -eq $2 2> /dev/null ]
then
        $OPENSSL x509 -noout -checkend $2 -in $1
        if [ $? -gt 0 ]
        then
                echo 1
        else
                echo 0
        fi
fi

Unfortunately there is no way to check the returncode of the command/script in Zabbix, so we have to echo our return value (0 for certificate doesn’t expire within the specified amount of seconds, 1 for certificate does expire).

Also, make sure you have allowed the execution of remote commands in zabbix_agentd.conf:


EnableRemoteCommands=1

Here is how you setup the check in Zabbix:

Zabbix Item – Checking if a certificate expires within 30 days (2592000 seconds)
Type: Zabbix agent
Key: system.run[/home/zabbix/bin/checkcert.sh /var/www/www.myvirtualhost.ch/cert/www.myvirtualhost.ch.crt 2592000]
Type of information: Numeric (unsigned)
Data Type: Decimal

Now, add a Trigger based on this Item and you’re ready to go.

More info
http://www.zabbix.com/documentation/1.8/manual/config/items#zabbix_agent

Sending Zabbix Alert SMS via USB modem

During some Zabbix sessions, I thought it would be nice to be able to alert via SMS. Zabbix, out of the box, supports the possibility to send SMS via attached GSM modems, so I gave it a try. I am currently using a Huawei USB modem:


Bus 003 Device 011: ID 12d1:1003 Huawei Technologies Co., Ltd. E220 HSDPA Modem / E230/E270/E870 HSDPA/HSUPA Modem

Unfortunately, this modem has some troubles with the AT command sequences Zabbix sends:
/var/log/zabbix-server/zabbix_server.log


   856:20120120:170920.965 Read from GSM modem [^MOK^M]
   856:20120120:170920.965 End of read_gsm():SUCCEED
   856:20120120:170920.965 Write to GSM modem [ATE0^M]
   856:20120120:170920.965 In read_gsm() [OK] [NULL] [NULL] [NULL]
   856:20120120:170921.069 Read from GSM modem [^MOK^M]
   856:20120120:170921.069 In check_modem_result()
   856:20120120:170921.069 End of check_modem_result():SUCCEED
   856:20120120:170921.069 End of read_gsm():SUCCEED
   856:20120120:170921.069 Write to GSM modem [AT^M]
   856:20120120:170921.069 In read_gsm() [OK] [NULL] [NULL] [NULL]
   856:20120120:170921.173 Read from GSM modem [^MOK^M]
   856:20120120:170921.174 In check_modem_result()
   856:20120120:170921.174 End of check_modem_result():SUCCEED
   856:20120120:170921.174 End of read_gsm():SUCCEED
   856:20120120:170921.174 Write to GSM modem [AT+CMGF=1^M]
   856:20120120:170921.174 In read_gsm() [OK] [NULL] [NULL] [NULL]
   856:20120120:170921.277 Read from GSM modem [^MOK^M]
   856:20120120:170921.277 In check_modem_result()
   856:20120120:170921.277 End of check_modem_result():SUCCEED
   856:20120120:170921.277 End of read_gsm():SUCCEED
   856:20120120:170921.277 Write to GSM modem [AT+CMGS="]
   856:20120120:170921.277 Write to GSM modem [0041791234567]
   856:20120120:170921.277 Write to GSM modem ["^M]
   856:20120120:170921.277 In read_gsm() [> ] [NULL] [NULL] [NULL]
   856:20120120:170921.385 Read from GSM modem [^M> ]
   856:20120120:170921.385 In check_modem_result()
   856:20120120:170921.385 End of check_modem_result():SUCCEED
   856:20120120:170921.385 End of read_gsm():SUCCEED
   856:20120120:170921.385 Write to GSM modem [Host xyz is unreachable: PROBLEM]
   856:20120120:170921.385 Write to GSM modem [^Z]
   856:20120120:170921.385 In read_gsm() [+CMGS: ] [NULL] [NULL] [NULL]
   856:20120120:170921.489 Read from GSM modem [^M]
   856:20120120:170921.489 In check_modem_result()
   856:20120120:170921.489 End of check_modem_result():FAIL
   856:20120120:170921.489 End of read_gsm():FAIL
   856:20120120:170921.489 Write to GSM modem [^MESC^Z]
   856:20120120:170921.489 In read_gsm() [] [NULL] [NULL] [NULL]
   856:20120120:170921.489 Error during wait for GSM modem.
   856:20120120:170921.489 Read from GSM modem []
   856:20120120:170921.489 End of read_gsm():SUCCEED
   856:20120120:170921.494 End of send_sms():FAIL
   856:20120120:170921.494 End execute_action()
   856:20120120:170921.494 Error sending alert ID [62]

After some research I figured out that it probably would be a better idea to write a wrapper-script to implement the SMS functionality. There is actually a way to fix this AT command sequence issue, but it would require recompiling some parts of Zabbix (which is not an option for me, as I use the Debian packaged Zabbix). To interface with the modem, I am finally using Gnokii:

/etc/zabbix/gnokii.conf


[global]
port = /dev/ttyUSB1
model = AT
connection = serial

Thats the script I use to send the alerts to (taken straight from zabbix.com):

/etc/zabbix/alert.d/zabbix-sms.sh


#!/bin/sh 
LOGFILE="/var/log/zabbix-server/zabbix-sms.log" 
echo "To: '$1' Text: '$3'" >> ${LOGFILE} 
PHONENR=`echo "$1" | sed s#\s##` 
/bin/echo "$3" | /usr/bin/gnokii --config /etc/zabbix/gnokii.conf --sendsms "${PHONENR}" 1>>${LOGFILE} 2>&1

Here are some screenshots on how to configure the SMS alert in the Zabbix GUI:

Links
http://www.zabbix.com/wiki/howto/config/alerts/sms
http://lab4.org/wiki/Zabbix_Medien_einrichten