iprop: Enable secondary KDC bootstrapping w/ PKINIT

With this change it's possible to bootstrap a KDC using a client
certificate with a PKINIT SAN for iprop/fqdn.  Given such a certificate
one could run ipropd-slave via kinit to pull down the initial copy of
the HDB, then start the KDC services using the HDBGET: keytab.

That should make bootstrapping new secondary KDCs very easy.

One could bootstrap the KDC with such a certificate using, e.g.,
Safeboot (https://github.com/osresearch/safeboot), enrolling the host as
a KDC.
This commit is contained in:
Nicolas Williams
2022-09-30 17:06:52 -05:00
parent 56c6120522
commit f90f055659
2 changed files with 77 additions and 15 deletions

View File

@@ -68,6 +68,8 @@
.Oo Fl r Ar string \*(Ba Xo Fl Fl realm= Ns Ar string Xc Oc .Oo Fl r Ar string \*(Ba Xo Fl Fl realm= Ns Ar string Xc Oc
.Oo Fl d Ar file \*(Ba Xo Fl Fl database= Ns Ar file Xc Oc .Oo Fl d Ar file \*(Ba Xo Fl Fl database= Ns Ar file Xc Oc
.Oo Fl k Ar kspec \*(Ba Xo Fl Fl keytab= Ns Ar kspec Xc Oc .Oo Fl k Ar kspec \*(Ba Xo Fl Fl keytab= Ns Ar kspec Xc Oc
.Oo Xo Fl Fl no-keytab Xc Oc
.Oo Xo Fl Fl cache= Ns Ar cspec Xc Oc
.Op Fl Fl statusfile= Ns Ar file .Op Fl Fl statusfile= Ns Ar file
.Op Fl Fl hostname= Ns Ar hostname .Op Fl Fl hostname= Ns Ar hostname
.Op Fl Fl port= Ns Ar port .Op Fl Fl port= Ns Ar port
@@ -125,10 +127,40 @@ This should normally be defined as
in in
.Pa /etc/services .Pa /etc/services
or another source of the services database. or another source of the services database.
The master and slaves .Pp
must each have access to a keytab with keys for the The
.Nm iprop .Nm ipropd-master
service principal on the local host. and
.Nm ipropd-slave
programs require acceptor and initiator credentials,
respectively, for host-based principals for the
.Ar iprop
service and the fully-qualified hostnames of the hosts on which
they run.
.Pp
The
.Nm ipropd-master
program uses, by default, the HDB-backed keytab
.Ar HDBGET: ,
though a file-based keytab can also be specified.
.Pp
The
.Nm ipropd-slave
program uses the default keytab, which is specified by the
.Ev KRB5_KTNAME
environment variable, or the value of the
.Ar default_keytab_name
configuration parameter in the
.Ar [libdefaults]
section.
However, if the
.Fl Fl no-keytab
option is given, then
.Nm ipropd-slave
will use the given or default credentials cache, and it will
expect that cache to be kept fresh externally (such as by the
.Nm kinit
program).
.Pp .Pp
There is a keep-alive feature logged in the master's There is a keep-alive feature logged in the master's
.Pa slave-stats .Pa slave-stats
@@ -150,6 +182,11 @@ Supported options for
Keytab for authenticating Keytab for authenticating
.Nm ipropd-slave .Nm ipropd-slave
clients. clients.
.It Fl Fl cache= Ns Ar cspec
If the keytab given is the empty string then credentials will be
used from the default credentials cache, or from the
.Ar cspec
if given.
.It Fl d Ar file , Fl Fl database= Ns Ar file .It Fl d Ar file , Fl Fl database= Ns Ar file
Database (default per KDC) Database (default per KDC)
.It Fl Fl slave-stats-file= Ns Ar file .It Fl Fl slave-stats-file= Ns Ar file
@@ -201,6 +238,7 @@ in the database directory, or in the directory named by the
.Ev HEIM_PIDFILE_DIR .Ev HEIM_PIDFILE_DIR
environment variable. environment variable.
.Sh SEE ALSO .Sh SEE ALSO
.Xr kinit 1 ,
.Xr krb5.conf 5 , .Xr krb5.conf 5 ,
.Xr hprop 8 , .Xr hprop 8 ,
.Xr hpropd 8 , .Xr hpropd 8 ,

View File

@@ -39,6 +39,9 @@ static const char *config_name = "ipropd-slave";
static int verbose; static int verbose;
static int async_hdb = 0; static int async_hdb = 0;
static int no_keytab_flag;
static char *ccache_str;
static char *keytab_str;
static krb5_log_facility *log_facility; static krb5_log_facility *log_facility;
static char five_min[] = "5 min"; static char five_min[] = "5 min";
@@ -115,8 +118,7 @@ connect_to_master (krb5_context context, const char *master,
} }
static void static void
get_creds(krb5_context context, const char *keytab_str, get_creds(krb5_context context, krb5_ccache *cache, const char *serverhost)
krb5_ccache *cache, const char *serverhost)
{ {
krb5_keytab keytab; krb5_keytab keytab;
krb5_principal client; krb5_principal client;
@@ -127,13 +129,33 @@ get_creds(krb5_context context, const char *keytab_str,
char keytab_buf[256]; char keytab_buf[256];
int aret; int aret;
if (no_keytab_flag) {
/* We're using an externally refreshed ccache */
if (*cache == NULL) {
if (ccache_str == NULL)
ret = krb5_cc_default(context, cache);
else
ret = krb5_cc_resolve(context, ccache_str, cache);
if (ret)
krb5_err(context, 1, ret, "Could not resolve the default cache");
}
return;
}
if (keytab_str == NULL) { if (keytab_str == NULL) {
ret = krb5_kt_default_name (context, keytab_buf, sizeof(keytab_buf)); ret = krb5_kt_default_name (context, keytab_buf, sizeof(keytab_buf));
if (ret) if (ret == 0) {
krb5_err (context, 1, ret, "krb5_kt_default_name"); keytab_str = keytab_buf;
keytab_str = keytab_buf; } else {
krb5_warn(context, ret, "Using HDBGET: as the default keytab");
keytab_str = "HDBGET:";
}
} }
if (*cache)
krb5_cc_destroy(context, *cache);
*cache = NULL;
ret = krb5_kt_resolve(context, keytab_str, &keytab); ret = krb5_kt_resolve(context, keytab_str, &keytab);
if(ret) if(ret)
krb5_err(context, 1, ret, "%s", keytab_str); krb5_err(context, 1, ret, "%s", keytab_str);
@@ -690,7 +712,6 @@ static char *status_file;
static char *config_file; static char *config_file;
static int version_flag; static int version_flag;
static int help_flag; static int help_flag;
static char *keytab_str;
static char *port_str; static char *port_str;
static int detach_from_console; static int detach_from_console;
static int daemon_child = -1; static int daemon_child = -1;
@@ -699,8 +720,12 @@ static struct getargs args[] = {
{ "config-file", 'c', arg_string, &config_file, NULL, NULL }, { "config-file", 'c', arg_string, &config_file, NULL, NULL },
{ "realm", 'r', arg_string, &realm, NULL, NULL }, { "realm", 'r', arg_string, &realm, NULL, NULL },
{ "database", 'd', arg_string, &database, "database", "file"}, { "database", 'd', arg_string, &database, "database", "file"},
{ "no-keytab", 0, arg_flag, &no_keytab_flag,
"use externally refreshed cache", NULL },
{ "ccache", 0, arg_string, &ccache_str,
"client credentials", "CCACHE" },
{ "keytab", 'k', arg_string, &keytab_str, { "keytab", 'k', arg_string, &keytab_str,
"keytab to get authentication from", "kspec" }, "client credentials keytab", "KEYTAB" },
{ "time-lost", 0, arg_string, &server_time_lost, { "time-lost", 0, arg_string, &server_time_lost,
"time before server is considered lost", "time" }, "time before server is considered lost", "time" },
{ "status-file", 0, arg_string, &status_file, { "status-file", 0, arg_string, &status_file,
@@ -740,7 +765,7 @@ main(int argc, char **argv)
kadm5_server_context *server_context; kadm5_server_context *server_context;
kadm5_config_params conf; kadm5_config_params conf;
int master_fd; int master_fd;
krb5_ccache ccache; krb5_ccache ccache = NULL;
krb5_principal server; krb5_principal server;
char **files; char **files;
int optidx = 0; int optidx = 0;
@@ -858,7 +883,7 @@ main(int argc, char **argv)
if (ret) if (ret)
krb5_err(context, 1, ret, "db->close"); krb5_err(context, 1, ret, "db->close");
get_creds(context, keytab_str, &ccache, master); get_creds(context, &ccache, master);
ret = krb5_sname_to_principal (context, master, IPROP_NAME, ret = krb5_sname_to_principal (context, master, IPROP_NAME,
KRB5_NT_SRV_HST, &server); KRB5_NT_SRV_HST, &server);
@@ -923,8 +948,7 @@ main(int argc, char **argv)
if (auth_context) { if (auth_context) {
krb5_auth_con_free(context, auth_context); krb5_auth_con_free(context, auth_context);
auth_context = NULL; auth_context = NULL;
krb5_cc_destroy(context, ccache); get_creds(context, &ccache, master);
get_creds(context, keytab_str, &ccache, master);
} }
if (verbose) if (verbose)
krb5_warnx(context, "authenticating to master"); krb5_warnx(context, "authenticating to master");