In order for an NFS client with support for the newer encryption types
(AES with SHA2 and Camellia) in its RPCSEC GSS kernel code to connect to
an NFS server without support for those encryption types in its RPCSEC
GSS kernel code, it is sometimes necessary for configuration changes on
the NFS server... particularly if the NFS server's userspace krb5 code
does have support for the newer encryption types and/or the NFS server's
keytab has "nfs" keys using the newer encryption types. Rather than
rehashing the whole discussion here in the cover letter, see the
description in the first patch for the gory details.
These patches make it easier for a "newer" NFS client to work with an
"older" NFS server.
The first patch adds support for an "allowed-enctypes" option in
nfs.conf, allowing the the client to restrict the permitted encryption
types to a subset of what is otherwise supported in its krb5 environment
so that it doesn't use an encryption type that the NFS server doesn't
support when negotiating a GSS context.
The second patch builds on this by adding an automatic backoff feature,
where if the NFS client fails to negotiate a GSS context with the NFS
server using the newer encryption types, it will try again without using
the newer encryption types.
With these patches in place on the NFS client, the "newer" NFS client
will work with an "older" NFS server without requiring any configuration
changes.
Scott Mayhew (2):
gssd: add support for an "allowed-enctypes" option in nfs.conf
gssd: add a "backoff" feature to limit_krb5_enctypes()
nfs.conf | 1 +
utils/gssd/gssd.c | 6 ++
utils/gssd/gssd.man | 9 +++
utils/gssd/gssd_proc.c | 15 ++++-
utils/gssd/krb5_util.c | 135 ++++++++++++++++++++++++++++++++++++++---
utils/gssd/krb5_util.h | 3 +-
6 files changed, 159 insertions(+), 10 deletions(-)
--
2.43.0
If the NFS server reset the connection when we tried to create a GSS
context with it, then there's a good chance that we used an encryption
type that it didn't support.
Add a one time backoff/retry mechanism, where we adjust the list of
encryption types that we set via gss_set_allowable_enctypes(). We can
do this easily because the list of encryption types should be ordered
from highest preference to lowest. We just need to find the first entry
that's not one of the newer encryption types, and then use that as the
start of the list.
Signed-off-by: Scott Mayhew <[email protected]>
---
utils/gssd/gssd_proc.c | 15 +++++++++++++--
utils/gssd/krb5_util.c | 40 +++++++++++++++++++++++++++++++++++++++-
utils/gssd/krb5_util.h | 2 +-
3 files changed, 53 insertions(+), 4 deletions(-)
diff --git a/utils/gssd/gssd_proc.c b/utils/gssd/gssd_proc.c
index 7629de0b..0da54598 100644
--- a/utils/gssd/gssd_proc.c
+++ b/utils/gssd/gssd_proc.c
@@ -337,6 +337,10 @@ create_auth_rpc_client(struct clnt_info *clp,
rpc_gss_options_req_t req;
rpc_gss_options_ret_t ret;
char mechanism[] = "kerberos_v5";
+#endif
+#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
+ bool backoff = false;
+ struct rpc_err err;
#endif
pthread_t tid = pthread_self();
@@ -354,14 +358,14 @@ create_auth_rpc_client(struct clnt_info *clp,
goto out_fail;
}
-
if (authtype == AUTHTYPE_KRB5) {
#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
+again:
/*
* Do this before creating rpc connection since we won't need
* rpc connection if it fails!
*/
- if (limit_krb5_enctypes(&sec)) {
+ if (limit_krb5_enctypes(&sec, backoff)) {
printerr(1, "WARNING: Failed while limiting krb5 "
"encryption types for user with uid %d\n",
uid);
@@ -445,6 +449,13 @@ create_auth_rpc_client(struct clnt_info *clp,
goto success;
}
}
+#endif
+#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
+ clnt_geterr(rpc_clnt, &err);
+ if (err.re_errno == ECONNRESET && !backoff) {
+ backoff = true;
+ goto again;
+ }
#endif
/* Our caller should print appropriate message */
printerr(2, "WARNING: Failed to create krb5 context for "
diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c
index 57b3cf8a..5502e74e 100644
--- a/utils/gssd/krb5_util.c
+++ b/utils/gssd/krb5_util.c
@@ -1675,7 +1675,7 @@ out:
*/
int
-limit_krb5_enctypes(struct rpc_gss_sec *sec)
+limit_krb5_enctypes(struct rpc_gss_sec *sec, bool backoff)
{
u_int maj_stat, min_stat;
krb5_enctype enctypes[] = { ENCTYPE_DES_CBC_CRC,
@@ -1689,6 +1689,17 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec)
int num_set_enctypes;
krb5_enctype *set_enctypes;
int err = -1;
+ int i, j;
+ bool done = false;
+
+ if (backoff && sec->cred != GSS_C_NO_CREDENTIAL) {
+ printerr(2, "%s: backoff: releasing old cred\n", __func__);
+ maj_stat = gss_release_cred(&min_stat, &sec->cred);
+ if (maj_stat != GSS_S_COMPLETE) {
+ printerr(2, "%s: gss_release_cred() failed\n", __func__);
+ return -1;
+ }
+ }
if (sec->cred == GSS_C_NO_CREDENTIAL) {
err = gssd_acquire_krb5_cred(&sec->cred);
@@ -1718,6 +1729,33 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec)
set_enctypes = krb5_enctypes;
}
+ if (backoff) {
+ j = num_set_enctypes;
+ for (i = 0; i < j && !done; i++) {
+ switch (*set_enctypes) {
+ case ENCTYPE_AES128_CTS_HMAC_SHA256_128:
+ case ENCTYPE_AES256_CTS_HMAC_SHA384_192:
+ case ENCTYPE_CAMELLIA128_CTS_CMAC:
+ case ENCTYPE_CAMELLIA256_CTS_CMAC:
+ printerr(2, "%s: backoff: removing enctype %d\n",
+ __func__, *set_enctypes);
+ set_enctypes++;
+ num_set_enctypes--;
+ break;
+ default:
+ done = true;
+ break;
+ }
+ }
+ printerr(2, "%s: backoff: %d remaining enctypes\n",
+ __func__, num_set_enctypes);
+ if (!num_set_enctypes) {
+ printerr(0, "%s: no remaining enctypes after backoff\n",
+ __func__);
+ return -1;
+ }
+ }
+
maj_stat = gss_set_allowable_enctypes(&min_stat, sec->cred,
&krb5oid, num_set_enctypes, set_enctypes);
diff --git a/utils/gssd/krb5_util.h b/utils/gssd/krb5_util.h
index 40ad3233..0be0c500 100644
--- a/utils/gssd/krb5_util.h
+++ b/utils/gssd/krb5_util.h
@@ -26,7 +26,7 @@ int gssd_k5_remove_bad_service_cred(char *srvname);
#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
extern int limit_to_legacy_enctypes;
-int limit_krb5_enctypes(struct rpc_gss_sec *sec);
+int limit_krb5_enctypes(struct rpc_gss_sec *sec, bool backoff);
int get_allowed_enctypes(void);
#endif
--
2.43.0
Newer kernels have support for newer krb5 encryption types, AES with
SHA2 and Camellia. An NFS client with an "old" kernel can talk to
and NFS server with a "new" kernel and it just works. An NFS client
with a "new" kernel can talk to an NFS server with an "old" kernel, but
that requires some additional configuration (particularly if the NFS
server does have support for the newer encryption types in its userspace
krb5 libraries) that may be unclear and/or burdensome to the admin.
1) If the NFS server has support for the newer encryption types in the
userspace krb5 libraries, but not in the kernel's RPCSEC_GSS code,
then its possible that it also already has "nfs" keys using those
newer encryption types in its keytab. In that case, it's necessary
to regenerate the "nfs" keys without the newer encryption types.
The reason this is necessary is because if the NFS client requests
an "nfs" service ticket from the KDC, and the list of enctypes in
in that TGS-REQ contains a newer encryption type, and the KDC had
previously generated a key for the NFS server using the newer
encryption type, then the resulting service ticket in the TGS-REP
will be using the newer encryption type and the NFS server will not
be able to decrypt it.
2) It is necessary to either modify the permitted_enctypes field of the
krb5.conf or create a custom crypto-policy module (if the
crypto-policies package is being used) on the NFS *client* so that it
does not include the newer encryption types. The reason this is
necessary is because it affects the list of encryption types that
will be present in the RPCSEC_GSS_INIT request that the NFS client
sends to the NFS server. The kernel on the NFS server cannot not
process the request on its own; it has to upcall to gssproxy to do
that... and again if the userspace krb5 libraries on the NFS server
have support for the newer encryption types, then it will select one
of those and the kernel will not be able to import the context when
it gets the downcall. Also note that modifying the permitted_enctypes
field and/or crypto policy has the side effect of impacting everything
krb5 related, not just just NFS.
So add support for an "allowed-enctypes" field in nfs.conf. This allows
the admin to restrict gssd to using a subset of the encryption types
that are supported by the kernel and krb5 libraries. This will remove
the need for steps 1 & 2 above, and will only affect NFS rather than
krb5 as a whole.
For example, for a "new" NFS client talking to an "old" NFS server, the
admin will probably want this in the client's nfs.conf:
allowed-enctypes=aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96
Signed-off-by: Scott Mayhew <[email protected]>
---
nfs.conf | 1 +
utils/gssd/gssd.c | 6 +++
utils/gssd/gssd.man | 9 ++++
utils/gssd/krb5_util.c | 95 +++++++++++++++++++++++++++++++++++++++---
utils/gssd/krb5_util.h | 1 +
5 files changed, 106 insertions(+), 6 deletions(-)
diff --git a/nfs.conf b/nfs.conf
index 323f072b..23b5f7d4 100644
--- a/nfs.conf
+++ b/nfs.conf
@@ -23,6 +23,7 @@
# use-gss-proxy=0
# avoid-dns=1
# limit-to-legacy-enctypes=0
+# allowed-enctypes=aes256-cts-hmac-sha384-192,aes128-cts-hmac-sha256-128,camellia256-cts-cmac,camellia128-cts-cmac,aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96
# context-timeout=0
# rpc-timeout=5
# keytab-file=/etc/krb5.keytab
diff --git a/utils/gssd/gssd.c b/utils/gssd/gssd.c
index ca9b3267..10c731ab 100644
--- a/utils/gssd/gssd.c
+++ b/utils/gssd/gssd.c
@@ -1232,6 +1232,12 @@ main(int argc, char *argv[])
daemon_init(fg);
+#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
+ rc = get_allowed_enctypes();
+ if (rc)
+ exit(EXIT_FAILURE);
+#endif
+
if (gssd_check_mechs() != 0)
errx(1, "Problem with gssapi library");
diff --git a/utils/gssd/gssd.man b/utils/gssd/gssd.man
index 2a5384d3..c735eff6 100644
--- a/utils/gssd/gssd.man
+++ b/utils/gssd/gssd.man
@@ -346,6 +346,15 @@ flag.
Equivalent to
.BR -l .
.TP
+.B allowed-enctypes
+Allows you to restrict
+.B rpc.gssd
+to using a subset of the encryption types permitted by the kernel and the krb5
+libraries. This is useful if you need to interoperate with an NFS server that
+does not have support for the newer SHA2 and Camellia encryption types, for
+example. This configuration file option does not have an equivalent
+command-line option.
+.TP
.B context-timeout
Equivalent to
.BR -t .
diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c
index 6f66ef4f..57b3cf8a 100644
--- a/utils/gssd/krb5_util.c
+++ b/utils/gssd/krb5_util.c
@@ -129,6 +129,7 @@
#include "err_util.h"
#include "gss_util.h"
#include "krb5_util.h"
+#include "conffile.h"
/*
* List of principals from our keytab that we
@@ -155,6 +156,8 @@ static pthread_mutex_t ple_lock = PTHREAD_MUTEX_INITIALIZER;
#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
int limit_to_legacy_enctypes = 0;
+krb5_enctype *allowed_enctypes = NULL;
+int num_allowed_enctypes = 0;
#endif
/*==========================*/
@@ -1596,6 +1599,68 @@ out_cred:
}
#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
+int
+get_allowed_enctypes(void)
+{
+ struct conf_list *allowed_etypes = NULL;
+ struct conf_list_node *node;
+ char *buf = NULL, *old = NULL;
+ int len, ret = 0;
+
+ allowed_etypes = conf_get_list("gssd", "allowed-enctypes");
+ if (allowed_etypes) {
+ TAILQ_FOREACH(node, &(allowed_etypes->fields), link) {
+ allowed_enctypes = realloc(allowed_enctypes,
+ (num_allowed_enctypes + 1) *
+ sizeof(*allowed_enctypes));
+ if (allowed_enctypes == NULL) {
+ ret = ENOMEM;
+ goto out_err;
+ }
+ ret = krb5_string_to_enctype(node->field,
+ &allowed_enctypes[num_allowed_enctypes]);
+ if (ret) {
+ printerr(0, "%s: invalid enctype %s",
+ __func__, node->field);
+ goto out_err;
+ }
+ if (get_verbosity() > 1) {
+ if (buf == NULL) {
+ len = asprintf(&buf, "%s(%d)", node->field,
+ allowed_enctypes[num_allowed_enctypes]);
+ if (len < 0) {
+ ret = ENOMEM;
+ goto out_err;
+ }
+ } else {
+ old = buf;
+ len = asprintf(&buf, "%s,%s(%d)", old, node->field,
+ allowed_enctypes[num_allowed_enctypes]);
+ if (len < 0) {
+ ret = ENOMEM;
+ goto out_err;
+ }
+ free(old);
+ old = NULL;
+ }
+ }
+ num_allowed_enctypes++;
+ }
+ printerr(2, "%s: allowed_enctypes = %s", __func__, buf);
+ }
+ goto out;
+out_err:
+ num_allowed_enctypes = 0;
+ free(allowed_enctypes);
+out:
+ free(buf);
+ if (old != buf)
+ free(old);
+ if (allowed_etypes)
+ conf_free_list(allowed_etypes);
+ return ret;
+}
+
/*
* this routine obtains a credentials handle via gss_acquire_cred()
* then calls gss_krb5_set_allowable_enctypes() to limit the encryption
@@ -1619,6 +1684,10 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec)
int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]);
extern int num_krb5_enctypes;
extern krb5_enctype *krb5_enctypes;
+ extern int num_allowed_enctypes;
+ extern krb5_enctype *allowed_enctypes;
+ int num_set_enctypes;
+ krb5_enctype *set_enctypes;
int err = -1;
if (sec->cred == GSS_C_NO_CREDENTIAL) {
@@ -1631,12 +1700,26 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec)
* If we failed for any reason to produce global
* list of supported enctypes, use local default here.
*/
- if (krb5_enctypes == NULL || limit_to_legacy_enctypes)
- maj_stat = gss_set_allowable_enctypes(&min_stat, sec->cred,
- &krb5oid, num_enctypes, enctypes);
- else
- maj_stat = gss_set_allowable_enctypes(&min_stat, sec->cred,
- &krb5oid, num_krb5_enctypes, krb5_enctypes);
+ if (krb5_enctypes == NULL || limit_to_legacy_enctypes ||
+ allowed_enctypes) {
+ if (allowed_enctypes) {
+ printerr(2, "%s: using allowed enctypes from config\n",
+ __func__);
+ num_set_enctypes = num_allowed_enctypes;
+ set_enctypes = allowed_enctypes;
+ } else {
+ printerr(2, "%s: using legacy enctypes\n", __func__);
+ num_set_enctypes = num_enctypes;
+ set_enctypes = enctypes;
+ }
+ } else {
+ printerr(2, "%s: using enctypes from the kernel\n", __func__);
+ num_set_enctypes = num_krb5_enctypes;
+ set_enctypes = krb5_enctypes;
+ }
+
+ maj_stat = gss_set_allowable_enctypes(&min_stat, sec->cred,
+ &krb5oid, num_set_enctypes, set_enctypes);
if (maj_stat != GSS_S_COMPLETE) {
pgsserr("gss_set_allowable_enctypes",
diff --git a/utils/gssd/krb5_util.h b/utils/gssd/krb5_util.h
index 7ef87018..40ad3233 100644
--- a/utils/gssd/krb5_util.h
+++ b/utils/gssd/krb5_util.h
@@ -27,6 +27,7 @@ int gssd_k5_remove_bad_service_cred(char *srvname);
#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
extern int limit_to_legacy_enctypes;
int limit_krb5_enctypes(struct rpc_gss_sec *sec);
+int get_allowed_enctypes(void);
#endif
/*
--
2.43.0
Re-adding the list.
On Mon, 04 Mar 2024, Scott Mayhew wrote:
> On Mon, 04 Mar 2024, Olga Kornievskaia wrote:
>
> > On Wed, Feb 28, 2024 at 5:23 PM Scott Mayhew <[email protected]> wrote:
> > >
> > > If the NFS server reset the connection when we tried to create a GSS
> > > context with it, then there's a good chance that we used an encryption
> > > type that it didn't support.
> > >
> > > Add a one time backoff/retry mechanism, where we adjust the list of
> > > encryption types that we set via gss_set_allowable_enctypes(). We can
> > > do this easily because the list of encryption types should be ordered
> > > from highest preference to lowest. We just need to find the first entry
> > > that's not one of the newer encryption types, and then use that as the
> > > start of the list.
> >
> > This doesn't seem like a good idea because it opens up a security
> > problem that a connection reset (by an attacker) during context
> > establishment would force the client to downgrade its security.
>
> I'm willing to drop this patch if that's the consensus. Or we could
> still it behind yet another config option.
>
> I'd still like to see the first one get merged though.
>
> -Scott
> >
> > >
> > > Signed-off-by: Scott Mayhew <[email protected]>
> > > ---
> > > utils/gssd/gssd_proc.c | 15 +++++++++++++--
> > > utils/gssd/krb5_util.c | 40 +++++++++++++++++++++++++++++++++++++++-
> > > utils/gssd/krb5_util.h | 2 +-
> > > 3 files changed, 53 insertions(+), 4 deletions(-)
> > >
> > > diff --git a/utils/gssd/gssd_proc.c b/utils/gssd/gssd_proc.c
> > > index 7629de0b..0da54598 100644
> > > --- a/utils/gssd/gssd_proc.c
> > > +++ b/utils/gssd/gssd_proc.c
> > > @@ -337,6 +337,10 @@ create_auth_rpc_client(struct clnt_info *clp,
> > > rpc_gss_options_req_t req;
> > > rpc_gss_options_ret_t ret;
> > > char mechanism[] = "kerberos_v5";
> > > +#endif
> > > +#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
> > > + bool backoff = false;
> > > + struct rpc_err err;
> > > #endif
> > > pthread_t tid = pthread_self();
> > >
> > > @@ -354,14 +358,14 @@ create_auth_rpc_client(struct clnt_info *clp,
> > > goto out_fail;
> > > }
> > >
> > > -
> > > if (authtype == AUTHTYPE_KRB5) {
> > > #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
> > > +again:
> > > /*
> > > * Do this before creating rpc connection since we won't need
> > > * rpc connection if it fails!
> > > */
> > > - if (limit_krb5_enctypes(&sec)) {
> > > + if (limit_krb5_enctypes(&sec, backoff)) {
> > > printerr(1, "WARNING: Failed while limiting krb5 "
> > > "encryption types for user with uid %d\n",
> > > uid);
> > > @@ -445,6 +449,13 @@ create_auth_rpc_client(struct clnt_info *clp,
> > > goto success;
> > > }
> > > }
> > > +#endif
> > > +#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
> > > + clnt_geterr(rpc_clnt, &err);
> > > + if (err.re_errno == ECONNRESET && !backoff) {
> > > + backoff = true;
> > > + goto again;
> > > + }
> > > #endif
> > > /* Our caller should print appropriate message */
> > > printerr(2, "WARNING: Failed to create krb5 context for "
> > > diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c
> > > index 57b3cf8a..5502e74e 100644
> > > --- a/utils/gssd/krb5_util.c
> > > +++ b/utils/gssd/krb5_util.c
> > > @@ -1675,7 +1675,7 @@ out:
> > > */
> > >
> > > int
> > > -limit_krb5_enctypes(struct rpc_gss_sec *sec)
> > > +limit_krb5_enctypes(struct rpc_gss_sec *sec, bool backoff)
> > > {
> > > u_int maj_stat, min_stat;
> > > krb5_enctype enctypes[] = { ENCTYPE_DES_CBC_CRC,
> > > @@ -1689,6 +1689,17 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec)
> > > int num_set_enctypes;
> > > krb5_enctype *set_enctypes;
> > > int err = -1;
> > > + int i, j;
> > > + bool done = false;
> > > +
> > > + if (backoff && sec->cred != GSS_C_NO_CREDENTIAL) {
> > > + printerr(2, "%s: backoff: releasing old cred\n", __func__);
> > > + maj_stat = gss_release_cred(&min_stat, &sec->cred);
> > > + if (maj_stat != GSS_S_COMPLETE) {
> > > + printerr(2, "%s: gss_release_cred() failed\n", __func__);
> > > + return -1;
> > > + }
> > > + }
> > >
> > > if (sec->cred == GSS_C_NO_CREDENTIAL) {
> > > err = gssd_acquire_krb5_cred(&sec->cred);
> > > @@ -1718,6 +1729,33 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec)
> > > set_enctypes = krb5_enctypes;
> > > }
> > >
> > > + if (backoff) {
> > > + j = num_set_enctypes;
> > > + for (i = 0; i < j && !done; i++) {
> > > + switch (*set_enctypes) {
> > > + case ENCTYPE_AES128_CTS_HMAC_SHA256_128:
> > > + case ENCTYPE_AES256_CTS_HMAC_SHA384_192:
> > > + case ENCTYPE_CAMELLIA128_CTS_CMAC:
> > > + case ENCTYPE_CAMELLIA256_CTS_CMAC:
> > > + printerr(2, "%s: backoff: removing enctype %d\n",
> > > + __func__, *set_enctypes);
> > > + set_enctypes++;
> > > + num_set_enctypes--;
> > > + break;
> > > + default:
> > > + done = true;
> > > + break;
> > > + }
> > > + }
> > > + printerr(2, "%s: backoff: %d remaining enctypes\n",
> > > + __func__, num_set_enctypes);
> > > + if (!num_set_enctypes) {
> > > + printerr(0, "%s: no remaining enctypes after backoff\n",
> > > + __func__);
> > > + return -1;
> > > + }
> > > + }
> > > +
> > > maj_stat = gss_set_allowable_enctypes(&min_stat, sec->cred,
> > > &krb5oid, num_set_enctypes, set_enctypes);
> > >
> > > diff --git a/utils/gssd/krb5_util.h b/utils/gssd/krb5_util.h
> > > index 40ad3233..0be0c500 100644
> > > --- a/utils/gssd/krb5_util.h
> > > +++ b/utils/gssd/krb5_util.h
> > > @@ -26,7 +26,7 @@ int gssd_k5_remove_bad_service_cred(char *srvname);
> > >
> > > #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
> > > extern int limit_to_legacy_enctypes;
> > > -int limit_krb5_enctypes(struct rpc_gss_sec *sec);
> > > +int limit_krb5_enctypes(struct rpc_gss_sec *sec, bool backoff);
> > > int get_allowed_enctypes(void);
> > > #endif
> > >
> > > --
> > > 2.43.0
> > >
> > >
> >
Re-adding the list.
On Mon, 04 Mar 2024, Scott Mayhew wrote:
> On Mon, 04 Mar 2024, Olga Kornievskaia wrote:
>
> > On Wed, Feb 28, 2024 at 5:23 PM Scott Mayhew <[email protected]> wrote:
> > >
> > > In order for an NFS client with support for the newer encryption types
> > > (AES with SHA2 and Camellia) in its RPCSEC GSS kernel code to connect to
> > > an NFS server without support for those encryption types in its RPCSEC
> > > GSS kernel code, it is sometimes necessary for configuration changes on
> > > the NFS server... particularly if the NFS server's userspace krb5 code
> > > does have support for the newer encryption types and/or the NFS server's
> > > keytab has "nfs" keys using the newer encryption types.
> >
> > I'm stuck on "the NFs server's keytab has nfs keys" with newer
> > encryption types. That sounds like a misconfigured server. The
> > administrator shouldn't have issued a keytab knowingly that those
> > encryption types are not supported.
>
> I think it's probably pretty common for administrators to omit the -e
> option when they use 'kadmin ktadd' or 'ipa-getkeytab', so they get keys
> for whatever's enabled in their krb5 configuration.
>
> But It turns out that part is not even necessary for the problem to occur.
> As long as the krb5 configuration has those encryption types enabled, the
> problem can occur. See the thread posted by Orion Poplawski on 2/29:
> https://lore.kernel.org/linux-nfs/[email protected]/T/#t
>
> -Scott
> >
> > > Rather than
> > > rehashing the whole discussion here in the cover letter, see the
> > > description in the first patch for the gory details.
> > >
> > > These patches make it easier for a "newer" NFS client to work with an
> > > "older" NFS server.
> > >
> > > The first patch adds support for an "allowed-enctypes" option in
> > > nfs.conf, allowing the the client to restrict the permitted encryption
> > > types to a subset of what is otherwise supported in its krb5 environment
> > > so that it doesn't use an encryption type that the NFS server doesn't
> > > support when negotiating a GSS context.
> > >
> > > The second patch builds on this by adding an automatic backoff feature,
> > > where if the NFS client fails to negotiate a GSS context with the NFS
> > > server using the newer encryption types, it will try again without using
> > > the newer encryption types.
> > >
> > > With these patches in place on the NFS client, the "newer" NFS client
> > > will work with an "older" NFS server without requiring any configuration
> > > changes.
> > >
> > > Scott Mayhew (2):
> > > gssd: add support for an "allowed-enctypes" option in nfs.conf
> > > gssd: add a "backoff" feature to limit_krb5_enctypes()
> > >
> > > nfs.conf | 1 +
> > > utils/gssd/gssd.c | 6 ++
> > > utils/gssd/gssd.man | 9 +++
> > > utils/gssd/gssd_proc.c | 15 ++++-
> > > utils/gssd/krb5_util.c | 135 ++++++++++++++++++++++++++++++++++++++---
> > > utils/gssd/krb5_util.h | 3 +-
> > > 6 files changed, 159 insertions(+), 10 deletions(-)
> > >
> > > --
> > > 2.43.0
> > >
> > >
> >
On 2/28/24 5:22 PM, Scott Mayhew wrote:
> Newer kernels have support for newer krb5 encryption types, AES with
> SHA2 and Camellia. An NFS client with an "old" kernel can talk to
> and NFS server with a "new" kernel and it just works. An NFS client
> with a "new" kernel can talk to an NFS server with an "old" kernel, but
> that requires some additional configuration (particularly if the NFS
> server does have support for the newer encryption types in its userspace
> krb5 libraries) that may be unclear and/or burdensome to the admin.
>
> 1) If the NFS server has support for the newer encryption types in the
> userspace krb5 libraries, but not in the kernel's RPCSEC_GSS code,
> then its possible that it also already has "nfs" keys using those
> newer encryption types in its keytab. In that case, it's necessary
> to regenerate the "nfs" keys without the newer encryption types.
> The reason this is necessary is because if the NFS client requests
> an "nfs" service ticket from the KDC, and the list of enctypes in
> in that TGS-REQ contains a newer encryption type, and the KDC had
> previously generated a key for the NFS server using the newer
> encryption type, then the resulting service ticket in the TGS-REP
> will be using the newer encryption type and the NFS server will not
> be able to decrypt it.
>
> 2) It is necessary to either modify the permitted_enctypes field of the
> krb5.conf or create a custom crypto-policy module (if the
> crypto-policies package is being used) on the NFS *client* so that it
> does not include the newer encryption types. The reason this is
> necessary is because it affects the list of encryption types that
> will be present in the RPCSEC_GSS_INIT request that the NFS client
> sends to the NFS server. The kernel on the NFS server cannot not
> process the request on its own; it has to upcall to gssproxy to do
> that... and again if the userspace krb5 libraries on the NFS server
> have support for the newer encryption types, then it will select one
> of those and the kernel will not be able to import the context when
> it gets the downcall. Also note that modifying the permitted_enctypes
> field and/or crypto policy has the side effect of impacting everything
> krb5 related, not just just NFS.
>
> So add support for an "allowed-enctypes" field in nfs.conf. This allows
> the admin to restrict gssd to using a subset of the encryption types
> that are supported by the kernel and krb5 libraries. This will remove
> the need for steps 1 & 2 above, and will only affect NFS rather than
> krb5 as a whole.
>
> For example, for a "new" NFS client talking to an "old" NFS server, the
> admin will probably want this in the client's nfs.conf:
>
> allowed-enctypes=aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96
>
> Signed-off-by: Scott Mayhew <[email protected]>
Committed (tag: nfs-utils-2-7-1-rc5)
steved.
> ---
> nfs.conf | 1 +
> utils/gssd/gssd.c | 6 +++
> utils/gssd/gssd.man | 9 ++++
> utils/gssd/krb5_util.c | 95 +++++++++++++++++++++++++++++++++++++++---
> utils/gssd/krb5_util.h | 1 +
> 5 files changed, 106 insertions(+), 6 deletions(-)
>
> diff --git a/nfs.conf b/nfs.conf
> index 323f072b..23b5f7d4 100644
> --- a/nfs.conf
> +++ b/nfs.conf
> @@ -23,6 +23,7 @@
> # use-gss-proxy=0
> # avoid-dns=1
> # limit-to-legacy-enctypes=0
> +# allowed-enctypes=aes256-cts-hmac-sha384-192,aes128-cts-hmac-sha256-128,camellia256-cts-cmac,camellia128-cts-cmac,aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96
> # context-timeout=0
> # rpc-timeout=5
> # keytab-file=/etc/krb5.keytab
> diff --git a/utils/gssd/gssd.c b/utils/gssd/gssd.c
> index ca9b3267..10c731ab 100644
> --- a/utils/gssd/gssd.c
> +++ b/utils/gssd/gssd.c
> @@ -1232,6 +1232,12 @@ main(int argc, char *argv[])
>
> daemon_init(fg);
>
> +#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
> + rc = get_allowed_enctypes();
> + if (rc)
> + exit(EXIT_FAILURE);
> +#endif
> +
> if (gssd_check_mechs() != 0)
> errx(1, "Problem with gssapi library");
>
> diff --git a/utils/gssd/gssd.man b/utils/gssd/gssd.man
> index 2a5384d3..c735eff6 100644
> --- a/utils/gssd/gssd.man
> +++ b/utils/gssd/gssd.man
> @@ -346,6 +346,15 @@ flag.
> Equivalent to
> .BR -l .
> .TP
> +.B allowed-enctypes
> +Allows you to restrict
> +.B rpc.gssd
> +to using a subset of the encryption types permitted by the kernel and the krb5
> +libraries. This is useful if you need to interoperate with an NFS server that
> +does not have support for the newer SHA2 and Camellia encryption types, for
> +example. This configuration file option does not have an equivalent
> +command-line option.
> +.TP
> .B context-timeout
> Equivalent to
> .BR -t .
> diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c
> index 6f66ef4f..57b3cf8a 100644
> --- a/utils/gssd/krb5_util.c
> +++ b/utils/gssd/krb5_util.c
> @@ -129,6 +129,7 @@
> #include "err_util.h"
> #include "gss_util.h"
> #include "krb5_util.h"
> +#include "conffile.h"
>
> /*
> * List of principals from our keytab that we
> @@ -155,6 +156,8 @@ static pthread_mutex_t ple_lock = PTHREAD_MUTEX_INITIALIZER;
>
> #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
> int limit_to_legacy_enctypes = 0;
> +krb5_enctype *allowed_enctypes = NULL;
> +int num_allowed_enctypes = 0;
> #endif
>
> /*==========================*/
> @@ -1596,6 +1599,68 @@ out_cred:
> }
>
> #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
> +int
> +get_allowed_enctypes(void)
> +{
> + struct conf_list *allowed_etypes = NULL;
> + struct conf_list_node *node;
> + char *buf = NULL, *old = NULL;
> + int len, ret = 0;
> +
> + allowed_etypes = conf_get_list("gssd", "allowed-enctypes");
> + if (allowed_etypes) {
> + TAILQ_FOREACH(node, &(allowed_etypes->fields), link) {
> + allowed_enctypes = realloc(allowed_enctypes,
> + (num_allowed_enctypes + 1) *
> + sizeof(*allowed_enctypes));
> + if (allowed_enctypes == NULL) {
> + ret = ENOMEM;
> + goto out_err;
> + }
> + ret = krb5_string_to_enctype(node->field,
> + &allowed_enctypes[num_allowed_enctypes]);
> + if (ret) {
> + printerr(0, "%s: invalid enctype %s",
> + __func__, node->field);
> + goto out_err;
> + }
> + if (get_verbosity() > 1) {
> + if (buf == NULL) {
> + len = asprintf(&buf, "%s(%d)", node->field,
> + allowed_enctypes[num_allowed_enctypes]);
> + if (len < 0) {
> + ret = ENOMEM;
> + goto out_err;
> + }
> + } else {
> + old = buf;
> + len = asprintf(&buf, "%s,%s(%d)", old, node->field,
> + allowed_enctypes[num_allowed_enctypes]);
> + if (len < 0) {
> + ret = ENOMEM;
> + goto out_err;
> + }
> + free(old);
> + old = NULL;
> + }
> + }
> + num_allowed_enctypes++;
> + }
> + printerr(2, "%s: allowed_enctypes = %s", __func__, buf);
> + }
> + goto out;
> +out_err:
> + num_allowed_enctypes = 0;
> + free(allowed_enctypes);
> +out:
> + free(buf);
> + if (old != buf)
> + free(old);
> + if (allowed_etypes)
> + conf_free_list(allowed_etypes);
> + return ret;
> +}
> +
> /*
> * this routine obtains a credentials handle via gss_acquire_cred()
> * then calls gss_krb5_set_allowable_enctypes() to limit the encryption
> @@ -1619,6 +1684,10 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec)
> int num_enctypes = sizeof(enctypes) / sizeof(enctypes[0]);
> extern int num_krb5_enctypes;
> extern krb5_enctype *krb5_enctypes;
> + extern int num_allowed_enctypes;
> + extern krb5_enctype *allowed_enctypes;
> + int num_set_enctypes;
> + krb5_enctype *set_enctypes;
> int err = -1;
>
> if (sec->cred == GSS_C_NO_CREDENTIAL) {
> @@ -1631,12 +1700,26 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec)
> * If we failed for any reason to produce global
> * list of supported enctypes, use local default here.
> */
> - if (krb5_enctypes == NULL || limit_to_legacy_enctypes)
> - maj_stat = gss_set_allowable_enctypes(&min_stat, sec->cred,
> - &krb5oid, num_enctypes, enctypes);
> - else
> - maj_stat = gss_set_allowable_enctypes(&min_stat, sec->cred,
> - &krb5oid, num_krb5_enctypes, krb5_enctypes);
> + if (krb5_enctypes == NULL || limit_to_legacy_enctypes ||
> + allowed_enctypes) {
> + if (allowed_enctypes) {
> + printerr(2, "%s: using allowed enctypes from config\n",
> + __func__);
> + num_set_enctypes = num_allowed_enctypes;
> + set_enctypes = allowed_enctypes;
> + } else {
> + printerr(2, "%s: using legacy enctypes\n", __func__);
> + num_set_enctypes = num_enctypes;
> + set_enctypes = enctypes;
> + }
> + } else {
> + printerr(2, "%s: using enctypes from the kernel\n", __func__);
> + num_set_enctypes = num_krb5_enctypes;
> + set_enctypes = krb5_enctypes;
> + }
> +
> + maj_stat = gss_set_allowable_enctypes(&min_stat, sec->cred,
> + &krb5oid, num_set_enctypes, set_enctypes);
>
> if (maj_stat != GSS_S_COMPLETE) {
> pgsserr("gss_set_allowable_enctypes",
> diff --git a/utils/gssd/krb5_util.h b/utils/gssd/krb5_util.h
> index 7ef87018..40ad3233 100644
> --- a/utils/gssd/krb5_util.h
> +++ b/utils/gssd/krb5_util.h
> @@ -27,6 +27,7 @@ int gssd_k5_remove_bad_service_cred(char *srvname);
> #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
> extern int limit_to_legacy_enctypes;
> int limit_krb5_enctypes(struct rpc_gss_sec *sec);
> +int get_allowed_enctypes(void);
> #endif
>
> /*
On 2/28/24 5:22 PM, Scott Mayhew wrote:
> If the NFS server reset the connection when we tried to create a GSS
> context with it, then there's a good chance that we used an encryption
> type that it didn't support.
>
> Add a one time backoff/retry mechanism, where we adjust the list of
> encryption types that we set via gss_set_allowable_enctypes(). We can
> do this easily because the list of encryption types should be ordered
> from highest preference to lowest. We just need to find the first entry
> that's not one of the newer encryption types, and then use that as the
> start of the list.
>
> Signed-off-by: Scott Mayhew <[email protected]>
Not Committed!
steved.
> ---
> utils/gssd/gssd_proc.c | 15 +++++++++++++--
> utils/gssd/krb5_util.c | 40 +++++++++++++++++++++++++++++++++++++++-
> utils/gssd/krb5_util.h | 2 +-
> 3 files changed, 53 insertions(+), 4 deletions(-)
>
> diff --git a/utils/gssd/gssd_proc.c b/utils/gssd/gssd_proc.c
> index 7629de0b..0da54598 100644
> --- a/utils/gssd/gssd_proc.c
> +++ b/utils/gssd/gssd_proc.c
> @@ -337,6 +337,10 @@ create_auth_rpc_client(struct clnt_info *clp,
> rpc_gss_options_req_t req;
> rpc_gss_options_ret_t ret;
> char mechanism[] = "kerberos_v5";
> +#endif
> +#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
> + bool backoff = false;
> + struct rpc_err err;
> #endif
> pthread_t tid = pthread_self();
>
> @@ -354,14 +358,14 @@ create_auth_rpc_client(struct clnt_info *clp,
> goto out_fail;
> }
>
> -
> if (authtype == AUTHTYPE_KRB5) {
> #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
> +again:
> /*
> * Do this before creating rpc connection since we won't need
> * rpc connection if it fails!
> */
> - if (limit_krb5_enctypes(&sec)) {
> + if (limit_krb5_enctypes(&sec, backoff)) {
> printerr(1, "WARNING: Failed while limiting krb5 "
> "encryption types for user with uid %d\n",
> uid);
> @@ -445,6 +449,13 @@ create_auth_rpc_client(struct clnt_info *clp,
> goto success;
> }
> }
> +#endif
> +#ifdef HAVE_SET_ALLOWABLE_ENCTYPES
> + clnt_geterr(rpc_clnt, &err);
> + if (err.re_errno == ECONNRESET && !backoff) {
> + backoff = true;
> + goto again;
> + }
> #endif
> /* Our caller should print appropriate message */
> printerr(2, "WARNING: Failed to create krb5 context for "
> diff --git a/utils/gssd/krb5_util.c b/utils/gssd/krb5_util.c
> index 57b3cf8a..5502e74e 100644
> --- a/utils/gssd/krb5_util.c
> +++ b/utils/gssd/krb5_util.c
> @@ -1675,7 +1675,7 @@ out:
> */
>
> int
> -limit_krb5_enctypes(struct rpc_gss_sec *sec)
> +limit_krb5_enctypes(struct rpc_gss_sec *sec, bool backoff)
> {
> u_int maj_stat, min_stat;
> krb5_enctype enctypes[] = { ENCTYPE_DES_CBC_CRC,
> @@ -1689,6 +1689,17 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec)
> int num_set_enctypes;
> krb5_enctype *set_enctypes;
> int err = -1;
> + int i, j;
> + bool done = false;
> +
> + if (backoff && sec->cred != GSS_C_NO_CREDENTIAL) {
> + printerr(2, "%s: backoff: releasing old cred\n", __func__);
> + maj_stat = gss_release_cred(&min_stat, &sec->cred);
> + if (maj_stat != GSS_S_COMPLETE) {
> + printerr(2, "%s: gss_release_cred() failed\n", __func__);
> + return -1;
> + }
> + }
>
> if (sec->cred == GSS_C_NO_CREDENTIAL) {
> err = gssd_acquire_krb5_cred(&sec->cred);
> @@ -1718,6 +1729,33 @@ limit_krb5_enctypes(struct rpc_gss_sec *sec)
> set_enctypes = krb5_enctypes;
> }
>
> + if (backoff) {
> + j = num_set_enctypes;
> + for (i = 0; i < j && !done; i++) {
> + switch (*set_enctypes) {
> + case ENCTYPE_AES128_CTS_HMAC_SHA256_128:
> + case ENCTYPE_AES256_CTS_HMAC_SHA384_192:
> + case ENCTYPE_CAMELLIA128_CTS_CMAC:
> + case ENCTYPE_CAMELLIA256_CTS_CMAC:
> + printerr(2, "%s: backoff: removing enctype %d\n",
> + __func__, *set_enctypes);
> + set_enctypes++;
> + num_set_enctypes--;
> + break;
> + default:
> + done = true;
> + break;
> + }
> + }
> + printerr(2, "%s: backoff: %d remaining enctypes\n",
> + __func__, num_set_enctypes);
> + if (!num_set_enctypes) {
> + printerr(0, "%s: no remaining enctypes after backoff\n",
> + __func__);
> + return -1;
> + }
> + }
> +
> maj_stat = gss_set_allowable_enctypes(&min_stat, sec->cred,
> &krb5oid, num_set_enctypes, set_enctypes);
>
> diff --git a/utils/gssd/krb5_util.h b/utils/gssd/krb5_util.h
> index 40ad3233..0be0c500 100644
> --- a/utils/gssd/krb5_util.h
> +++ b/utils/gssd/krb5_util.h
> @@ -26,7 +26,7 @@ int gssd_k5_remove_bad_service_cred(char *srvname);
>
> #ifdef HAVE_SET_ALLOWABLE_ENCTYPES
> extern int limit_to_legacy_enctypes;
> -int limit_krb5_enctypes(struct rpc_gss_sec *sec);
> +int limit_krb5_enctypes(struct rpc_gss_sec *sec, bool backoff);
> int get_allowed_enctypes(void);
> #endif
>