Zabbix External Check ile Disk Space Monitoring

Selamlar Arkadaşlar bu yazımda sizlere Zabbix’in External Check özelliğini kullanarak Windows 10 Parametreleri ile Disc Monitoring’i analatacacağım. Yazım sayesinde edineceğiniz bilgiler Zabbix içerisinde script çalıştırmakta olacaktır.

İlk olarak Python Scriptimizi çalıştırabilmesi için Zabbix’i hazırlayalım :

> nano /usr/lib/zabbix/externalscripts/zbxwmi.py

// Kodumuz ile Python kodumuzu içerecek olan dosyamızı oluşturuyoruz.

Aşağıdaki kodları yukarıdaki zbxwmi.py dosyasının içerisine yapıştırıyoruz.

#!/usr/bin/env python3
#
# zbxwmi : discovery and bulk checks of WMI items with Zabbix
# Requires impacket (python pkg) and optionally zabbix_sender
#
# Author:
#  Vitaly Chekryzhev (13hakta@gmail.com)

import argparse, sys, os

def main():
	parser = argparse.ArgumentParser(add_help = True, description = "Zabbix WMI connector")
	parser.add_argument('-v', '--version', action='version', version='0.1.3')
	parser.add_argument('cls', metavar='class', help='WMI class')
	parser.add_argument('target', help='target address')

	parser.add_argument('-action', default='get', choices=['get', 'bulk', 'json', 'discover', 'both'], help='Action to take (default: %(default)s)')
	parser.add_argument('-namespace', default='//./root/cimv2', help='namespace name (default: %(default)s)')

	parser.add_argument('-key', help='Key')
	parser.add_argument('-fields', help='Field list delimited by comma')
	parser.add_argument('-type', help='Field type hint delimited by comma: n - number, s - string (default)')
	parser.add_argument('-filter', default='', help='Filter')
	parser.add_argument('-item', default='', help='Selected item')

	group = parser.add_argument_group('Zabbix')

	group.add_argument('-server', metavar='address', default='127.0.0.1', help='Zabbix server (default: %(default)s)')
	group.add_argument('-sender', metavar='path', default='/usr/bin/zabbix_sender', help='Zabbix sender (default: %(default)s)')

	group = parser.add_argument_group('Authentication')

	group.add_argument('-cred', type=argparse.FileType('r'), default='/etc/zabbix/wmi.pw', help='Credential file (default: %(default)s)')

	group.add_argument('-dc-ip', metavar = "ip address", help='IP Address of the domain controller. If '
										'ommited it use the domain part (FQDN) specified in the target parameter')
	group.add_argument('-rpc-auth-level', choices=['integrity', 'privacy', 'default'], nargs='?', default='default',
										help='integrity (RPC_C_AUTHN_LEVEL_PKT_INTEGRITY) or privacy '
													'(RPC_C_AUTHN_LEVEL_PKT_PRIVACY). (default: %(default)s)')

	if len(sys.argv) == 1:
			parser.print_help()
			sys.exit(1)

	options = parser.parse_args()

	# Extract the arguments
	emulatekey = False

	if options.key:
		key = options.key
	else:
		key = 'Name'
		emulatekey = True

	fieldlist = options.fields.split(',')

	if (options.action == 'get' and options.fields and len(fieldlist) != 1):
		print("action 'get' requires only one item")
		exit(1)

	idx = 0
	hintlist = options.type.split(',')
	for f in fieldlist:
		if idx < len(hintlist):
			hints[f] = hintlist[idx]
		else:
			hints[f] = 's'

		idx += 1

	username = options.cred.readline().strip()
	password = options.cred.readline().strip()
	domain = options.cred.readline().strip()

	from impacket.dcerpc.v5.dtypes import NULL
	from impacket.dcerpc.v5.dcom import wmi
	from impacket.dcerpc.v5.dcomrt import DCOMConnection
	from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_NONE

	try:
		dcom = DCOMConnection(options.target, username, password, domain, '', '', '', oxidResolver=True,
													doKerberos=False, kdcHost=options.dc_ip)
		iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
		iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
		iWbemServices = iWbemLevel1Login.NTLMLogin(options.namespace, NULL, NULL)

		if options.rpc_auth_level == 'privacy':
			iWbemServices.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY)
		elif options.rpc_auth_level == 'integrity':
			iWbemServices.get_dce_rpc().set_auth_level(RPC_C_AUTHN_LEVEL_PKT_INTEGRITY)

		iWbemLevel1Login.RemRelease()

		# Construct the query
		query = "SELECT "

		if options.fields is None:
			options.fields = key
			query += key
		else:
			query += key + ',' + options.fields

		query += " FROM " + options.cls

		# Conditional request
		if options.filter or options.item:
			query += " WHERE "
			wheres = []

			if options.filter:
				wheres.append(options.filter)

			if options.item:
				wheres.append(key + '="' + options.item + '"')

			query += ' AND '.join(wheres)

		response = []

		try:
			iEnum = iWbemServices.ExecQuery(query)

			while True:
				try:
					pEnum = iEnum.Next(0xffffffff, 1)[0]
					record = pEnum.getProperties()

					j = {}

					for k in record:
						if type(record[k]['value']) is list:
							j[k] = []
							for item in record[k]['value']:
								j[k].append(item)
						else:
							j[k] = record[k]['value']

					response.append(j) # the response output
				except Exception as e:
					if str(e).find('S_FALSE') < 0:
						raise
					else:
						break

			iEnum.RemRelease()
		except Exception as e:
			print("An error occured: " + str(e))

		iWbemServices.RemRelease()
		dcom.disconnect()

		# What to do with the results ?
		if options.action == 'get':
			print(str(response[0][options.fields]))
		elif options.action == 'bulk':
			send2zabbix(response, key, options.target, options.sender, options.server)
		elif options.action == 'json':
			send2zabbix_json(response)
		elif options.action == 'discover':
			discovery_json(response)
		elif options.action == 'both': # Discover + bulk
			send2zabbix(response, key, options.target, options.sender, options.server)
			discovery_json(response)
		else:
			print("Invalid action.")
			exit(1)
	except Exception as e:
		print("An error occured: " + str(e))
		try:
			dcom.disconnect()
		except:
			pass

def tval(k, v):
	if hints[k] == 's':
		return ['"' + v + '"', '""'][v is None]

	if hints[k] == 'n':
		return [str(v), '0'][v is None]

def discovery_json(data):

	"""
	Display a JSON-formatted index for Zabbix LLD discovery
	"""

	output = []

	for eachItem in data:
		res = []
		for k in eachItem:
			key = '{#WMI.' + k.upper() + '}'
			res.append('"' + key + '": ' + tval(k, eachItem[k]))
		output.append('{' + ', '.join(res) + '}')

	print('{ "data": [', ', '.join(output), '] }')

def send2zabbix_json(data):

	"""
	Display a JSON-formatted index for Zabbix bulk
	"""

	output = []

	for eachItem in data:
		res = ['"' + k + '": ' + tval(k, eachItem[k]) for k in eachItem]

		output.append('{' + ', '.join(res) + '}')

	print("[" + ','.join(output) + "]")


def send2zabbix(data, key, host, sender, server):

	"""
	Bulk inserts data into Zabbix using zabbix_sender
	"""

	# Contruct the input for zabbix_sender
	output = ""
	for eachItem in data:
		val = "[" + str(eachItem.pop(key)) + "] "
		for k in eachItem:
			output += host + " " + k + val + ' ' + tval(k, eachItem[k]) + "\n"

	# Send the values to the server
	import subprocess

	f = open(os.devnull, "w")

	try:
		p = subprocess.Popen([sender, '-r', '-z', server, '-i', '-'], stdin=subprocess.PIPE, stdout=f, stderr=f)
		p.communicate(output.encode())
	except Exception as e:
		print("An error occured with zabbix_sender. " + str(e))
		exit(1)

if __name__ == '__main__':
	hints = {'Name': 's'}
	main()

> chmod 755 /usr/lib/zabbix/externalscripts/zbxwmi.py
> chown root.root /usr/lib/zabbix/externalscripts/zbxwmi.py

// Daha sonra dosyamızı çalıştırabilmesi için gerekli izinleri veriyoruz.

> apt install python3-six python3-pycryptodome python3-pyasn1 -y
> apt install python3-pip -y
> pip install impacket

// Python dosyalarımızı çalıştırsın diye Python araçlarını ve uzaktan komut çalıştırabilmesi için impacket programlarımızı yüklüyoruz.

> nano /etc/zabbix/ist_administrator_p.pw

[Username]
– [Password]
– [DomainOrWORKGROUP]

// Dosyasına giderek yukarıdaki ayarlamaları yapmanız gerekmektedir.

> chmod 640 /etc/zabbix/administrator_p.pw && chown zabbix.zabbix /etc/zabbix/administrator_p.pw

// Kodları ile administrator_p.pw dosyamızın izinlerini ayarlamış olduk.

> /usr/lib/zabbix/externalscripts/zbxwmi.py -k "Name,FreeMegabytes,PercentFreeSpace" -a json -type s,n,n -fields "Name,FreeMegabytes,PercentFreeSpace" "Win32_PerfFormattedData_PerfDisk_LogicalDisk" -cred /etc/zabbix/administrator_p.pw 192.168.1.109

// Komutumuzu çalıştırarak Windows Server 2019 cihazımızdan üzerinde bulunan Disklerin bilgisini almış olduk.

> nano /etc/zabbix/zabbix_server.conf

– Timeout=20

// Dosyamıza gidiyoruz ve Timeout alanını 20 yapıyoruz. Bu sayede cihazımızdan dataları çekerken 20 sn’lik bir gecikme süresi tanıyoruz. Burada kodunuz daha uzun süre çalışırsa ona göre bir süre belirlemeniz gerekiyor.

> systemctl restart zabbix-server zabbix-agent nginx php7.4-fpm

// Kodu ile Zabbix Server’ımızın Servislerini Restart ediyoruz.

Zabbix içerisinde Template Oluşturma adımları :

Bunun için yeni bir Template oluşturuyorum ve adını ExternalCheck – Disk olarak veriyorum. Groups kısmını Templates/Operating Systems olarak belirliyorum. Oluşturduğum Template’e giriyorum ve Items sekmesine gelerek Create Item’a tıklıyorum.

Name = C: Percent Free Space
Tpye = External Check
KEY = zbxwmi.py[“-k”,”Name,FreeMegabytes,PercentFreeSpace”,”-a”,”json”,”-type”,”s,n,n”,”-fields”,”Name,FreeMegabytes,PercentFreeSpace”,”-cred”,”{$WMI_AUTHFILE}”,”Win32_PerfFormattedData_PerfDisk_LogicalDisk”,{HOST.IP}]
Type of information = Numeric (Unsigned)

Bilgilerini girdim.

Name = Regular Expression
Parameters = “Name”: “C:”, “PercentFreeSpace”: (\d+)}
// Key’im için zbxwmi.py dosyasında kodu çalıştırdığımda ki sonuç kısmında çıkan parçayı aldım ve sonuna (\d+) ekleyerek o sayı değerini belirttim.
Value = \1
// \1 yazarak sadece o değeri dönmesini istediğimi belirttim ve Add tuşuyla item’mimizi ekledik.

Host Ekleme İşlemi :

Configuration > Hosts ardından Create Host tuşuna basıyoruz.

Host name ve Groups belirliyoruz. Agent olarak Server IP adresimizi yazıyoruz.

Templates alanında oluşturduğumuz ExternalCheck – Disk’i seçiyoruz.

Macros kısmında ise Macro olarak {$WMI_AUTHFILE} yazıyoruz ve Value olarak ise /etc/zabbix/administrator_p.pw yazarak Password kontrolü yapmasını sağlıyoruz. Add tuşuyla gerekli Host ekleme işlemini tamamlamış oluyoruz.

Monitoring > Latest Data altında Yüzdelik olarak C alanının ne kadar Boş olduğunu sadece rakam olarak döndüren kontrolümüzün çalıştığını görüyoruz.

Bu yazımda bu kadardı arkadaşlar. Bir sonraki yazılarımda görüşmek üzere. Yorum ve sorularınızı bekliyorum. Kolay gelsin.

2 yorum

  1. Şahini olan çocuk Yanıtla

    Mükemmel bir yazı olmuş ! Şu zor günlerde , zor konuları bu site sayesinde öğrenebiliyoruz. ALLAH yazandan RAZI olsun. (Amin)

    • Semih YazarYanıtla

      Yorumların için teşekkür ederim. Yardımcı olabildiysem ne mutlu. İyi çalışmalar. 🙂

Bir Cevap Yazın