From ba98690a0a98536ac3f0c12b96575f6945711838 Mon Sep 17 00:00:00 2001 From: Nicolas Williams Date: Wed, 6 Oct 2021 21:59:43 -0500 Subject: [PATCH] kadmin: Add add_alias, del_alias --- kadmin/del.c | 114 +++++++++++++++++++++++++++ kadmin/kadmin-commands.in | 14 ++++ kadmin/kadmin.1 | 62 +++++++++++++++ kadmin/kadmin_locl.h | 3 + kadmin/mod.c | 159 ++++++++++++++++++++++++++++++++++++++ kadmin/util.c | 2 +- tests/db/check-aliases.in | 29 ++++++- 7 files changed, 378 insertions(+), 5 deletions(-) diff --git a/kadmin/del.c b/kadmin/del.c index b2ed73500..089ee8b0d 100644 --- a/kadmin/del.c +++ b/kadmin/del.c @@ -97,3 +97,117 @@ del_namespace(void *opt, int argc, char **argv) } return ret != 0; } + +int +del_alias(void *opt, int argc, char **argv) +{ + krb5_error_code ret; + size_t i; + + + if (argc < 1) { + krb5_warnx(context, "No aliases given"); + return 1; + } + + for (; argc; argc--, argv++) { + kadm5_principal_ent_rec princ; + krb5_principal p; + HDB_Ext_Aliases *a; + HDB_extension ext; + krb5_tl_data *tl; + krb5_data d; + + if ((ret = krb5_parse_name(context, argv[0], &p))) { + krb5_warn(context, ret, "Invalid principal: %s", argv[0]); + return 1; + } + + memset(&princ, 0, sizeof(princ)); + ret = kadm5_get_principal(kadm_handle, p, &princ, + KADM5_PRINCIPAL_NORMAL_MASK | KADM5_TL_DATA); + if (ret) { + krb5_warn(context, ret, "Principal alias not found %s", argv[0]); + continue; + } + + if (krb5_principal_compare(context, p, princ.principal)) { + krb5_warn(context, ret, "Not deleting principal %s because it is " + "not an alias; use 'delete' to delete the principal", + argv[0]); + continue; + } + + a = &ext.data.u.aliases; + a->case_insensitive = 0; + a->aliases.len = 0; + a->aliases.val = 0; + if ((tl = get_tl(&princ, KRB5_TL_ALIASES)) == NULL) { + krb5_warnx(context, "kadm5_get_principal() found principal %s but " + "not its aliases", argv[0]); + kadm5_free_principal_ent(kadm_handle, &princ); + krb5_free_principal(context, p); + return 1; + } + + ret = decode_HDB_Ext_Aliases(tl->tl_data_contents, tl->tl_data_length, + a, NULL); + if (ret) { + krb5_warn(context, ret, "Principal alias list could not be decoded"); + kadm5_free_principal_ent(kadm_handle, &princ); + krb5_free_principal(context, p); + return 1; + } + + /* + * Remove alias, but also, don't assume it appears only once in aliases + * list. + */ + i = 0; + while (i < a->aliases.len) { + if (!krb5_principal_compare(context, p, &a->aliases.val[i])) { + i++; + continue; + } + free_Principal(&a->aliases.val[i]); + if (i + 1 < a->aliases.len) + memmove(&a->aliases.val[i], + &a->aliases.val[i + 1], + sizeof(a->aliases.val[i]) * (a->aliases.len - (i + 1))); + if (a->aliases.len) + a->aliases.len--; + continue; + } + + krb5_data_zero(&d); + ext.data.element = choice_HDB_extension_data_aliases; + ext.mandatory = 0; + if (ret == 0) + ASN1_MALLOC_ENCODE(HDB_extension, d.data, d.length, &ext, &i, ret); + free_HDB_Ext_Aliases(a); + if (ret == 0) { + int16_t len = d.length; + + if (len < 0 || d.length != (size_t)len) { + krb5_warnx(context, "Too many aliases; does not fit in 32767 bytes"); + ret = EOVERFLOW; + } else { + add_tl(&princ, KRB5_TL_EXTENSION, &d); + krb5_data_zero(&d); + } + } + if (ret == 0) { + ret = kadm5_modify_principal(kadm_handle, &princ, + KADM5_PRINCIPAL | KADM5_TL_DATA); + if (ret) + krb5_warn(context, ret, "kadm5_modify_principal"); + } + + kadm5_free_principal_ent(kadm_handle, &princ); + krb5_free_principal(context, p); + krb5_data_free(&d); + p = NULL; + } + + return ret == 0 ? 0 : 1; +} diff --git a/kadmin/kadmin-commands.in b/kadmin/kadmin-commands.in index 505aac14b..e8a1e8a08 100644 --- a/kadmin/kadmin-commands.in +++ b/kadmin/kadmin-commands.in @@ -609,6 +609,20 @@ command = { max_args = "1" help = "Modifies some attributes of the specified principal." } +command = { + name = "add_alias" + function = "add_alias" + argument = "principal" + min_args = "2" + help = "Add one or more aliases to the given principal." +} +command = { + name = "del_alias" + function = "del_alias" + argument = "principal" + min_args = "1" + help = "Delete one or more aliases without deleting their canonical principals." +} command = { name = "prune" argument = "principal" diff --git a/kadmin/kadmin.1 b/kadmin/kadmin.1 index 09381a6b3..91b717465 100644 --- a/kadmin/kadmin.1 +++ b/kadmin/kadmin.1 @@ -140,6 +140,37 @@ The only policy supported by Heimdal servers is .Ql default . .Ed .Pp +.Nm add_alias +.Ar principal +.Ar alias... +.Bd -ragged -offset indent +Adds one or more aliases to the given principal. +.Pp +When a client requests a service ticket for a service principal +name that is an alias of a principal in a different realm, the +TGS will return a referral to that realm. +This compares favorably to using +.Ar [domain_realm] +entries in the KDC's +.Ar krb5.conf , +but may be managed via the +.Nm kadmin +command and its +.Nm add_alias +and +.Nm del_alias +sub-commands rather than having to edit the KDC's configuration +file and having to restart the KDC. +.Pp +However, there is currently no way to alias namespaces via HDB +entry aliases. +To issue referrals for entire namespaces use the +.Ar [domain_realm] +section of the KDC's +.Ar krb5.conf +file. +.Ed +.Pp .Nm add_namespace .Ar Fl Fl key-rotation-epoch= Ns Ar time .Ar Fl Fl key-rotation-period= Ns Ar time @@ -202,6 +233,19 @@ supported. .Ar principal... .Bd -ragged -offset indent Removes a principal. +It is an error to delete an alias. +To remove a principal's alias or aliases, use the +.Nm del_alias +command. +To remove a principal given an alias, first +.Nm get +the principal to get its canonical name and then delete that. +.Ed +.Pp +.Nm del_alias +.Ar alias... +.Bd -ragged -offset indent +Deletes the given aliases, but not their canonical principals. .Ed .Pp .Nm del_enctype @@ -320,6 +364,7 @@ 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 Fl alias= Ns Ar alias-name .Op Fl C Ar path | Fl Fl krb5-config-file= Ns Ar path .Ar principal... .Bd -ragged -offset indent @@ -327,6 +372,23 @@ 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 +The +.Fl Fl alias= Ns Ar alias-name +option may be given multiple times, which will set the complete +list of aliases for the principal. +Use the +.Nm add_alias +command instead to add an alias without having to list all +existing aliases to keep. +.Pp +The +.Fl Fl alias= +option without a value allows the user to set an empty list of +aliases. +Use the +.Nm del_alias +command to delete one or more aliases. +.Pp The only policy supported by Heimdal is .Ql default . .Pp diff --git a/kadmin/kadmin_locl.h b/kadmin/kadmin_locl.h index ef9420f16..d76265b6c 100644 --- a/kadmin/kadmin_locl.h +++ b/kadmin/kadmin_locl.h @@ -161,4 +161,7 @@ handle_mit(krb5_context, void *, size_t, int, int); void add_tl(kadm5_principal_ent_rec *, int, krb5_data *); +krb5_tl_data * +get_tl(kadm5_principal_ent_rec *, int); + #endif /* __ADMIN_LOCL_H__ */ diff --git a/kadmin/mod.c b/kadmin/mod.c index 005cc277f..9541c6efc 100644 --- a/kadmin/mod.c +++ b/kadmin/mod.c @@ -45,6 +45,9 @@ add_tl(kadm5_principal_ent_rec *princ, int type, krb5_data *data) tl->tl_data_length = data->length; tl->tl_data_contents = data->data; + if (tl->tl_data_length < 0 || data->length != (size_t)tl->tl_data_length) + errx(1, "TL data overflow"); + princ->n_tl_data++; ptl = &princ->tl_data; while (*ptl != NULL) @@ -54,6 +57,20 @@ add_tl(kadm5_principal_ent_rec *princ, int type, krb5_data *data) return; } +/* + * Find a TL data of type KRB5_TL_EXTENSION that has an extension of type + * `etype' in it. + */ +krb5_tl_data * +get_tl(kadm5_principal_ent_rec *princ, int type) +{ + krb5_tl_data *tl = princ->tl_data; + + while (tl && tl->tl_data_type != type) + tl = tl->tl_data_next; + return tl; +} + static void add_constrained_delegation(krb5_context contextp, kadm5_principal_ent_rec *princ, @@ -656,3 +673,145 @@ modify_ns_kr(struct modify_namespace_key_rotation_options *opt, return ret != 0; return 0; } + +#define princ_realm(P) ((P)->realm) +#define princ_num_comp(P) ((P)->name.name_string.len) +#define princ_ncomp(P, N) ((P)->name.name_string.val[(N)]) + +static int +princ_cmp(const void *a, const void *b) +{ + krb5_const_principal pa = a; + krb5_const_principal pb = b; + size_t i; + int r; + + r = strcmp(princ_realm(pa), princ_realm(pb)); + if (r == 0) + r = princ_num_comp(pa) - princ_num_comp(pb); + for (i = 0; r == 0 && i < princ_num_comp(pa); i++) + r = strcmp(princ_ncomp(pa, i), princ_ncomp(pb, i)); + return r; +} + +/* Sort and remove dups */ +static void +uniq(HDB_Ext_Aliases *a) +{ + size_t i = 0; + + qsort(a->aliases.val, a->aliases.len, sizeof(a->aliases.val[0]), + princ_cmp); + + /* While there are at least two principals left to look at... */ + while (i + 1 < a->aliases.len) { + if (princ_cmp(&a->aliases.val[i], &a->aliases.val[i + 1])) { + /* ...if they are different, increment i and loop */ + i++; + continue; + } + /* ...else drop the one on the right and loop w/o incrementing i */ + free_Principal(&a->aliases.val[i + 1]); + if (i + 2 < a->aliases.len) + memmove(&a->aliases.val[i + 1], + &a->aliases.val[i + 2], + sizeof(a->aliases.val[i + 1]) * (a->aliases.len - (i + 2))); + a->aliases.len--; + } +} + +int +add_alias(void *opt, int argc, char **argv) +{ + kadm5_principal_ent_rec princ; + krb5_error_code ret; + krb5_principal p = NULL; + HDB_Ext_Aliases *a; + HDB_extension ext; + krb5_tl_data *tl = NULL; + krb5_data d; + size_t i; + + memset(&princ, 0, sizeof(princ)); + krb5_data_zero(&d); + + if (argc < 2) { + krb5_warnx(context, "Principal not given"); + return 1; + } + ret = krb5_parse_name(context, argv[0], &p); + if (ret) { + krb5_warn(context, ret, "Invalid principal: %s", argv[0]); + return 1; + } + + ret = kadm5_get_principal(kadm_handle, p, &princ, + KADM5_PRINCIPAL_NORMAL_MASK | KADM5_TL_DATA); + if (ret) { + krb5_warn(context, ret, "Principal not found %s", argv[0]); + return 1; + } + krb5_free_principal(context, p); + p = NULL; + + a = &ext.data.u.aliases; + a->case_insensitive = 0; + a->aliases.len = 0; + a->aliases.val = 0; + if ((tl = get_tl(&princ, KRB5_TL_ALIASES))) { + ret = decode_HDB_Ext_Aliases(tl->tl_data_contents, tl->tl_data_length, + a, NULL); + if (ret) { + kadm5_free_principal_ent(kadm_handle, &princ); + krb5_warn(context, ret, "Principal has invalid aliases extension " + "contents: %s", argv[0]); + return 1; + } + } + + argv++; + argc--; + + a->aliases.val = realloc(a->aliases.val, + sizeof(a->aliases.val[0]) * (a->aliases.len + argc)); + if (a->aliases.val == NULL) + krb5_err(context, 1, errno, "Out of memory"); + for (i = 0; ret == 0 && i < argc; i++) { + ret = krb5_parse_name(context, argv[i], &p); + if (ret) { + krb5_warn(context, ret, "krb5_parse_name"); + break; + } + ret = copy_Principal(p, &a->aliases.val[a->aliases.len]); + krb5_free_principal(context, p); + if (ret == 0) + a->aliases.len++; + } + uniq(a); + + ext.data.element = choice_HDB_extension_data_aliases; + ext.mandatory = 0; + if (ret == 0) + ASN1_MALLOC_ENCODE(HDB_extension, d.data, d.length, &ext, &i, ret); + free_HDB_extension(&ext); + if (ret == 0) { + int16_t len = d.length; + + if (len < 0 || d.length != (size_t)len) { + krb5_warnx(context, "Too many aliases; does not fit in 32767 bytes"); + ret = EOVERFLOW; + } + } + if (ret == 0) { + add_tl(&princ, KRB5_TL_EXTENSION, &d); + krb5_data_zero(&d); + ret = kadm5_modify_principal(kadm_handle, &princ, + KADM5_PRINCIPAL | KADM5_TL_DATA); + if (ret) + krb5_warn(context, ret, "kadm5_modify_principal"); + } + + kadm5_free_principal_ent(kadm_handle, &princ); + krb5_data_free(&d); + return ret == 0 ? 0 : 1; +} diff --git a/kadmin/util.c b/kadmin/util.c index 3bedaf9f4..7a999d358 100644 --- a/kadmin/util.c +++ b/kadmin/util.c @@ -635,8 +635,8 @@ foreach_principal(const char *exp_str, } ret = (*func)(princ_ent, data); if(ret) { - krb5_clear_error_message(context); krb5_warn(context, ret, "%s %s", funcname, princs[i]); + krb5_clear_error_message(context); if (saved_ret == 0) saved_ret = ret; } diff --git a/tests/db/check-aliases.in b/tests/db/check-aliases.in index 5601905e4..b5a1069d8 100644 --- a/tests/db/check-aliases.in +++ b/tests/db/check-aliases.in @@ -70,7 +70,14 @@ ${kadmin} modify --alias=foo-alias1@${R} --alias=foo-alias2@${R} foo@${R} || exi echo "Adding bar" ${kadmin} add -p foo --use-defaults bar@${R} || exit 1 -${kadmin} modify --alias=bar-alias1@${R} bar@${R} || exit 1 +${kadmin} add_alias bar@${R} bar-alias1@${R} bar-alias2@${R} || exit 1 +${kadmin} add_alias bar@${R} bar-alias4@${R} bar-alias3@${R} || exit 1 +${kadmin} get -o principal bar@${R} | grep "Principal:.bar@${R}" >/dev/null || exit 1 +${kadmin} get -o principal bar-alias1@${R} | grep "Principal:.bar@${R}" >/dev/null || exit 1 +${kadmin} get -o aliases bar@${R} | grep "Aliases:.*bar-alias1@${R}" >/dev/null || exit 1 +${kadmin} get -o aliases bar@${R} | grep "Aliases:.*bar-alias2@${R}" >/dev/null || exit 1 +${kadmin} get -o aliases bar@${R} | grep "Aliases:.*bar-alias3@${R}" >/dev/null || exit 1 +${kadmin} get -o aliases bar@${R} | grep "Aliases:.*bar-alias4@${R}" >/dev/null || exit 1 echo "Baz does not exists" @@ -85,11 +92,19 @@ ${kadmin} rename bar${R} foo${R} 2>/dev/null && exit 1 ${kadmin} rename baz${R} foo-alias1${R} 2>/dev/null && exit 1 ${kadmin} rename baz${R} foo${R} 2>/dev/null && exit 1 -echo "Delete alias" +echo "Delete alias (must fail)" ${kadmin} delete foo-alias1${R} 2>/dev/null && exit 1 ${kadmin} delete bar-alias1${R} 2>/dev/null && exit 1 ${kadmin} delete baz-alias1${R} 2>/dev/null && exit 1 +echo "Delete aliases with del_alias (must succeed)" +${kadmin} del_alias bar-alias2@${R} bar-alias3@${R} bar-alias4@${R} || exit 1 +${kadmin} get -o principal bar@${R} | grep "Principal:.bar@${R}" >/dev/null || exit 1 +${kadmin} get -o aliases bar@${R} | grep "Aliases:.*bar-alias1@${R}" >/dev/null || exit 1 +${kadmin} get -o aliases bar@${R} | grep "Aliases:.*bar-alias2@${R}" >/dev/null && exit 1 +${kadmin} get -o aliases bar@${R} | grep "Aliases:.*bar-alias3@${R}" >/dev/null && exit 1 +${kadmin} get -o aliases bar@${R} | grep "Aliases:.*bar-alias4@${R}" >/dev/null && exit 1 + echo "Delete" ${kadmin} delete bar@${R} || exit 1 ${kadmin} delete bar@${R} 2>/dev/null && exit 1 @@ -99,6 +114,13 @@ echo "Add alias to deleted name" ${kadmin} modify --alias=bar-alias1@${R} foo@${R} || exit 1 ${kadmin} modify --alias=bar@${R} foo@${R} || exit 1 ${kadmin} modify --alias=bar@${R} --alias=baz@${R} foo@${R} || exit 1 +${kadmin} get -o principal foo@${R} | grep "Principal:.foo@${R}" >/dev/null || exit 1 +${kadmin} get -o principal bar@${R} | grep "Principal:.foo@${R}" >/dev/null || exit 1 +${kadmin} get -o principal baz@${R} | grep "Principal:.foo@${R}" >/dev/null || exit 1 +${kadmin} get -o aliases foo@${R} |grep "Aliases:.*bar@${R}" >/dev/null || exit 1 +${kadmin} get -o aliases foo@${R} |grep "Aliases:.*baz@${R}" >/dev/null || exit 1 +${kadmin} get -o aliases foo@${R} |grep "Aliases:.*bar-alias1@${R}" >/dev/null && exit 1 +${kadmin} get bar-alias1@${R} 2>/dev/null && exit 1 echo "Rename over self alias key" ${kadmin} rename foo@${R} foo-alias1@${R} 2>/dev/null && exit 1 @@ -109,5 +131,4 @@ ${kadmin} modify --alias=foo foo-alias1@${R} || exit 1 echo "Doing database check" ${kadmin} check ${R} || exit 1 - -exit $ec +exit 0