Hi linux-nfs, Please review attached patches for gssapi-related code of nfs4.0 server test (pynfs). This is a continuation of previous work to make GSS tests work correctly with the recent gssapi python library (using python3). $ nfs4.0/testserver.py server.fqdn:/export gss noGSS8 --security=krb5 ... Command line asked for 8 of 673 tests Of those: 3 Skipped, 0 Failed, 0 Warned, 5 Passed $ nfs4.0/testserver.py server.fqdn:/export GSS8 --security=krb5 ... Command line asked for 1 of 673 tests Of those: 0 Skipped, 0 Failed, 0 Warned, 1 Passed $ nfs4.0/testserver.py server.fqdn:/export gss noGSS8 --security=krb5i ... Command line asked for 8 of 673 tests Of those: 1 Skipped, 0 Failed, 0 Warned, 7 Passed Thanks, Volodymyr Khomenko.
From fe6e9fee4bd1979e2e02d9f46d76dace90b5909c Mon Sep 17 00:00:00 2001 From: Volodymyr Khomenko <volodymyr@xxxxxxxxxxxx> Date: Thu, 18 Nov 2021 00:06:02 +0200 Subject: [PATCH 2/2] Fixed gss flag for nfs4.0 server test (GSS* tests) - Fixed scope/visibility issue of caught exception 'e' for try/except - Added OSError exception as a valid use-case for testBadGssSeqnum (aka GSS1): server may close TCP connection as a possible reaction for bad GSS seq_num - Fixed python3 issues for concatenating bytes + string Signed-off-by: Volodymyr Khomenko <volodymyr@xxxxxxxxxxxx> --- nfs4.0/servertests/st_gss.py | 82 ++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/nfs4.0/servertests/st_gss.py b/nfs4.0/servertests/st_gss.py index e10e849..f651e57 100644 --- a/nfs4.0/servertests/st_gss.py +++ b/nfs4.0/servertests/st_gss.py @@ -78,6 +78,8 @@ def testBadGssSeqnum(t, env): res = c.compound([op.putrootfh()]) except timeout: success = True + except OSError: + success = True if not success: t.fail("Using old gss_seq_num %i should cause dropped reply" % (orig + 1)) @@ -106,17 +108,19 @@ def testInconsistentGssSeqnum(t, env): try: c.security.secure_data = bad_secure_data + err = None try: res = c.compound([op.putrootfh()]) - e = "operation erroneously suceeding" + err = "operation erroneously suceeding" except rpc.RPCAcceptError as e: if e.stat == rpc.GARBAGE_ARGS: # This is correct response return + err = str(e) except Exception as e: - pass + err = str(e) t.fail("Using inconsistent gss_seq_nums in header and body of message " - "should return GARBAGE_ARGS, instead got %s" % e) + "should return GARBAGE_ARGS, instead got %s" % err) finally: c.security.secure_data = orig_funct @@ -131,21 +135,23 @@ def testBadVerfChecksum(t, env): orig_funct = c.security.make_verf def bad_make_verf(data): # Mess up verifier - return orig_funct(data + "x") + return orig_funct(data + b"x") try: c.security.make_verf = bad_make_verf + err = None try: res = c.compound([op.putrootfh()]) - e = "peration erroneously suceeding" + err = "peration erroneously suceeding" except rpc.RPCDeniedError as e: if e.stat == rpc.AUTH_ERROR and e.astat == rpc.RPCSEC_GSS_CREDPROBLEM: - # This is correct response - return + # This is correct response + return + err = str(e) except Exception as e: - pass + err = str(e) t.fail("Using bad verifier checksum in header " - "should return RPCSEC_GSS_CREDPROBLEM, instead got %s" % e) + "should return RPCSEC_GSS_CREDPROBLEM, instead got %s" % err) finally: c.security.make_verf = orig_funct @@ -164,24 +170,26 @@ def testBadDataChecksum(t, env): # Mess up checksum data = orig_funct(data, seqnum) if data[-4]: - tail = chr(0) + data[-3:] + tail = b'\x00' + data[-3:] else: - tail = chr(1) + data[-3:] + tail = b'\x01' + data[-3:] return data[:-4] + tail try: c.security.secure_data = bad_secure_data + err = None try: res = c.compound([op.putrootfh()]) - e = "operation erroneously suceeding" + err = "operation erroneously suceeding" except rpc.RPCAcceptError as e: if e.stat == rpc.GARBAGE_ARGS: # This is correct response return + err = str(e) except Exception as e: - pass + err = str(e) t.fail("Using bad data checksum for body of message " - "should return GARBAGE_ARGS, instead got %s" % e) + "should return GARBAGE_ARGS, instead got %s" % err) finally: c.security.secure_data = orig_funct @@ -211,19 +219,22 @@ def testBadVersion(t, env): c.security = BadGssHeader(orig, bad_version) bad_versions = [0, 2, 3, 1024] for version in bad_versions: + err = None try: res = c.compound([op.putrootfh()]) - e = "operation erroneously suceeding" + err = "operation erroneously suceeding" except rpc.RPCDeniedError as e: if e.stat == rpc.AUTH_ERROR and e.astat == rpc.AUTH_BADCRED: # This is correct response - e = None + pass + else: + err = str(e) except Exception as e: - pass - if e is not None: + err = str(e) + if err is not None: t.fail("Using bad gss version number %i " "should return AUTH_BADCRED, instead got %s" % - (version, e)) + (version, err)) finally: c.security = orig @@ -238,17 +249,18 @@ def testHighSeqNum(t, env): orig_seq = c.security.gss_seq_num try: c.security.gss_seq_num = gss.MAXSEQ + 1 + err = None try: res = c.compound([op.putrootfh()]) - e = "operation erroneously suceeding" + err = "operation erroneously suceeding" except rpc.RPCDeniedError as e: if e.stat == rpc.AUTH_ERROR and e.astat == rpc.RPCSEC_GSS_CTXPROBLEM: # This is correct response return except Exception as e: - pass + err = str(e) t.fail("Using gss_seq_num over MAXSEQ " - "should return RPCSEC_GSS_CTXPROBLEM, instead got %s" % e) + "should return RPCSEC_GSS_CTXPROBLEM, instead got %s" % err) finally: c.security.gss_seq_num = orig_seq @@ -274,21 +286,24 @@ def testBadProcedure(t, env): try: c.security = BadGssHeader(orig, bad_proc) + err = None bad_procss = [4, 5, 1024] for proc in bad_procss: try: res = c.compound([op.putrootfh()]) - e = "operation erroneously suceeding" + err = "operation erroneously suceeding" except rpc.RPCDeniedError as e: if e.stat == rpc.AUTH_ERROR and e.astat == rpc.AUTH_BADCRED: # This is correct response - e = None + pass + else: + err = str(e) except Exception as e: - pass - if e is not None: + err = str(e) + if err is not None: t.fail("Using bad gss procedure number %i " "should return AUTH_BADCRED, instead got %s" % - (proc, e)) + (proc, err)) finally: c.security = orig @@ -316,20 +331,23 @@ def testBadService(t, env): try: c.security = BadGssHeader(orig, bad_service) + err = None bad_services = [0, 4, 5, 1024] for service in bad_services: try: res = c.compound([op.putrootfh()]) - e = "operation erroneously suceeding" + err = "operation erroneously suceeding" except rpc.RPCDeniedError as e: if e.stat == rpc.AUTH_ERROR and e.astat == rpc.AUTH_BADCRED: # This is correct response - e = None + pass + else: + err = str(e) except Exception as e: - pass - if e is not None: + err = str(e) + if err is not None: t.fail("Using bad gss service number %i " "should return AUTH_BADCRED, instead got %s" % - (service, e)) + (service, err)) finally: c.security = orig -- 2.24.1
From 3f94e9aa811cfff033a8d5438b7679ce0fa55b73 Mon Sep 17 00:00:00 2001 From: Volodymyr Khomenko <volodymyr@xxxxxxxxxxxx> Date: Wed, 17 Nov 2021 23:58:20 +0200 Subject: [PATCH 1/2] Fixed gssapi usage (RPCGSS) for nfs4.0 server test gssapi library used in the code has been changed and current code is not compatible with API of new library version. Fixed the code to work with recent gssapi (tested with 1.6.2). Tested with krb5, krb5i and krb5p security, like this: nfs4.0/testserver.py server.fqdn:/export gss noGSS8 --security=krb5 Note - GSS8 is known to cause the problems for other tests that run after it, however it's safe to run it alone. Signed-off-by: Volodymyr Khomenko <volodymyr@xxxxxxxxxxxx> --- nfs4.0/lib/rpc/rpcsec/sec_auth_gss.py | 116 ++++++++++---------------- 1 file changed, 45 insertions(+), 71 deletions(-) diff --git a/nfs4.0/lib/rpc/rpcsec/sec_auth_gss.py b/nfs4.0/lib/rpc/rpcsec/sec_auth_gss.py index 1b5eb93..6577fcf 100644 --- a/nfs4.0/lib/rpc/rpcsec/sec_auth_gss.py +++ b/nfs4.0/lib/rpc/rpcsec/sec_auth_gss.py @@ -1,8 +1,8 @@ from .base import SecFlavor, SecError from rpc.rpc_const import RPCSEC_GSS from rpc.rpc_type import opaque_auth -from gss_const import * -import gss_pack +from .gss_const import * +from . import gss_pack import gss_type import gssapi import threading @@ -125,50 +125,41 @@ class SecAuthGss(SecFlavor): def initialize(self, client): # Note this is not thread safe """Set seq_num, init, handle, and context""" self.gss_seq_num = 0 - d = gssapi.importName("nfs@%s" % client.remotehost) - if d['major'] != gssapi.GSS_S_COMPLETE: - raise SecError("gssapi.importName returned: %s" % \ - show_major(d['major'])) - name = d['name'] - # We need to send NULLPROCs with token from initSecContext - good_major = [gssapi.GSS_S_COMPLETE, gssapi.GSS_S_CONTINUE_NEEDED] + name = gssapi.Name("nfs@%s" % client.remotehost, gssapi.NameType.hostbased_service) + # We need to send NULLPROCs with token from SecurityContext + good_major = [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED] self.init = 1 - reply_token = None - reply_major = '' - context = None + input_token = None + + # RFC2203 5.2.2. Context Creation Requests + # When GSS_Init_sec_context() is called, the parameters + # replay_det_req_flag and sequence_req_flag must be turned off. + + # Note - by default, out_of_sequence_detection flag (sequence_req_flag) is used by gssapi.init_sec_context() + # and we have 'An expected per-message token was not received' error (GSS_S_GAP_TOKEN). + # To prevent this, we need to use default flags without out_of_sequence_detection bit. + flags = gssapi.IntEnumFlagSet(gssapi.RequirementFlag, [gssapi.RequirementFlag.mutual_authentication]) + context = gssapi.SecurityContext(name=name, flags=flags) while True: - d = gssapi.initSecContext(name, context, reply_token) - major = d['major'] - context = d['context'] - if major not in good_major: - raise SecError("gssapi.initSecContext returned: %s" % \ - show_major(major)) - if (major == gssapi.GSS_S_CONTINUE_NEEDED) and \ - (reply_major == gssapi.GSS_S_COMPLETE): - raise SecError("Unexpected GSS_S_COMPLETE from server") - token = d['token'] - if reply_major != gssapi.GSS_S_COMPLETE: - # FRED - sec 5.2.2 of RFC 2203 mentions possibility that - # no token is returned. But then how get handle? - p = self.getpacker() - p.reset() - p.pack_opaque(token) - data = p.get_buffer() - reply = client.call(0, data) - up = self.getunpacker() - up.reset(reply) - res = up.unpack_rpc_gss_init_res() - up.done() - reply_major = res.gss_major - if reply_major not in good_major: - raise SecError("Server returned: %s" % \ - show_major(reply_major)) - self.init = 2 - reply_token = res.gss_token - if major == gssapi.GSS_S_COMPLETE: - if reply_major != gssapi.GSS_S_COMPLETE: - raise SecError("Unexpected COMPLETE from client") + # note - gssapi will raise an exception here automatically in case of failure + output_token = context.step(input_token) + if context.complete: break + p = self.getpacker() + p.reset() + p.pack_opaque(output_token) + data = p.get_buffer() + reply = client.call(0, data) + up = self.getunpacker() + up.reset(reply) + res = up.unpack_rpc_gss_init_res() + up.done() + reply_major = res.gss_major + if reply_major not in good_major: + raise SecError("Server returned: %s" % \ + show_major(reply_major)) + self.init = 2 + input_token = res.gss_token self.gss_context = context self.gss_handle = res.handle self.init = 0 @@ -176,7 +167,7 @@ class SecAuthGss(SecFlavor): def make_cred(self): """Credential sent with each RPC call""" if self.init == 1: # first call in context creation - cred = self._make_cred_gss('', rpc_gss_svc_none, RPCSEC_GSS_INIT) + cred = self._make_cred_gss(b'', rpc_gss_svc_none, RPCSEC_GSS_INIT) elif self.init > 1: # subsequent calls in context creation cred = self._make_cred_gss('', rpc_gss_svc_none, RPCSEC_GSS_CONTINUE_INIT) @@ -237,12 +228,8 @@ class SecAuthGss(SecFlavor): if self.init: return self._none else: - d = gssapi.getMIC(self.gss_context, data) - major = d['major'] - if major != gssapi.GSS_S_COMPLETE: - raise SecError("gssapi.getMIC returned: %s" % \ - show_major(major)) - return opaque_auth(RPCSEC_GSS, d['token']) + token = self.gss_context.get_signature(data) + return opaque_auth(RPCSEC_GSS, token) def _make_cred_gss(self, handle, service, gss_proc=RPCSEC_GSS_DATA, seq=0): data = gss_type.rpc_gss_cred_vers_1_t(gss_proc, seq, service, handle) @@ -264,13 +251,10 @@ class SecAuthGss(SecFlavor): p.reset() p.pack_uint(gss_cred.seq_num) data = p.get_buffer() + data - d = gssapi.getMIC(self.gss_context, data) - if d['major'] != gssapi.GSS_S_COMPLETE: - raise SecError("gssapi.getMIC returned: %s" % \ - show_major(d['major'])) + token = self.gss_context.get_signature(data) p.reset() p.pack_opaque(data) - p.pack_opaque(d['token']) + p.pack_opaque(token) data = p.get_buffer() elif gss_cred.service == rpc_gss_svc_privacy: # data = opaque[wrap([gss_seq_num+data])] @@ -278,12 +262,9 @@ class SecAuthGss(SecFlavor): p.reset() p.pack_uint(gss_cred.seq_num) data = p.get_buffer() + data - d = gssapi.wrap(self.gss_context, data) - if d['major'] != gssapi.GSS_S_COMPLETE: - raise SecError("gssapi.wrap returned: %s" % \ - show_major(d['major'])) + wrap_data = self.gss_context.wrap(data, encrypt=True) p.reset() - p.pack_opaque(d['msg']) + p.pack_opaque(wrap_data.message) data = p.get_buffer() else: # Not really necessary, should have already raised XDRError @@ -303,10 +284,7 @@ class SecAuthGss(SecFlavor): data = p.unpack_opaque() checksum = p.unpack_opaque() p.done() - d = gssapi.verifyMIC(self.gss_context, data, checksum) - if d['major'] != gssapi.GSS_S_COMPLETE: - raise SecError("gssapi.verifyMIC returned: %s" % \ - show_major(d['major'])) + qop = self.gss_context.verify_signature(data, checksum) p.reset(data) seqnum = p.unpack_uint() if seqnum != gss_cred.seq_num: @@ -320,11 +298,8 @@ class SecAuthGss(SecFlavor): p.reset(data) data = p.unpack_opaque() p.done() - d = gssapi.unwrap(self.gss_context, data) - if d['major'] != gssapi.GSS_S_COMPLETE: - raise SecError("gssapi.unwrap returned %s" % \ - show_major(d['major'])) - p.reset(d['msg']) + data, encrypted, qop = self.gss_context.unwrap(data) + p.reset(data) seqnum = p.unpack_uint() if seqnum != gss_cred.seq_num: raise SecError(\ @@ -373,8 +348,7 @@ class SecAuthGss(SecFlavor): p = self.getpacker() p.reset() p.pack_uint(cred.seq_num) - d = gssapi.verifyMIC(self.gss_context, p.get_buffer(), rverf.body) - #print("Verify(%i):"%cred.seq_num, show_major(d['major']), show_minor(d['minor'])) + qop = self.gss_context.verify_signature(p.get_buffer(), rverf.body) else: pass -- 2.24.1