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 <simo@redhat.com>
Reviewed-By: Martin Basti <mbasti@redhat.com>
Reviewed-By: Christian Heimes <cheimes@redhat.com>
This commit is contained in:
Simo Sorce 2017-03-06 18:47:56 -05:00 committed by Martin Basti
parent 2e5cc369fd
commit 7cab959555
3 changed files with 239 additions and 22 deletions

View File

@ -56,7 +56,7 @@ from ipalib import errors, capabilities
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 @@ if six.PY3:
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``.

View File

@ -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)

View File

@ -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