From 8bfba28196485236125ad363ed3b96c461025d94 Mon Sep 17 00:00:00 2001 From: Bill MacAllister Date: Mon, 2 May 2016 20:44:19 +0000 Subject: Update AD keytab policies * Make sure userPrincipalName is created for all keytabs and use it to search for entries in AD. * Allow the creation of any service principal. This requires making sure that the cn used to create AD entries for service accounts not be any longer than 20 characters. --- contrib/ad-keytab | 282 ++++++++++++++++++++++----------------- perl/lib/Wallet/Config.pm | 27 ++++ perl/lib/Wallet/Kadmin/AD.pm | 304 +++++++++++++++++++++++++------------------ 3 files changed, 371 insertions(+), 242 deletions(-) diff --git a/contrib/ad-keytab b/contrib/ad-keytab index 2af9f85..977c07b 100755 --- a/contrib/ad-keytab +++ b/contrib/ad-keytab @@ -27,16 +27,20 @@ my $opt_debug; my $opt_dump; my $opt_help; my $opt_manual; -my $opt_realm; +my $opt_prefix; my $opt_user_rdn; +# LDAP conneciton +my $LDAP; + # Configuration variables +our $AD_BASE_DN; +our $AD_COMPUTER_RDN; our $AD_DEBUG; our $AD_SERVER; -our $AD_COMPUTER_RDN; +our $AD_SERVICE_PREFIX; our $AD_USER_RDN; our $KEYTAB_REALM; -our $AD_BASE_DN; ############################################################################## # Subroutines @@ -100,49 +104,147 @@ sub list_userAccountControl { # GSS-API bind to the active directory server sub ldap_connect { - my $ldap; if ($AD_DEBUG) { dbg('binding to ' . $AD_SERVER); } + + if ($LDAP) { + if ($AD_DEBUG) { + dbg('Already bound to ' . $AD_SERVER); + } + return $LDAP; + } + if (!$AD_SERVER) { croak("Missing ldap host name, specify ad_server=\n"); } eval { my $sasl = Authen::SASL->new(mechanism => 'GSSAPI'); - $ldap = Net::LDAP->new($AD_SERVER, onerror => 'die'); - my $mesg = eval { $ldap->bind(undef, sasl => $sasl) }; + $LDAP = Net::LDAP->new($AD_SERVER, onerror => 'die'); + my $mesg = eval { $LDAP->bind(undef, sasl => $sasl) }; }; if ($@) { my $error = $@; die "ldap bind to AD failed: $error\n"; } - return $ldap; + return $LDAP; +} + +# Take in a base and a filter and return the assoicated DN. +sub get_dn { + my ($base, $filter) = @_; + my $dn; + + if ($AD_DEBUG) { + dbg("base:$base filter:$filter scope:subtree\n"); + } + + ldap_connect(); + my @attrs = ('objectclass'); + my $result; + eval { + $result = $LDAP->search( + base => $base, + scope => 'subtree', + filter => $filter, + attrs => \@attrs + ); + }; + if ($@) { + my $error = $@; + die "LDAP search error: $error\n"; + } + if ($result->code) { + msg("INFO base:$base filter:$filter scope:subtree\n"); + die $result->error; + } + if ($AD_DEBUG) { + dbg('returned: ' . $result->count); + } + + if ($result->count == 1) { + for my $entry ($result->entries) { + $dn = $entry->dn; + } + } elsif ($result->count > 1) { + msg('ERROR: too many AD entries for this keytab'); + for my $entry ($result->entries) { + msg('INFO: dn found ' . $entry->dn . "\n"); + } + die("INFO: use show to examine the problem\n"); + } + + return $dn; } # Take a principal and split into parts. The parts are keytab type, -# keytab identifier, the base dn, an LDAP filter, and if the keytab -# type is host the host name. +# keytab identifier, the base dn, the cn, and an LDAP filter. sub kerberos_attrs { my ($principal) = @_; - my %attr = (); + my %attr; + $attr{principal} = $principal; + my $dn; my $host; my $k_type; my $k_id; - if ($principal =~ m,^(host|service)/(\S+),xms) { + if ($principal =~ m,^(.*?)/(\S+),xms) { $attr{type} = $1; $attr{id} = $2; + # Create a filter to find the objects we create + if ($attr{id} =~ s/@(.*)//xms) { + $attr{realm} = $1; + $attr{filter} = "(userPrincipalName=${principal})"; + } elsif ($KEYTAB_REALM) { + $attr{realm} = $KEYTAB_REALM; + $attr{filter} + = "(userPrincipalName=${principal}\@${KEYTAB_REALM})"; + } else { + $attr{filter} = "(userPrincipalName=${principal}\@*)"; + } if ($attr{type} eq 'host') { - $attr{base} = $AD_COMPUTER_RDN . ',' . $AD_BASE_DN; - $attr{host} = $attr{id}; - $attr{host} =~ s/[.].*//; - $attr{dn} = "cn=$attr{host},$attr{base}"; - $attr{filter} = "(samAccountName=$attr{host}\$)"; - } elsif ($attr{'type'} eq 'service') { - $attr{base} = $AD_USER_RDN . ',' . $AD_BASE_DN; - $attr{dn} = "cn=srv-$attr{id},$attr{base}"; - $attr{filter} = "(servicePrincipalName=$attr{type}/$attr{id})"; + # Host keytab attributes + $attr{base} = $AD_COMPUTER_RDN . ',' . $AD_BASE_DN; + $attr{cn} = $attr{id}; + $attr{cn} =~ s/[.].*//; + $attr{dn} = "cn=$attr{cn},$attr{base}"; + } else { + # Service keytab attributes + $attr{base} = $AD_USER_RDN . ',' . $AD_BASE_DN; + $attr{cn} = "${AD_SERVICE_PREFIX}$attr{id}"; + $attr{dn} = "cn=$attr{cn},$attr{base}"; + my $real_dn = get_dn($attr{base}, $attr{filter}); + if ($real_dn) { + if (lc($real_dn) ne lc($attr{dn})) { + $attr{dn} = $real_dn; + $attr{cn} = $real_dn; + $attr{cn} =~ s/,.*//xms; + $attr{cn} =~ s/.*?=//xms; + } + } else { + if (length($attr{cn})>20) { + my $cnt = 0; + my $this_dn; + my $this_prefix = substr($attr{cn}, 0, 18); + $attr{dn} = ''; + while ($cnt<100) { + my $this_cn = $this_prefix . sprintf('%02i', $cnt); + $this_dn = get_dn($attr{base}, "cn=$this_cn"); + if (!$this_dn) { + $attr{dn} = $this_cn . ',' . $attr{base}; + $attr{cn} = $attr{dn}; + $attr{cn} =~ s/,.*//xms; + $attr{cn} =~ s/.*?=//xms; + last; + } + $cnt++; + } + if (!$attr{dn}) { + die "ERROR: Cannot file unique dn for keytab\n"; + } + } + } } } if ($AD_DEBUG) { @@ -158,7 +260,7 @@ sub kerberos_attrs { sub ad_show { my ($principal, $kattr_ref) = @_; - my $ldap = ldap_connect(); + ldap_connect(); my %kattr = %{$kattr_ref}; my $base = $kattr{base}; my $filter = $kattr{filter}; @@ -180,7 +282,7 @@ sub ad_show { my $result; eval { - $result = $ldap->search( + $result = $LDAP->search( base => $base, scope => 'subtree', filter => $filter, @@ -223,56 +325,6 @@ sub ad_show { return; } -# Check to see if a keytab exists -sub ad_exists { - my ($principal, $kattr_ref) = @_; - - my $ldap = ldap_connect(); - my %kattr = %{$kattr_ref}; - my $base = $kattr{base}; - my $filter = $kattr{filter}; - my @attrs = ('objectClass', 'msds-KeyVersionNumber'); - if ($AD_DEBUG) { - dbg("base:$base filter:$filter scope:subtree\n"); - } - - my $result; - eval { - $result = $ldap->search( - base => $base, - scope => 'subtree', - filter => $filter, - attrs => \@attrs - ); - }; - if ($@) { - my $error = $@; - die "LDAP search error: $error\n"; - } - if ($result->code) { - msg("INFO base:$base filter:$filter scope:subtree\n"); - die $result->error; - } - if ($AD_DEBUG) { - dbg('returned: ' . $result->count); - } - if ($result->count > 1) { - msg('ERROR: too many AD entries for this keytab'); - for my $entry ($result->entries) { - msg('INFO: dn found ' . $entry->dn . "\n"); - } - die("INFO: use show to examine the problem\n"); - } - if ($result->count) { - for my $entry ($result->entries) { - return $entry->get_value('msds-KeyVersionNumber'); - } - } else { - return 0; - } - return; -} - # Run a shell command. In this case the command will always be msktutil. sub run_cmd { my @cmd = @_; @@ -314,7 +366,9 @@ sub run_cmd { # Either create or update a keytab for the principal. Return the name # of the keytab file created. sub ad_create_update { - my ($principal, $file, $action) = @_; + my ($file, $action, $kattr_ref) = @_; + my %kattr = %{$kattr_ref}; + my @cmd = ('/usr/sbin/msktutil'); push @cmd, '--' . $action; push @cmd, '--server', $AD_SERVER; @@ -322,24 +376,21 @@ sub ad_create_update { push @cmd, '--enctypes', '0x8'; push @cmd, '--enctypes', '0x10'; push @cmd, '--keytab', $file; - if ($KEYTAB_REALM) { - push @cmd, '--realm', $KEYTAB_REALM; + push @cmd, '--upn', $kattr{principal}; + if ($kattr{realm}) { + push @cmd, '--realm', $kattr{realm}; } - if ($principal =~ m,^host/(\S+),xms) { - my $fqdn = $1; - my $host = $fqdn; - $host =~ s/[.].*//xms; + if ($kattr{type} eq 'host') { push @cmd, '--base', $AD_COMPUTER_RDN; push @cmd, '--dont-expire-password'; - push @cmd, '--computer-name', $host; - push @cmd, '--upn', "host/$fqdn"; - push @cmd, '--hostname', $fqdn; - } elsif ($principal =~ m,^service/(\S+),xms) { + push @cmd, '--computer-name', $kattr{cn}; + push @cmd, '--hostname', $kattr{id}; + } else { my $service_id = $1; push @cmd, '--base', $AD_USER_RDN; push @cmd, '--use-service-account'; - push @cmd, '--service', "service/$service_id"; - push @cmd, '--account-name', "srv-${service_id}"; + push @cmd, '--service', $kattr{principal}; + push @cmd, '--account-name', $kattr{cn}; push @cmd, '--no-pac'; } run_cmd(@cmd); @@ -349,23 +400,25 @@ sub ad_create_update { # Delete a principal from Kerberos. For AD this means just delete the # object using LDAP. sub ad_delete { - my ($principal, $kattr_ref) = @_; - + my ($kattr_ref) = @_; my %kattr = %{$kattr_ref}; - if (!ad_exists($principal, $kattr_ref)) { - msg("WARN: the keytab for $principal does not appear to exist."); - msg("INFO: attempting the delete anyway.\n"); - } - my $ldap = ldap_connect(); - my $msgid = $ldap->delete($kattr{dn}); - if ($msgid->code) { - my $m; - $m .= "ERROR: Problem deleting $kattr{dn}\n"; - $m .= $msgid->error; - die $m; + my $del_dn = get_dn($kattr{base}, $kattr{filter}); + + if (!$del_dn) { + msg("WARN: the keytab for $kattr{principal} does not exist."); + return 1; + } else { + ldap_connect(); + my $msgid = $LDAP->delete($del_dn); + if ($msgid->code) { + my $m; + $m .= "ERROR: Problem deleting $kattr{dn}\n"; + $m .= $msgid->error; + die $m; + } } - return 1; + return; } ############################################################################## @@ -381,8 +434,8 @@ GetOptions( 'debug' => \$opt_debug, 'dump' => \$opt_dump, 'help' => \$opt_help, + 'prefix' => \$opt_prefix, 'manual' => \$opt_manual, - 'realm' => \$opt_realm, 'user_rdn=s' => \$opt_user_rdn ); @@ -397,7 +450,8 @@ if ($opt_help || !$ARGV[0]) { # Make sure that we have kerberos credentials and that KRB5CCNAME # points to them. if (!$ENV{'KRB5CCNAME'}) { - msg('ERROR: Kerberos credentials are required ... try kinit'); + msg('INFO: environment variable KRB5CCNAME not found.'); + msg('ERROR: Kerberos credentials are required.'); pod2usage(-verbose => 0); } @@ -426,6 +480,9 @@ if ($opt_ad_server) { if ($opt_base_dn) { $AD_BASE_DN = $opt_base_dn; } +if ($opt_prefix) { + $AD_SERVICE_PREFIX = $opt_prefix; +} if ($opt_computer_rdn) { $AD_COMPUTER_RDN = $opt_computer_rdn; } @@ -448,29 +505,22 @@ if ($ARGV[0]) { my %kattr = kerberos_attrs($id); # Validate that the keytab id makes sense for the keytab type -if ($kattr{type} eq 'service') { - if ($kattr{id} =~ /[.]/xms) { - msg('ERROR: service principal names may not contain periods'); - pod2usage(-verbose => 0); - } - if (length($kattr{id}) > 22) { - msg('ERROR: service principal name too long'); - pod2usage(-verbose => 0); - } -} elsif ($kattr{type} eq 'host') { +if ($kattr{type} eq 'host') { if ($kattr{id} !~ /[.]/xms) { msg('ERROR: FQDN is required'); pod2usage(-verbose => 0); } } else { - msg("ERROR: unknown keytab type $kattr{type}"); - pod2usage(-verbose => 0); + if ($kattr{id} =~ /[.]/xms) { + msg('ERROR: service principal names may not contain periods'); + pod2usage(-verbose => 0); + } } if ($action =~ /^(create|update)/xms) { - ad_create_update($id, $keytab, $1); + ad_create_update($keytab, $action, \%kattr); } elsif ($action =~ /^del/xms) { - ad_delete($id, \%kattr); + ad_delete(\%kattr); } elsif ($action =~ /^sh/xms) { ad_show($id, \%kattr); } else { @@ -500,7 +550,7 @@ boot strapping the kerberos credentials required to use Active Directory as a backend keytab store for wallet. The script shares the wallet configuration file. -Generally, two keytabs will need to be created to setup update. One +Generally, two keytabs will need to be created to setup wallet. One host keytab for the wallet server host and one service keytab for wallet to use when connecting to an Active Directory Domain Controller. diff --git a/perl/lib/Wallet/Config.pm b/perl/lib/Wallet/Config.pm index 2222aba..5d40978 100644 --- a/perl/lib/Wallet/Config.pm +++ b/perl/lib/Wallet/Config.pm @@ -463,6 +463,33 @@ default PATH. our $AD_MSKTUTIL = 'msktutil'; +=item AD_SERVICE_LIMIT + +Used to limit the number of iterations used in attempting to find a +unique account name for service principals. Defaults to 999. + +=cut + +our $AD_SERVICE_LIMIT = '999'; + +=item AD_SERVICE_PREFIX + +For service principals the AD_SERVICE_PREFIX will be combined with the +principal identifier to form the account name, i.e. the CN, used to +store the keytab entry in the Active Directory. Active Directory +limits these CN's to a maximum of 20 characters. If the resulting CN +is greater than 20 characters the CN will be truncated and an integer +will be appended to it. The integer will be incremented until a +unique CN is found. + +The AD_SERVICE_PREFIX is generally useful only prevent name collisions +when the service keytabs are store in branch of the DIT that also +contains other similar objects. + +=cut + +our $AD_SERVICE_PREFIX; + =item AD_SERVER The hostname of the Active Directory Domain Controller. diff --git a/perl/lib/Wallet/Kadmin/AD.pm b/perl/lib/Wallet/Kadmin/AD.pm index 0ffd7d9..83912dd 100644 --- a/perl/lib/Wallet/Kadmin/AD.pm +++ b/perl/lib/Wallet/Kadmin/AD.pm @@ -28,6 +28,8 @@ use Wallet::Kadmin; our @ISA = qw(Wallet::Kadmin); our $VERSION = '1.04'; +my $LDAP; + ############################################################################## # kadmin Interaction ############################################################################## @@ -71,17 +73,7 @@ sub ad_cmd_string { # Note that we do not permit realm information here. sub valid_principal { my ($self, $principal) = @_; - my $valid = 0; - if ($principal =~ m,^(host|service)(/[\w_.-]+)?\z,) { - my $k_type = $1; - my $k_id = $2; - if ($k_type eq 'host') { - $valid = 1 if $k_id =~ m/[.]/xms; - } elsif ($k_type eq 'service') { - $valid = 1 if length($k_id) < 19; - } - } - return $valid; + return scalar ($principal =~ m,^[\w-]+(/[\w_.-]+)?\z,); } # Connect to the Active Directory server using LDAP. The connection is @@ -90,49 +82,110 @@ sub valid_principal { sub ldap_connect { my ($self) = @_; - if (!-e $Wallet::Config::AD_CACHE) { - die 'Missing kerberos ticket cache ' . $Wallet::Config::AD_CACHE; - } - - my $ldap; - eval { - local $ENV{KRB5CCNAME} = $Wallet::Config::AD_CACHE; - my $sasl = Authen::SASL->new(mechanism => 'GSSAPI'); - $ldap = Net::LDAP->new($Wallet::Config::KEYTAB_HOST, onerror => 'die'); - my $mesg = eval { $ldap->bind(undef, sasl => $sasl) }; - }; - if ($@) { - my $error = $@; - chomp $error; - 1 while ($error =~ s/ at \S+ line \d+\.?\z//); - die "LDAP bind to AD failed: $error\n"; + if (!$LDAP) { + eval { + local $ENV{KRB5CCNAME} = $Wallet::Config::AD_CACHE; + my $sasl = Authen::SASL->new(mechanism => 'GSSAPI'); + $LDAP = Net::LDAP->new($Wallet::Config::KEYTAB_HOST, + onerror => 'die'); + my $mesg = eval { $LDAP->bind(undef, sasl => $sasl) }; + }; + if ($@) { + my $error = $@; + chomp $error; + 1 while ($error =~ s/ at \S+ line \d+\.?\z//); + die "LDAP bind to AD failed: $error\n"; + } } - - return $ldap; + return $LDAP; } # Construct a base filter for searching Active Directory. sub ldap_base_filter { my ($self, $principal) = @_; + my $base; my $filter; - if ($principal =~ m,^host/(\S+),xms) { - my $fqdn = $1; - my $host = $fqdn; - $host =~ s/[.].*//xms; - $filter = "(samAccountName=${host}\$)"; - $base = $Wallet::Config::AD_COMPUTER_RDN . ',' - . $Wallet::Config::AD_BASE_DN; - } elsif ($principal =~ m,^service/(\S+),xms) { - my $id = $1; - $filter = "(servicePrincipalName=service/${id})"; - $base - = $Wallet::Config::AD_USER_RDN . ',' . $Wallet::Config::AD_BASE_DN; + my $this_type; + my $this_id; + + if ($principal =~ m,^(.*?)/(\S+),xms) { + $this_type = $1; + $this_id = $2; + } else { + $this_id = $principal; + } + + # Create a filter to find the objects we create + if ($this_id =~ s/@(.*)//xms) { + $filter = "(userPrincipalName=${principal})"; + } elsif ($Wallet::Config::KEYTAB_REALM) { + $filter = '(userPrincipalName=' . $principal + . '@' . $Wallet::Config::KEYTAB_REALM . ')'; + } else { + $filter = "(userPrincipalName=${principal}\@*)"; } + + # Set the base distinguished name + if ($this_type && $this_type eq 'host') { + $base = $Wallet::Config::AD_COMPUTER_RDN; + } else { + $base = $Wallet::Config::AD_USER_RDN; + } + $base .= ',' . $Wallet::Config::AD_BASE_DN; + return ($base, $filter); } +# Take in a base and a filter and return the assoicated DN or return +# null if there is no matching entry. +sub ldap_get_dn { + my ($self, $base, $filter) = @_; + my $dn; + + if ($Wallet::Config::AD_DEBUG) { + $self->ad_debug('debug', "base:$base filter:$filter scope:subtree\n"); + } + + $self->ldap_connect(); + my @attrs = ('objectclass'); + my $result; + eval { + $result = $LDAP->search( + base => $base, + scope => 'subtree', + filter => $filter, + attrs => \@attrs + ); + }; + if ($@) { + my $error = $@; + die "LDAP search error: $error\n"; + } + if ($result->code) { + msg("INFO base:$base filter:$filter scope:subtree\n"); + die $result->error; + } + if ($Wallet::Config::AD_DEBUG) { + $self->ad_debug('debug', 'returned: ' . $result->count); + } + + if ($result->count == 1) { + for my $entry ($result->entries) { + $dn = $entry->dn; + } + } elsif ($result->count > 1) { + msg('ERROR: too many AD entries for this keytab'); + for my $entry ($result->entries) { + msg('INFO: dn found ' . $entry->dn . "\n"); + } + die("INFO: use show to examine the problem\n"); + } + + return $dn; +} + # TODO: Get a keytab from the keytab bucket. sub get_ad_keytab { my ($self, $principal) = @_; @@ -200,10 +253,53 @@ sub msktutil { return $out; } +# The unique identifier that Active Directory used to store keytabs +# has a maximum length of 20 characters. This routine takes a +# principal name an generates a unique ID based on the principal name. +sub get_service_id { + my ($self, $this_princ) = @_; + + my $this_id; + my ($this_base, $this_filter) = $self->ldap_base_filter($this_princ); + my $real_dn = $self->ldap_get_dn($this_base, $this_filter); + if ($real_dn) { + $this_id = $real_dn; + $this_id =~ s/,.*//xms; + $this_id =~ s/.*?=//xms; + } else { + my $this_cn = $this_princ; + $this_cn =~ s{.*?/}{}xms; + if ($Wallet::Config::AD_SERVICE_PREFIX) { + $this_cn = $Wallet::Config::AD_SERVICE_PREFIX . $this_cn; + } + my $loop_limit = $Wallet::Config::AD_SERVICE_LIMIT; + if (length($this_cn)>20) { + my $cnt = 0; + my $this_dn; + my $suffix_size = length("$loop_limit"); + my $this_prefix = substr($this_cn, 0, 20-$suffix_size); + my $this_format = "%0${suffix_size}i"; + while ($cnt<$loop_limit) { + my $this_cn = $this_prefix . sprintf($this_format, $cnt); + $this_dn = ldap_get_dn($this_base, "cn=$this_cn"); + if (!$this_dn) { + $this_id = $this_cn; + last; + } + $cnt++; + } + } else { + $this_id = $this_cn; + } + } + return $this_id; +} + # Either create or update a keytab for the principal. Return the # name of the keytab file created. sub ad_create_update { my ($self, $principal, $action) = @_; + return unless $self->valid_principal($principal); my $keytab = $Wallet::Config::KEYTAB_TMP . "/keytab.$$"; if (-e $keytab) { unlink $keytab or die "Problem deleting $keytab\n"; @@ -213,31 +309,41 @@ sub ad_create_update { push @cmd, '--enctypes', '0x1C'; push @cmd, '--keytab', $keytab; push @cmd, '--realm', $Wallet::Config::KEYTAB_REALM; - - if ($principal =~ m,^host/(\S+),xms) { - my $fqdn = $1; - my $host = $fqdn; - $host =~ s/[.].*//xms; - push @cmd, '--base', $Wallet::Config::AD_COMPUTER_RDN; - push @cmd, '--dont-expire-password'; - push @cmd, '--computer-name', $host; - push @cmd, '--upn', "host/$fqdn"; - push @cmd, '--hostname', $fqdn; - } elsif ($principal =~ m,^service/(\S+),xms) { - my $service_id = $1; - push @cmd, '--base', $Wallet::Config::AD_USER_RDN; - push @cmd, '--use-service-account'; - push @cmd, '--service', "service/$service_id"; - push @cmd, '--account-name', "srv-${service_id}"; - push @cmd, '--no-pac'; - } - my $out = $self->msktutil(\@cmd); - if ($out =~ /Error:\s+\S+\s+failed/xms) { - $self->ad_delete($principal); - my $m = "ERROR: problem creating keytab:\n" . $out; - $m .= 'INFO: the keytab used to by wallet probably has' - . " insufficient access to AD\n"; - die $m; + push @cmd, '--upn', $principal; + + my $this_type; + my $this_id; + if ($principal =~ m,^(.*?)/(\S+),xms) { + $this_type = $1; + $this_id = $2; + if ($this_type eq 'host') { + my $host = $this_id; + $host =~ s/[.].*//xms; + push @cmd, '--base', $Wallet::Config::AD_COMPUTER_RDN; + push @cmd, '--dont-expire-password'; + push @cmd, '--computer-name', $host; + push @cmd, '--hostname', $this_id; + } else { + my $service_id = $self->get_service_id($this_id); + push @cmd, '--base', $Wallet::Config::AD_USER_RDN; + push @cmd, '--use-service-account'; + push @cmd, '--service', $principal; + push @cmd, '--account-name', $service_id; + push @cmd, '--no-pac'; + } + my $out = $self->msktutil(\@cmd); + if ($out =~ /Error:\s+\S+\s+failed/xms + || !$self->exists($principal)) + { + $self->ad_delete($principal); + my $m = "ERROR: problem creating keytab for $principal"; + $self->ad_debug('error', $m); + $self->ad_debug('error', + 'Problem command:' . ad_cmd_string(\@cmd)); + die "$m\n"; + } + } else { + die "ERROR: Invalid principal format ($principal)\n"; } return $keytab; @@ -260,45 +366,9 @@ sub exists { my ($self, $principal) = @_; return unless $self->valid_principal($principal); - my $ldap = $self->ldap_connect(); my ($base, $filter) = $self->ldap_base_filter($principal); - my @attrs = ('objectClass', 'msds-KeyVersionNumber'); - - my $result; - eval { - $result = $ldap->search( - base => $base, - scope => 'subtree', - filter => $filter, - attrs => \@attrs - ); - }; - if ($@) { - my $error = $@; - die "LDAP search error: $error\n"; - } - if ($result->code) { - my $m; - $m .= "INFO base:$base filter:$filter scope:subtree\n"; - $m .= 'ERROR:' . $result->error . "\n"; - die $m; - } - if ($result->count > 1) { - my $m = "ERROR: too many AD entries for this keytab\n"; - for my $entry ($result->entries) { - $m .= 'INFO: dn found ' . $entry->dn . "\n"; - } - die $m; - } - if ($result->count) { - for my $entry ($result->entries) { - return $entry->get_value('msds-KeyVersionNumber'); - } - } else { - return 0; - } - return; + return $self->ldap_get_dn($base, $filter); } # Call msktutil to Create a principal in Kerberos. Sets the error and @@ -371,7 +441,7 @@ sub destroy { } my $exists = $self->exists($principal); if (!defined $exists) { - return; + return 1; } elsif (not $exists) { return 1; } @@ -384,29 +454,11 @@ sub destroy { sub ad_delete { my ($self, $principal) = @_; - my $k_type; - my $k_id; - my $dn; - if ($principal =~ m,^(host|service)/(\S+),xms) { - $k_type = $1; - $k_id = $2; - if ($k_type eq 'host') { - my $host = $k_id; - $host =~ s/[.].*//; - $dn - = "cn=${host}," - . $Wallet::Config::AD_COMPUTER_RDN . ',' - . $Wallet::Config::AD_BASE_DN; - } elsif ($k_type eq 'service') { - $dn - = "cn=srv-${k_id}," - . $Wallet::Config::AD_USER_RDN . ',' - . $Wallet::Config::AD_BASE_DN; - } - } + my ($base, $filter) = $self->ldap_base_filter($principal); + my $dn = $self->ldap_get_dn($base, $filter); - my $ldap = $self->ldap_connect(); - my $msgid = $ldap->delete($dn); + $self->ldap_connect(); + my $msgid = $LDAP->delete($dn); if ($msgid->code) { my $m; $m .= "ERROR: Problem deleting $dn\n"; -- cgit v1.2.3