Active Directory Dashboard
Active Directory Dashboard

The purpose of this project is to show how to use WMI and Grafana to build an Active Directory dashboard.

The Python script uses the linux wmic client you can find here

The software requirements are the following :

  • a debian / ubuntu system (you can choose another os but prefer those ones if you need help :))
  • influxdb database
  • grafana
  • python (for me I have the version 2.7.3)
  • InfluxDBClient for python
  • wmic tool

In this example, you will have to create one influxdb database called admon

The first step, is to use a user account only member of “Domain Users” to query WMI : for us, I will use the username called wmiusername

On each domain controller, you will have to add this user to the WMI security :

wmi1

wmi2.1

wmi3

wmi4

When it is done, restart the WMI service.

Configure the config file below according to your environnement

[config]
FOREST=forestname.net
NSZONE=_msdcs
DNSRECTYPE=NS
WMIC=/usr/bin/wmic
WMIUSR=wmiusername
WMIPWD=wmipassword
DBUSER=dbusername
DBPWD=dnpassword
DBNAME=admon
DBIP=10.1.2.3


[mon---1]
WMICLASS=Win32_PerfFormattedData_PerfOS_Processor
WMIINSTANCE=_total
WMIOBJECT=PercentIdleTime

[mon---2]
WMICLASS=Win32_PerfFormattedData_PerfOS_Memory
WMIINSTANCE=
WMIOBJECT=PagesOutputPersec

[mon---3]
WMICLASS=Win32_PerfFormattedData_PerfDisk_LogicalDisk
WMIINSTANCE=_total
WMIOBJECT=PercentIdleTime

[mon---4]
WMICLASS=Win32_PerfFormattedData_DirectoryServices_DirectoryServices
WMIINSTANCE=
WMIOBJECT=DSDirectoryReadsPersec

[mon---5]
WMICLASS=Win32_PerfFormattedData_DirectoryServices_DirectoryServices
WMIINSTANCE=
WMIOBJECT=DSDirectorySearchesPersec

[mon---6]
WMICLASS=Win32_PerfFormattedData_DirectoryServices_DirectoryServices
WMIINSTANCE=
WMIOBJECT=DSDirectoryWritesPersec

[mon---7]
WMICLASS=Win32_PerfFormattedData_DNS_DNS
WMIINSTANCE=
WMIOBJECT=UDPQueryReceivedPersec

[mon---8]
WMICLASS=Win32_PerfFormattedData_DNS_DNS
WMIINSTANCE=
WMIOBJECT=TCPQueryReceivedPersec

[mon---9]
WMICLASS=Win32_PerfFormattedData_DNS_DNS
WMIINSTANCE=
WMIOBJECT=RecursiveQueriesPersec

[mon---10]
WMICLASS=Win32_PerfFormattedData_PerfDisk_LogicalDisk
WMIINSTANCE=C:
WMIOBJECT=PercentFreeSpace

[mon---11]
WMICLASS=Win32_PerfFormattedData_PerfDisk_LogicalDisk
WMIINSTANCE=C:
WMIOBJECT=FreeMegabytes

[mon---12]
WMICLASS=Win32_PerfFormattedData_Lsa_SecuritySystemWideStatistics
WMIINSTANCE=
WMIOBJECT=NTLMAuthentications

[mon---13]
WMICLASS=Win32_PerfFormattedData_Lsa_SecuritySystemWideStatistics
WMIINSTANCE=
WMIOBJECT=KerberosAuthentications

[mon---14]
WMICLASS=Win32_PerfFormattedData_Lsa_SecuritySystemWideStatistics
WMIINSTANCE=
WMIOBJECT=KDCTGSRequests

[mon---15]
WMICLASS=Win32_PerfFormattedData_PerfNet_Server
WMIINSTANCE=
WMIOBJECT=ServerSessions

[mon---16]
WMICLASS=Win32_PerfFormattedData_PerfNet_Server
WMIINSTANCE=
WMIOBJECT=ErrorsLogon

[mon---17]
WMICLASS=Win32_PerfFormattedData_NTDS_NTDS
WMIINSTANCE=
WMIOBJECT=LDAPBindTime

Copy the python script to your linux box and run it

#!/usr/bin/python

from __future__ import division
import socket
import dns.resolver
import ConfigParser
import os
import subprocess
import threading
import re
import time
from influxdb import InfluxDBClient

config = ConfigParser.RawConfigParser()
config.read(os.path.dirname(os.path.realpath(__file__))+'/config.ini')
forestname = config.get('config', 'FOREST')
nszone = config.get('config', 'NSZONE')
recordtype = config.get('config', 'DNSRECTYPE')
wmic = config.get('config', 'WMIC')
wmiusr = config.get('config', 'WMIUSR')
wmipwd = config.get('config', 'WMIPWD')
dbuser = config.get('config', 'DBUSER')
dbpwd = config.get('config', 'DBPWD')
dbname = config.get('config', 'DBNAME')
dbip = config.get('config', 'DBIP')
sct = re.compile("^mon---", re.IGNORECASE)
client = InfluxDBClient(dbip, 8096, dbuser, dbpwd, dbname)

monsct = filter(sct.search, config.sections())

forestzonefqdn = nszone + '.' + forestname
dclist = []

threadLimiter = threading.BoundedSemaphore(value=200)

class myThread (threading.Thread):
	def __init__(self, threadID, name, wmicmd, wmimoninfo):
		threading.Thread.__init__(self)
		self.threadID = threadID
		self.name = name
		self.wmicmd = wmicmd
		self.wmimoninfo = wmimoninfo
	def run(self):
		threadLimiter.acquire()
		try:
				print_adrepl(self.name, self.wmicmd, self.wmimoninfo )
		finally:
				threadLimiter.release()

def print_adrepl(threadName, wmicmd, wmimoninfo):
		timeserie = threadName + wmimoninfo
		proc = subprocess.Popen(wmicmd,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
		errorlines = filter(lambda x:len(x)>0,(line.strip() for line in proc.stderr))
		if errorlines :
				t = 1
		outputlines = filter(lambda x:len(x)>0,(line.strip() for line in proc.stdout))
		if "NumConsecutiveSyncFailures_MSAD_ReplNeighbor" in wmimoninfo:
				oulen = len(outputlines)
				fsynctot = 0
				for line in outputlines[2:]:
						fsyncsgl = int(line.split(";")[1])
						fsynctot += fsyncsgl
				fsync = fsynctot / oulen

				json_body = [{
								"points": [
									[int(time.time()), float(fsync)]
								],
								"name": timeserie,
								"columns": ["time", "value"]
							}]
				client.write_points(json_body, time_precision='s')

		else:
				if len(outputlines)>2 :
						json_body = [{
										"points": [
											[int(time.time()), int(re.findall(r'\b\d+\b', outputlines[2:][0])[0])]
										],
										"name": timeserie,
										"columns": ["time", "value"]
									}]
						client.write_points(json_body, time_precision='s')

threads = []

cred = '-U'+forestname+'/'+wmiusr+'%'+wmipwd
wmidstip = "//"+socket.gethostbyname(str(forestname))
wmiselect = "select * from MSAD_NamingContext"
wminamespace = "--namespace=Root/MicrosoftActiveDirectory"
wmidelimiter = "--delimiter=;"
wmicmd = [wmic , cred , wmidstip  , wmiselect , wminamespace , wmidelimiter ]

while 1:
		i = 1
		for rdata in dns.resolver.query(forestzonefqdn, recordtype) :
				print str(rdata.target)
				dcip = socket.gethostbyname(str(rdata.target))
				dcdomain = str(rdata.target).split('.',1)[1]
				dclist.append([str(rdata.target) , dcdomain , dcip])
				wmidstip = "//" + dcip
				wmiselect = "select NumConsecutiveSyncFailures,sourceDsaCN,TimeOfLastSyncAttempt,TimeOfLastSyncSuccess from MSAD_ReplNeighbor"
				wmicmd = [wmic , cred , wmidstip  , wmiselect , wminamespace , wmidelimiter ]
				wmimoninfo = "NumConsecutiveSyncFailures_MSAD_ReplNeighbor"
				thread = myThread(i, str(rdata.target), wmicmd, wmimoninfo )
				thread.start()
				threads.append(thread)
				i += 1

				for monitor in monsct :
						if config.get(monitor, 'WMIINSTANCE') != "" :
								wmiinstance = " where name='"+config.get(monitor, 'WMIINSTANCE')+"'"
						else :
								wmiinstance = " "

						wmiselect = "select " + config.get(monitor, 'WMIOBJECT') + " from " + config.get(monitor, 'WMICLASS') + wmiinstance
						wmidelimiter = "--delimiter=;"
						wmicmd = [wmic , cred , wmidstip  , wmiselect , wmidelimiter ]
						wmimoninfo = config.get(monitor, 'WMICLASS')+"_"+config.get(monitor, 'WMIOBJECT')
						thread = myThread(i, str(rdata.target), wmicmd, wmimoninfo )
						thread.start()
						threads.append(thread)
						i += 1

						#limitation on x86 system
						while threading.active_count() > 200 :
								time.sleep(1)


		for t in threads:
				t.join()
		print "Exiting Main Thread"

You can use the following AD_Grafana_Dashboard to import in Grafana to get a dashboard like this :

addash2

You can download the archive that contains all required files admon

Do not hesitate to contact me if you have any questions

<>

My Powershell script categories

Active Directory Dashboard

2 thoughts on “Active Directory Dashboard

  • March 8, 2016 at 5:15 pm
    Permalink

    This looks really cool but how did you get this all set up on the server? As someone who is fairly new to linux, I am not sure how to get this all up and running. Thanks

    Reply
  • March 8, 2016 at 5:15 pm
    Permalink

    This looks really cool but how did you get this all set up on the server? As someone who is fairly new to linux, I am not sure how to get this all up and running. Thanks

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *