run, attempting authentication every minute as if authentication had
failed after it had started. Patch from Rasmus Borup Hansen.
- If -i is given to krenew and the initial ticket renewal failed, start
- with the shorter wake-up interval of one minute just as if a
- subsequent renewal failed. Patch from Rasmus Borup Hansen.
+ For both k5start with a command or -K and no -x flag, and krenew with
+ the -i flag, repeatedly retry the initial authentication. The first
+ retry will be immediate, and then the commands will keep trying with
+ exponential backoff to one minute intervals, and then continuously at
+ one minute intervals until the command is killed or authentication
+ succeeds. k5start and krenew will no longer start any other command
+ until the initial authentication succeeds, fixing startup behavior
+ when running a command that must have valid Kerberos tickets
+ immediately on start. Based on a patch by Lars Hanke.
Clean up the temporary ticket cache on k5start failure if -o, -g, or
-m were given. Based on a patch by Rasmus Borup Hansen.
Andrew Deason.
Fix k5start and krenew to not incorrectly reject the -b flag in
- conjunction with -K or a command. Patch from Dr. Lars Hanke.
+ conjunction with -K or a command. Patch from Lars Hanke.
kstart 4.1 (2012-01-07)
* other is handled via callbacks.
*
* Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2015 Russ Allbery <eagle@eyrie.org>
* Copyright 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2014
* The Board of Trustees of the Leland Stanford Junior University
*
}
+/*
+ * Retry the initial authentication when the program is first starting. Retry
+ * the authentication immediately, then after one second, and keep trying with
+ * exponential backoff, maxing out at one minute and continuing until
+ * authentication succeeds or we exit due to signal.
+ */
+static krb5_error_code
+retry_auth(krb5_context ctx, struct config *config)
+{
+ krb5_error_code code;
+ struct timeval timeout;
+ unsigned int delay = 1;
+
+ code = config->auth(ctx, config, 0);
+ while (code != 0) {
+ timeout.tv_sec = delay;
+ timeout.tv_usec = 0;
+ delay = (delay < 30) ? delay * 2 : delay;
+ select(0, NULL, NULL, NULL, &timeout);
+ if (exit_signaled)
+ exit_cleanup(ctx, config, 1);
+ code = config->auth(ctx, config, 0);
+ }
+ return code;
+}
+
+
/*
* Write out a PID file given the path to the file and the PID to write.
* Errors are reported but otherwise ignored.
* isn't expired.
*/
if (config->happy_ticket == 0)
- code = config->auth(ctx, config, code);
+ code = config->auth(ctx, config, 0);
else {
code = ticket_expired(ctx, config);
if (code != 0)
exit_cleanup(ctx, config, status);
/* If requested, run the aklog program. */
- if (config->do_aklog)
+ if (code == 0 && config->do_aklog)
command_run(aklog, config->verbose);
/*
if (config->pidfile != NULL)
write_pidfile(config->pidfile, getpid());
+ /*
+ * Now, if the initial authentication failed and we're ignoring initial
+ * failures, retry authentication until it succeeds so that we never start
+ * the command without authentication. We don't set up signal handlers
+ * here, which means SIGHUP may terminate the program during this period
+ * but not after the command is started. Any approach here is potentially
+ * inconsistent; that seems the simplest.
+ *
+ * Set up some signal handlers so that we remove the PID file if we exit
+ * via signal. These will be overwritten by the command signal handlers
+ * if we start a command later.
+ */
+ if (code != 0 && config->ignore_errors) {
+ add_handler(ctx, config, exit_handler, SIGHUP, "SIGHUP");
+ add_handler(ctx, config, exit_handler, SIGTERM, "SIGTERM");
+ code = retry_auth(ctx, config);
+ if (code == 0 && config->do_aklog)
+ command_run(aklog, config->verbose);
+ }
+
/* Spawn the external command, if we were told to run one. */
if (config->command != NULL) {
child = command_start(config->command[0], config->command);
If a running B<k5start> receives an ALRM signal, it immediately refreshes
the ticket cache regardless of whether it is in danger of expiring.
+If B<k5start> is run with a command or the B<-K> flag and the B<-x> flag
+is not given, it will keep trying even if the initial authentication
+fails. It will retry the initial authentication immediately and then with
+exponential backoff to once per minute, and keep trying until
+authentication succeeds or it is killed. The command, if any, will not be
+started until authentication succeeds.
+
=head1 OPTIONS
=over 4
If this option is not given but a command was given on the command line,
the default interval is 60 minutes (1 hour).
-If an error occurs in the initial authentication or in refreshing the
-ticket cache, the wake-up interval will be shortened to one minute and
-the operation retried at that interval for as long as the error
-persists.
+If an error occurs in refreshing the ticket cache, the wake-up interval
+will be shortened to one minute and the operation retried at that interval
+for as long as the error persists.
=item B<-k> I<ticket cache>
host/example.com
Do the same, but using the default ticket cache and run the command
-/usr/local/bin/auth-backup. B<k5start> will continue running until the
-command finishes.
+F</usr/local/bin/auth-backup>. B<k5start> will continue running until the
+command finishes. If the initial authentication fails, keep trying, and
+don't start the command until it succeeds. This could be used during
+system startup for a command that must have valid tickets before starting,
+and tolerates having B<k5start> start before the network is completely set
+up.
k5start -f /etc/krb5.keytab -K 10 -l 10h host/example.com \
/usr/local/bin/auth-backup
ticket cache and B<krenew> should stay around and act on that recreated
ticket cache once it's present.
-Setup errors opening the Kerberos ticket cache or running the command will
-still cause B<krenew> to exit, even if this flag is given. Only Kerberos
-errors and errors after B<krenew> is running and any command has been
-started will be ignored.
+If the initial ticket cache renew fails, B<krenew> will retry the renewal
+immediately and then with exponential backoff to once per minute, and keep
+trying until authentication succeeds or it is killed. The command, if
+any, will not be started until cache renewal succeeds.
This flag is only useful in daemon mode or when a command was given.
my $file = shift;
open (OUT, '>', $file) or die "Cannot write to $file: $!\n";
OUT->autoflush (1);
-print OUT "$$\n", getcwd, "\n", $ENV{KRB5CCNAME}, "\n";
+
+# Make sure that the ticket cache exists and is non-zero. If so, report its
+# value to standard output; otherwise, report the empty string. Either way,
+# report the current working directory.
+my $cache = '';
+if ($ENV{KRB5CCNAME}) {
+ $path = $ENV{KRB5CCNAME};
+ $path =~ s/^FILE://;
+ if (-s $path) {
+ $cache = $ENV{KRB5CCNAME};
+ }
+}
+print OUT "$$\n", getcwd, "\n", $cache, "\n";
sleep 1000 while 1;
# Tests for k5start daemon functionality.
#
# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2015 Russ Allbery <eagle@eyrie.org>
# Copyright 2008, 2009, 2011, 2012, 2014
# The Board of Trustees of the Leland Stanford Junior University
#
# See LICENSE for licensing terms.
use Cwd;
+use File::Copy qw(copy);
use Test::More;
# Decide whether we have the configuration to run the tests.
if (-f "$DATA/test.keytab" and -f "$DATA/test.principal") {
- plan tests => 84;
+ plan tests => 86;
} else {
plan skip_all => "no keytab configuration";
exit 0;
$pid = contents ("$TMP/pid");
ok (kill (0, $pid), ' and the PID file is correct');
kill (15, $pid) or warn "Can't kill $pid: $!\n";
-select (undef, undef, undef, 0.2);
+select (undef, undef, undef, 2.0);
ok (!-f "$TMP/pid", ' and the PID file was removed');
unlink "$TMP/pid";
ok (!-f "$TMP/pid", 'PID file cleaned up');
ok (!-f "$TMP/child-pid", 'Child PID file cleaned up');
-# One more time to test SIGQUIT.
+# Finally, start a child program with an invalid keytab path. This should
+# start and keep trying to authenticate but never start the actual child
+# process until the ticket cache does exist. We also test SIGQUIT handling.
unlink "$TMP/krb5cc_child", "$TMP/child-out";
($out, $err, $status)
= command ($K5START, '-bK', 1, '-k', "$TMP/krb5cc_child", '-f',
- "$DATA/test.keytab", '-p', "$TMP/pid", '-c', "$TMP/child-pid",
+ "$TMP/test.keytab", '-p', "$TMP/pid", '-c', "$TMP/child-pid",
$principal, '--', "$ENV{SOURCE}/data/command",
"$TMP/child-out");
is ($status, 0, 'Backgrounding k5start works');
-is ($err, '', ' with no error output');
+like ($err, qr/^k5start: error getting credentials: /, ' with error output');
is ($out, '', ' and output was redirected properly');
+$pid = contents ("$TMP/pid");
+ok (kill (0, $pid), 'k5start is running');
+select (undef, undef, undef, 1);
+ok (!-f "$TMP/child-pid", ' child did not start');
+ok (!-f "$TMP/child-out", ' and has no output');
+copy ("$DATA/test.keytab", "$TMP/test.keytab");
$tries = 0;
while (not -f "$TMP/child-pid" and $tries < 100) {
select (undef, undef, undef, 0.1);
$tries++;
}
-$pid = contents ("$TMP/pid");
-ok (kill (0, $pid), 'k5start is running');
$child = contents ("$TMP/child-pid");
ok (kill (0, $child), 'The child process is running');
$tries = 0;
ok (!-f "$TMP/child-pid", 'Child PID file cleaned up');
# Clean up.
-unlink "$TMP/krb5cc_child", "$TMP/child-out";
+unlink "$TMP/krb5cc_child", "$TMP/child-out", "$TMP/test.keytab";
rmdir $TMP;
# Tests for basic krenew functionality.
#
# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2015 Russ Allbery <eagle@eyrie.org>
# Copyright 2008, 2009, 2011
# The Board of Trustees of the Leland Stanford Junior University
#
# See LICENSE for licensing terms.
+use IPC::Open3 qw(open3);
use Test::More;
# The full path to the newly-built krenew client.
is ($out, '', ' with no output');
like ($err, qr/^krenew: error reading ticket cache: /, ' and the right error');
-# We should still exit with the same error if -i was given.
-($out, $err, $status) = command ($KRENEW, '-vi');
+# If -i was given, we keep running and keep trying and have to be explicitly
+# killed.
+my $pid = open3 ('<&0', \*OUT, \*ERR, $KRENEW, '-vi')
+ or BAIL_OUT ("can't run $KRENEW: $!");
+select (undef, undef, undef, 1.0);
+kill (15, $pid);
+$out = <OUT>;
+$err = <ERR>;
+close OUT;
+close ERR;
+waitpid ($pid, 0);
+$status = ($? >> 8);
is ($status, 1, 'krenew -i fails with no ticket cache');
-is ($out, '', ' with no output');
+is ($out, undef, ' with no output');
like ($err, qr/^krenew: error reading ticket cache: /, ' and the right error');
-($out, $err, $status) = command ($KRENEW, '-viH', 5);
+$pid = open3 ('<&0', \*OUT, \*ERR, $KRENEW, '-viH', 5)
+ or BAIL_OUT ("can't run $KRENEW: $!");
+select (undef, undef, undef, 1.0);
+kill (15, $pid);
+$out = <OUT>;
+$err = <ERR>;
+close OUT;
+close ERR;
+waitpid ($pid, 0);
+$status = ($? >> 8);
is ($status, 1, 'krenew -iH fails with no ticket cache');
-is ($out, '', ' with no output');
+is ($out, undef, ' with no output');
like ($err, qr/^krenew: error reading ticket cache: /, ' and the right error');