URL: https://github.com/freeipa/freeipa/pull/546 Author: simo5 Title: #546: Store session cookie in a ccache option Action: synchronized
To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/546/head:pr546 git checkout pr546
From c1ae93acad645c7725041cc10bf14b10fb94533c Mon Sep 17 00:00:00 2001 From: Simo Sorce <s...@redhat.com> Date: Mon, 6 Mar 2017 18:47:56 -0500 Subject: [PATCH] Store session cookie in a ccache option Instead of using the kernel keyring, store the session cookie within the ccache. This way kdestroy will really wipe away all credentials. Ticket: https://pagure.io/freeipa/issue/6661 Signed-off-by: Simo Sorce <s...@redhat.com> --- ipalib/rpc.py | 27 +--- ipapython/session_storage.py | 197 ++++++++++++++++++++++++ ipatests/test_ipapython/test_session_storage.py | 37 +++++ 3 files changed, 239 insertions(+), 22 deletions(-) create mode 100644 ipapython/session_storage.py create mode 100644 ipatests/test_ipapython/test_session_storage.py diff --git a/ipalib/rpc.py b/ipalib/rpc.py index 8d1bba5..3a589cb 100644 --- a/ipalib/rpc.py +++ b/ipalib/rpc.py @@ -56,7 +56,7 @@ from ipalib.request import context, Connection from ipapython.ipa_log_manager import root_logger from ipapython import ipautil -from ipapython import kernel_keyring +from ipapython import session_storage from ipapython.cookie import Cookie from ipapython.dnsutil import DNSName from ipalib.text import _ @@ -84,19 +84,11 @@ unicode = str COOKIE_NAME = 'ipa_session' -KEYRING_COOKIE_NAME = '%s_cookie:%%s' % COOKIE_NAME +CCACHE_COOKIE_KEY = 'X-IPA-Session-Cookie' errors_by_code = dict((e.errno, e) for e in public_errors) -def client_session_keyring_keyname(principal): - ''' - Return the key name used for storing the client session data for - the given principal. - ''' - - return KEYRING_COOKIE_NAME % principal - def update_persistent_client_session_data(principal, data): ''' Given a principal create or update the session data for that @@ -106,13 +98,10 @@ def update_persistent_client_session_data(principal, data): ''' try: - keyname = client_session_keyring_keyname(principal) + session_storage.store_data(principal, CCACHE_COOKIE_KEY, data) except Exception as e: raise ValueError(str(e)) - # kernel_keyring only raises ValueError (why??) - kernel_keyring.update_key(keyname, data) - def read_persistent_client_session_data(principal): ''' Given a principal return the stored session data for that @@ -122,13 +111,10 @@ def read_persistent_client_session_data(principal): ''' try: - keyname = client_session_keyring_keyname(principal) + return session_storage.get_data(principal, CCACHE_COOKIE_KEY) except Exception as e: raise ValueError(str(e)) - # kernel_keyring only raises ValueError (why??) - return kernel_keyring.read_key(keyname) - def delete_persistent_client_session_data(principal): ''' Given a principal remove the session data for that @@ -138,13 +124,10 @@ def delete_persistent_client_session_data(principal): ''' try: - keyname = client_session_keyring_keyname(principal) + session_storage.remove_data(principal, CCACHE_COOKIE_KEY) except Exception as e: raise ValueError(str(e)) - # kernel_keyring only raises ValueError (why??) - kernel_keyring.del_key(keyname) - def xml_wrap(value, version): """ Wrap all ``str`` in ``xmlrpc.client.Binary``. diff --git a/ipapython/session_storage.py b/ipapython/session_storage.py new file mode 100644 index 0000000..7fe17fb --- /dev/null +++ b/ipapython/session_storage.py @@ -0,0 +1,197 @@ +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# + +import ctypes + + +KRB5_CC_NOSUPP = -1765328137 + + +try: + LIBKRB5 = ctypes.CDLL('libkrb5.so.3') +except OSError as e: # pragma: no cover + raise ImportError(str(e)) + + +class _krb5_context(ctypes.Structure): # noqa + """krb5/krb5.h struct _krb5_context""" + _fields_ = [] + + +class _krb5_ccache(ctypes.Structure): # noqa + """krb5/krb5.h struct _krb5_ccache""" + _fields_ = [] + + +class _krb5_data(ctypes.Structure): # noqa + """krb5/krb5.h struct _krb5_data""" + _fields_ = [ + ("magic", ctypes.c_int32), + ("length", ctypes.c_uint), + ("data", ctypes.c_char_p), + ] + + +class krb5_principal_data(ctypes.Structure): # noqa + """krb5/krb5.h struct krb5_principal_data""" + _fields_ = [] + + +class KRB5Error(Exception): + pass + + +def krb5_errcheck(result, func, arguments): + """Error checker for krb5_error return value""" + if result != 0: + raise KRB5Error(result, func.__name__, arguments) + + +krb5_principal = ctypes.POINTER(krb5_principal_data) +krb5_context = ctypes.POINTER(_krb5_context) +krb5_ccache = ctypes.POINTER(_krb5_ccache) +krb5_data_p = ctypes.POINTER(_krb5_data) +krb5_error = ctypes.c_int32 + +krb5_init_context = LIBKRB5.krb5_init_context +krb5_init_context.argtypes = (ctypes.POINTER(krb5_context), ) +krb5_init_context.restype = krb5_error +krb5_init_context.errcheck = krb5_errcheck + +krb5_free_context = LIBKRB5.krb5_free_context +krb5_free_context.argtypes = (krb5_context, ) +krb5_free_context.retval = None + +krb5_free_principal = LIBKRB5.krb5_free_principal +krb5_free_principal.argtypes = (krb5_context, krb5_principal) +krb5_free_principal.retval = None + +krb5_free_data_contents = LIBKRB5.krb5_free_data_contents +krb5_free_data_contents.argtypes = (krb5_context, krb5_data_p) +krb5_free_data_contents.retval = None + +krb5_cc_default = LIBKRB5.krb5_cc_default +krb5_cc_default.argtypes = (krb5_context, ctypes.POINTER(krb5_ccache), ) +krb5_cc_default.restype = krb5_error +krb5_cc_default.errcheck = krb5_errcheck + +krb5_cc_close = LIBKRB5.krb5_cc_close +krb5_cc_close.argtypes = (krb5_context, krb5_ccache, ) +krb5_cc_close.retval = krb5_error +krb5_cc_close.errcheck = krb5_errcheck + +krb5_parse_name = LIBKRB5.krb5_parse_name +krb5_parse_name.argtypes = (krb5_context, ctypes.c_char_p, + ctypes.POINTER(krb5_principal), ) +krb5_parse_name.retval = krb5_error +krb5_parse_name.errcheck = krb5_errcheck + +krb5_cc_set_config = LIBKRB5.krb5_cc_set_config +krb5_cc_set_config.argtypes = (krb5_context, krb5_ccache, krb5_principal, + ctypes.c_char_p, krb5_data_p, ) +krb5_cc_set_config.retval = krb5_error +krb5_cc_set_config.errcheck = krb5_errcheck + +krb5_cc_get_config = LIBKRB5.krb5_cc_get_config +krb5_cc_get_config.argtypes = (krb5_context, krb5_ccache, krb5_principal, + ctypes.c_char_p, krb5_data_p, ) +krb5_cc_get_config.retval = krb5_error +krb5_cc_get_config.errcheck = krb5_errcheck + + +def store_data(princ_name, key, value): + """ + Stores the session cookie in a hidden ccache entry. + """ + context = krb5_context() + principal = krb5_principal() + ccache = krb5_ccache() + + try: + krb5_init_context(ctypes.byref(context)) + + krb5_parse_name(context, ctypes.c_char_p(princ_name), + ctypes.byref(principal)) + + krb5_cc_default(context, ctypes.byref(ccache)) + + buf = ctypes.create_string_buffer(value) + data = _krb5_data() + data.data = buf.value + data.length = len(buf) + krb5_cc_set_config(context, ccache, principal, key, + ctypes.byref(data)) + + finally: + if principal: + krb5_free_principal(context, principal) + if ccache: + krb5_cc_close(context, ccache) + if context: + krb5_free_context(context) + + +def get_data(princ_name, key): + """ + Gets the session cookie in a hidden ccache entry. + """ + context = krb5_context() + principal = krb5_principal() + ccache = krb5_ccache() + data = _krb5_data() + + try: + krb5_init_context(ctypes.byref(context)) + + krb5_parse_name(context, ctypes.c_char_p(princ_name), + ctypes.byref(principal)) + + krb5_cc_default(context, ctypes.byref(ccache)) + + krb5_cc_get_config(context, ccache, principal, key, + ctypes.byref(data)) + + return str(data.data) + + finally: + if principal: + krb5_free_principal(context, principal) + if ccache: + krb5_cc_close(context, ccache) + if data: + krb5_free_data_contents(context, data) + if context: + krb5_free_context(context) + + +def remove_data(princ_name, key): + """ + Removes the hidden ccache entry with the session cookie. + """ + context = krb5_context() + principal = krb5_principal() + ccache = krb5_ccache() + + try: + krb5_init_context(ctypes.byref(context)) + + krb5_parse_name(context, ctypes.c_char_p(princ_name), + ctypes.byref(principal)) + + krb5_cc_default(context, ctypes.byref(ccache)) + + try: + krb5_cc_set_config(context, ccache, principal, key, None) + except KRB5Error as e: + if e.args[0] == KRB5_CC_NOSUPP: + # removal not supported with this CC type, just pass + pass + + finally: + if principal: + krb5_free_principal(context, principal) + if ccache: + krb5_cc_close(context, ccache) + if context: + krb5_free_context(context) diff --git a/ipatests/test_ipapython/test_session_storage.py b/ipatests/test_ipapython/test_session_storage.py new file mode 100644 index 0000000..a89fdd9 --- /dev/null +++ b/ipatests/test_ipapython/test_session_storage.py @@ -0,0 +1,37 @@ +# +# Copyright (C) 2017 FreeIPA Contributors see COPYING for license +# + +""" +Test the `session_storage.py` module. +""" + +from ipapython import session_storage + + +class test_session_storage(object): + """ + Test the session storage interface + """ + + def setup(self): + # TODO: set up test user and kinit to it + # tmpdir = tempfile.mkdtemp(prefix = "tmp-") + # os.environ['KRB5CCNAME'] = 'FILE:%s/ccache' % tmpdir + self.principal = 'admin' + self.key = 'X-IPA-test-session-storage' + self.data = 'Test Data' + + def test_01(self): + session_storage.store_data(self.principal, self.key, self.data) + + def test_02(self): + data = session_storage.get_data(self.principal, self.key) + assert(data == self.data) + + def test_03(self): + session_storage.remove_data(self.principal, self.key) + try: + session_storage.get_data(self.principal, self.key) + except session_storage.KRB5Error: + pass
-- Manage your subscription for the Freeipa-devel mailing list: https://www.redhat.com/mailman/listinfo/freeipa-devel Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code