#!/usr/bin/perl -w # # mkADPrincipals.pl # Create host service principals for a Unix hosts # in an Active Directory domain. # # Written by: # Jason Testart - jatestart@uwaterloo.ca # August 2006 # # Command-line options explained: # # -h hostname # Fully qualified hostname of an AD domain controller. # The DC must have LDAPS enabled. TLS_CACERT should # be set in your .ldaprc. # -D BindDN # The distinguished name of a user in the AD with enough # privilege to create new users (likely a Domain Admin) # -b BaseDN # The location in AD where unix principals are to be created. # -f filename # A file containing the list of hosts to add. The hosts listed need to be # FQDN. This script will skip hosts that already exist in the BaseDN specified. # -R realm # The name of the AD domain/realm. # -s service # The name of the service for the service principal(s) that you are creating. # This value is typically either 'host', 'nfs', 'ldap', or 'imap'. # If not specified, we'll assume 'host'. # use Getopt::Std; use Net::LDAPS; use Net::LDAP::Util qw(ldap_error_text); use Term::ReadKey; use String::Random; use Unicode::Map8; use Unicode::String qw(utf16); my $serviceName = "host"; my @fqdnList = (); my @hostList = (); my $userAccountControl = "2097664"; my $charmap = Unicode::Map8->new('latin1') or die; getopts('h:D:b:f:R:s:'); if ( !$opt_h || !$opt_D || !$opt_b || !$opt_f || !$opt_R ) { die "Usage: $0 -h hostname -D BindDN -b BaseDN -f filename -R realm [-s service]"; } if ( $opt_s ) { $serviceName = $opt_s; } my $inFileName = $opt_f; my $realm = $opt_R; my $baseDN = $opt_b; my $URI = "ldaps://" . $opt_h; my $bindDN = $opt_D; # Open input file and read open(INFILE, $inFileName) || die("Unable to open file ". $inFileName ."\n"); my $i = 0; while () { my $junk = ""; chomp; $fqdnList[$i] = $_; ($hostList[$i], $junk) = split(/\./); $i++; } close(INFILE) || die("Unable to close file ". $inFileName ."\n"); # Ask for a password my $ldapPassword = ""; print "Authenticating to domain controller " . $URI . " with DN: ". $bindDN ."\n"; print "Password: "; ReadMode('noecho'); chomp($ldapPassword = ReadLine(0)); ReadMode('restore'); print "\n\n"; # Bind to the domain controller my $ldap = Net::LDAPS->new($URI, verify => 'require', capath => '/etc/ssl/certs/', # debug => 9, ) or die "$@"; my $mesg = $ldap->bind( $bindDN, version => 3, password => $ldapPassword, ) or die "$@"; die ldap_error_text($mesg->code) if $mesg->code; # Go through the list of hosts for ($x=0; $x <= $#hostList; $x++) { my $servicePrincipal = $serviceName ."/". $fqdnList[$x]; my $userPrincipal = $servicePrincipal ."@". $realm; my $commonName = $hostList[$x]."-".$serviceName; # check if the entry for this host already exists my $ldapQuery = $ldap->search( base => $baseDN, attrs => ["cn"], filter => "cn=".$commonName ) or die "$@"; $ldapQuery->code && die ldap_error_text($ldapQuery->code); if ( $ldapQuery->count() > 0 ) { print "Entry ".$commonName." already exists in AD.\n"; } else { # create the entry for this host in AD my $dn = "cn=".$commonName.",".$baseDN; my $pass = new String::Random; my $password = $pass->randpattern("sssssssssssss"); my $unicodePassword = $charmap->tou('"'.$password.'"')->byteswap()->utf16(); print "Creating entry ".$commonName.". \n"; $mesg = $ldap->add( $dn, attrs => [ cn => $commonName, objectClass => "user", sAMAccountName => $commonName, displayName => $commonName, userPrincipalName => $userPrincipal, servicePrincipalName => $servicePrincipal, description => "Kerberos ".$serviceName. " principal for ".$fqdnList[$x] ] ) or die "$@"; $mesg->code && die ldap_error_text($mesg->code); # Set the password in AD. $mesg = $ldap->modify( $dn, replace => [ unicodePwd => $unicodePassword, userAccountControl => $userAccountControl ] ) or die "$@"; $mesg->code && die ldap_error_text($mesg->code); # Get the KVNO for this entry. my $ldapQuery = $ldap->search( base => $baseDN, attrs => ["msDS-KeyVersionNumber"], filter => "cn=".$commonName ) or die "$@"; $ldapQuery->code && die ldap_error_text($ldapQuery->code); map { $_->dump } $ldapQuery->all_entries; if ( $ldapQuery->count() == 0 ) { print "Added entry not found.\n"; next; } my $kvno = $ldapQuery->pop_entry()->get_value("msDS-KeyVersionNumber"); $kvno->code && die ldap_error_text($kvno->code); # print what's needed print "You need to create a keytab file for principal ". $servicePrincipal; print " with password ".$password." and KVNO ".$kvno.".\n"; } } print "\n"; # Close the connection with the domain controller $ldap->unbind();