405 lines
15 KiB
Diff
405 lines
15 KiB
Diff
From ea2ad3330aa39ef4e62d8856ea7e8eed2843b3f2 Mon Sep 17 00:00:00 2001
|
|
From: Greg Hudson <ghudson@mit.edu>
|
|
Date: Wed, 17 Jun 2020 20:48:38 -0400
|
|
Subject: [PATCH] Add three kvno options from Heimdal kgetcred
|
|
|
|
Add the flags --cached-only and --no-store, which pass the
|
|
corresponding options to krb5_get_credentials(). Add the option
|
|
--out-cache to write the retrieved credentials to a specified output
|
|
cache.
|
|
|
|
Add a Python test script for kvno command-line options, including
|
|
tests for the new options.
|
|
|
|
ticket: 8917 (new)
|
|
(cherry picked from commit 876bab8418d7dd134c9d9db812ee2118d5ad58f0)
|
|
[rharwood@redhat.com: slush around option unification]
|
|
---
|
|
doc/user/user_commands/kvno.rst | 13 ++++
|
|
src/clients/kvno/Makefile.in | 3 +
|
|
src/clients/kvno/kvno.c | 112 +++++++++++++++++++++++---------
|
|
src/clients/kvno/t_kvno.py | 75 +++++++++++++++++++++
|
|
src/man/kvno.man | 13 ++++
|
|
5 files changed, 185 insertions(+), 31 deletions(-)
|
|
create mode 100644 src/clients/kvno/t_kvno.py
|
|
|
|
diff --git a/doc/user/user_commands/kvno.rst b/doc/user/user_commands/kvno.rst
|
|
index 53e569651..6fd8577a5 100644
|
|
--- a/doc/user/user_commands/kvno.rst
|
|
+++ b/doc/user/user_commands/kvno.rst
|
|
@@ -75,6 +75,19 @@ OPTIONS
|
|
client principal with the X.509 certificate in *cert_file*. The
|
|
certificate file must be in PEM format.
|
|
|
|
+**--cached-only**
|
|
+ Only retrieve credentials already present in the cache, not from
|
|
+ the KDC.
|
|
+
|
|
+**--no-store**
|
|
+ Do not store retrieved credentials in the cache. If
|
|
+ **--out-cache** is also specified, credentials will still be
|
|
+ stored into the output credential cache.
|
|
+
|
|
+**--out-cache** *ccache*
|
|
+ Initialize *ccache* and store all retrieved credentials into it.
|
|
+ Do not store acquired credentials in the input cache.
|
|
+
|
|
**--u2u** *ccache*
|
|
Requests a user-to-user ticket. *ccache* must contain a local
|
|
krbtgt ticket for the server principal. The reported version
|
|
diff --git a/src/clients/kvno/Makefile.in b/src/clients/kvno/Makefile.in
|
|
index 1c3f79392..5ba877271 100644
|
|
--- a/src/clients/kvno/Makefile.in
|
|
+++ b/src/clients/kvno/Makefile.in
|
|
@@ -26,6 +26,9 @@ kvno: kvno.o $(KRB5_BASE_DEPLIBS)
|
|
##WIN32## link $(EXE_LINKOPTS) /out:$@ $**
|
|
##WIN32## $(_VC_MANIFEST_EMBED_EXE)
|
|
|
|
+check-pytests: kvno
|
|
+ $(RUNPYTEST) $(srcdir)/t_kvno.py $(PYTESTFLAGS)
|
|
+
|
|
clean-unix::
|
|
$(RM) kvno.o kvno
|
|
|
|
diff --git a/src/clients/kvno/kvno.c b/src/clients/kvno/kvno.c
|
|
index 8edd97361..c5f6bf700 100644
|
|
--- a/src/clients/kvno/kvno.c
|
|
+++ b/src/clients/kvno/kvno.c
|
|
@@ -47,15 +47,17 @@ xusage()
|
|
"[-u | -S sname]" XUSAGE_BREAK
|
|
"[[{-F cert_file | {-I | -U} for_user} [-P]] | "
|
|
"--u2u ccache]" XUSAGE_BREAK
|
|
+ "[--cached-only] [--no-store] [--out-cache] "
|
|
"service1 service2 ...\n"),
|
|
prog);
|
|
exit(1);
|
|
}
|
|
|
|
static void do_v5_kvno(int argc, char *argv[], char *ccachestr, char *etypestr,
|
|
- char *keytab_name, char *sname, int canon, int unknown,
|
|
- char *for_user, int for_user_enterprise,
|
|
- char *for_user_cert_file, int proxy,
|
|
+ char *keytab_name, char *sname, int cached_only,
|
|
+ int canon, int no_store, int unknown, char *for_user,
|
|
+ int for_user_enterprise, char *for_user_cert_file,
|
|
+ int proxy, const char *out_ccname,
|
|
const char *u2u_ccname);
|
|
|
|
#include <com_err.h>
|
|
@@ -65,18 +67,21 @@ static void extended_com_err_fn(const char *myprog, errcode_t code,
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
- enum { OPTION_U2U = 256 };
|
|
- struct option lopts[] = {
|
|
- { "u2u", 1, NULL, OPTION_U2U },
|
|
- { NULL, 0, NULL, 0 }
|
|
- };
|
|
+ enum { OPTION_U2U = 256, OPTION_OUT_CACHE = 257 };
|
|
const char *shopts = "uCc:e:hk:qPS:I:U:F:";
|
|
int option;
|
|
char *etypestr = NULL, *ccachestr = NULL, *keytab_name = NULL;
|
|
char *sname = NULL, *for_user = NULL, *u2u_ccname = NULL;
|
|
- char *for_user_cert_file = NULL;
|
|
+ char *for_user_cert_file = NULL, *out_ccname = NULL;
|
|
int canon = 0, unknown = 0, proxy = 0, for_user_enterprise = 0;
|
|
- int impersonate = 0;
|
|
+ int impersonate = 0, cached_only = 0, no_store = 0;
|
|
+ struct option lopts[] = {
|
|
+ { "cached-only", 0, &cached_only, 1 },
|
|
+ { "no-store", 0, &no_store, 1 },
|
|
+ { "out-cache", 1, NULL, OPTION_OUT_CACHE },
|
|
+ { "u2u", 1, NULL, OPTION_U2U },
|
|
+ { NULL, 0, NULL, 0 }
|
|
+ };
|
|
|
|
setlocale(LC_ALL, "");
|
|
set_com_err_hook(extended_com_err_fn);
|
|
@@ -139,6 +144,12 @@ main(int argc, char *argv[])
|
|
case OPTION_U2U:
|
|
u2u_ccname = optarg;
|
|
break;
|
|
+ case OPTION_OUT_CACHE:
|
|
+ out_ccname = optarg;
|
|
+ break;
|
|
+ case 0:
|
|
+ /* If this option set a flag, do nothing else now. */
|
|
+ break;
|
|
default:
|
|
xusage();
|
|
break;
|
|
@@ -163,8 +174,9 @@ main(int argc, char *argv[])
|
|
xusage();
|
|
|
|
do_v5_kvno(argc - optind, argv + optind, ccachestr, etypestr, keytab_name,
|
|
- sname, canon, unknown, for_user, for_user_enterprise,
|
|
- for_user_cert_file, proxy, u2u_ccname);
|
|
+ sname, cached_only, canon, no_store, unknown, for_user,
|
|
+ for_user_enterprise, for_user_cert_file, proxy, out_ccname,
|
|
+ u2u_ccname);
|
|
return 0;
|
|
}
|
|
|
|
@@ -278,14 +290,16 @@ static krb5_error_code
|
|
kvno(const char *name, krb5_ccache ccache, krb5_principal me,
|
|
krb5_enctype etype, krb5_keytab keytab, const char *sname,
|
|
krb5_flags options, int unknown, krb5_principal for_user_princ,
|
|
- krb5_data *for_user_cert, int proxy, krb5_data *u2u_ticket)
|
|
+ krb5_data *for_user_cert, int proxy, krb5_data *u2u_ticket,
|
|
+ krb5_creds **creds_out)
|
|
{
|
|
krb5_error_code ret;
|
|
krb5_principal server = NULL;
|
|
krb5_ticket *ticket = NULL;
|
|
- krb5_creds in_creds, *out_creds = NULL;
|
|
+ krb5_creds in_creds, *creds = NULL;
|
|
char *princ = NULL;
|
|
|
|
+ *creds_out = NULL;
|
|
memset(&in_creds, 0, sizeof(in_creds));
|
|
|
|
if (sname != NULL) {
|
|
@@ -325,13 +339,12 @@ kvno(const char *name, krb5_ccache ccache, krb5_principal me,
|
|
in_creds.client = for_user_princ;
|
|
in_creds.server = me;
|
|
ret = krb5_get_credentials_for_user(context, options, ccache,
|
|
- &in_creds, for_user_cert,
|
|
- &out_creds);
|
|
+ &in_creds, for_user_cert, &creds);
|
|
} else {
|
|
in_creds.client = me;
|
|
in_creds.server = server;
|
|
ret = krb5_get_credentials(context, options, ccache, &in_creds,
|
|
- &out_creds);
|
|
+ &creds);
|
|
}
|
|
|
|
if (ret) {
|
|
@@ -340,7 +353,7 @@ kvno(const char *name, krb5_ccache ccache, krb5_principal me,
|
|
}
|
|
|
|
/* We need a native ticket. */
|
|
- ret = krb5_decode_ticket(&out_creds->ticket, &ticket);
|
|
+ ret = krb5_decode_ticket(&creds->ticket, &ticket);
|
|
if (ret) {
|
|
com_err(prog, ret, _("while decoding ticket for %s"), princ);
|
|
goto cleanup;
|
|
@@ -366,15 +379,15 @@ kvno(const char *name, krb5_ccache ccache, krb5_principal me,
|
|
}
|
|
|
|
if (proxy) {
|
|
- in_creds.client = out_creds->client;
|
|
- out_creds->client = NULL;
|
|
- krb5_free_creds(context, out_creds);
|
|
- out_creds = NULL;
|
|
+ in_creds.client = creds->client;
|
|
+ creds->client = NULL;
|
|
+ krb5_free_creds(context, creds);
|
|
+ creds = NULL;
|
|
in_creds.server = server;
|
|
|
|
ret = krb5_get_credentials_for_proxy(context, KRB5_GC_CANONICALIZE,
|
|
ccache, &in_creds, ticket,
|
|
- &out_creds);
|
|
+ &creds);
|
|
krb5_free_principal(context, in_creds.client);
|
|
if (ret) {
|
|
com_err(prog, ret, _("%s: constrained delegation failed"),
|
|
@@ -383,10 +396,13 @@ kvno(const char *name, krb5_ccache ccache, krb5_principal me,
|
|
}
|
|
}
|
|
|
|
+ *creds_out = creds;
|
|
+ creds = NULL;
|
|
+
|
|
cleanup:
|
|
krb5_free_principal(context, server);
|
|
krb5_free_ticket(context, ticket);
|
|
- krb5_free_creds(context, out_creds);
|
|
+ krb5_free_creds(context, creds);
|
|
krb5_free_unparsed_name(context, princ);
|
|
return ret;
|
|
}
|
|
@@ -432,19 +448,28 @@ cleanup:
|
|
|
|
static void
|
|
do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,
|
|
- char *keytab_name, char *sname, int canon, int unknown,
|
|
- char *for_user, int for_user_enterprise,
|
|
- char *for_user_cert_file, int proxy, const char *u2u_ccname)
|
|
+ char *keytab_name, char *sname, int cached_only, int canon,
|
|
+ int no_store, int unknown, char *for_user, int for_user_enterprise,
|
|
+ char *for_user_cert_file, int proxy, const char *out_ccname,
|
|
+ const char *u2u_ccname)
|
|
{
|
|
krb5_error_code ret;
|
|
- int i, errors, flags;
|
|
+ int i, errors, flags, initialized = 0;
|
|
krb5_enctype etype;
|
|
- krb5_ccache ccache;
|
|
+ krb5_ccache ccache, out_ccache = NULL;
|
|
krb5_principal me;
|
|
krb5_keytab keytab = NULL;
|
|
krb5_principal for_user_princ = NULL;
|
|
- krb5_flags options = canon ? KRB5_GC_CANONICALIZE : 0;
|
|
+ krb5_flags options = 0;
|
|
krb5_data cert_data = empty_data(), *user_cert = NULL, *u2u_ticket = NULL;
|
|
+ krb5_creds *creds;
|
|
+
|
|
+ if (canon)
|
|
+ options |= KRB5_GC_CANONICALIZE;
|
|
+ if (cached_only)
|
|
+ options |= KRB5_GC_CACHED;
|
|
+ if (no_store || out_ccname != NULL)
|
|
+ options |= KRB5_GC_NO_STORE;
|
|
|
|
ret = krb5_init_context(&context);
|
|
if (ret) {
|
|
@@ -471,6 +496,14 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,
|
|
exit(1);
|
|
}
|
|
|
|
+ if (out_ccname != NULL) {
|
|
+ ret = krb5_cc_resolve(context, out_ccname, &out_ccache);
|
|
+ if (ret) {
|
|
+ com_err(prog, ret, _("while resolving output ccache"));
|
|
+ exit(1);
|
|
+ }
|
|
+ }
|
|
+
|
|
if (keytab_name != NULL) {
|
|
ret = krb5_kt_resolve(context, keytab_name, &keytab);
|
|
if (ret) {
|
|
@@ -517,8 +550,25 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,
|
|
errors = 0;
|
|
for (i = 0; i < count; i++) {
|
|
if (kvno(names[i], ccache, me, etype, keytab, sname, options, unknown,
|
|
- for_user_princ, user_cert, proxy, u2u_ticket) != 0)
|
|
+ for_user_princ, user_cert, proxy, u2u_ticket, &creds) != 0) {
|
|
errors++;
|
|
+ } else if (out_ccache != NULL) {
|
|
+ if (!initialized) {
|
|
+ ret = krb5_cc_initialize(context, out_ccache, creds->client);
|
|
+ if (ret) {
|
|
+ com_err(prog, ret, _("while initializing output ccache"));
|
|
+ exit(1);
|
|
+ }
|
|
+ initialized = 1;
|
|
+ }
|
|
+ ret = krb5_cc_store_cred(context, out_ccache, creds);
|
|
+ if (ret) {
|
|
+ com_err(prog, ret, _("while storing creds in output ccache"));
|
|
+ exit(1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ krb5_free_creds(context, creds);
|
|
}
|
|
|
|
if (keytab != NULL)
|
|
diff --git a/src/clients/kvno/t_kvno.py b/src/clients/kvno/t_kvno.py
|
|
new file mode 100644
|
|
index 000000000..e98b90e8a
|
|
--- /dev/null
|
|
+++ b/src/clients/kvno/t_kvno.py
|
|
@@ -0,0 +1,75 @@
|
|
+from k5test import *
|
|
+
|
|
+realm = K5Realm()
|
|
+
|
|
+def check_cache(ccache, expected_services):
|
|
+ # Fetch the klist output and skip past the header.
|
|
+ lines = realm.run([klist, '-c', ccache]).splitlines()
|
|
+ lines = lines[4:]
|
|
+
|
|
+ # For each line not beginning with an indent, match against the
|
|
+ # expected service principals.
|
|
+ svcs = {x: True for x in expected_services}
|
|
+ for l in lines:
|
|
+ if not l.startswith('\t'):
|
|
+ svcprinc = l.split()[4]
|
|
+ if svcprinc in svcs:
|
|
+ del svcs[svcprinc]
|
|
+ else:
|
|
+ fail('unexpected service princ ' + svcprinc)
|
|
+
|
|
+ if svcs:
|
|
+ fail('services not found in klist output: ' + ' '.join(svcs.keys()))
|
|
+
|
|
+
|
|
+mark('no options')
|
|
+realm.run([kvno, realm.user_princ], expected_msg='user@KRBTEST.COM: kvno = 1')
|
|
+check_cache(realm.ccache, [realm.krbtgt_princ, realm.user_princ])
|
|
+
|
|
+mark('-e')
|
|
+msgs = ('etypes requested in TGS request: camellia128-cts',
|
|
+ '/KDC has no support for encryption type')
|
|
+realm.run([kvno, '-e', 'camellia128-cts', realm.host_princ],
|
|
+ expected_code=1, expected_trace=msgs)
|
|
+
|
|
+mark('--cached-only')
|
|
+realm.run([kvno, '--cached-only', realm.user_princ], expected_msg='kvno = 1')
|
|
+realm.run([kvno, '--cached-only', realm.host_princ],
|
|
+ expected_code=1, expected_msg='Matching credential not found')
|
|
+check_cache(realm.ccache, [realm.krbtgt_princ, realm.user_princ])
|
|
+
|
|
+mark('--no-store')
|
|
+realm.run([kvno, '--no-store', realm.host_princ], expected_msg='kvno = 1')
|
|
+check_cache(realm.ccache, [realm.krbtgt_princ, realm.user_princ])
|
|
+
|
|
+mark('--out-cache') # and multiple services
|
|
+out_ccache = os.path.join(realm.testdir, 'ccache.out')
|
|
+realm.run([kvno, '--out-cache', out_ccache,
|
|
+ realm.host_princ, realm.admin_princ])
|
|
+check_cache(realm.ccache, [realm.krbtgt_princ, realm.user_princ])
|
|
+check_cache(out_ccache, [realm.host_princ, realm.admin_princ])
|
|
+
|
|
+mark('--out-cache --cached-only') # tests out-cache overwriting, and -q
|
|
+realm.run([kvno, '--out-cache', out_ccache, '--cached-only', realm.host_princ],
|
|
+ expected_code=1, expected_msg='Matching credential not found')
|
|
+out = realm.run([kvno, '-q', '--out-cache', out_ccache, '--cached-only',
|
|
+ realm.user_princ])
|
|
+if out:
|
|
+ fail('unexpected kvno output with -q')
|
|
+check_cache(out_ccache, [realm.user_princ])
|
|
+
|
|
+mark('-U') # and -c
|
|
+svc_ccache = os.path.join(realm.testdir, 'ccache.svc')
|
|
+realm.run([kinit, '-k', '-c', svc_ccache, realm.host_princ])
|
|
+realm.run([kvno, '-c', svc_ccache, '-U', 'user', realm.host_princ])
|
|
+realm.run([klist, '-c', svc_ccache], expected_msg='for client user@')
|
|
+realm.run([kvno, '-c', svc_ccache, '-U', 'user', '--out-cache', out_ccache,
|
|
+ realm.host_princ])
|
|
+out = realm.run([klist, '-c', out_ccache])
|
|
+if ('Default principal: user@KRBTEST.COM' not in out):
|
|
+ fail('wrong default principal in klist output')
|
|
+
|
|
+# More S4U options are tested in tests/gssapi/t_s4u.py.
|
|
+# --u2u is tested in tests/t_u2u.py.
|
|
+
|
|
+success('kvno tests')
|
|
diff --git a/src/man/kvno.man b/src/man/kvno.man
|
|
index 9eb9e1852..7c9565bdb 100644
|
|
--- a/src/man/kvno.man
|
|
+++ b/src/man/kvno.man
|
|
@@ -96,6 +96,19 @@ Specifies that protocol transition is to be used, identifying the
|
|
client principal with the X.509 certificate in \fIcert_file\fP\&. The
|
|
certificate file must be in PEM format.
|
|
.TP
|
|
+\fB\-\-cached\-only\fP
|
|
+Only retrieve credentials already present in the cache, not from
|
|
+the KDC.
|
|
+.TP
|
|
+\fB\-\-no\-store\fP
|
|
+Do not store retrieved credentials in the cache. If
|
|
+\fB\-\-out\-cache\fP is also specified, credentials will still be
|
|
+stored into the output credential cache.
|
|
+.TP
|
|
+\fB\-\-out\-cache\fP \fIccache\fP
|
|
+Initialize \fIccache\fP and store all retrieved credentials into it.
|
|
+Do not store acquired credentials in the input cache.
|
|
+.TP
|
|
\fB\-\-u2u\fP \fIccache\fP
|
|
Requests a user\-to\-user ticket. \fIccache\fP must contain a local
|
|
krbtgt ticket for the server principal. The reported version
|