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).
|
* (Royal Institute of Technology, Stockholm, Sweden).
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
@ -32,6 +32,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ktutil_locl.h"
|
#include "ktutil_locl.h"
|
||||||
|
#include <heimbase.h>
|
||||||
|
#include <base64.h>
|
||||||
|
|
||||||
RCSID("$Id$");
|
RCSID("$Id$");
|
||||||
|
|
||||||
@ -153,6 +155,178 @@ kt_add(struct add_options *opt, int argc, char **argv)
|
|||||||
krb5_warn(context, ret, "add");
|
krb5_warn(context, ret, "add");
|
||||||
out:
|
out:
|
||||||
krb5_kt_free_entry(context, &entry);
|
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;
|
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
|
int
|
||||||
kt_copy (void *opt, int argc, char **argv)
|
kt_copy (struct copy_options *opt, int argc, char **argv)
|
||||||
{
|
{
|
||||||
krb5_error_code ret;
|
krb5_error_code ret;
|
||||||
krb5_keytab src_keytab, dst_keytab;
|
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",
|
"already exists for %s, keytype %s, kvno %d",
|
||||||
name_str, etype_str, entry.vno);
|
name_str, etype_str, entry.vno);
|
||||||
}
|
}
|
||||||
krb5_kt_free_entry(context, &dummy);
|
if (!opt->copy_duplicates_flag) {
|
||||||
krb5_kt_free_entry (context, &entry);
|
krb5_kt_free_entry(context, &dummy);
|
||||||
free(name_str);
|
krb5_kt_free_entry (context, &entry);
|
||||||
free(etype_str);
|
free(name_str);
|
||||||
continue;
|
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) {
|
} else if(ret != KRB5_KT_NOTFOUND) {
|
||||||
krb5_warn (context, ret, "%s: fetching %s/%s/%u",
|
krb5_warn (context, ret, "%s: fetching %s/%s/%u",
|
||||||
to, name_str, etype_str, entry.vno);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = kadm5_create_principal(kadm_handle, &princ, mask, "thisIs_aUseless.password123");
|
if (opt->create_flag) {
|
||||||
if(ret == 0)
|
ret = kadm5_create_principal(kadm_handle, &princ, mask, "thisIs_aUseless.password123");
|
||||||
created = 1;
|
if(ret == 0)
|
||||||
else if(ret != KADM5_DUP) {
|
created = 1;
|
||||||
krb5_warn(context, ret, "kadm5_create_principal(%s)", argv[a]);
|
else if(ret != KADM5_DUP) {
|
||||||
krb5_free_principal(context, princ_ent);
|
krb5_warn(context, ret, "kadm5_create_principal(%s)", argv[a]);
|
||||||
failed++;
|
krb5_free_principal(context, princ_ent);
|
||||||
continue;
|
failed++;
|
||||||
}
|
continue;
|
||||||
ret = kadm5_randkey_principal_3(kadm_handle, princ_ent, keep, nks, ks,
|
}
|
||||||
&keys, &n_keys);
|
}
|
||||||
if (ret) {
|
if (opt->change_keys_flag) {
|
||||||
krb5_warn(context, ret, "kadm5_randkey_principal(%s)", argv[a]);
|
ret = kadm5_randkey_principal_3(kadm_handle, princ_ent, keep, nks, ks,
|
||||||
krb5_free_principal(context, princ_ent);
|
&keys, &n_keys);
|
||||||
failed++;
|
if (ret) {
|
||||||
continue;
|
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,
|
ret = kadm5_get_principal(kadm_handle, princ_ent, &princ,
|
||||||
KADM5_PRINCIPAL | KADM5_KVNO | KADM5_ATTRIBUTES);
|
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).
|
* (Royal Institute of Technology, Stockholm, Sweden).
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
@ -151,11 +151,17 @@ command = {
|
|||||||
}
|
}
|
||||||
command = {
|
command = {
|
||||||
name = "copy"
|
name = "copy"
|
||||||
|
name = "merge"
|
||||||
function = "kt_copy"
|
function = "kt_copy"
|
||||||
|
option = {
|
||||||
|
long = "copy-duplicates"
|
||||||
|
type = "flag"
|
||||||
|
help = "copy entries for the same principal and kvno, but different keys"
|
||||||
|
}
|
||||||
argument = "source destination"
|
argument = "source destination"
|
||||||
min_args = "2"
|
min_args = "2"
|
||||||
max_args = "2"
|
max_args = "2"
|
||||||
help = "Copies one keytab to another."
|
help = "Merges one keytab into another."
|
||||||
}
|
}
|
||||||
command = {
|
command = {
|
||||||
name = "get"
|
name = "get"
|
||||||
@ -166,6 +172,16 @@ command = {
|
|||||||
help = "admin principal"
|
help = "admin principal"
|
||||||
argument = "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 = {
|
option = {
|
||||||
long = "enctypes"
|
long = "enctypes"
|
||||||
short = "e"
|
short = "e"
|
||||||
@ -214,6 +230,14 @@ command = {
|
|||||||
argument = "principal..."
|
argument = "principal..."
|
||||||
help = "Change keys for specified principals, and add them to the keytab."
|
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 = {
|
command = {
|
||||||
name = "list"
|
name = "list"
|
||||||
option = {
|
option = {
|
||||||
|
@ -82,29 +82,67 @@ server for the realm of a keytab entry. Otherwise it will use the
|
|||||||
values specified by the options.
|
values specified by the options.
|
||||||
.Pp
|
.Pp
|
||||||
If no principals are given, all the ones in the keytab are updated.
|
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
|
Copies all the entries from
|
||||||
.Ar keytab-src
|
.Ar keytab-src
|
||||||
to
|
to
|
||||||
.Ar keytab-dest .
|
.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 \
|
.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 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 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 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 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 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 ...
|
Oo Fl Fl server-port= Ns Ar server port Oc Ar principal ...
|
||||||
|
.Pp
|
||||||
For each
|
For each
|
||||||
.Ar principal ,
|
.Ar principal ,
|
||||||
generate a new key for it (creating it if it doesn't already exist),
|
get a the principal's keys from the KDC via the kadmin protocol,
|
||||||
and put that key in the keytab.
|
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
|
.Pp
|
||||||
If no
|
If no
|
||||||
.Ar realm
|
.Ar realm
|
||||||
is specified, the realm to operate on is taken from the first
|
is specified, the realm to operate on is taken from the first
|
||||||
principal.
|
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
|
.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.
|
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 \
|
.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 V kvno Oc Oo Fl Fl kvno= Ns Ar kvno Oc Oo Fl e enctype Oc \
|
||||||
Oo Fl Fl enctype= Ns Ar 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
|
removes keys with any version number. Not specifying an
|
||||||
.Ar enctype
|
.Ar enctype
|
||||||
removes keys of any type.
|
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
|
.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
|
.Ar from-principal
|
||||||
to
|
to
|
||||||
.Ar to-principal .
|
.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
|
that is at least
|
||||||
.Ar age
|
.Ar age
|
||||||
(default one week) old.
|
(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
|
.El
|
||||||
.Sh SEE ALSO
|
.Sh SEE ALSO
|
||||||
.Xr kadmin 1
|
.Xr kadmin 1
|
||||||
|
Reference in New Issue
Block a user