mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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:
parent
2e5cc369fd
commit
7cab959555
@ -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``.
|
||||
|
197
ipapython/session_storage.py
Normal file
197
ipapython/session_storage.py
Normal 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)
|
37
ipatests/test_ipapython/test_session_storage.py
Normal file
37
ipatests/test_ipapython/test_session_storage.py
Normal 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
|
Loading…
Reference in New Issue
Block a user