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 ipalib.request import context, Connection
|
||||||
from ipapython.ipa_log_manager import root_logger
|
from ipapython.ipa_log_manager import root_logger
|
||||||
from ipapython import ipautil
|
from ipapython import ipautil
|
||||||
from ipapython import kernel_keyring
|
from ipapython import session_storage
|
||||||
from ipapython.cookie import Cookie
|
from ipapython.cookie import Cookie
|
||||||
from ipapython.dnsutil import DNSName
|
from ipapython.dnsutil import DNSName
|
||||||
from ipalib.text import _
|
from ipalib.text import _
|
||||||
@ -84,19 +84,11 @@ if six.PY3:
|
|||||||
unicode = str
|
unicode = str
|
||||||
|
|
||||||
COOKIE_NAME = 'ipa_session'
|
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)
|
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):
|
def update_persistent_client_session_data(principal, data):
|
||||||
'''
|
'''
|
||||||
Given a principal create or update the session data for that
|
Given a principal create or update the session data for that
|
||||||
@ -106,13 +98,10 @@ def update_persistent_client_session_data(principal, data):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
keyname = client_session_keyring_keyname(principal)
|
session_storage.store_data(principal, CCACHE_COOKIE_KEY, data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(str(e))
|
raise ValueError(str(e))
|
||||||
|
|
||||||
# kernel_keyring only raises ValueError (why??)
|
|
||||||
kernel_keyring.update_key(keyname, data)
|
|
||||||
|
|
||||||
def read_persistent_client_session_data(principal):
|
def read_persistent_client_session_data(principal):
|
||||||
'''
|
'''
|
||||||
Given a principal return the stored session data for that
|
Given a principal return the stored session data for that
|
||||||
@ -122,13 +111,10 @@ def read_persistent_client_session_data(principal):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
keyname = client_session_keyring_keyname(principal)
|
return session_storage.get_data(principal, CCACHE_COOKIE_KEY)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(str(e))
|
raise ValueError(str(e))
|
||||||
|
|
||||||
# kernel_keyring only raises ValueError (why??)
|
|
||||||
return kernel_keyring.read_key(keyname)
|
|
||||||
|
|
||||||
def delete_persistent_client_session_data(principal):
|
def delete_persistent_client_session_data(principal):
|
||||||
'''
|
'''
|
||||||
Given a principal remove the session data for that
|
Given a principal remove the session data for that
|
||||||
@ -138,13 +124,10 @@ def delete_persistent_client_session_data(principal):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
keyname = client_session_keyring_keyname(principal)
|
session_storage.remove_data(principal, CCACHE_COOKIE_KEY)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(str(e))
|
raise ValueError(str(e))
|
||||||
|
|
||||||
# kernel_keyring only raises ValueError (why??)
|
|
||||||
kernel_keyring.del_key(keyname)
|
|
||||||
|
|
||||||
def xml_wrap(value, version):
|
def xml_wrap(value, version):
|
||||||
"""
|
"""
|
||||||
Wrap all ``str`` in ``xmlrpc.client.Binary``.
|
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