URL: https://github.com/freeipa/freeipa/pull/543
Author: simo5
 Title: #543: Add options to allow ticket caching
Action: synchronized

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/543/head:pr543
git checkout pr543
From 513c118d741594bf6bab6302a4b24c23168c4c44 Mon Sep 17 00:00:00 2001
From: Simo Sorce <s...@redhat.com>
Date: Mon, 6 Mar 2017 13:46:44 -0500
Subject: [PATCH 1/3] Add options to allow ticket caching

This new option (planned to land in gssproxy 0.7) we cache the ldap
ticket properly and avoid a ticket lookup to the KDC on each and every
ldap connection. (Also requires krb5 libs 1.15.1 to benefit from caching).

Signed-off-by: Simo Sorce <s...@redhat.com>
---
 install/share/gssproxy.conf.template | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/install/share/gssproxy.conf.template b/install/share/gssproxy.conf.template
index fbb158a..9d11100 100644
--- a/install/share/gssproxy.conf.template
+++ b/install/share/gssproxy.conf.template
@@ -4,6 +4,7 @@
   cred_store = keytab:$HTTP_KEYTAB
   cred_store = client_keytab:$HTTP_KEYTAB
   allow_protocol_transition = true
+  allow_client_ccache_sync = true
   cred_usage = both
   euid = $HTTPD_USER
 
@@ -12,5 +13,6 @@
   cred_store = keytab:$HTTP_KEYTAB
   cred_store = client_keytab:$HTTP_KEYTAB
   allow_constrained_delegation = true
+  allow_client_ccache_sync = true
   cred_usage = initiate
   euid = $IPAAPI_USER

From 34553627ebd709dea371030b03607c9c167732b0 Mon Sep 17 00:00:00 2001
From: Simo Sorce <s...@redhat.com>
Date: Mon, 6 Mar 2017 14:19:30 -0500
Subject: [PATCH 2/3] Use GSS-SPNEGO if connecting locally

GSS-SPNEGO allows us to negotiate a sasl bind with less roundrtrips
therefore use it when possible.

We only enable it for local connections for now because we only
recently fixed Cyrus SASL to do proper GSS-SPNEGO negotiation. This
change means a newer and an older version are not compatible.

Restricting ourselves to the local host prevents issues with
incomaptible services, and it is ok for us as we are only really lloking
at speedups for the local shortlived connections performed by the
framework. Most other clients have llonger lived connections, so
peformance improvements there are not as important.

Signed-off-by: Simo Sorce <s...@redhat.com>
---
 ipapython/ipaldap.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 82d45b9..b158598 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -52,6 +52,7 @@
 
 # Global variable to define SASL auth
 SASL_GSSAPI = ldap.sasl.sasl({}, 'GSSAPI')
+SASL_GSS_SPNEGO = ldap.sasl.sasl({}, 'GSS-SPNEGO')
 
 _debug_log_ldap = False
 
@@ -1112,7 +1113,10 @@ def gssapi_bind(self, server_controls=None, client_controls=None):
         Perform SASL bind operation using the SASL GSSAPI mechanism.
         """
         with self.error_handler():
-            auth_tokens = ldap.sasl.sasl({}, 'GSSAPI')
+            if self._protocol == 'ldapi':
+                auth_tokens = SASL_GSS_SPNEGO
+            else:
+                auth_tokens = SASL_GSSAPI
             self._flush_schema()
             self.conn.sasl_interactive_bind_s(
                 '', auth_tokens, server_controls, client_controls)

From 4a9b4a7769e36890f95d87053388579928088dd3 Mon Sep 17 00:00:00 2001
From: Simo Sorce <s...@redhat.com>
Date: Mon, 6 Mar 2017 18:47:56 -0500
Subject: [PATCH 3/3] Store session cookie in a ccache option

Instead of using the kernel keyring,s tore the session cookie within the
ccache. This way kdestroy will really wipe away all creedntials.

Ticket: https://pagure.io/freeipa/issue/6661

Signed-off-by: Simo Sorce <s...@redhat.com>
---
 ipalib/rpc.py               |  30 ++----
 ipapython/ccache_storage.py | 234 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 242 insertions(+), 22 deletions(-)
 create mode 100644 ipapython/ccache_storage.py

diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index 8d1bba5..be31333 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 ccache_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_NAME = '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,11 @@ def update_persistent_client_session_data(principal, data):
     '''
 
     try:
-        keyname = client_session_keyring_keyname(principal)
+        s = ccache_storage.session_store(CCACHE_COOKIE_KEY_NAME)
+        s.store_data(principal, 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 +112,11 @@ def read_persistent_client_session_data(principal):
     '''
 
     try:
-        keyname = client_session_keyring_keyname(principal)
+        s = ccache_storage.session_store(CCACHE_COOKIE_KEY_NAME)
+        s.get_data(principal)
     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 +126,11 @@ def delete_persistent_client_session_data(principal):
     '''
 
     try:
-        keyname = client_session_keyring_keyname(principal)
+        s = ccache_storage.session_store(CCACHE_COOKIE_KEY_NAME)
+        s.remove_data(principal)
     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/ccache_storage.py b/ipapython/ccache_storage.py
new file mode 100644
index 0000000..f2de301
--- /dev/null
+++ b/ipapython/ccache_storage.py
@@ -0,0 +1,234 @@
+#
+# Copyright (C) 2017  FreeIPA Contributors see COPYING for license
+#
+
+import ctypes
+import os
+import sys
+
+import six
+
+
+class KRB5Error(Exception):
+    pass
+
+
+PY3 = sys.version_info[0] == 3
+
+
+try:
+    LIBKRB5 = ctypes.CDLL('libkrb5.so.3')
+except OSError as e:  # pragma: no cover
+    LIBKRB5 = e
+else:
+    class c_text_p(ctypes.c_char_p):  # noqa
+        """A c_char_p variant that can handle UTF-8 text"""
+        @classmethod
+        def from_param(cls, value):
+            if value is None:
+                return None
+            if PY3 and isinstance(value, str):
+                return value.encode('utf-8')
+            elif not PY3 and isinstance(value, unicode):  # noqa
+                return value.encode('utf-8')
+            elif not isinstance(value, bytes):
+                raise TypeError(value)
+            else:
+                return value
+
+        @property
+        def text(self):
+            value = self.value
+            if value is None:
+                return None
+            elif not isinstance(value, str):
+                return value.decode('utf-8')
+            return value
+
+    class _krb5_context(ctypes.Structure):  # noqa
+        """krb5/krb5.h struct _krb5_context"""
+        __slots__ = ()
+        _fields_ = []
+
+    class _krb5_ccache(ctypes.Structure):  # noqa
+        """krb5/krb5.h struct _krb5_ccache"""
+        __slots__ = ()
+        _fields_ = []
+
+    class _krb5_data(ctypes.Structure):  # noqa
+        """krb5/krb5.h struct _krb5_data"""
+        __slots__ = ()
+        _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"""
+        __slots__ = ()
+        _fields_ = []
+
+    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
+
+class session_store:
+    def __init__(self, name='X-IPA-Session-Cookie'):
+        self.__context = None
+        if isinstance(LIBKRB5, Exception):  # pragma: no cover
+            raise LIBKRB5
+        context = krb5_context()
+        krb5_init_context(ctypes.byref(context))
+        self.__context = context
+
+        self._hidden_cred_name = name
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, type, value, traceback):
+        if self.__context:
+            krb5_free_context(self.__context)
+            self.__context = None
+
+    def __del__(self):
+        self.__exit__(None, None, None)
+
+    def store_cookie(self, client, value):
+        """
+        Stores the session cookie in a hidden ccache entry.
+        """
+        assert isinstance(client, six.string_types)
+        assert isinstance(value, bytes)
+
+        principal = ccache = None
+
+        try:
+            principal = krb5_principal()
+            krb5_parse_name(self.__context, ctypes.c_char_p(client),
+                            ctypes.byref(principal))
+
+            ccache = krb5_ccache()
+            krb5_cc_default(self.__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(self.__context, ccache, principal,
+                               self._hidden_cred_name, ctypes.byref(data))
+
+        finally:
+            if principal:
+                krb5_free_principal(self.__context, principal)
+            if ccache:
+                krb5_cc_close(self.__context, ccache)
+
+    def get_cookie(self, client):
+        """
+        Gets the session cookie in a hidden ccache entry.
+        """
+        assert isinstance(client, six.string_types)
+
+        principal = ccache = data = None
+
+        try:
+            principal = krb5_principal()
+            krb5_parse_name(self.__context, ctypes.c_char_p(client),
+                            ctypes.byref(principal))
+
+            ccache = krb5_ccache()
+            krb5_cc_default(self.__context, ctypes.byref(ccache))
+
+            data = _krb5_data()
+            krb5_cc_get_config(self.__context, ccache, principal,
+                               self._hidden_cred_name, ctypes.byref(data))
+
+            return str(data.data)
+
+        finally:
+            if principal:
+                krb5_free_principal(self.__context, principal)
+            if ccache:
+                krb5_cc_close(self.__context, ccache)
+            if data:
+                krb5_free_data_contents(self.__context, data)
+
+    def remove_cookie(self, client):
+        """
+        Stores the session cookie in a hidden ccache entry.
+        """
+        assert isinstance(client, six.string_types)
+
+        principal = ccache = None
+
+        try:
+            principal = krb5_principal()
+            krb5_parse_name(self.__context, ctypes.c_char_p(client),
+                            ctypes.byref(principal))
+
+            ccache = krb5_ccache()
+            krb5_cc_default(self.__context, ctypes.byref(ccache))
+
+            krb5_cc_set_config(self.__context, ccache, principal,
+                               self._hidden_cred_name, None)
+
+        finally:
+            if principal:
+                krb5_free_principal(self.__context, principal)
+            if ccache:
+                krb5_cc_close(self.__context, ccache)
+
-- 
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

Reply via email to