ktutil: Add import command and other improvements
- Add an import command that imports JSON as output by `ktutil list --json --keys`. This is enables one to filter/edit keytabs with jq! - Add a `merge` alias for the `copy` command, since that's effectively what it does. - Add a `--copy-duplicates` option to the `copy`/`merge` command. - Add a `--no-create` option to the `get` command. - Add a `--no-change-keys` option to the `get` command. - Make `add` complain if it can't finish writing to the keytab.
This commit is contained in:
178
admin/add.c
178
admin/add.c
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997-2005 Kungliga Tekniska Högskolan
|
||||
* Copyright (c) 1997-2022 Kungliga Tekniska Högskolan
|
||||
* (Royal Institute of Technology, Stockholm, Sweden).
|
||||
* All rights reserved.
|
||||
*
|
||||
@ -32,6 +32,8 @@
|
||||
*/
|
||||
|
||||
#include "ktutil_locl.h"
|
||||
#include <heimbase.h>
|
||||
#include <base64.h>
|
||||
|
||||
RCSID("$Id$");
|
||||
|
||||
@ -153,6 +155,178 @@ kt_add(struct add_options *opt, int argc, char **argv)
|
||||
krb5_warn(context, ret, "add");
|
||||
out:
|
||||
krb5_kt_free_entry(context, &entry);
|
||||
krb5_kt_close(context, keytab);
|
||||
if (ret == 0) {
|
||||
ret = krb5_kt_close(context, keytab);
|
||||
if (ret)
|
||||
krb5_warn(context, ret, "Could not write the keytab");
|
||||
} else {
|
||||
krb5_kt_close(context, keytab);
|
||||
}
|
||||
return ret != 0;
|
||||
}
|
||||
|
||||
/* We might be reading from a pipe, so we can't use rk_undumpdata() */
|
||||
static char *
|
||||
read_file(FILE *f)
|
||||
{
|
||||
size_t alloced;
|
||||
size_t len = 0;
|
||||
size_t bytes;
|
||||
char *res, *end, *p;
|
||||
|
||||
if ((res = malloc(1024)) == NULL)
|
||||
err(1, "Out of memory");
|
||||
alloced = 1024;
|
||||
|
||||
end = res + alloced;
|
||||
p = res;
|
||||
do {
|
||||
if (p == end) {
|
||||
char *tmp;
|
||||
|
||||
if ((tmp = realloc(res, alloced + (alloced > 1))) == NULL)
|
||||
err(1, "Out of memory");
|
||||
alloced += alloced > 1;
|
||||
p = tmp + (p - res);
|
||||
res = tmp;
|
||||
end = res + alloced;
|
||||
}
|
||||
bytes = fread(p, 1, end - p, f);
|
||||
len += bytes;
|
||||
p += bytes;
|
||||
} while (bytes && !feof(f) && !ferror(f));
|
||||
|
||||
if (ferror(f))
|
||||
errx(1, "Could not read all input");
|
||||
if (p == end) {
|
||||
char *tmp;
|
||||
|
||||
if ((tmp = strndup(res, len)) == NULL)
|
||||
err(1, "Out of memory");
|
||||
free(res);
|
||||
res = tmp;
|
||||
}
|
||||
if (strlen(res) != len)
|
||||
err(1, "Embedded NULs in input!");
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
json2keytab_entry(heim_dict_t d, krb5_keytab kt, size_t idx)
|
||||
{
|
||||
krb5_keytab_entry e;
|
||||
krb5_error_code ret;
|
||||
heim_object_t v;
|
||||
uint64_t u;
|
||||
int64_t i;
|
||||
char *buf = NULL;
|
||||
|
||||
memset(&e, 0, sizeof(e));
|
||||
|
||||
v = heim_dict_get_value(d, HSTR("timestamp"));
|
||||
if (heim_get_tid(v) != HEIM_TID_NUMBER)
|
||||
goto bad;
|
||||
u = heim_number_get_long(v);
|
||||
e.timestamp = u;
|
||||
if (u != (uint64_t)e.timestamp)
|
||||
goto bad;
|
||||
|
||||
v = heim_dict_get_value(d, HSTR("kvno"));
|
||||
if (heim_get_tid(v) != HEIM_TID_NUMBER)
|
||||
goto bad;
|
||||
i = heim_number_get_long(v);
|
||||
e.vno = i;
|
||||
if (i != (int64_t)e.vno)
|
||||
goto bad;
|
||||
|
||||
v = heim_dict_get_value(d, HSTR("enctype_number"));
|
||||
if (heim_get_tid(v) != HEIM_TID_NUMBER)
|
||||
goto bad;
|
||||
i = heim_number_get_long(v);
|
||||
e.keyblock.keytype = i;
|
||||
if (i != (int64_t)e.keyblock.keytype)
|
||||
goto bad;
|
||||
|
||||
v = heim_dict_get_value(d, HSTR("key"));
|
||||
if (heim_get_tid(v) != HEIM_TID_STRING)
|
||||
goto bad;
|
||||
{
|
||||
const char *s = heim_string_get_utf8(v);
|
||||
int declen;
|
||||
|
||||
if ((buf = malloc(strlen(s))) == NULL)
|
||||
err(1, "Out of memory");
|
||||
declen = rk_base64_decode(s, buf);
|
||||
if (declen < 0)
|
||||
goto bad;
|
||||
e.keyblock.keyvalue.data = buf;
|
||||
e.keyblock.keyvalue.length = declen;
|
||||
}
|
||||
|
||||
v = heim_dict_get_value(d, HSTR("principal"));
|
||||
if (heim_get_tid(v) != HEIM_TID_STRING)
|
||||
goto bad;
|
||||
ret = krb5_parse_name(context, heim_string_get_utf8(v), &e.principal);
|
||||
if (ret == 0)
|
||||
ret = krb5_kt_add_entry(context, kt, &e);
|
||||
|
||||
/* For now, ignore aliases; besides, they're never set anywhere in-tree */
|
||||
|
||||
if (ret)
|
||||
krb5_warn(context, ret,
|
||||
"Could not parse or write keytab entry %lu",
|
||||
(unsigned long)idx);
|
||||
bad:
|
||||
krb5_free_principal(context, e.principal);
|
||||
}
|
||||
|
||||
int
|
||||
kt_import(void *opt, int argc, char **argv)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
krb5_keytab kt;
|
||||
heim_object_t o;
|
||||
heim_error_t json_err = NULL;
|
||||
heim_json_flags_t flags = HEIM_JSON_F_STRICT;
|
||||
FILE *f = argc == 0 ? stdin : fopen(argv[0], "r");
|
||||
size_t alen, i;
|
||||
char *json;
|
||||
|
||||
if (f == NULL)
|
||||
err(1, "Could not open file %s", argv[0]);
|
||||
|
||||
json = read_file(f);
|
||||
o = heim_json_create(json, 10, flags, &json_err);
|
||||
free(json);
|
||||
if (o == NULL) {
|
||||
if (json_err != NULL) {
|
||||
o = heim_error_copy_string(json_err);
|
||||
if (o)
|
||||
errx(1, "Could not parse JSON: %s", heim_string_get_utf8(o));
|
||||
}
|
||||
errx(1, "Could not parse JSON");
|
||||
}
|
||||
|
||||
if (heim_get_tid(o) != HEIM_TID_ARRAY)
|
||||
errx(1, "JSON text must be an array");
|
||||
|
||||
alen = heim_array_get_length(o);
|
||||
if (alen == 0)
|
||||
errx(1, "Empty JSON array; not overwriting keytab");
|
||||
|
||||
if ((kt = ktutil_open_keytab()) == NULL)
|
||||
err(1, "Could not open keytab");
|
||||
|
||||
for (i = 0; i < alen; i++) {
|
||||
heim_object_t e = heim_array_get_value(o, i);
|
||||
|
||||
if (heim_get_tid(e) != HEIM_TID_DICT)
|
||||
warnx("Element %ld of JSON text array is not an object", (long)i);
|
||||
else
|
||||
json2keytab_entry(heim_array_get_value(o, i), kt, i);
|
||||
}
|
||||
ret = krb5_kt_close(context, kt);
|
||||
if (ret)
|
||||
krb5_warn(context, ret, "Could not write the keytab");
|
||||
return ret != 0;
|
||||
}
|
||||
|
19
admin/copy.c
19
admin/copy.c
@ -47,7 +47,7 @@ compare_keyblock(const krb5_keyblock *a, const krb5_keyblock *b)
|
||||
}
|
||||
|
||||
int
|
||||
kt_copy (void *opt, int argc, char **argv)
|
||||
kt_copy (struct copy_options *opt, int argc, char **argv)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
krb5_keytab src_keytab, dst_keytab;
|
||||
@ -106,11 +106,18 @@ kt_copy (void *opt, int argc, char **argv)
|
||||
"already exists for %s, keytype %s, kvno %d",
|
||||
name_str, etype_str, entry.vno);
|
||||
}
|
||||
krb5_kt_free_entry(context, &dummy);
|
||||
krb5_kt_free_entry (context, &entry);
|
||||
free(name_str);
|
||||
free(etype_str);
|
||||
continue;
|
||||
if (!opt->copy_duplicates_flag) {
|
||||
krb5_kt_free_entry(context, &dummy);
|
||||
krb5_kt_free_entry (context, &entry);
|
||||
free(name_str);
|
||||
free(etype_str);
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
* Because we can end up trying all keys that match the enctype,
|
||||
* copying entries with duplicate principal, vno, and enctype, but
|
||||
* different keys, can be useful.
|
||||
*/
|
||||
} else if(ret != KRB5_KT_NOTFOUND) {
|
||||
krb5_warn (context, ret, "%s: fetching %s/%s/%u",
|
||||
to, name_str, etype_str, entry.vno);
|
||||
|
38
admin/get.c
38
admin/get.c
@ -197,23 +197,27 @@ kt_get(struct get_options *opt, int argc, char **argv)
|
||||
break;
|
||||
}
|
||||
|
||||
ret = kadm5_create_principal(kadm_handle, &princ, mask, "thisIs_aUseless.password123");
|
||||
if(ret == 0)
|
||||
created = 1;
|
||||
else if(ret != KADM5_DUP) {
|
||||
krb5_warn(context, ret, "kadm5_create_principal(%s)", argv[a]);
|
||||
krb5_free_principal(context, princ_ent);
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
ret = kadm5_randkey_principal_3(kadm_handle, princ_ent, keep, nks, ks,
|
||||
&keys, &n_keys);
|
||||
if (ret) {
|
||||
krb5_warn(context, ret, "kadm5_randkey_principal(%s)", argv[a]);
|
||||
krb5_free_principal(context, princ_ent);
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
if (opt->create_flag) {
|
||||
ret = kadm5_create_principal(kadm_handle, &princ, mask, "thisIs_aUseless.password123");
|
||||
if(ret == 0)
|
||||
created = 1;
|
||||
else if(ret != KADM5_DUP) {
|
||||
krb5_warn(context, ret, "kadm5_create_principal(%s)", argv[a]);
|
||||
krb5_free_principal(context, princ_ent);
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (opt->change_keys_flag) {
|
||||
ret = kadm5_randkey_principal_3(kadm_handle, princ_ent, keep, nks, ks,
|
||||
&keys, &n_keys);
|
||||
if (ret) {
|
||||
krb5_warn(context, ret, "kadm5_randkey_principal(%s)", argv[a]);
|
||||
krb5_free_principal(context, princ_ent);
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
ret = kadm5_get_principal(kadm_handle, princ_ent, &princ,
|
||||
KADM5_PRINCIPAL | KADM5_KVNO | KADM5_ATTRIBUTES);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Kungliga Tekniska Högskolan
|
||||
* Copyright (c) 2004-2022 Kungliga Tekniska Högskolan
|
||||
* (Royal Institute of Technology, Stockholm, Sweden).
|
||||
* All rights reserved.
|
||||
*
|
||||
@ -151,11 +151,17 @@ command = {
|
||||
}
|
||||
command = {
|
||||
name = "copy"
|
||||
name = "merge"
|
||||
function = "kt_copy"
|
||||
option = {
|
||||
long = "copy-duplicates"
|
||||
type = "flag"
|
||||
help = "copy entries for the same principal and kvno, but different keys"
|
||||
}
|
||||
argument = "source destination"
|
||||
min_args = "2"
|
||||
max_args = "2"
|
||||
help = "Copies one keytab to another."
|
||||
help = "Merges one keytab into another."
|
||||
}
|
||||
command = {
|
||||
name = "get"
|
||||
@ -166,6 +172,16 @@ command = {
|
||||
help = "admin principal"
|
||||
argument = "principal"
|
||||
}
|
||||
option = {
|
||||
long = "create"
|
||||
type = "-flag"
|
||||
help = "do not create the principal"
|
||||
}
|
||||
option = {
|
||||
long = "change-keys"
|
||||
type = "-flag"
|
||||
help = "do not change the principal's keys"
|
||||
}
|
||||
option = {
|
||||
long = "enctypes"
|
||||
short = "e"
|
||||
@ -214,6 +230,14 @@ command = {
|
||||
argument = "principal..."
|
||||
help = "Change keys for specified principals, and add them to the keytab."
|
||||
}
|
||||
command = {
|
||||
name = "import"
|
||||
function = "kt_import"
|
||||
help = "Imports a keytab from JSON output of ktutil list --json --keys."
|
||||
min_args = "0"
|
||||
max_args = "1"
|
||||
argument = "JSON-FILE"
|
||||
}
|
||||
command = {
|
||||
name = "list"
|
||||
option = {
|
||||
|
@ -82,29 +82,67 @@ server for the realm of a keytab entry. Otherwise it will use the
|
||||
values specified by the options.
|
||||
.Pp
|
||||
If no principals are given, all the ones in the keytab are updated.
|
||||
.It Nm copy Ar keytab-src Ar keytab-dest
|
||||
.It Nm copy Oo Fl Fl copy-duplicates Oc Ar keytab-src Ar keytab-dest
|
||||
Copies all the entries from
|
||||
.Ar keytab-src
|
||||
to
|
||||
.Ar keytab-dest .
|
||||
Because entries already in
|
||||
.Ar keytab-dest
|
||||
are kept, this command functions to merge keytabs.
|
||||
Entries for the same principal, key version number, and
|
||||
encryption type in the
|
||||
.Ar keytab-src
|
||||
that are also in the
|
||||
.Ar keytab-dest
|
||||
will not be copied to the
|
||||
.Ar keytab-dest
|
||||
unless the
|
||||
.Fl Fl copy-duplicates
|
||||
option is given.
|
||||
.It Nm get Oo Fl p Ar admin principal Oc \
|
||||
Oo Fl Fl principal= Ns Ar admin principal Oc Oo Fl e Ar enctype Oc \
|
||||
Oo Fl Fl no-create Oc \
|
||||
Oo Fl Fl no-change-keys Oc \
|
||||
Oo Fl Fl keepold | Fl Fl keepallold | Fl Fl pruneall Oc \
|
||||
Oo Fl Fl enctypes= Ns Ar enctype Oc Oo Fl r Ar realm Oc \
|
||||
Oo Fl Fl realm= Ns Ar realm Oc Oo Fl a Ar admin server Oc \
|
||||
Oo Fl Fl admin-server= Ns Ar admin server Oc Oo Fl s Ar server port Oc \
|
||||
Oo Fl Fl server-port= Ns Ar server port Oc Ar principal ...
|
||||
.Pp
|
||||
For each
|
||||
.Ar principal ,
|
||||
generate a new key for it (creating it if it doesn't already exist),
|
||||
and put that key in the keytab.
|
||||
get a the principal's keys from the KDC via the kadmin protocol,
|
||||
creating the principal if it doesn't exist (unless
|
||||
.Fl Fl no-create
|
||||
is given), and changing its keys to new random keys (unless
|
||||
.Fl Fl no-change-keys
|
||||
is given).
|
||||
.Pp
|
||||
If no
|
||||
.Ar realm
|
||||
is specified, the realm to operate on is taken from the first
|
||||
principal.
|
||||
.It Nm import Oo JSON-FILE Oc
|
||||
Read an array of keytab entries in a JSON file and copy them to
|
||||
the keytab.
|
||||
Use the
|
||||
.Nm list
|
||||
command with its
|
||||
.Fl Fl json
|
||||
option
|
||||
and
|
||||
.Fl Fl keys
|
||||
option to export a keytab.
|
||||
.It Nm list Oo Fl Fl keys Oc Op Fl Fl timestamp Oo Op Fl Fl json Oc
|
||||
List the keys stored in the keytab.
|
||||
Use the
|
||||
.Fl Fl json
|
||||
and
|
||||
.Fl Fl keys
|
||||
options to export a keytab as JSON for importing with the
|
||||
.Nm import
|
||||
command.
|
||||
.It Nm remove Oo Fl p Ar principal Oc Oo Fl Fl principal= Ns Ar principal Oc \
|
||||
Oo Fl V kvno Oc Oo Fl Fl kvno= Ns Ar kvno Oc Oo Fl e enctype Oc \
|
||||
Oo Fl Fl enctype= Ns Ar enctype Oc
|
||||
@ -113,8 +151,14 @@ Removes the specified key or keys. Not specifying a
|
||||
removes keys with any version number. Not specifying an
|
||||
.Ar enctype
|
||||
removes keys of any type.
|
||||
.It Nm merge Oo Fl Fl copy-duplicates Oc Ar keytab-src Ar keytab-dest
|
||||
An alias for the
|
||||
.Nm copy
|
||||
command.
|
||||
.It Nm rename Ar from-principal Ar to-principal
|
||||
Renames all entries in the keytab that match the
|
||||
Renames all entries for the
|
||||
.Ar from-principal
|
||||
in the keytab
|
||||
.Ar from-principal
|
||||
to
|
||||
.Ar to-principal .
|
||||
@ -123,6 +167,12 @@ Removes all old versions of a key for which there is a newer version
|
||||
that is at least
|
||||
.Ar age
|
||||
(default one week) old.
|
||||
Note that this does not update the KDC database.
|
||||
The
|
||||
.Xr kadmin 1
|
||||
command has a
|
||||
.Nm prune
|
||||
command that can do this on the KDC side.
|
||||
.El
|
||||
.Sh SEE ALSO
|
||||
.Xr kadmin 1
|
||||
|
Reference in New Issue
Block a user