aboutsummaryrefslogtreecommitdiff
path: root/perl/lib/Wallet/Kadmin
diff options
context:
space:
mode:
authorRuss Allbery <eagle@eyrie.org>2016-01-17 19:43:13 -0800
committerRuss Allbery <eagle@eyrie.org>2016-01-17 19:43:13 -0800
commitcf5297c4ec8815ecc7f5139ef05b9867843db2f7 (patch)
treefef6ba149883530c7e7fba771be6ac2e59c4dfe9 /perl/lib/Wallet/Kadmin
parent7e03241ce323be7447b085a8e7b07b78c770b0dc (diff)
parent4b3f858ef567c0d12511e7fea2a56f08f2729635 (diff)
Merge tag 'upstream/1.3' into debian/master
Upstream version 1.3
Diffstat (limited to 'perl/lib/Wallet/Kadmin')
-rw-r--r--perl/lib/Wallet/Kadmin/AD.pm472
-rw-r--r--perl/lib/Wallet/Kadmin/Heimdal.pm18
-rw-r--r--perl/lib/Wallet/Kadmin/MIT.pm23
3 files changed, 489 insertions, 24 deletions
diff --git a/perl/lib/Wallet/Kadmin/AD.pm b/perl/lib/Wallet/Kadmin/AD.pm
new file mode 100644
index 0000000..5b71d41
--- /dev/null
+++ b/perl/lib/Wallet/Kadmin/AD.pm
@@ -0,0 +1,472 @@
+# 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
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+##############################################################################
+# Modules and declarations
+##############################################################################
+
+package Wallet::Kadmin::AD;
+
+use 5.008;
+use strict;
+use warnings;
+
+use Authen::SASL;
+use Net::LDAP;
+use IPC::Run qw(run timeout);
+use Sys::Syslog qw(:standard :macros);
+use Wallet::Config;
+use Wallet::Kadmin;
+
+our @ISA = qw(Wallet::Kadmin);
+our $VERSION = '1.03';
+
+##############################################################################
+# kadmin Interaction
+##############################################################################
+
+# Send debugging output to syslog.
+
+sub ad_debug {
+ my ($self, $l, $m) = @_;
+ if (!$self->{SYSLOG}) {
+ openlog('wallet-server', 'ndelay,nofatal', 'local3');
+ $self->{SYSLOG} = 1;
+ }
+ syslog($l, $m);
+ return;
+}
+
+# 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;
+}
+
+# Connect to the Active Directory server using LDAP. The connection is
+# used to retrieve information about existing keytabs since msktutil
+# does not have this functionality.
+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";
+ }
+
+ 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})";
+ }
+ return ($base, $filter);
+}
+
+# TODO: Get a keytab from the keytab cache.
+sub get_ad_keytab {
+ my ($self, $principal) = @_;
+ return;
+}
+
+# Run a msktutil command and capture the output. Returns the output,
+# either as a list of lines or, in scalar context, as one string.
+# Depending on the exit status of msktutil or on the eval trap to know
+# when the msktutil command fails. The error string returned from the
+# call to run frequently contains information about a success rather
+# that error output.
+sub msktutil {
+ my ($self, $args_ref) = @_;
+ unless (defined($Wallet::Config::KEYTAB_HOST)
+ 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))
+ {
+ die "Active Directory support not configured\n";
+ }
+ my @args = @{$args_ref};
+ my @cmd = ($Wallet::Config::AD_MSKTUTIL);
+ push @cmd, @args;
+ if ($Wallet::Config::AD_DEBUG) {
+ $self->ad_debug('debug', join(' ', @cmd));
+ }
+
+ my $in;
+ my $out;
+ my $err;
+ my $err_msg;
+ my $err_no;
+ eval {
+ local $ENV{KRB5CCNAME} = $Wallet::Config::AD_CACHE;
+ run \@cmd, \$in, \$out, \$err, timeout(120);
+ if ($?) {
+ $err_no = $?;
+ }
+ };
+ if ($@) {
+ $err_msg .= "ERROR ($err_no): $@\n";
+ }
+ if ($err_no || $err_msg) {
+ if ($err) {
+ $err_msg .= "ERROR: $err\n";
+ $err_msg .= 'Problem command: ' . join(' ', @cmd) . "\n";
+ }
+ die $err_msg;
+ } else {
+ if ($err) {
+ $out .= "\n" . $err;
+ }
+ }
+ if ($Wallet::Config::AD_DEBUG) {
+ $self->ad_debug('debug', $out);
+ }
+ return $out;
+}
+
+# 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) = @_;
+ 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, '--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;
+ }
+
+ return $keytab;
+}
+
+##############################################################################
+# Public interfaces
+##############################################################################
+
+# Set a callback to be called for forked kadmin processes.
+sub fork_callback {
+ my ($self, $callback) = @_;
+ $self->{fork_callback} = $callback;
+}
+
+# Check whether a given principal already exists. Returns true if so,
+# false otherwise. The best way to do this with AD is to perform an
+# ldap query.
+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;
+}
+
+# Call msktutil to Create a principal in Kerberos. Sets the error and
+# returns undef on failure, and returns 1 on either success or if the
+# principal already exists. Note, this creates a keytab that is never
+# used because it is not returned to the user.
+sub create {
+ my ($self, $principal) = @_;
+ unless ($self->valid_principal($principal)) {
+ die "ERROR: invalid principal name $principal\n";
+ return;
+ }
+ if ($self->exists($principal)) {
+ if ($Wallet::Config::AD_DEBUG) {
+ $self->ad_debug('debug', "$principal exists");
+ }
+ return 1;
+ }
+ my $file = $self->ad_create_update($principal, 'create');
+ if (-e $file) {
+ unlink $file or die "Problem deleting $file\n";
+ }
+ return 1;
+}
+
+# TODO: Return a keytab. Need to create a local keytab cache when
+# a keytab is marked unchanging and return that.
+sub keytab {
+ my ($self, $principal) = @_;
+ unless ($self->valid_principal($principal)) {
+ die "ERROR: invalid principal name $principal\n";
+ return;
+ }
+ my $file = 'call to route to get the file name of local keytab file';
+ if (!-e $file) {
+ die "ERROR: keytab file $file does not exist.\n";
+ }
+ return $self->read_keytab($file);
+}
+
+# Update a keytab for a principal. This action changes the AD
+# password for the principal and increments the kvno. The enctypes
+# passed in are ignored.
+sub keytab_rekey {
+ my ($self, $principal, @enctypes) = @_;
+ unless ($self->valid_principal($principal)) {
+ die "ERROR: invalid principal name: $principal\n";
+ return;
+ }
+ if (!$self->exists($principal)) {
+ die "ERROR: $principal does not exist\n";
+ }
+ unless ($self->valid_principal($principal)) {
+ die "ERROR: invalid principal name $principal\n";
+ return;
+ }
+ my $file = $self->ad_create_update($principal, 'update');
+ return $self->read_keytab($file);
+}
+
+# Delete a principal from Kerberos. Return true if successful, false
+# otherwise. If the deletion fails, sets the error. If the principal
+# doesn't exist, return success; we're bringing reality in line with
+# our expectations. For AD this means just delete the object using
+# LDAP.
+sub destroy {
+ my ($self, $principal) = @_;
+ unless ($self->valid_principal($principal)) {
+ $self->error("invalid principal name: $principal");
+ }
+ my $exists = $self->exists($principal);
+ if (!defined $exists) {
+ return;
+ } elsif (not $exists) {
+ return 1;
+ }
+
+ return $self->ad_delete($principal);
+}
+
+# Delete an entry from AD using LDAP.
+
+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 $ldap = $self->ldap_connect();
+ my $msgid = $ldap->delete($dn);
+ if ($msgid->code) {
+ my $m;
+ $m .= "ERROR: Problem deleting $dn\n";
+ $m .= $msgid->error;
+ die $m;
+ }
+ return 1;
+}
+
+# Create a new AD kadmin object. Very empty for the moment, but later it
+# will probably fill out if we go to using a module rather than calling
+# kadmin directly.
+sub new {
+ my ($class) = @_;
+ unless (defined($Wallet::Config::KEYTAB_TMP)) {
+ die "KEYTAB_TMP configuration variable not set\n";
+ }
+ my $self = {};
+ $self->{SYSLOG} = undef;
+ bless($self, $class);
+ return $self;
+}
+
+1;
+__END__
+
+##############################################################################
+# Documentation
+##############################################################################
+
+=for stopwords
+rekeying rekeys remctl backend keytabs keytab kadmin KDC API Allbery
+unlinked MacAllister msktutil
+
+=head1 NAME
+
+Wallet::Kadmin::AD - Wallet Kerberos administration API for Active Directory
+
+=head1 SYNOPSIS
+
+ my $kadmin = Wallet::Kadmin::AD->new;
+ $kadmin->create ('host/foo.example.com');
+ my $data = $kadmin->keytab_rekey ('host/foo.example.com');
+ $data = $kadmin->keytab ('host/foo.example.com');
+ my $exists = $kadmin->exists ('host/oldshell.example.com');
+ $kadmin->destroy ('host/oldshell.example.com') if $exists;
+
+=head1 DESCRIPTION
+
+Wallet::Kadmin::AD implements the Wallet::Kadmin API for Active
+Directory Kerberos, providing an interface to create and delete
+principals and create keytabs. It provides the API documented in
+L<Wallet::Kadmin> for an Active Directory Kerberos KDC.
+
+AD Kerberos does not provide any method via msktutil to retrieve a
+keytab for a principal without rekeying it, so the keytab() method (as
+opposed to keytab_rekey(), which rekeys the principal) is implemented
+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
+than using a native Perl module and therefore requires B<msktutil> be
+installed and parses its output.
+
+=head1 SEE ALSO
+
+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/>.
+
+=head1 AUTHORS
+
+Bill MacAllister <whm@dropbox.com>
+and Russ Allbery <eagle@eyrie.org>
+and Jon Robertson <jonrober@stanford.edu>.
+
+=cut
diff --git a/perl/lib/Wallet/Kadmin/Heimdal.pm b/perl/lib/Wallet/Kadmin/Heimdal.pm
index 1208801..22bdd59 100644
--- a/perl/lib/Wallet/Kadmin/Heimdal.pm
+++ b/perl/lib/Wallet/Kadmin/Heimdal.pm
@@ -1,6 +1,7 @@
-# Wallet::Kadmin::Heimdal -- Wallet Kerberos administration API for Heimdal.
+# Wallet::Kadmin::Heimdal -- Wallet Kerberos administration API for Heimdal
#
# Written by Jon Robertson <jonrober@stanford.edu>
+# Copyright 2016 Russ Allbery <eagle@eyrie.org>
# Copyright 2009, 2010, 2014
# The Board of Trustees of the Leland Stanford Junior University
#
@@ -11,22 +12,17 @@
##############################################################################
package Wallet::Kadmin::Heimdal;
-require 5.006;
+use 5.008;
use strict;
use warnings;
-use vars qw(@ISA $VERSION);
use Heimdal::Kadm5 qw(KRB5_KDB_DISALLOW_ALL_TIX);
-use Wallet::Config ();
-use Wallet::Kadmin ();
+use Wallet::Config;
+use Wallet::Kadmin;
-@ISA = qw(Wallet::Kadmin);
-
-# This version should be increased on any code change to this module. Always
-# use two digits for the minor version with a leading zero if necessary so
-# that it will sort properly.
-$VERSION = '0.04';
+our @ISA = qw(Wallet::Kadmin);
+our $VERSION = '1.03';
##############################################################################
# Utility functions
diff --git a/perl/lib/Wallet/Kadmin/MIT.pm b/perl/lib/Wallet/Kadmin/MIT.pm
index ac45265..9f0f50f 100644
--- a/perl/lib/Wallet/Kadmin/MIT.pm
+++ b/perl/lib/Wallet/Kadmin/MIT.pm
@@ -1,7 +1,8 @@
-# Wallet::Kadmin::MIT -- Wallet Kerberos administration API for MIT.
+# Wallet::Kadmin::MIT -- Wallet Kerberos administration API for MIT
#
# 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
# The Board of Trustees of the Leland Stanford Junior University
#
@@ -12,21 +13,17 @@
##############################################################################
package Wallet::Kadmin::MIT;
-require 5.006;
+use 5.008;
use strict;
use warnings;
-use vars qw(@ISA $VERSION);
-use Wallet::Config ();
-use Wallet::Kadmin ();
+use POSIX qw(_exit);
+use Wallet::Config;
+use Wallet::Kadmin;
-@ISA = qw(Wallet::Kadmin);
-
-# This version should be increased on any code change to this module. Always
-# use two digits for the minor version with a leading zero if necessary so
-# that it will sort properly.
-$VERSION = '0.03';
+our @ISA = qw(Wallet::Kadmin);
+our $VERSION = '1.03';
##############################################################################
# kadmin Interaction
@@ -65,11 +62,11 @@ sub kadmin {
$self->{fork_callback} () if $self->{fork_callback};
unless (open (STDERR, '>&STDOUT')) {
warn "wallet: cannot dup stdout: $!\n";
- exit 1;
+ _exit(1);
}
unless (exec ($Wallet::Config::KEYTAB_KADMIN, @args)) {
warn "wallet: cannot run $Wallet::Config::KEYTAB_KADMIN: $!\n";
- exit 1;
+ _exit(1);
}
}
local $_;