hdb: Move virtual principals into HDB layer

This is a large commit that adds several features:

 - Revamps and moves virtual host-based service principal functionality
   from kdc/ to lib/hdb/ so that it may be automatically visible to
   lib/kadm5/, as well as kadmin(1)/kadmind(8) and ktutil(1).

   The changes are backwards-incompatible.

 - Completes support for documenting a service principal's supported
   enctypes in its HDB entry independently of its long-term keys.  This
   will reduce HDB bloat by not requiring that service principals have
   more long-term keys than they need just to document the service's
   supported enctypes.

 - Adds support for storing krb5.conf content in principals' HDB
   entries.  This may eventually be used for causing Heimdal KDC
   services to reconfigure primary/secondary roles automatically by
   discovering the configured primary in an HDB entry for the realm.

   For now this will be used to help reduce the amount of configuration
   needed by clients of an upcoming HTTP binding of the kadmin service.
This commit is contained in:
Nicolas Williams
2020-08-24 16:16:59 -05:00
parent ef06b94132
commit 5447b81fb1
50 changed files with 4017 additions and 530 deletions

View File

@@ -34,6 +34,9 @@
#include "kadmin_locl.h"
#include "kadmin-commands.h"
/* No useful password policies for namespaces */
#define NSPOLICY "default"
/*
* fetch the default principal corresponding to `princ'
*/
@@ -308,3 +311,209 @@ add_new_key(struct add_options *opt, int argc, char **argv)
free(kstuple);
return ret != 0;
}
static krb5_error_code
kstuple2etypes(kadm5_principal_ent_rec *rec,
int *maskp,
size_t nkstuple,
krb5_key_salt_tuple *kstuple)
{
krb5_error_code ret;
HDB_EncTypeList etypes;
krb5_data buf;
size_t len, i;
etypes.len = 0;
if ((etypes.val = calloc(nkstuple, sizeof(etypes.val[0]))) == NULL)
return krb5_enomem(context);
for (i = 0; i < nkstuple; i++)
etypes.val[i] = kstuple[i].ks_enctype;
ASN1_MALLOC_ENCODE(HDB_EncTypeList, buf.data, buf.length,
&etypes, &len, ret);
if (ret == 0)
add_tl(rec, KRB5_TL_ETYPES, &buf);
free(etypes.val);
if (ret == 0)
(*maskp) |= KADM5_TL_DATA;
return ret;
}
/*
* Add the namespace `name' to the database.
* Prompt for all data not given by the input parameters.
*/
static krb5_error_code
add_one_namespace(const char *name,
size_t nkstuple,
krb5_key_salt_tuple *kstuple,
const char *max_ticket_life,
const char *max_renewable_life,
const char *key_rotation_epoch,
const char *key_rotation_period,
const char *attributes)
{
krb5_error_code ret;
kadm5_principal_ent_rec princ;
kadm5_principal_ent_rec *default_ent = NULL;
krb5_principal princ_ent = NULL;
int mask = 0;
int default_mask = 0;
HDB_extension ext;
krb5_data buf;
const char *comp0;
const char *comp1;
time_t kre;
char pwbuf[1024];
krb5_deltat krp;
if (!key_rotation_epoch) {
krb5_warnx(context, "key rotation epoch defaulted to \"now\"");
key_rotation_epoch = "now";
}
if (!key_rotation_period) {
krb5_warnx(context, "key rotation period defaulted to \"5d\"");
key_rotation_period = "5d";
}
if ((ret = str2time_t(key_rotation_epoch, &kre)) != 0) {
krb5_warn(context, ret, "invalid rotation epoch: %s",
key_rotation_epoch);
return ret;
}
if (ret == 0 && (ret = str2deltat(key_rotation_period, &krp)) != 0) {
krb5_warn(context, ret, "invalid rotation period: %s",
key_rotation_period);
return ret;
}
if (ret == 0) {
memset(&princ, 0, sizeof(princ));
princ.kvno = 1;
ret = krb5_parse_name(context, name, &princ_ent);
if (ret)
krb5_warn(context, ret, "krb5_parse_name");
}
if (ret != 0)
return ret;
/*
* Check that namespace has exactly one component, and prepend
* WELLKNOWN/HOSTBASED-NAMESPACE
*/
if (krb5_principal_get_num_comp(context, princ_ent) != 2
|| (comp0 = krb5_principal_get_comp_string(context, princ_ent, 0)) == 0
|| (comp1 = krb5_principal_get_comp_string(context, princ_ent, 1)) == 0
|| *comp0 == 0 || *comp1 == 0
|| strcmp(comp0, "krbtgt") == 0)
krb5_warn(context, ret = EINVAL,
"namespaces must have exactly two non-empty components "
"like host-base principal names");
if (ret == 0)
ret = krb5_principal_set_comp_string(context, princ_ent, 2, comp0);
if (ret == 0)
ret = krb5_principal_set_comp_string(context, princ_ent, 3, comp1);
if (ret == 0)
ret = krb5_principal_set_comp_string(context, princ_ent, 0,
"WELLKNOWN");
if (ret == 0)
ret = krb5_principal_set_comp_string(context, princ_ent, 1,
HDB_WK_NAMESPACE);
/* Set up initial key rotation extension */
if (ret == 0) {
KeyRotation kr;
size_t size;
/* Setup key rotation metadata in a convenient way */
kr.flags = int2KeyRotationFlags(0);
kr.base_key_kvno = 1;
/*
* Avoid kvnos 0/1/2 which don't normally appear in fully created
* principals.
*/
kr.base_kvno = 3;
/* XXX: Sanity check */
kr.epoch = kre;
kr.period = krp;
memset(&ext, 0, sizeof(ext));
ext.mandatory = FALSE;
ext.data.element = choice_HDB_extension_data_key_rotation;
ext.data.u.key_rotation.len = 1;
ext.data.u.key_rotation.val = &kr;
ASN1_MALLOC_ENCODE(HDB_extension, buf.data, buf.length,
&ext, &size, ret);
add_tl(&princ, KRB5_TL_EXTENSION, &buf);
mask |= KADM5_TL_DATA;
}
if (ret == 0) {
princ.principal = princ_ent;
mask |= KADM5_PRINCIPAL | KADM5_KVNO;
ret = set_entry(context, &princ, &mask,
max_ticket_life, max_renewable_life,
"never", "never", attributes, NSPOLICY);
}
if (ret == 0)
ret = edit_entry(&princ, &mask, default_ent, default_mask);
if (ret == 0)
ret = kstuple2etypes(&princ, &mask, nkstuple, kstuple);
/* XXX Shouldn't need a password for this */
random_password(pwbuf, sizeof(pwbuf));
ret = kadm5_create_principal_3(kadm_handle, &princ, mask,
nkstuple, kstuple, pwbuf);
if (ret)
krb5_warn(context, ret, "kadm5_create_principal_3");
kadm5_free_principal_ent(kadm_handle, &princ); /* frees princ_ent */
if (default_ent)
kadm5_free_principal_ent(kadm_handle, default_ent);
memset(pwbuf, 0, sizeof(pwbuf));
return ret;
}
int
add_new_namespace(struct add_namespace_options *opt, int argc, char **argv)
{
krb5_error_code ret = 0;
krb5_key_salt_tuple *kstuple = NULL;
const char *enctypes;
size_t i, nkstuple;
if (argc < 1) {
fprintf(stderr, "at least one namespace name required\n");
return 1;
}
enctypes = opt->enctypes_string;
if (enctypes == NULL || enctypes[0] == '\0')
enctypes = krb5_config_get_string(context, NULL, "libdefaults",
"supported_enctypes", NULL);
if (enctypes == NULL || enctypes[0] == '\0')
enctypes = "aes128-cts-hmac-sha1-96";
ret = krb5_string_to_keysalts2(context, enctypes, &nkstuple, &kstuple);
if (ret) {
fprintf(stderr, "enctype(s) unknown\n");
return ret;
}
for (i = 0; i < argc; i++) {
ret = add_one_namespace(argv[i], nkstuple, kstuple,
opt->max_ticket_life_string,
opt->max_renewable_life_string,
opt->key_rotation_epoch_string,
opt->key_rotation_period_string,
opt->attributes_string);
if (ret) {
krb5_warn(context, ret, "adding namespace %s", argv[i]);
break;
}
}
free(kstuple);
return ret != 0;
}

View File

@@ -53,3 +53,47 @@ del_entry(void *opt, int argc, char **argv)
}
return ret != 0;
}
static int
do_del_ns_entry(krb5_principal nsp, void *data)
{
krb5_error_code ret;
krb5_principal p = NULL;
const char *comp0 = krb5_principal_get_comp_string(context, nsp, 0);
const char *comp1 = krb5_principal_get_comp_string(context, nsp, 1);
char *unsp = NULL;
if (krb5_principal_get_num_comp(context, nsp) != 2) {
(void) krb5_unparse_name(context, nsp, &unsp);
krb5_warn(context, ret = EINVAL, "Not a valid namespace name %s",
unsp ? unsp : "<Out of memory>");
return EINVAL;
}
ret = krb5_make_principal(context, &p,
krb5_principal_get_realm(context, nsp),
"WELLKNOWN", HDB_WK_NAMESPACE, NULL);
if (ret == 0)
ret = krb5_principal_set_comp_string(context, p, 2, comp0);
if (ret == 0)
ret = krb5_principal_set_comp_string(context, p, 3, comp1);
if (ret == 0)
ret = kadm5_delete_principal(kadm_handle, p);
krb5_free_principal(context, p);
free(unsp);
return ret;
}
int
del_namespace(void *opt, int argc, char **argv)
{
int i;
krb5_error_code ret = 0;
for(i = 0; i < argc; i++) {
ret = foreach_principal(argv[i], do_del_ns_entry, "del_ns", NULL);
if (ret)
break;
}
return ret != 0;
}

View File

@@ -61,6 +61,7 @@ static struct field_name {
{ "fail_auth_count", KADM5_FAIL_AUTH_COUNT, 0, 0, "Fail count", "Failed login count", RTBL_ALIGN_RIGHT },
{ "policy", KADM5_POLICY, 0, 0, "Policy", "Policy", 0 },
{ "keytypes", KADM5_KEY_DATA, 0, KADM5_PRINCIPAL | KADM5_KVNO, "Keytypes", "Keytypes", 0 },
{ "server-keytypes", KADM5_TL_DATA, KRB5_TL_ETYPES, 0, "Server keytypes", "Supported keytypes (servers)", 0 },
{ "password", KADM5_TL_DATA, KRB5_TL_PASSWORD, KADM5_KEY_DATA, "Password", "Password", 0 },
{ "pkinit-acl", KADM5_TL_DATA, KRB5_TL_PKINIT_ACL, 0, "PK-INIT ACL", "PK-INIT ACL", 0 },
{ "aliases", KADM5_TL_DATA, KRB5_TL_ALIASES, 0, "Aliases", "Aliases", 0 },
@@ -81,6 +82,8 @@ struct get_entry_data {
uint32_t mask;
uint32_t extra_mask;
struct field_info *chead, **ctail;
const char *krb5_config_fname;
uint32_t n;
};
static int
@@ -174,9 +177,59 @@ format_keytype(krb5_key_data *k, krb5_salt *def_salt, char *buf, size_t buf_len)
free(s);
}
static int
is_special_file(const char *fname)
{
#ifdef WIN32
if (strcasecmp(fname, "con") == 0 || strcasecmp(fname, "nul") == 0 ||
strcasecmp(fname, "aux") == 0 || strcasecmp(fname, "prn") == 0)
return 1;
if ((strncasecmp(fname, "com", sizeof("com") - 1) == 0 ||
strncasecmp(fname, "lpt", sizeof("lpt") - 1) == 0) &&
fname[sizeof("lpt")] >= '0' && fname[sizeof("lpt")] <= '9' &&
fname[sizeof("lpt") + 1] == '\0')
return 1;
#else
if (strncmp(fname, "/dev/", sizeof("/dev/") - 1) == 0)
return 1;
#endif
return 0;
}
static char *
write_krb5_config(krb5_tl_data *tl,
const char *fn,
uint32_t i)
{
char *s = NULL;
FILE *f = NULL;
if (fn == NULL)
return NULL;
if (i == 0 || is_special_file(fn))
s = strdup(fn);
else if (asprintf(&s, "%s-%u", fn, i) == -1)
s = NULL;
if (s == NULL)
krb5_err(context, 1, errno, "Out of memory");
/* rk_dumpdata() doesn't allow error checking :( */
if ((f = fopen(s, "w")) &&
fwrite(tl->tl_data_contents, tl->tl_data_length, 1, f) != 1)
krb5_warn(context, errno, "Could not write to %s", fn);
if (f && fclose(f))
krb5_warn(context, errno, "Could not write to %s", fn);
return s;
}
static void
format_field(kadm5_principal_ent_t princ, unsigned int field,
unsigned int subfield, char *buf, size_t buf_len, int condensed)
format_field(struct get_entry_data *data,
kadm5_principal_ent_t princ,
unsigned int field,
unsigned int subfield,
char *buf,
size_t buf_len,
int condensed)
{
switch(field) {
case KADM5_PRINCIPAL:
@@ -276,6 +329,31 @@ format_field(kadm5_principal_ent_t princ, unsigned int field,
(int)tl->tl_data_length,
(const char *)tl->tl_data_contents);
break;
case KRB5_TL_ETYPES: {
HDB_EncTypeList etypes;
size_t i, size;
char *str;
int ret;
ret = decode_HDB_EncTypeList(tl->tl_data_contents,
tl->tl_data_length,
&etypes, &size);
if (ret) {
snprintf(buf, buf_len, "failed to decode server etypes");
break;
}
buf[0] = '\0';
for (i = 0; i < etypes.len; i++) {
ret = krb5_enctype_to_string(context, etypes.val[i], &str);
if (ret == 0) {
if (i)
strlcat(buf, ",", buf_len);
strlcat(buf, str, buf_len);
}
}
free_HDB_EncTypeList(&etypes);
break;
}
case KRB5_TL_PKINIT_ACL: {
HDB_Ext_PKINIT_acl acl;
size_t size;
@@ -309,6 +387,16 @@ format_field(kadm5_principal_ent_t princ, unsigned int field,
free_HDB_Ext_PKINIT_acl(&acl);
break;
}
case KRB5_TL_KRB5_CONFIG: {
char *fname;
fname = write_krb5_config(tl, data->krb5_config_fname, data->n);
if (fname) {
strlcat(buf, fname, buf_len);
free(fname);
}
break;
}
case KRB5_TL_ALIASES: {
HDB_Ext_Aliases alias;
size_t size;
@@ -356,7 +444,8 @@ print_entry_short(struct get_entry_data *data, kadm5_principal_ent_t princ)
struct field_info *f;
for(f = data->chead; f != NULL; f = f->next) {
format_field(princ, f->ff->fieldvalue, f->ff->subvalue, buf, sizeof(buf), 1);
format_field(data, princ, f->ff->fieldvalue, f->ff->subvalue, buf,
sizeof(buf), 1);
rtbl_add_column_entry_by_id(data->table, f->ff->fieldvalue, buf);
}
}
@@ -374,7 +463,8 @@ print_entry_long(struct get_entry_data *data, kadm5_principal_ent_t princ)
width = w;
}
for(f = data->chead; f != NULL; f = f->next) {
format_field(princ, f->ff->fieldvalue, f->ff->subvalue, buf, sizeof(buf), 0);
format_field(data, princ, f->ff->fieldvalue, f->ff->subvalue, buf,
sizeof(buf), 0);
printf("%*s: %s\n", width, f->header ? f->header : f->ff->def_longheader, buf);
}
printf("\n");
@@ -391,13 +481,13 @@ do_get_entry(krb5_principal principal, void *data)
ret = kadm5_get_principal(kadm_handle, principal,
&princ,
e->mask | e->extra_mask);
if(ret)
return ret;
else {
(e->format)(e, &princ);
kadm5_free_principal_ent(kadm_handle, &princ);
if (ret == 0) {
(e->format)(e, &princ);
kadm5_free_principal_ent(kadm_handle, &princ);
}
return 0;
e->n++;
return ret;
}
static void
@@ -467,7 +557,7 @@ listit(const char *funcname, int argc, char **argv)
}
#define DEFAULT_COLUMNS_SHORT "principal,princ_expire_time,pw_expiration,last_pwd_change,max_life,max_rlife"
#define DEFAULT_COLUMNS_LONG "principal,princ_expire_time,pw_expiration,last_pwd_change,max_life,max_rlife,kvno,mkvno,last_success,last_failed,fail_auth_count,mod_time,mod_name,attributes,keytypes,pkinit-acl,aliases"
#define DEFAULT_COLUMNS_LONG "principal,princ_expire_time,pw_expiration,last_pwd_change,max_life,max_rlife,kvno,mkvno,last_success,last_failed,fail_auth_count,mod_time,mod_name,attributes,server-keytypes,keytypes,pkinit-acl,aliases"
static int
getit(struct get_options *opt, const char *name, int argc, char **argv)
@@ -493,6 +583,8 @@ getit(struct get_options *opt, const char *name, int argc, char **argv)
data.ctail = &data.chead;
data.mask = 0;
data.extra_mask = 0;
data.krb5_config_fname = opt->krb5_config_file_string;
data.n = 0;
if(opt->short_flag) {
data.table = rtbl_create();

View File

@@ -215,6 +215,128 @@ command = {
min_args = "1"
help = "Adds a principal to the database."
}
command = {
name = "add_namespace"
name = "add_ns"
function = "add_new_namespace"
option = {
long = "enctypes"
short = "e"
type = "string"
help = "encryption type(s)"
}
option = {
long = "max-ticket-life"
type = "string"
argument ="lifetime"
help = "max ticket lifetime"
}
option = {
long = "max-renewable-life"
type = "string"
argument = "lifetime"
help = "max renewable life"
}
option = {
long = "key-rotation-epoch"
type = "string"
argument = "time"
help = "absolute start time (or +timespec for relative to now with default unit of month)"
}
option = {
long = "key-rotation-period"
type = "string"
argument = "time"
help = "automatic key rotation period"
}
option = {
long = "attributes"
type = "string"
argument = "attributes"
help = "principal attributes"
}
argument = "principal..."
min_args = "1"
help = "Adds a namespace of virtual principals with derived keys to the database."
}
command = {
name = "modify_namespace"
name = "mod_ns"
function = "modify_namespace"
option = {
long = "enctypes"
short = "e"
type = "strings"
help = "encryption type(s)"
}
option = {
long = "max-ticket-life"
type = "string"
argument ="lifetime"
help = "max ticket lifetime"
}
option = {
long = "max-renewable-life"
type = "string"
argument = "lifetime"
help = "max renewable life"
}
option = {
long = "attributes"
type = "string"
argument = "attributes"
help = "principal attributes"
}
option = {
long = "krb5-config-file"
short = "C"
type = "string"
help = "filename to save the principal's krb5.confg in"
}
argument = "principal..."
min_args = "1"
help = "Modifies a namespace of virtual principals with derived keys to the database."
}
command = {
name = "modify_namespace_key_rotation"
name = "mod_ns_kr"
function = "modify_ns_kr"
option = {
long = "force"
short = "f"
type = "flag"
help = "change schedule even if it would revoke some extant tickets"
}
option = {
long = "keep-base-key"
short = "k"
type = "flag"
help = "keep current base key for new key rotation schedule"
}
option = {
long = "revoke-old"
short = "r"
type = "string"
argument = "time"
help = "delete base keys older than this to revoke old tickets"
}
option = {
long = "new-key-rotation-epoch"
type = "string"
argument = "time"
help = "new start time relative to now"
}
option = {
long = "new-key-rotation-period"
type = "string"
argument = "time"
help = "new automatic key rotation period"
}
argument = "principal..."
min_args = "1"
max_args = "1"
help = "Adds or changes new key rotation schedule for the given namespace."
}
command = {
name = "passwd"
name = "cpw"
@@ -276,6 +398,14 @@ command = {
min_args = "1"
help = "Deletes all principals matching the expressions."
}
command = {
name = "delete_namespace"
name = "del_ns"
function = "del_namespace"
argument = "principal..."
min_args = "1"
help = "Deletes the given virtual principal namespaces"
}
command = {
name = "del_enctype"
argument = "principal enctype..."
@@ -364,6 +494,12 @@ command = {
type = "string"
help = "columns to print for short output"
}
option = {
long = "krb5-config-file"
short = "C"
type = "string"
help = "filename to save the principal's krb5.confg in"
}
argument = "principal..."
min_args = "1"
help = "Shows information about principals matching the expressions."
@@ -378,6 +514,7 @@ command = {
}
command = {
name = "modify"
name = "mod"
function = "mod_entry"
option = {
long = "max-ticket-life"
@@ -440,6 +577,13 @@ command = {
argument = "policy"
help = "policy name"
}
option = {
long = "service-enctypes"
short = "e"
type = "strings"
argument = "enctype"
help = "set enctypes supported by service"
}
option = {
long = "hist-kvno-diff-clnt"
type = "integer"
@@ -454,6 +598,12 @@ command = {
help = "historic keys allowed for service"
default = "-1"
}
option = {
long = "krb5-config-file"
short = "C"
type = "string"
help = "krb5.conf to save in principal record"
}
argument = "principal"
min_args = "1"
max_args = "1"
@@ -507,6 +657,10 @@ command = {
type = "string"
help = "columns to print for short output"
}
option = {
long = "krb5-config-file"
type = "string"
}
argument = "principal..."
min_args = "1"
help = "Lists principals in a terse format. Equivalent to \"get -t\"."

View File

@@ -140,6 +140,55 @@ The only policy supported by Heimdal servers is
.Ql default .
.Ed
.Pp
.Nm add_namespace
.Ar Fl Fl key-rotation-epoch= Ns Ar time
.Ar Fl Fl key-rotation-period= Ns Ar time
.Op Fl Fl enctypes= Ns Ar string
.Op Fl Fl max-ticket-life= Ns Ar lifetime
.Op Fl Fl max-renewable-life= Ns Ar lifetime
.Op Fl Fl attributes= Ns Ar attributes
.Ar principal...
.Bd -ragged -offset indent
Adds a new namespace of virtual host-based or domain-based
principals to the database, whose keys will be automatically
derived from base keys stored in the namespace record, and which
keys will be rotated automatically.
The namespace names should look like
.Ar hostname@REALM
and these will match all host-based or domain-based service names
where hostname component of such a principal ends in the labels
of the hostname in the namespace name.
.Pp
For example,
.Ar bar.baz.example@BAZ.EXAMPLE
will match
.Ar host/foo.bar.baz.example@BAZ.EXAMPLE
but not
.Ar host/foobar.baz.example@BAZ.EXAMPLE .
.Pp
Note well that services are expected to
.Ar ext_keytab
or otherwise re-fetch their keytabs at least as often as one
quarter of the key rotation period, otherwise they risk not
having keys they need to decrypt tickets with.
.Pp
The epoch must be given as either an absolute time,
.Ar "now",
or as
.Ar "+<N>[<unit>]"
where
.Ar N
is a natural and
.Ar unit
is one "s", "m", "h", "day", "week", "month", defaulting to
"month".
The default key rotation period is
.Ar 7d .
The default enctypes is as for the
.Nm add
command.
.Ed
.Pp
.Nm add_enctype
.Op Fl r | Fl Fl random-key
.Ar principal enctypes...
@@ -213,6 +262,7 @@ behavior is the default if none of these are given.
.Op Fl s | Fl Fl short
.Op Fl t | Fl Fl terse
.Op Fl o Ar string | Fl Fl column-info= Ns Ar string
.Op Fl C Ar path | Fl Fl krb5-config-file= Ns Ar path
.Ar principal...
.Bd -ragged -offset indent
Lists the matching principals, short prints the result as a table,
@@ -229,6 +279,16 @@ The default terse output format is similar to
.Fl s o Ar principal= ,
just printing the names of matched principals.
.Pp
If
.Fl C
or
.Fl Fl krb5-config-file
is given and the principal has krb5 config file contents saved
in its HDB entry, then that will be saved in the given file.
Note that if multiple principals are requested, then the second,
third, and so on will have -1, -2, and so on appended to the
given filename unless the given filename is a device name.
.Pp
Possible column names include:
.Li principal ,
.Li princ_expire_time ,
@@ -260,15 +320,18 @@ and
.Op Fl Fl pw-expiration-time= Ns Ar time
.Op Fl Fl kvno= Ns Ar number
.Op Fl Fl policy= Ns Ar policy-name
.Op Fl C Ar path | Fl Fl krb5-config-file= Ns Ar path
.Ar principal...
.Bd -ragged -offset indent
Modifies certain attributes of a principal. If run without command
line options, you will be prompted. With command line options, it will
only change the ones specified.
.Pp
Only policy supported by Heimdal is
The only policy supported by Heimdal is
.Ql default .
.Pp
If a krb5 config file is given, it will be saved in the entry.
.Pp
Possible attributes are:
.Li new-princ ,
.Li support-desmd5 ,

View File

@@ -156,5 +156,9 @@ kadmind_loop (krb5_context, krb5_keytab, int);
int
handle_mit(krb5_context, void *, size_t, int);
/* mod.c */
void
add_tl(kadm5_principal_ent_rec *, int, krb5_data *);
#endif /* __ADMIN_LOCL_H__ */

View File

@@ -34,7 +34,7 @@
#include "kadmin_locl.h"
#include "kadmin-commands.h"
static void
void
add_tl(kadm5_principal_ent_rec *princ, int type, krb5_data *data)
{
krb5_tl_data *tl, **ptl;
@@ -185,6 +185,43 @@ add_pkinit_acl(krb5_context contextp, kadm5_principal_ent_rec *princ,
add_tl(princ, KRB5_TL_EXTENSION, &buf);
}
static krb5_error_code
add_etypes(krb5_context contextp,
kadm5_principal_ent_rec *princ,
struct getarg_strings *strings)
{
krb5_error_code ret = 0;
HDB_EncTypeList etypes;
krb5_data buf;
size_t i, size;
etypes.len = strings->num_strings;
if ((etypes.val = calloc(strings->num_strings,
sizeof(etypes.val[0]))) == NULL)
krb5_err(contextp, 1, ret, "Out of memory");
for (i = 0; i < strings->num_strings; i++) {
krb5_enctype etype;
ret = krb5_string_to_enctype(contextp, strings->strings[i], &etype);
if (ret) {
krb5_warn(contextp, ret, "Could not parse enctype %s",
strings->strings[i]);
return ret;
}
etypes.val[i] = etype;
}
if (ret == 0) {
ASN1_MALLOC_ENCODE(HDB_EncTypeList, buf.data, buf.length,
&etypes, &size, ret);
}
if (ret || buf.length != size)
abort();
add_tl(princ, KRB5_TL_ETYPES, &buf);
return 0;
}
static void
add_kvno_diff(krb5_context contextp, kadm5_principal_ent_rec *princ,
int is_svc_diff, krb5_kvno kvno_diff)
@@ -216,6 +253,35 @@ add_kvno_diff(krb5_context contextp, kadm5_principal_ent_rec *princ,
add_tl(princ, KRB5_TL_EXTENSION, &buf);
}
static void
add_krb5_config(kadm5_principal_ent_rec *princ, const char *fname)
{
HDB_extension ext;
krb5_data buf;
size_t size;
int ret;
memset(&ext, 0, sizeof(ext));
ext.mandatory = FALSE;
ext.data.element = choice_HDB_extension_data_krb5_config;
if ((ret = rk_undumpdata(fname,
&ext.data.u.krb5_config.data,
&ext.data.u.krb5_config.length))) {
krb5_warn(context, ret, "Could not read %s", fname);
return;
}
ASN1_MALLOC_ENCODE(HDB_extension, buf.data, buf.length,
&ext, &size, ret);
free_HDB_extension(&ext);
if (ret)
abort();
if (buf.length != size)
abort();
add_tl(princ, KRB5_TL_EXTENSION, &buf);
}
static int
do_mod_entry(krb5_principal principal, void *data)
{
@@ -240,9 +306,11 @@ do_mod_entry(krb5_principal principal, void *data)
e->attributes_string ||
e->policy_string ||
e->kvno_integer != -1 ||
e->service_enctypes_strings.num_strings ||
e->constrained_delegation_strings.num_strings ||
e->alias_strings.num_strings ||
e->pkinit_acl_strings.num_strings ||
e->krb5_config_file_string ||
e->hist_kvno_diff_clnt_integer != -1 ||
e->hist_kvno_diff_svc_integer != -1) {
ret = set_entry(context, &princ, &mask,
@@ -269,6 +337,10 @@ do_mod_entry(krb5_principal principal, void *data)
add_pkinit_acl(context, &princ, &e->pkinit_acl_strings);
mask |= KADM5_TL_DATA;
}
if (e->service_enctypes_strings.num_strings) {
ret = add_etypes(context, &princ, &e->service_enctypes_strings);
mask |= KADM5_TL_DATA;
}
if (e->hist_kvno_diff_clnt_integer != -1) {
add_kvno_diff(context, &princ, 0, e->hist_kvno_diff_clnt_integer);
mask |= KADM5_TL_DATA;
@@ -277,6 +349,10 @@ do_mod_entry(krb5_principal principal, void *data)
add_kvno_diff(context, &princ, 1, e->hist_kvno_diff_svc_integer);
mask |= KADM5_TL_DATA;
}
if (e->krb5_config_file_string) {
add_krb5_config(&princ, e->krb5_config_file_string);
mask |= KADM5_TL_DATA;
}
} else
ret = edit_entry(&princ, &mask, NULL, 0);
if(ret == 0) {
@@ -303,3 +379,280 @@ mod_entry(struct modify_options *opt, int argc, char **argv)
return ret != 0;
}
static int
do_mod_ns_entry(krb5_principal principal, void *data)
{
krb5_error_code ret;
kadm5_principal_ent_rec princ;
int mask = 0;
struct modify_namespace_options *e = data;
memset (&princ, 0, sizeof(princ));
ret = kadm5_get_principal(kadm_handle, principal, &princ,
KADM5_PRINCIPAL | KADM5_ATTRIBUTES |
KADM5_MAX_LIFE | KADM5_MAX_RLIFE |
KADM5_PRINC_EXPIRE_TIME |
KADM5_PW_EXPIRATION);
if(ret)
return ret;
if(e->max_ticket_life_string ||
e->max_renewable_life_string ||
e->attributes_string ||
e->enctypes_strings.num_strings ||
e->krb5_config_file_string) {
ret = set_entry(context, &princ, &mask, e->max_ticket_life_string,
e->max_renewable_life_string, NULL, NULL,
e->attributes_string, NULL);
if (e->enctypes_strings.num_strings) {
ret = add_etypes(context, &princ, &e->enctypes_strings);
mask |= KADM5_TL_DATA;
}
if (e->krb5_config_file_string) {
add_krb5_config(&princ, e->krb5_config_file_string);
mask |= KADM5_TL_DATA;
}
} else
ret = edit_entry(&princ, &mask, NULL, 0);
if(ret == 0) {
ret = kadm5_modify_principal(kadm_handle, &princ, mask);
if(ret)
krb5_warn(context, ret, "kadm5_modify_principal");
}
kadm5_free_principal_ent(kadm_handle, &princ);
return ret;
}
int
modify_namespace(struct modify_namespace_options *opt, int argc, char **argv)
{
krb5_error_code ret = 0;
int i;
for(i = 0; i < argc; i++) {
ret = foreach_principal(argv[i], do_mod_ns_entry, "mod_ns", opt);
if (ret)
break;
}
return ret != 0;
}
#if 0
struct modify_namespace_key_rotation_options {
int force_flag;
int keep_base_key_flag;
char* revoke_old_string;
char* new_key_rotation_epoch_string;
char* new_key_rotation_period_string;
};
#endif
static int
princ2kstuple(kadm5_principal_ent_rec *princ,
unsigned int kvno,
krb5_key_salt_tuple **kstuple,
size_t *nkstuple)
{
krb5_error_code ret = 0;
HDB_EncTypeList etypes;
krb5_tl_data *tl;
size_t i;
*kstuple = 0;
*nkstuple = 0;
etypes.len = 0;
etypes.val = 0;
for (tl = princ->tl_data; tl; tl = tl->tl_data_next) {
if (tl->tl_data_type != KRB5_TL_ETYPES || tl->tl_data_length < 0)
continue;
ret = decode_HDB_EncTypeList(tl->tl_data_contents, tl->tl_data_length,
&etypes, NULL);
if (ret)
break;
*nkstuple = etypes.len;
*kstuple = ecalloc(etypes.len, sizeof(kstuple[0][0]));
for (i = 0; i < etypes.len; i++) {
(*kstuple)[i].ks_enctype = etypes.val[i];
(*kstuple)[i].ks_salttype = 0;
}
return 0;
}
if (princ->n_key_data > 0) {
*kstuple = ecalloc(1, sizeof(kstuple[0][0]));
*nkstuple = 1;
for (i = 0; i < princ->n_key_data; i++) {
if (princ->key_data->key_data_kvno == kvno) {
(*kstuple)[0].ks_enctype = princ->key_data->key_data_type[0];
(*kstuple)[0].ks_salttype = princ->key_data->key_data_type[1];
return 0;
}
}
}
krb5_warnx(context, "Could not determine what enctypes to generate "
"keys for; recreate namespace?");
return EINVAL;
}
static int
randkey_kr(kadm5_principal_ent_rec *princ,
unsigned int old_kvno,
unsigned int kvno)
{
krb5_key_salt_tuple *kstuple = 0;
krb5_error_code ret = 0;
size_t nkstuple = 0;
/*
* We might be using kadm5clnt, so we'll use kadm5_randkey_principal_3(),
* which will generate new keys on the server side. This allows a race,
* but it will be detected by the key rotation update checks in lib/kadm5
* and lib/hdb.
*/
ret = princ2kstuple(princ, old_kvno, &kstuple, &nkstuple);
if (ret == 0)
ret = kadm5_randkey_principal_3(kadm_handle, princ->principal, 1,
nkstuple, kstuple, NULL, NULL);
free(kstuple);
return ret;
}
static int
do_mod_ns_kr(krb5_principal principal, void *data)
{
krb5_error_code ret;
kadm5_principal_ent_rec princ;
struct modify_namespace_key_rotation_options *e = data;
HDB_Ext_KeyRotation existing;
HDB_Ext_KeyRotation new_kr;
HDB_extension ext;
KeyRotation new_krs[3];
krb5_tl_data *tl;
krb5_data d;
time_t now = time(NULL);
size_t size;
int freeit = 0;
d.data = 0;
d.length = 0;
new_kr.len = 0;
new_kr.val = new_krs;
ext.mandatory = 0;
ext.data.element = choice_HDB_extension_data_key_rotation;
ext.data.u.key_rotation.len = 0;
ext.data.u.key_rotation.val = 0;
existing.len = 0;
existing.val = 0;
memset(&new_krs, 0, sizeof(new_krs));
memset(&princ, 0, sizeof(princ));
if (e->force_flag || e->revoke_old_string) {
krb5_warnx(context, "--force and --revoke-old not implemented yet");
return ENOTSUP;
}
ret = kadm5_get_principal(kadm_handle, principal, &princ,
KADM5_PRINCIPAL | KADM5_KVNO |
KADM5_KEY_DATA | KADM5_TL_DATA);
if (ret == 0) {
freeit = 1;
for (tl = princ.tl_data; tl; tl = tl->tl_data_next) {
if (tl->tl_data_type != KRB5_TL_KRB5_CONFIG)
continue;
ret = decode_HDB_Ext_KeyRotation(tl->tl_data_contents,
tl->tl_data_length, &existing, NULL);
if (ret) {
krb5_warn(context, ret, "unable to decode existing key "
"rotation schedule");
kadm5_free_principal_ent(kadm_handle, &princ);
return ret;
}
}
if (!existing.len) {
krb5_warnx(context, "no key rotation schedule; "
"re-create namespace?");
kadm5_free_principal_ent(kadm_handle, &princ);
return EINVAL;
}
}
if (ret) {
krb5_warn(context, ret, "No such namespace");
kadm5_free_principal_ent(kadm_handle, &princ);
return ret;
}
if (existing.len > 1)
new_kr.val[1] = existing.val[0];
if (existing.len > 2)
new_kr.val[2] = existing.val[1];
new_kr.val[0].flags = existing.val[0].flags;
new_kr.val[0].base_kvno = princ.kvno + 2; /* XXX Compute better */
new_kr.val[0].base_key_kvno = existing.val[0].base_key_kvno + 1;
if (e->new_key_rotation_epoch_string) {
if ((ret = str2time_t(e->new_key_rotation_epoch_string,
&new_kr.val[0].epoch)))
krb5_warn(context, ret, "Invalid epoch specification: %s",
e->new_key_rotation_epoch_string);
} else {
new_kr.val[0].epoch = existing.val[0].epoch +
existing.val[0].period * (princ.kvno - new_kr.val[0].base_kvno);
}
if (ret == 0 && e->new_key_rotation_period_string) {
time_t t;
if ((ret = str2time_t(e->new_key_rotation_period_string, &t)))
krb5_warn(context, ret, "Invalid period specification: %s",
e->new_key_rotation_period_string);
else
new_kr.val[0].period = t;
} else {
new_kr.val[0].period = existing.val[0].period +
existing.val[0].period * (princ.kvno - new_kr.val[0].base_kvno);
}
if (new_kr.val[0].epoch < now) {
krb5_warnx(context, "New epoch cannot be in the past");
ret = EINVAL;
}
if (new_kr.val[0].epoch < 30) {
krb5_warnx(context, "New period cannot be less than 30s");
ret = EINVAL;
}
if (ret == 0)
ret = randkey_kr(&princ, princ.kvno, new_kr.val[0].base_key_kvno);
ext.data.u.key_rotation = new_kr;
if (ret == 0)
ASN1_MALLOC_ENCODE(HDB_extension, d.data, d.length,
&ext, &size, ret);
if (ret == 0)
add_tl(&princ, KRB5_TL_EXTENSION, &d);
if (ret == 0) {
ret = kadm5_modify_principal(kadm_handle, &princ,
KADM5_PRINCIPAL | KADM5_TL_DATA);
if (ret)
krb5_warn(context, ret, "Could not update namespace");
}
krb5_data_free(&d);
free_HDB_Ext_KeyRotation(&existing);
if (freeit)
kadm5_free_principal_ent(kadm_handle, &princ);
return ret;
}
int
modify_ns_kr(struct modify_namespace_key_rotation_options *opt,
int argc,
char **argv)
{
krb5_error_code ret = 0;
int i;
for(i = 0; i < argc; i++) {
ret = foreach_principal(argv[i], do_mod_ns_kr, "mod_ns", opt);
if (ret)
break;
}
return ret != 0;
return 0;
}