aboutsummaryrefslogtreecommitdiff
path: root/perl/lib/Wallet/Kadmin
diff options
context:
space:
mode:
Diffstat (limited to 'perl/lib/Wallet/Kadmin')
-rw-r--r--perl/lib/Wallet/Kadmin/AD.pm374
-rw-r--r--perl/lib/Wallet/Kadmin/Heimdal.pm8
-rw-r--r--perl/lib/Wallet/Kadmin/MIT.pm8
3 files changed, 236 insertions, 154 deletions
diff --git a/perl/lib/Wallet/Kadmin/AD.pm b/perl/lib/Wallet/Kadmin/AD.pm
index 5b71d41..f2f86b9 100644
--- a/perl/lib/Wallet/Kadmin/AD.pm
+++ b/perl/lib/Wallet/Kadmin/AD.pm
@@ -1,12 +1,12 @@
# Wallet::Kadmin::AD -- Wallet Kerberos administration API for AD
#
-# Written by Bill MacAllister <bill@ca-zephyr.org>
-# Copyright 2016 Russ Allbery <eagle@eyrie.org>
-# Copyright 2015 Dropbox, Inc.
-# Copyright 2007, 2008, 2009, 2010, 2014
+# Written by Bill MacAllister <whm@dropbox.com>
+# Copyright 2016, 2018 Russ Allbery <eagle@eyrie.org>
+# Copyright 2015-2016 Dropbox, Inc.
+# Copyright 2007-2010, 2014
# The Board of Trustees of the Leland Stanford Junior University
#
-# See LICENSE for licensing terms.
+# SPDX-License-Identifier: MIT
##############################################################################
# Modules and declarations
@@ -26,7 +26,9 @@ use Wallet::Config;
use Wallet::Kadmin;
our @ISA = qw(Wallet::Kadmin);
-our $VERSION = '1.03';
+our $VERSION = '1.04';
+
+my $LDAP;
##############################################################################
# kadmin Interaction
@@ -34,33 +36,47 @@ our $VERSION = '1.03';
# Send debugging output to syslog.
-sub ad_debug {
+sub ad_syslog {
my ($self, $l, $m) = @_;
if (!$self->{SYSLOG}) {
openlog('wallet-server', 'ndelay,nofatal', 'local3');
$self->{SYSLOG} = 1;
}
+ if ($l !~ /^(debug|info|err|warning)$/xms) {
+ $l = 'err';
+ }
syslog($l, $m);
return;
}
+# Return a string given an array whose elements are command line arguments
+# passws to IPC::Run. Quote any strings that have embedded spaces. Replace
+# null elements with the string #NULL#.
+
+sub ad_cmd_string {
+ my ($self, $cmd_ref) = @_;
+ my $z = '';
+ my $ws = ' ';
+ for my $e (@{ $cmd_ref }) {
+ if (!$e) {
+ $z .= $ws . '#NULL#';
+ } elsif ($e =~ /\s/xms) {
+ $z .= $ws . '"' . $e . '"';
+ } else {
+ $z .= $ws . $e;
+ }
+ $ws = ' ';
+ }
+ return $z;
+}
+
# Make sure that principals are well-formed and don't contain
# characters that will cause us problems when talking to kadmin.
# Takes a principal and returns true if it's okay, false otherwise.
# 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
@@ -69,48 +85,111 @@ 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;
- $base = $Wallet::Config::AD_COMPUTER_DN;
- $filter = "(samAccountName=${host}\$)";
- } elsif ($principal =~ m,^service/(\S+),xms) {
- my $id = $1;
- $base = $Wallet::Config::AD_USER_DN;
- $filter = "(servicePrincipalName=service/${id})";
+ 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);
}
-# TODO: Get a keytab from the keytab cache.
+# 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_syslog('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) {
+ $self->ad_syslog('info', "base:$base filter:$filter scope:subtree\n");
+ die $result->error;
+ }
+ if ($Wallet::Config::AD_DEBUG) {
+ $self->ad_syslog('debug', 'returned: ' . $result->count);
+ }
+
+ if ($result->count == 1) {
+ for my $entry ($result->entries) {
+ $dn = $entry->dn;
+ }
+ } elsif ($result->count > 1) {
+ $self->ad_syslog('err', 'too many AD entries for this keytab');
+ for my $entry ($result->entries) {
+ $self->ad_syslog('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) = @_;
return;
@@ -125,13 +204,16 @@ sub get_ad_keytab {
sub msktutil {
my ($self, $args_ref) = @_;
unless (defined($Wallet::Config::KEYTAB_HOST)
+ and defined($Wallet::Config::KEYTAB_PRINCIPAL)
+ and defined($Wallet::Config::KEYTAB_FILE)
and defined($Wallet::Config::KEYTAB_REALM))
{
die "keytab object implementation not configured\n";
}
- unless (defined($Wallet::Config::AD_CACHE)
- and defined($Wallet::Config::AD_COMPUTER_DN)
- and defined($Wallet::Config::AD_USER_DN))
+ unless (-e $Wallet::Config::AD_MSKTUTIL
+ and defined($Wallet::Config::AD_BASE_DN)
+ and defined($Wallet::Config::AD_COMPUTER_RDN)
+ and defined($Wallet::Config::AD_USER_RDN))
{
die "Active Directory support not configured\n";
}
@@ -139,7 +221,7 @@ sub msktutil {
my @cmd = ($Wallet::Config::AD_MSKTUTIL);
push @cmd, @args;
if ($Wallet::Config::AD_DEBUG) {
- $self->ad_debug('debug', join(' ', @cmd));
+ $self->ad_syslog('debug', $self->ad_cmd_string(\@cmd));
}
my $in;
@@ -162,6 +244,7 @@ sub msktutil {
$err_msg .= "ERROR: $err\n";
$err_msg .= 'Problem command: ' . join(' ', @cmd) . "\n";
}
+ $self->ad_syslog('err', $err_msg);
die $err_msg;
} else {
if ($err) {
@@ -169,49 +252,107 @@ sub msktutil {
}
}
if ($Wallet::Config::AD_DEBUG) {
- $self->ad_debug('debug', $out);
+ $self->ad_syslog('debug', $out);
}
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_account_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_type, $this_cn) = split '/', $this_princ, 2;
+ my $max_len;
+ if ($this_type eq 'host') {
+ $max_len = $Wallet::Config::AD_SERVICE_LENGTH - 1;
+ } else {
+ $max_len = $Wallet::Config::AD_SERVICE_LENGTH;
+ 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)>$max_len) {
+ my $cnt = 0;
+ my $this_dn;
+ my $suffix_size = length("$loop_limit");
+ my $this_prefix = substr($this_cn, 0, $max_len - $suffix_size);
+ my $this_format = "%0${suffix_size}i";
+ while ($cnt<$loop_limit) {
+ $this_cn = $this_prefix . sprintf($this_format, $cnt);
+ $this_dn = $self->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";
}
my @cmd = ('--' . $action);
push @cmd, '--server', $Wallet::Config::AD_SERVER;
- push @cmd, '--enctypes', '0x4';
- push @cmd, '--enctypes', '0x8';
- push @cmd, '--enctypes', '0x10';
+ 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, '--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, '--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;
+ my $account_id = $self->get_account_id($principal);
+ 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', $account_id;
+ push @cmd, '--hostname', $this_id;
+ } else {
+ push @cmd, '--base', $Wallet::Config::AD_USER_RDN;
+ push @cmd, '--use-service-account';
+ push @cmd, '--service', $principal;
+ push @cmd, '--account-name', $account_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_syslog('err', $m);
+ $self->ad_syslog('err',
+ 'Problem command:' . ad_cmd_string(\@cmd));
+ die "$m\n";
+ }
+ } else {
+ die "ERROR: Invalid principal format ($principal)\n";
}
return $keytab;
@@ -234,45 +375,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
@@ -287,7 +392,7 @@ sub create {
}
if ($self->exists($principal)) {
if ($Wallet::Config::AD_DEBUG) {
- $self->ad_debug('debug', "$principal exists");
+ $self->ad_syslog('debug', "$principal exists");
}
return 1;
}
@@ -345,7 +450,7 @@ sub destroy {
}
my $exists = $self->exists($principal);
if (!defined $exists) {
- return;
+ return 1;
} elsif (not $exists) {
return 1;
}
@@ -358,27 +463,16 @@ 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_DN;
- } elsif ($k_type eq 'service') {
- $dn = "cn=srv-${k_id}," . $Wallet::Config::AD_USER_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";
$m .= $msgid->error;
+ $self->ad_syslog('err', $m);
die $m;
}
return 1;
@@ -437,18 +531,6 @@ using a local keytab cache.
To use this class, several configuration parameters must be set. See
L<Wallet::Config/"KEYTAB OBJECT CONFIGURATION"> for details.
-=head1 FILES
-
-=over 4
-
-=item KEYTAB_TMP/keytab.<pid>
-
-The keytab is created in this file and then read into memory. KEYTAB_TMP
-is set in the wallet configuration, and <pid> is the process ID of the
-current process. The file is unlinked after being read.
-
-=back
-
=head1 LIMITATIONS
Currently, this implementation calls an external B<msktutil> program rather
@@ -461,7 +543,7 @@ msktutil, Wallet::Config(3), Wallet::Kadmin(3),
Wallet::Object::Keytab(3), wallet-backend(8)
This module is part of the wallet system. The current version is
-available from L<http://www.eyrie.org/~eagle/software/wallet/>.
+available from L<https://www.eyrie.org/~eagle/software/wallet/>.
=head1 AUTHORS
diff --git a/perl/lib/Wallet/Kadmin/Heimdal.pm b/perl/lib/Wallet/Kadmin/Heimdal.pm
index 22bdd59..57013de 100644
--- a/perl/lib/Wallet/Kadmin/Heimdal.pm
+++ b/perl/lib/Wallet/Kadmin/Heimdal.pm
@@ -2,10 +2,10 @@
#
# Written by Jon Robertson <jonrober@stanford.edu>
# Copyright 2016 Russ Allbery <eagle@eyrie.org>
-# Copyright 2009, 2010, 2014
+# Copyright 2009-2010, 2014
# The Board of Trustees of the Leland Stanford Junior University
#
-# See LICENSE for licensing terms.
+# SPDX-License-Identifier: MIT
##############################################################################
# Modules and declarations
@@ -22,7 +22,7 @@ use Wallet::Config;
use Wallet::Kadmin;
our @ISA = qw(Wallet::Kadmin);
-our $VERSION = '1.03';
+our $VERSION = '1.04';
##############################################################################
# Utility functions
@@ -302,7 +302,7 @@ kadmin(8), Wallet::Config(3), Wallet::Kadmin(3),
Wallet::Object::Keytab(3), wallet-backend(8)
This module is part of the wallet system. The current version is
-available from L<http://www.eyrie.org/~eagle/software/wallet/>.
+available from L<https://www.eyrie.org/~eagle/software/wallet/>.
=head1 AUTHORS
diff --git a/perl/lib/Wallet/Kadmin/MIT.pm b/perl/lib/Wallet/Kadmin/MIT.pm
index 9f0f50f..373d4cf 100644
--- a/perl/lib/Wallet/Kadmin/MIT.pm
+++ b/perl/lib/Wallet/Kadmin/MIT.pm
@@ -3,10 +3,10 @@
# Written by Russ Allbery <eagle@eyrie.org>
# Pulled into a module by Jon Robertson <jonrober@stanford.edu>
# Copyright 2016 Russ Allbery <eagle@eyrie.org>
-# Copyright 2007, 2008, 2009, 2010, 2014
+# Copyright 2007-2010, 2014
# The Board of Trustees of the Leland Stanford Junior University
#
-# See LICENSE for licensing terms.
+# SPDX-License-Identifier: MIT
##############################################################################
# Modules and declarations
@@ -23,7 +23,7 @@ use Wallet::Config;
use Wallet::Kadmin;
our @ISA = qw(Wallet::Kadmin);
-our $VERSION = '1.03';
+our $VERSION = '1.04';
##############################################################################
# kadmin Interaction
@@ -312,7 +312,7 @@ kadmin(8), Wallet::Config(3), Wallet::Kadmin(3),
Wallet::Object::Keytab(3), wallet-backend(8)
This module is part of the wallet system. The current version is
-available from L<http://www.eyrie.org/~eagle/software/wallet/>.
+available from L<https://www.eyrie.org/~eagle/software/wallet/>.
=head1 AUTHORS