Tighter AD/LDAP and Nagios XI integration

I’ve finally done it! I’ve written the follow up to last years presentation on designing a scalable configuration architecture! I left off talking about how automating Nagios can be something of a creative process rather than a thing that just happens. With that in mind I will be showing you today an example script that I put together a few years ago for importing AD users as both Nagios XI users and as Nagios contacts and contact groups.

You can then use the information in my previous articles to assign those new contact groups as “view groups” making it nearly effortless to provide roles based authentication! Now it’s important to note that you can’t use the provided script “as-is”, this isn’t one of my usual solution in a can type scripts.

It’s a guide, a template for building your own version that works how you want it… there are likely bugs in what I’ve provided as it’s been stripped of all business logic. It’s also not the pinnacle of good efficient code as it was written during the height of frantic 12 hour work days and has never been revisited since.

What it is though, is a really cool way of automating an administration heavy part of the product. I hope some one out there finds it as useful as I did!

#!/usr/bin/perl -w

use strict;
use warnings;
use Net::LDAP;
use DBI;
use String::Random;

# Nagios import directory location
my $dirConfigLocation = "/usr/local/nagios/etc/import/";

# AD/LDAP Bind information
my $adUser = "CN=ldapBindUser,OU=Users,DC=my,DC=domain,DC=controller,dc=net";
my $adPassword = "my_password";
my $adDomain = "my.domain.controller.net";
my $adSearchBase = "DC=my,DC=domain,DC=controller,dc=net";

# CCM / NagiosQL DB information
my $dbUser = "nagiosql_user";
my $dbPassword = "nagiosql_password";
my $dbDatabase = "nagiosql";
my $dbHost = "my.nagiosql.host";

# Nagios XI DB information
my $dbXiUser = "nagiosxi_db_user";
my $dbXiDatabase = "nagiosxi_db_password";
my $dbXiHost = "my.nagiosxi.db.host";

# AD Groups you wish to import to Nagios XI
my @adGroupMap = ( 'ADUserGroup1',
                   'ADUserGroup2',
                   'etc');

my %adGroupHashMap;
my %adUserHash;

# Bind to the LDAP server
my $adConnection = Net::LDAP->new($adDomain) or die "$@";
$adConnection->bind($adUser,password => $adPassword);

# Search for the AD groups specified for import and get the details of the users in those groups.
foreach my $adGroup (@adGroupMap) {
	print $adGroup . "n";
	my $adQueryResults = $adConnection->search( base => $adSearchBase, scope => 'sub', filter => "(sAMAccountName=$adGroup)");
	foreach my $adGroupMemberDN ($adQueryResults->entry->get_value('member')) {
		my $adUserQueryResults = $adConnection->search( base => $adSearchBase, scope => 'sub', filter => "(distinguishedName=$adGroupMemberDN)");
		my $adUserEntry = $adUserQueryResults->entry;
		my $userEntry = $adUserEntry->get_value('sAMAccountName');
		my %dbUserHash;
		$adUserHash{$userEntry}{'email'} = $adUserEntry->get_value('mail');
		$adUserHash{$userEntry}{'displayname'} = $adUserEntry->get_value('displayName');
		$adGroupHashMap{$adGroup}{$userEntry} = $userEntry;
	}
}

# Connect to the MySQL database
my $dbHandler = DBI->connect("dbi:mysql:database=$dbDatabase;host=$dbHost;port=3306", $dbUser, $dbPassword);
$dbHandler->{AutoCommit} = 0;

# Get the Nagios contact groups and the linked contacts and store them.
my $dbQuery = "SELECT contact_name,contactgroup_name FROM tbl_contact INNER JOIN tbl_lnkContactToContactgroup ON tbl_contact.id = tbl_lnkContactToContactgroup.idMaster INNER JOIN tbl_contactgroup ON tbl_contactgroup.id = tbl_lnkContactToContactgroup.idSlave";
my $dbQueryResults = $dbHandler->prepare($dbQuery);
$dbQueryResults->execute();

my %dbResultsHash;
while (my $dbRow = $dbQueryResults->fetchrow_hashref() ) {
	my $contactgroup_name = $$dbRow{'contactgroup_name'};
	my $contact_name = $$dbRow{'contact_name'};
	$dbResultsHash{$contactgroup_name}{$contact_name} = $contact_name;
}

# Get all of the Nagios contacts and store them.
$dbQuery = "SELECT contact_name,id FROM tbl_contact";
$dbQueryResults = $dbHandler->prepare($dbQuery);
$dbQueryResults->execute();

my %dbUserHash;
while (my $dbRow = $dbQueryResults->fetchrow_hashref()) {
	my $contact_name = $$dbRow{'contact_name'};
	$dbUserHash{$contact_name}{'id'} = $$dbRow{'id'};
}

# Get all of the Nagios contact groups and store them.
$dbQuery = "SELECT contactgroup_name,id FROM tbl_contactgroup";
$dbQueryResults = $dbHandler->prepare($dbQuery);
$dbQueryResults->execute();

my %dbGroupHash;
while (my $dbRow = $dbQueryResults->fetchrow_hashref()) {
	my $contactgroup_name = $$dbRow{'contactgroup_name'};
	$dbGroupHash{$contactgroup_name}{'id'} = $$dbRow{'id'};
}

# Connect to the Nagios XI Database
my $dbXiHandler = DBI->connect("dbi:Pg:database=$dbXiDatabase;host=$dbXiHost;port=5432", $dbXiUser);
$dbXiHandler->{AutoCommit} = 0;

# Get all of the Nagios XI users.
my $dbXiQuery = "SELECT username,user_id FROM xi_users";
my $dbXiQueryResults = $dbXiHandler->prepare($dbXiQuery);
$dbXiQueryResults->execute();
my %dbXiUserHash;

while (my $dbXiRow = $dbXiQueryResults->fetchrow_hashref()) {
	my $xiuser = $$dbXiRow{'username'};
	$dbXiUserHash{$xiuser}{'user_id'} = $$dbXiRow{'user_id'};
}

# Iterate through all of the AD Groups and users and work out what users and groups we need to create.
my %linkUserToGroup;
foreach my $adGroup (keys %adGroupHashMap) {
	foreach my $adUser (keys %{$adGroupHashMap{$adGroup}}) {
		$adGroup =~ s/[^a-zA-Z0-9]//g; # Remove any non-alphanumeric characters, because we're lazy and can't be bothered sanitizing properly.
		if (exists $dbGroupHash{$adGroup}) {
			if (!exists $dbUserHash{$adUser}) {
				# Create user and add to group
				print "Group: " . $adGroup . " exists, user " . $adUser . " does notn";
				$linkUserToGroup{$adUser} = {$adGroup => $adGroup, 'userExists' => "false"};
			} elsif (!exists $dbResultsHash{$adGroup}{$adUser}) {
				# Add user to group
				print "Group: " . $adGroup . " and user " . $adUser . " exist but not syncedn";
				$linkUserToGroup{$adUser} = {$adGroup => $adGroup, 'userExists' => "true"};
			}
		} else {
			# Read the contact group template file and create the new group config.
			my @newFileContents;
			open TEMPFILE, "templates/cg-template.cfg";
			while (<TEMPFILE>) {
				$_ =~ s/CGNAME/$adGroup/;
				$_ =~ s/CGALIAS/$adGroup/;
				push @newFileContents, $_;
			}
			close TEMPFILE;

			open NEWCONFIG, ">" . $dirConfigLocation . $adGroup . ".cfg" or die $!;
			print NEWCONFIG @newFileContents;
			close NEWCONFIG;

			if (!exists $dbUserHash{$adUser}) {
				# Create user and group
				print "Group: " . $adGroup . " and user " . $adUser . " do not existn";
				$linkUserToGroup{$adUser} = {$adGroup => $adGroup, 'userExists' => "false"};
			} else {
				# Create group and add user
				print "Group: " . $adGroup . " does not exist, user " . $adUser . " does existn";
				$linkUserToGroup{$adUser} = {$adGroup => $adGroup, 'userExists' => "true"};
			}
		}

		if (!exists $dbXiUserHash{$adUser}) {
			# Insert new user to XI DB
			my $stringRandom = new String::Random;
			$stringRandom->{'A'} = [ 'A'..'Z', 'a'..'z', '0'..'9' ];
			# AD auth is the primary source for authentication so we don't really care what the default backup is in Nagios.
			my $newPassword = $stringRandom->randpattern("AAAAAAAAAAAA");
			my $backendTicket = $stringRandom->randpattern("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
			my $email = $adUserHash{$adUser}{'email'};
			$email =~ s/'//; # More lazy sanitization to get around your o'malleys
			my $displayName = $adUserHash{$adUser}{'displayname'};
			$displayName =~ s/'//; # Even more lazy sanitization to get around your o'reillys

			# Insert the new users into the Nagios XI Database
			$dbXiQuery = "INSERT INTO xi_users (username,email,name,password,backend_ticket) VALUES ('" . $adUser . "','" . $email . "','" . $displayName . "','" . $newPassword . "','" . $backendTicket . "')";
			my $dbXiPrepare = $dbXiHandler->prepare($dbXiQuery);
			$dbXiPrepare->execute();
			$dbXiHandler->commit();

			# Get the Nagios XI uid of the newly created user so we can create the required XI meta data for the new user.
			$dbXiQuery = "SELECT user_id FROM xi_users WHERE username='" . $adUser . "'";
			$dbXiQueryResults = $dbXiHandler->prepare($dbXiQuery);
			$dbXiQueryResults->execute();
			my $newUserID;
			foreach (my $dbXiRow = $dbXiQueryResults->fetchrow_hashref()) {
				$newUserID = $$dbXiRow{'user_id'};
			}

			my @dbXiUserMeta;
			my $notificationTimes = 'a:7:{i:0;a:2:{s:5:"start";s:5:"00:00";s:3:"end";s:5:"24:00";}i:1;a:2:{s:5:"start";s:5:"00:00";s:3:"end";s:5:"24:00";}i:2;a:2:{s:5:"start";s:5:"00:00";s:3:"end";s:5:"24:00";}i:3;a:2:{s:5:"start";s:5:"00:00";s:3:"end";s:5:"24:00";}i:4;a:2:{s:5:"start";s:5:"00:00";s:3:"end";s:5:"24:00";}i:5;a:2:{s:5:"start";s:5:"00:00";s:3:"end";s:5:"24:00";}i:6;a:2:{s:5:"start";s:5:"00:00";s:3:"end";s:5:"24:00";}}';
			$dbXiUserMeta[0] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','userlevel','1','1')";
			$dbXiUserMeta[1] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','enable_notifications','1','0')";
			$dbXiUserMeta[2] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','notify_by_email','1','0')";
			$dbXiUserMeta[3] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','notify_host_down','1','0')";
			$dbXiUserMeta[4] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','notify_host_unreachable','1','0')";
			$dbXiUserMeta[5] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','notify_host_recovery','1','0')";
			$dbXiUserMeta[6] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','notify_host_flapping','1','0')";
			$dbXiUserMeta[7] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','notify_host_downtime','1','0')";
			$dbXiUserMeta[8] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','notify_service_warning','1','0')";
			$dbXiUserMeta[9] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','notify_service_unknown','1','0')";
			$dbXiUserMeta[10] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','notify_service_critical','1','0')";
			$dbXiUserMeta[11] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','notify_service_recovery','1','0')";
			$dbXiUserMeta[12] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','notify_service_flapping','1','0')";
			$dbXiUserMeta[13] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','notify_service_downtime','1','0')";
			$dbXiUserMeta[14] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','notification_times','" . $notificationTimes . "','0')";
			$dbXiUserMeta[15] = "INSERT INTO xi_usermeta (user_id,keyname,keyvalue,autoload) VALUES ('" . $newUserID . "','advanced_user','1','0')";

			# Insert the meta data into the Nagios XI database.
			foreach my $metaEntry (@dbXiUserMeta) {
				$dbXiPrepare = $dbXiHandler->prepare($metaEntry);
				$dbXiPrepare->execute();
				$dbXiHandler->commit;
			}
			print "XI User: " . $adUser . " added\n";
		}
	}
}

# Create the newly discovered users
foreach my $user (keys %linkUserToGroup) {
	if ($linkUserToGroup{$user}{'userExists'} eq "false") {
		my $groupString;
		foreach my $group (keys %{$linkUserToGroup{$user}}) {
			# Create the group string based on the number of synced groups that the new user is a member of.
			if (!defined $groupString && $group ne "userExists") {
				$groupString = $group;
			} elsif ($group ne "userExists") {
				$groupString = $groupString . "," . $group;
			}
		}

		# Read the contact template and generate the new user config file.
		my @newFileContents;
		open TEMPFILE, "templates/cu-template.cfg";
		while (<TEMPFILE>) {
			$_ =~ s/CONTACTNAME/$user/;
			$_ =~ s/CONTACTGROUP/$groupString/;
			push @newFileContents, $_;
		}
		close TEMPFILE;

		open NEWCONFIG, ">" . $dirConfigLocation . $user . ".cfg" or die $!;
		print NEWCONFIG @newFileContents;
		close NEWCONFIG;
	}
}

# Change to the Nagios XI Scripts directory and run the reconfigure script to import the newly generated configuration files into Nagios and CCM/NagiosQL
chdir "/usr/local/nagiosxi/scripts/";
my $importReturn = `/usr/local/nagiosxi/scripts/reconfigure_nagios.sh`;

# If both the user and group already existed in Nagios but was added to it after creation we need to link the two.
foreach my $user (keys %linkUserToGroup) {
	if ($linkUserToGroup{$user}{'userExists'} eq "true") {
		foreach my $group (keys %{$linkUserToGroup{$user}}) {
			if ($group ne "userExists") {
				$dbQuery = "INSERT INTO tbl_lnkContactToContactgroup VALUES ($dbUserHash{$user}{'id'},$dbGroupHash{$group}{'id'})";
				my $insert = $dbHandler->prepare($dbQuery);
				$insert->execute();
				$dbHandler->commit;
			}
		}
	}
}

# Done!
$dbHandler->disconnect();
$dbXiHandler->disconnect();

You will notice in the script it refers to reading templates a couple of times… this is basically a template nagios configuration that we use for generating the new users and groups. The templates would look something like this:

define contactgroup {
  contactgroup_name CGNAME
  alias CGALIAS
}
define contact {
  contact_name CONTACTNAME
  contactgroups CONTACTGROUP
  use my-contact-template
}

There we have it! A little bit of pain for a lot less work. Next update we’ll finally see the first proper release of scombag the SCOM -> Nagios integrator!

comments powered by Disqus