summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimo Sorce <simo@redhat.com>2016-08-19 09:23:55 -0400
committerJan Cholasta <jcholast@redhat.com>2017-02-15 07:13:37 +0100
commitc894ebefc5c4c4c7ea340d6ddc4cd3c081917e4a (patch)
tree8511e93ca9e8e1df6c504b8f18d2fec733686d26
parent11ef2cacbf2ebb67f80a0cf4a3e7b39da700188b (diff)
downloadfreeipa-c894ebefc5c4c4c7ea340d6ddc4cd3c081917e4a.tar.gz
freeipa-c894ebefc5c4c4c7ea340d6ddc4cd3c081917e4a.tar.xz
freeipa-c894ebefc5c4c4c7ea340d6ddc4cd3c081917e4a.zip
Change session handling
Stop using memcache, use mod_auth_gssapi filesystem based ccaches. Remove custom session handling, use mod_auth_gssapi and mod_session to establish and keep a session cookie. Add loopback to mod_auth_gssapi to do form absed auth and pass back a valid session cookie. And now that we do not remove ccaches files to move them to the memcache, we can avoid the risk of pollutting the filesystem by keeping a common ccache file for all instances of the same user. https://fedorahosted.org/freeipa/ticket/5959 Signed-off-by: Simo Sorce <simo@redhat.com> Reviewed-By: Jan Cholasta <jcholast@redhat.com>
-rw-r--r--freeipa.spec.in10
-rw-r--r--init/Makefile.am6
-rw-r--r--init/ipa_memcached.in5
-rw-r--r--init/systemd/Makefile.am2
-rw-r--r--init/systemd/ipa_memcached.service.in12
-rw-r--r--init/tmpfilesd/ipa.conf.in1
-rw-r--r--install/conf/ipa.conf22
-rw-r--r--install/share/Makefile.am4
-rw-r--r--install/share/gssapi.login0
-rw-r--r--install/share/memcache-remove.uldif1
-rw-r--r--ipalib/krb_utils.py2
-rw-r--r--ipaplatform/base/paths.py3
-rw-r--r--ipapython/cookie.py2
-rw-r--r--ipaserver/install/installutils.py2
-rw-r--r--ipaserver/install/ipa_backup.py4
-rw-r--r--ipaserver/install/memcacheinstance.py24
-rw-r--r--ipaserver/install/server/install.py7
-rw-r--r--ipaserver/install/server/replicainstall.py5
-rw-r--r--ipaserver/install/server/upgrade.py18
-rw-r--r--ipaserver/install/service.py1
-rw-r--r--ipaserver/plugins/session.py15
-rw-r--r--ipaserver/rpcserver.py334
-rw-r--r--ipaserver/session.py1262
-rwxr-xr-xipaserver/setup.py1
-rw-r--r--pylint_plugins.py3
25 files changed, 173 insertions, 1573 deletions
diff --git a/freeipa.spec.in b/freeipa.spec.in
index 63f0e73ef..b65588a57 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -128,7 +128,6 @@ BuildRequires: pylint >= 1.0
# workaround for https://bugzilla.redhat.com/show_bug.cgi?id=1096506
BuildRequires: python2-polib
BuildRequires: python-libipa_hbac
-BuildRequires: python-memcached
BuildRequires: python-lxml
# 5.0.0: QRCode.print_ascii
BuildRequires: python-qrcode-core >= 5.0.0
@@ -230,13 +229,12 @@ Requires: cyrus-sasl-gssapi%{?_isa}
Requires: ntp
Requires: httpd >= 2.4.6-31
Requires: mod_wsgi
-Requires: mod_auth_gssapi >= 1.4.0
+Requires: mod_auth_gssapi >= 1.5.0
Requires: mod_nss >= 1.0.8-26
+Requires: mod_session
Requires: python-ldap >= 2.4.15
Requires: python-gssapi >= 1.2.0
Requires: acl
-Requires: memcached
-Requires: python-memcached
Requires: systemd-units >= 38
Requires(pre): shadow-utils
Requires(pre): systemd-units
@@ -1188,18 +1186,15 @@ fi
%license COPYING
%ghost %verify(not owner group) %dir %{_sharedstatedir}/kdcproxy
%dir %attr(0755,root,root) %{_sysconfdir}/ipa/kdcproxy
-%config(noreplace) %{_sysconfdir}/sysconfig/ipa_memcached
%config(noreplace) %{_sysconfdir}/sysconfig/ipa-dnskeysyncd
%config(noreplace) %{_sysconfdir}/sysconfig/ipa-ods-exporter
%config(noreplace) %{_sysconfdir}/ipa/kdcproxy/kdcproxy.conf
-%dir %attr(0700,apache,apache) %{_localstatedir}/run/ipa_memcached/
%dir %attr(0700,root,root) %{_localstatedir}/run/ipa/
%dir %attr(0700,apache,apache) %{_localstatedir}/run/httpd/ipa/
%dir %attr(0700,apache,apache) %{_localstatedir}/run/httpd/ipa/clientcaches/
%dir %attr(0700,apache,apache) %{_localstatedir}/run/httpd/ipa/krbcache/
# NOTE: systemd specific section
%{_tmpfilesdir}/ipa.conf
-%attr(644,root,root) %{_unitdir}/ipa_memcached.service
%attr(644,root,root) %{_unitdir}/ipa-custodia.service
%ghost %attr(644,root,root) %{etc_systemd_dir}/httpd.d/ipa.conf
# END
@@ -1289,6 +1284,7 @@ fi
%dir %attr(0700,root,root) %{_sysconfdir}/ipa/custodia
%dir %{_usr}/share/ipa/schema.d
%attr(0644,root,root) %{_usr}/share/ipa/schema.d/README
+%attr(0644,root,root) %{_usr}/share/ipa/gssapi.login
%files server-dns
%defattr(-,root,root,-)
diff --git a/init/Makefile.am b/init/Makefile.am
index 47f844ac1..8f4d1d0a8 100644
--- a/init/Makefile.am
+++ b/init/Makefile.am
@@ -8,13 +8,7 @@ dist_sysconfenv_DATA = \
ipa-dnskeysyncd \
ipa-ods-exporter
-nodist_sysconfenv_DATA = \
- ipa_memcached
-
CLEANFILES = $(nodist_sysconfenv_DATA)
-dist_noinst_DATA = \
- ipa_memcached.in
-
%: %.in Makefile
sed -e 's|@localstatedir[@]|$(localstatedir)|g' '$(srcdir)/$@.in' >$@
diff --git a/init/ipa_memcached.in b/init/ipa_memcached.in
deleted file mode 100644
index 56701383d..000000000
--- a/init/ipa_memcached.in
+++ /dev/null
@@ -1,5 +0,0 @@
-SOCKET_PATH=@localstatedir@/run/ipa_memcached/ipa_memcached
-USER=apache
-MAXCONN=1024
-CACHESIZE=64
-OPTIONS=
diff --git a/init/systemd/Makefile.am b/init/systemd/Makefile.am
index e978fe974..325e85748 100644
--- a/init/systemd/Makefile.am
+++ b/init/systemd/Makefile.am
@@ -4,12 +4,10 @@ AUTOMAKE_OPTIONS = 1.7
dist_noinst_DATA = \
ipa-custodia.service.in \
- ipa_memcached.service.in \
ipa.service.in
systemdsystemunit_DATA = \
ipa-custodia.service \
- ipa_memcached.service \
ipa.service
CLEANFILES = $(systemdsystemunit_DATA)
diff --git a/init/systemd/ipa_memcached.service.in b/init/systemd/ipa_memcached.service.in
deleted file mode 100644
index 0e163203a..000000000
--- a/init/systemd/ipa_memcached.service.in
+++ /dev/null
@@ -1,12 +0,0 @@
-[Unit]
-Description=IPA memcached daemon, increases IPA server performance
-After=network.target
-
-[Service]
-Type=forking
-EnvironmentFile=@sysconfenvdir@/ipa_memcached
-PIDFile=@localstatedir@/run/ipa_memcached/ipa_memcached.pid
-ExecStart=@bindir@/memcached -d -s $SOCKET_PATH -u $USER -m $CACHESIZE -c $MAXCONN -P @localstatedir@/run/ipa_memcached/ipa_memcached.pid $OPTIONS
-
-[Install]
-WantedBy=multi-user.target
diff --git a/init/tmpfilesd/ipa.conf.in b/init/tmpfilesd/ipa.conf.in
index 4954000a3..5ac96f20f 100644
--- a/init/tmpfilesd/ipa.conf.in
+++ b/init/tmpfilesd/ipa.conf.in
@@ -1,4 +1,3 @@
-d @localstatedir@/run/ipa_memcached 0700 apache apache
d @localstatedir@/run/ipa 0700 root root
d @localstatedir@/run/httpd/ipa 0700 apache apache
d @localstatedir@/run/httpd/ipa/clientcaches 0700 apache apache
diff --git a/install/conf/ipa.conf b/install/conf/ipa.conf
index 3e7435903..6ae416353 100644
--- a/install/conf/ipa.conf
+++ b/install/conf/ipa.conf
@@ -63,10 +63,15 @@ WSGIScriptReloading Off
<Location "/ipa">
AuthType GSSAPI
AuthName "Kerberos Login"
+ GssapiUseSessions On
+ Session On
+ SessionCookieName ipa_session path=/ipa;httponly;secure;
+ SessionHeader IPASESSION
+ GssapiSessionKey file:/etc/httpd/alias/ipasession.key
+
GssapiCredStore keytab:/etc/httpd/conf/ipa.keytab
GssapiCredStore client_keytab:/etc/httpd/conf/ipa.keytab
GssapiDelegCcacheDir /var/run/httpd/ipa/clientcaches
- GssapiDelegCcacheUnique On
GssapiUseS4U2Proxy on
GssapiAllowedMech krb5
Require valid-user
@@ -77,19 +82,10 @@ WSGIScriptReloading Off
Header always append Content-Security-Policy "frame-ancestors 'none'"
</Location>
-# Turn off Apache authentication for sessions
-<Location "/ipa/session/json">
- Satisfy Any
- Order Deny,Allow
- Allow from all
-</Location>
-
-<Location "/ipa/session/xml">
- Satisfy Any
- Order Deny,Allow
- Allow from all
-</Location>
+# Target for login with internal connections
+Alias /ipa/session/cookie "/usr/share/ipa/gssapi.login"
+# Turn off Apache authentication for password/token based login pages
<Location "/ipa/session/login_password">
Satisfy Any
Order Deny,Allow
diff --git a/install/share/Makefile.am b/install/share/Makefile.am
index 715912d8b..6f35a329e 100644
--- a/install/share/Makefile.am
+++ b/install/share/Makefile.am
@@ -86,7 +86,9 @@ dist_app_DATA = \
vault.ldif \
kdcproxy-enable.uldif \
kdcproxy-disable.uldif \
- ipa-httpd.conf.template
+ ipa-httpd.conf.template \
+ gssapi.login \
+ $(NULL)
kdcproxyconfdir = $(IPA_SYSCONF_DIR)/kdcproxy
dist_kdcproxyconf_DATA = \
diff --git a/install/share/gssapi.login b/install/share/gssapi.login
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/install/share/gssapi.login
diff --git a/install/share/memcache-remove.uldif b/install/share/memcache-remove.uldif
new file mode 100644
index 000000000..e6ca1a617
--- /dev/null
+++ b/install/share/memcache-remove.uldif
@@ -0,0 +1 @@
+deleteentry: cn=MEMCACHE,cn=$FQDN,cn=masters,cn=ipa,cn=etc,$SUFFIX
diff --git a/ipalib/krb_utils.py b/ipalib/krb_utils.py
index d005a87b6..47d24c984 100644
--- a/ipalib/krb_utils.py
+++ b/ipalib/krb_utils.py
@@ -208,3 +208,5 @@ def get_credentials_if_valid(name=None, ccache_name=None):
return None
except gssapi.exceptions.ExpiredCredentialsError:
return None
+ except gssapi.exceptions.GSSError:
+ return None
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index 423478917..44108e52a 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -328,8 +328,7 @@ class BasePathNamespace(object):
KRB5CC_HTTPD = "/var/run/httpd/ipa/krbcache/krb5ccache"
IPA_RENEWAL_LOCK = "/var/run/ipa/renewal.lock"
SVC_LIST_FILE = "/var/run/ipa/services.list"
- IPA_MEMCACHED_DIR = "/var/run/ipa_memcached"
- VAR_RUN_IPA_MEMCACHED = "/var/run/ipa_memcached/ipa_memcached"
+ IPA_HTTPD_DIR = "/var/run/httpd"
KRB5CC_SAMBA = "/var/run/samba/krb5cc_samba"
SLAPD_INSTANCE_SOCKET_TEMPLATE = "/var/run/slapd-%s.socket"
ALL_SLAPD_INSTANCE_SOCKETS = "/var/run/slapd-*.socket"
diff --git a/ipapython/cookie.py b/ipapython/cookie.py
index 8abd9617b..57523a402 100644
--- a/ipapython/cookie.py
+++ b/ipapython/cookie.py
@@ -112,7 +112,7 @@ class Cookie(object):
cookie = Cookie('session', session_id,
domain=my_domain, path=mypath,
- httpOnly=True, secure=True, expires=expiration)
+ httponly=True, secure=True, expires=expiration)
headers.append(('Set-Cookie', str(cookie)))
diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index ab2596c8c..8602f59ca 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -64,7 +64,7 @@ if six.PY3:
# Used to determine install status
IPA_MODULES = [
'httpd', 'kadmin', 'dirsrv', 'pki-tomcatd', 'install', 'krb5kdc', 'ntpd',
- 'named', 'ipa_memcached']
+ 'named']
class BadHostError(Exception):
diff --git a/ipaserver/install/ipa_backup.py b/ipaserver/install/ipa_backup.py
index 536914638..4ba61e529 100644
--- a/ipaserver/install/ipa_backup.py
+++ b/ipaserver/install/ipa_backup.py
@@ -36,11 +36,13 @@ from ipapython import admintool
from ipapython.dn import DN
from ipaserver.install.replication import wait_for_task
from ipaserver.install import installutils
-from ipaserver.session import ISO8601_DATETIME_FMT
from ipapython import ipaldap
from ipaplatform.constants import constants
from ipaplatform.tasks import tasks
+
+ISO8601_DATETIME_FMT = '%Y-%m-%dT%H:%M:%S'
+
"""
A test gpg can be generated like this:
diff --git a/ipaserver/install/memcacheinstance.py b/ipaserver/install/memcacheinstance.py
deleted file mode 100644
index 547ac2ba4..000000000
--- a/ipaserver/install/memcacheinstance.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Authors: John Dennis <jdennis@redhat.com>
-#
-# Copyright (C) 2011 Red Hat
-# see file 'COPYING' for use and warranty information
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-
-from ipaserver.install import service
-
-class MemcacheInstance(service.SimpleServiceInstance):
- def __init__(self):
- service.SimpleServiceInstance.__init__(self, "ipa_memcached")
diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
index 8178d4e29..8628572a7 100644
--- a/ipaserver/install/server/install.py
+++ b/ipaserver/install/server/install.py
@@ -32,7 +32,7 @@ from ipalib.util import (
import ipaclient.install.ntpconf
from ipaserver.install import (
bindinstance, ca, cainstance, certs, dns, dsinstance,
- httpinstance, installutils, kra, krbinstance, memcacheinstance,
+ httpinstance, installutils, kra, krbinstance,
ntpinstance, otpdinstance, custodiainstance, replication, service,
sysupgrade)
from ipaserver.install.installutils import (
@@ -804,10 +804,6 @@ def install(installer):
# generated
ds.add_cert_to_service()
- memcache = memcacheinstance.MemcacheInstance()
- memcache.create_instance('MEMCACHE', host_name,
- ipautil.realm_to_suffix(realm_name))
-
otpd = otpdinstance.OtpdInstance()
otpd.create_instance('OTPD', host_name,
ipautil.realm_to_suffix(realm_name))
@@ -1052,7 +1048,6 @@ def uninstall(installer):
if _server_trust_ad_installed:
adtrustinstance.ADTRUSTInstance(fstore).uninstall()
custodiainstance.CustodiaInstance().uninstall()
- memcacheinstance.MemcacheInstance().uninstall()
otpdinstance.OtpdInstance().uninstall()
tasks.restore_hostname(fstore, sstore)
fstore.restore_all_files()
diff --git a/ipaserver/install/server/replicainstall.py b/ipaserver/install/server/replicainstall.py
index fcb979c15..649184cbe 100644
--- a/ipaserver/install/server/replicainstall.py
+++ b/ipaserver/install/server/replicainstall.py
@@ -37,7 +37,7 @@ from ipalib.util import (
from ipaclient.install.client import configure_krb5_conf, purge_host_keytab
from ipaserver.install import (
bindinstance, ca, certs, dns, dsinstance, httpinstance,
- installutils, kra, krbinstance, memcacheinstance,
+ installutils, kra, krbinstance,
ntpinstance, otpdinstance, custodiainstance, service)
from ipaserver.install.installutils import (
create_replica_config, ReplicaConfig, load_pkcs12, is_ipa_configured)
@@ -163,9 +163,6 @@ def install_http(config, auto_redirect, ca_is_configured, ca_file,
pkcs12_info = make_pkcs12_info(config.dir, "httpcert.p12",
"http_pin.txt")
- memcache = memcacheinstance.MemcacheInstance()
- memcache.create_instance('MEMCACHE', config.host_name,
- ipautil.realm_to_suffix(config.realm_name))
http = httpinstance.HTTPInstance()
http.create_instance(
diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py
index 0e034efac..2bdf6eede 100644
--- a/ipaserver/install/server/upgrade.py
+++ b/ipaserver/install/server/upgrade.py
@@ -34,7 +34,6 @@ from ipaplatform.paths import paths
from ipaserver.install import installutils
from ipaserver.install import dsinstance
from ipaserver.install import httpinstance
-from ipaserver.install import memcacheinstance
from ipaserver.install import ntpinstance
from ipaserver.install import bindinstance
from ipaserver.install import service
@@ -74,6 +73,21 @@ def uninstall_ipa_kpasswd():
if enabled is not None and not enabled:
ipa_kpasswd.remove()
+
+def uninstall_ipa_memcached():
+ """
+ We can't use the full service uninstaller because that will attempt
+ to stop and disable the service which by now doesn't exist. We just
+ want to clean up sysrestore.state to remove all references to
+ ipa_kpasswd.
+ """
+ ipa_memcached = service.SimpleServiceInstance('ipa_memcached')
+
+ enabled = not ipa_memcached.restore_state("enabled")
+
+ if enabled is not None and not enabled:
+ ipa_memcached.remove()
+
def backup_file(filename, ext):
"""Make a backup of filename using ext as the extension. Do not overwrite
previous backups."""
@@ -1570,6 +1584,7 @@ def upgrade_configuration():
update_dbmodules(api.env.realm)
uninstall_ipa_kpasswd()
+ uninstall_ipa_memcached()
removed_sysconfig_file = paths.SYSCONFIG_HTTPD
if fstore.has_file(removed_sysconfig_file):
@@ -1620,7 +1635,6 @@ def upgrade_configuration():
uninstall_dogtag_9(ds, http)
simple_service_list = (
- (memcacheinstance.MemcacheInstance(), 'MEMCACHE'),
(otpdinstance.OtpdInstance(), 'OTPD'),
)
diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py
index fbe3f23e5..b80044f4b 100644
--- a/ipaserver/install/service.py
+++ b/ipaserver/install/service.py
@@ -46,7 +46,6 @@ SERVICE_LIST = {
'KDC': ('krb5kdc', 10),
'KPASSWD': ('kadmin', 20),
'DNS': ('named', 30),
- 'MEMCACHE': ('ipa_memcached', 39),
'HTTP': ('httpd', 40),
'KEYS': ('ipa-custodia', 41),
'NTP': ('ntpd', 45),
diff --git a/ipaserver/plugins/session.py b/ipaserver/plugins/session.py
index 0efb53c88..c700ab9ba 100644
--- a/ipaserver/plugins/session.py
+++ b/ipaserver/plugins/session.py
@@ -5,7 +5,7 @@
from ipalib import Command
from ipalib.request import context
from ipalib.plugable import Registry
-from ipaserver.session import get_session_mgr
+from ipaserver.session import logout
register = Registry()
@@ -18,15 +18,10 @@ class session_logout(Command):
NO_CLI = True
def execute(self, *args, **options):
- session_data = getattr(context, 'session_data', None)
- if session_data is None:
- self.debug('session logout command: no session_data found')
- else:
- session_id = session_data.get('session_id')
- self.debug('session logout command: session_id=%s', session_id)
+ ccache_name = getattr(context, 'ccache_name', None)
+ if ccache_name is None:
+ self.debug('session logout command: no ccache_name found')
- # Notifiy registered listeners
- session_mgr = get_session_mgr()
- session_mgr.auth_mgr.logout(session_data)
+ logout(ccache_name)
return dict(result=None)
diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py
index 45550fb1f..2b1e42bf6 100644
--- a/ipaserver/rpcserver.py
+++ b/ipaserver/rpcserver.py
@@ -25,11 +25,10 @@ Also see the `ipalib.rpc` module.
from xml.sax.saxutils import escape
import os
-import datetime
import json
import traceback
import gssapi
-import time
+import requests
import ldap.controls
from pyasn1.type import univ, namedtype
@@ -51,22 +50,24 @@ from ipalib.errors import (PublicError, InternalError, JSONError,
from ipalib.request import context, destroy_context
from ipalib.rpc import (xml_dumps, xml_loads,
json_encode_binary, json_decode_binary)
-from ipalib.util import parse_time_duration, normalize_name
+from ipalib.util import normalize_name
from ipapython.dn import DN
from ipaserver.plugins.ldap2 import ldap2
from ipaserver.session import (
- get_session_mgr, AuthManager, get_ipa_ccache_name,
- load_ccache_data, bind_ipa_ccache, release_ipa_ccache, fmt_time,
- default_max_session_duration, krbccache_dir, krbccache_prefix)
+ get_ipa_ccache_name,
+ krbccache_dir, krbccache_prefix)
from ipalib.backend import Backend
from ipalib.krb_utils import (
- krb_ticket_expiration_threshold, krb5_format_principal_name,
- krb5_format_service_principal_name, get_credentials, get_credentials_if_valid)
+ krb5_format_principal_name,
+ krb5_format_service_principal_name, get_credentials_if_valid)
from ipapython import ipautil
from ipaplatform.paths import paths
from ipapython.version import VERSION
from ipalib.text import _
+from base64 import b64decode, b64encode
+from requests.auth import AuthBase
+
if six.PY3:
unicode = str
@@ -303,6 +304,7 @@ class WSGIExecutioner(Executioner):
Base class for execution backends with a WSGI application interface.
"""
+ headers = None
content_type = None
key = ''
@@ -424,22 +426,17 @@ class WSGIExecutioner(Executioner):
try:
status = HTTP_STATUS_SUCCESS
response = self.wsgi_execute(environ)
- headers = [('Content-Type', self.content_type + '; charset=utf-8')]
+ if self.headers:
+ headers = self.headers
+ else:
+ headers = [('Content-Type',
+ self.content_type + '; charset=utf-8')]
except Exception:
self.exception('WSGI %s.__call__():', self.name)
status = HTTP_STATUS_SERVER_ERROR
response = status.encode('utf-8')
headers = [('Content-Type', 'text/plain; charset=utf-8')]
- session_data = getattr(context, 'session_data', None)
- if session_data is not None:
- # Send session cookie back and store session data
- # FIXME: the URL path should be retreived from somewhere (but where?), not hardcoded
- session_mgr = get_session_mgr()
- session_cookie = session_mgr.generate_cookie('/ipa', session_data['session_id'],
- session_data['session_expiration_timestamp'])
- headers.append(('Set-Cookie', session_cookie))
-
start_response(status, headers)
return [response]
@@ -521,36 +518,73 @@ class jsonserver(WSGIExecutioner, HTTP_Status):
options = dict((str(k), v) for (k, v) in options.items())
return (method, args, options, _id)
-class AuthManagerKerb(AuthManager):
- '''
- Instances of the AuthManger class are used to handle
- authentication events delivered by the SessionManager. This class
- specifcally handles the management of Kerbeos credentials which
- may be stored in the session.
- '''
- def __init__(self, name):
- super(AuthManagerKerb, self).__init__(name)
+class NegotiateAuth(AuthBase):
+ """Negotiate Augh using python GSSAPI"""
+ def __init__(self, target_host, ccache_name=None):
+ self.context = None
+ self.target_host = target_host
+ self.ccache_name = ccache_name
+
+ def __call__(self, request):
+ self.initial_step(request)
+ request.register_hook('response', self.handle_response)
+ return request
+
+ def deregister(self, response):
+ response.request.deregister_hook('response', self.handle_response)
+
+ def _get_negotiate_token(self, response):
+ token = None
+ if response is not None:
+ h = response.headers.get('www-authenticate', '')
+ if h.startswith('Negotiate'):
+ val = h[h.find('Negotiate') + len('Negotiate'):].strip()
+ if len(val) > 0:
+ token = b64decode(val)
+ return token
+
+ def _set_authz_header(self, request, token):
+ request.headers['Authorization'] = 'Negotiate ' + b64encode(token)
+
+ def initial_step(self, request, response=None):
+ if self.context is None:
+ store = {'ccache': self.ccache_name}
+ creds = gssapi.Credentials(usage='initiate', store=store)
+ name = gssapi.Name('HTTP@{0}'.format(self.target_host),
+ name_type=gssapi.NameType.hostbased_service)
+ self.context = gssapi.SecurityContext(creds=creds, name=name,
+ usage='initiate')
+
+ in_token = self._get_negotiate_token(response)
+ out_token = self.context.step(in_token)
+ self._set_authz_header(request, out_token)
+
+ def handle_response(self, response, **kwargs):
+ status = response.status_code
+ if status >= 400 and status != 401:
+ return response
+
+ in_token = self._get_negotiate_token(response)
+ if in_token is not None:
+ out_token = self.context.step(in_token)
+ if self.context.complete:
+ return response
+ elif not out_token:
+ return response
+
+ self._set_authz_header(response.request, out_token)
+ # use response so we can make another request
+ _ = response.content # pylint: disable=unused-variable
+ response.raw.release_conn()
+ newresp = response.connection.send(response.request, **kwargs)
+ newresp.history.append(response)
+ return self.handle_response(newresp, **kwargs)
- def logout(self, session_data):
- '''
- The current user has requested to be logged out. To accomplish
- this we remove the user's kerberos credentials from their
- session. This does not destroy the session, it just prevents
- it from being used for fast authentication. Because the
- credentials are no longer in the session cache any future
- attempt will require the acquisition of credentials using one
- of the login mechanisms.
- '''
-
- if 'ccache_data' in session_data:
- self.debug('AuthManager.logout.%s: deleting ccache_data', self.name)
- del session_data['ccache_data']
- else:
- self.error('AuthManager.logout.%s: session_data does not contain ccache_data', self.name)
+ return response
-class KerberosSession(object):
+class KerberosSession(HTTP_Status):
'''
Functionally shared by all RPC handlers using both sessions and
Kerberos. This class must be implemented as a mixin class rather
@@ -558,101 +592,44 @@ class KerberosSession(object):
needing this do not share a common base class.
'''
- def kerb_session_on_finalize(self):
- '''
- Initialize values from the Env configuration.
-
- Why do it this way and not simply reference
- api.env.session_auth_duration? Because that config item cannot
- be used directly, it must be parsed and converted to an
- integer. It would be inefficient to reparse it on every
- request. So we parse it once and store the result in the class
- instance.
- '''
- # Set the session expiration time
- try:
- seconds = parse_time_duration(self.api.env.session_auth_duration)
- self.session_auth_duration = int(seconds)
- self.debug("session_auth_duration: %s", datetime.timedelta(seconds=self.session_auth_duration))
- except Exception as e:
- self.session_auth_duration = default_max_session_duration
- self.error('unable to parse session_auth_duration, defaulting to %d: %s',
- self.session_auth_duration, e)
-
- def update_session_expiration(self, session_data, krb_endtime):
- '''
- Each time a session is created or accessed we need to update
- it's expiration time. The expiration time is set inside the
- session_data.
-
- :parameters:
- session_data
- The session data whose expiration is being updatded.
- krb_endtime
- The UNIX timestamp for when the Kerberos credentials expire.
- :returns:
- None
- '''
-
- # Account for clock skew and/or give us some time leeway
- krb_expiration = krb_endtime - krb_ticket_expiration_threshold
-
- # Set the session expiration time
- session_mgr = get_session_mgr()
- session_mgr.set_session_expiration_time(session_data,
- duration=self.session_auth_duration,
- max_age=krb_expiration,
- duration_type=self.api.env.session_duration_type)
-
def finalize_kerberos_acquisition(self, who, ccache_name, environ, start_response, headers=None):
if headers is None:
headers = []
- # Retrieve the session data (or newly create)
- session_mgr = get_session_mgr()
- session_data = session_mgr.load_session_data(environ.get('HTTP_COOKIE'))
- session_id = session_data['session_id']
-
- self.debug('finalize_kerberos_acquisition: %s ccache_name="%s" session_id="%s"',
- who, ccache_name, session_id)
-
- # Copy the ccache file contents into the session data
- session_data['ccache_data'] = load_ccache_data(ccache_name)
-
- # Set when the session will expire
- creds = get_credentials(ccache_name=ccache_name)
- endtime = creds.lifetime + time.time()
- self.update_session_expiration(session_data, endtime)
-
- # Store the session data now that it's been updated with the ccache
- session_mgr.store_session_data(session_data)
-
- # The request is finished with the ccache, destroy it.
- release_ipa_ccache(ccache_name)
+ # Connect back to ourselves to get mod_auth_gssapi to
+ # generate a cookie for us.
+ try:
+ target = self.api.env.host
+ r = requests.get('http://{0}/ipa/session/cookie'.format(target),
+ auth=NegotiateAuth(target, ccache_name))
+ session_cookie = r.cookies.get("ipa_session")
+ if not session_cookie:
+ raise ValueError('No session cookie found')
+ except Exception as e:
+ return self.unauthorized(environ, start_response,
+ str(e),
+ 'Authentication failed')
- # Return success and set session cookie
- session_cookie = session_mgr.generate_cookie('/ipa', session_id,
- session_data['session_expiration_timestamp'])
- headers.append(('Set-Cookie', session_cookie))
+ headers.append(('IPASESSION', session_cookie))
start_response(HTTP_STATUS_SUCCESS, headers)
return ['']
-class KerberosWSGIExecutioner(WSGIExecutioner, HTTP_Status, KerberosSession):
+class KerberosWSGIExecutioner(WSGIExecutioner, KerberosSession):
"""Base class for xmlserver and jsonserver_kerb
"""
def _on_finalize(self):
super(KerberosWSGIExecutioner, self)._on_finalize()
- self.kerb_session_on_finalize()
def __call__(self, environ, start_response):
self.debug('KerberosWSGIExecutioner.__call__:')
user_ccache=environ.get('KRB5CCNAME')
- headers = [('Content-Type', '%s; charset=utf-8' % self.content_type)]
+ self.headers = [('Content-Type',
+ '%s; charset=utf-8' % self.content_type)]
if user_ccache is None:
@@ -664,18 +641,19 @@ class KerberosWSGIExecutioner(WSGIExecutioner, HTTP_Status, KerberosSession):
'KRB5CCNAME not defined in HTTP request environment')
return self.marshal(None, CCacheError())
+
+ logout_cookie = getattr(context, 'logout_cookie', None)
+ if logout_cookie:
+ self.headers.append(('IPASESSION', logout_cookie))
+
try:
self.create_context(ccache=user_ccache)
response = super(KerberosWSGIExecutioner, self).__call__(
environ, start_response)
- session_data = getattr(context, 'session_data', None)
- if (session_data is None and self.env.context != 'lite'):
- self.finalize_kerberos_acquisition(
- 'xmlserver', user_ccache, environ, start_response, headers)
except PublicError as e:
status = HTTP_STATUS_SUCCESS
response = status.encode('utf-8')
- start_response(status, headers)
+ start_response(status, self.headers)
return self.marshal(None, e)
finally:
destroy_context()
@@ -773,14 +751,9 @@ class jsonserver_session(jsonserver, KerberosSession):
def __init__(self, api):
super(jsonserver_session, self).__init__(api)
- name = '{0}_{1}'.format(self.__class__.__name__, id(self))
- auth_mgr = AuthManagerKerb(name)
- session_mgr = get_session_mgr()
- session_mgr.auth_mgr.register(auth_mgr.name, auth_mgr)
def _on_finalize(self):
super(jsonserver_session, self)._on_finalize()
- self.kerb_session_on_finalize()
def need_login(self, start_response):
status = '401 Unauthorized'
@@ -798,68 +771,32 @@ class jsonserver_session(jsonserver, KerberosSession):
self.debug('WSGI jsonserver_session.__call__:')
- # Load the session data
- session_mgr = get_session_mgr()
- session_data = session_mgr.load_session_data(environ.get('HTTP_COOKIE'))
- session_id = session_data['session_id']
-
- self.debug('jsonserver_session.__call__: session_id=%s start_timestamp=%s access_timestamp=%s expiration_timestamp=%s',
- session_id,
- fmt_time(session_data['session_start_timestamp']),
- fmt_time(session_data['session_access_timestamp']),
- fmt_time(session_data['session_expiration_timestamp']))
-
- ccache_data = session_data.get('ccache_data')
+ ccache_name = environ.get('KRB5CCNAME')
# Redirect to login if no Kerberos credentials
- if ccache_data is None:
+ if ccache_name is None:
self.debug('no ccache, need login')
return self.need_login(start_response)
- ipa_ccache_name = bind_ipa_ccache(ccache_data)
-
# Redirect to login if Kerberos credentials are expired
- creds = get_credentials_if_valid(ccache_name=ipa_ccache_name)
+ creds = get_credentials_if_valid(ccache_name=ccache_name)
if not creds:
self.debug('ccache expired, deleting session, need login')
# The request is finished with the ccache, destroy it.
- release_ipa_ccache(ipa_ccache_name)
return self.need_login(start_response)
- # Update the session expiration based on the Kerberos expiration
- endtime = creds.lifetime + time.time()
- self.update_session_expiration(session_data, endtime)
-
- # Store the session data in the per-thread context
- setattr(context, 'session_data', session_data)
+ # Store the ccache name in the per-thread context
+ setattr(context, 'ccache_name', ccache_name)
# This may fail if a ticket from wrong realm was handled via browser
try:
- self.create_context(ccache=ipa_ccache_name)
+ self.create_context(ccache=ccache_name)
except ACIError as e:
return self.unauthorized(environ, start_response, str(e), 'denied')
try:
response = super(jsonserver_session, self).__call__(environ, start_response)
finally:
- # Kerberos may have updated the ccache data during the
- # execution of the command therefore we need refresh our
- # copy of it in the session data so the next command sees
- # the same state of the ccache.
- #
- # However we must be careful not to restore the ccache
- # data in the session data if it was explicitly deleted
- # during the execution of the command. For example the
- # logout command removes the ccache data from the session
- # data to invalidate the session credentials.
-
- if 'ccache_data' in session_data:
- session_data['ccache_data'] = load_ccache_data(ipa_ccache_name)
-
- # The request is finished with the ccache, destroy it.
- release_ipa_ccache(ipa_ccache_name)
- # Store the session data.
- session_mgr.store_session_data(session_data)
destroy_context()
return response
@@ -873,13 +810,12 @@ class jsonserver_kerb(jsonserver, KerberosWSGIExecutioner):
key = '/json'
-class KerberosLogin(Backend, KerberosSession, HTTP_Status):
+class KerberosLogin(Backend, KerberosSession):
key = None
def _on_finalize(self):
super(KerberosLogin, self)._on_finalize()
self.api.Backend.wsgi_dispatch.mount(self, self.key)
- self.kerb_session_on_finalize()
def __call__(self, environ, start_response):
self.debug('WSGI KerberosLogin.__call__:')
@@ -901,7 +837,7 @@ class login_x509(KerberosLogin):
key = '/session/login_x509'
-class login_password(Backend, KerberosSession, HTTP_Status):
+class login_password(Backend, KerberosSession):
content_type = 'text/plain'
key = '/session/login_password'
@@ -909,7 +845,6 @@ class login_password(Backend, KerberosSession, HTTP_Status):
def _on_finalize(self):
super(login_password, self)._on_finalize()
self.api.Backend.wsgi_dispatch.mount(self, self.key)
- self.kerb_session_on_finalize()
def __call__(self, environ, start_response):
self.debug('WSGI login_password.__call__:')
@@ -1243,14 +1178,9 @@ class xmlserver_session(xmlserver, KerberosSession):
def __init__(self, api):
super(xmlserver_session, self).__init__(api)
- name = '{0}_{1}'.format(self.__class__.__name__, id(self))
- auth_mgr = AuthManagerKerb(name)
- session_mgr = get_session_mgr()
- session_mgr.auth_mgr.register(auth_mgr.name, auth_mgr)
def _on_finalize(self):
super(xmlserver_session, self)._on_finalize()
- self.kerb_session_on_finalize()
def need_login(self, start_response):
status = '401 Unauthorized'
@@ -1268,64 +1198,26 @@ class xmlserver_session(xmlserver, KerberosSession):
self.debug('WSGI xmlserver_session.__call__:')
- # Load the session data
- session_mgr = get_session_mgr()
- session_data = session_mgr.load_session_data(environ.get('HTTP_COOKIE'))
- session_id = session_data['session_id']
-
- self.debug('xmlserver_session.__call__: session_id=%s start_timestamp=%s access_timestamp=%s expiration_timestamp=%s',
- session_id,
- fmt_time(session_data['session_start_timestamp']),
- fmt_time(session_data['session_access_timestamp']),
- fmt_time(session_data['session_expiration_timestamp']))
-
- ccache_data = session_data.get('ccache_data')
+ ccache_name = environ.get('KRB5CCNAME')
# Redirect to /ipa/xml if no Kerberos credentials
- if ccache_data is None:
+ if ccache_name is None:
self.debug('xmlserver_session.__call_: no ccache, need TGT')
return self.need_login(start_response)
- ipa_ccache_name = bind_ipa_ccache(ccache_data)
-
# Redirect to /ipa/xml if Kerberos credentials are expired
- creds = get_credentials_if_valid(ccache_name=ipa_ccache_name)
+ creds = get_credentials_if_valid(ccache_name=ccache_name)
if not creds:
self.debug('xmlserver_session.__call_: ccache expired, deleting session, need login')
# The request is finished with the ccache, destroy it.
- release_ipa_ccache(ipa_ccache_name)
return self.need_login(start_response)
- # Update the session expiration based on the Kerberos expiration
- endtime = creds.lifetime + time.time()
- self.update_session_expiration(session_data, endtime)
-
# Store the session data in the per-thread context
- setattr(context, 'session_data', session_data)
-
- environ['KRB5CCNAME'] = ipa_ccache_name
+ setattr(context, 'ccache_name', ccache_name)
try:
response = super(xmlserver_session, self).__call__(environ, start_response)
finally:
- # Kerberos may have updated the ccache data during the
- # execution of the command therefore we need refresh our
- # copy of it in the session data so the next command sees
- # the same state of the ccache.
- #
- # However we must be careful not to restore the ccache
- # data in the session data if it was explicitly deleted
- # during the execution of the command. For example the
- # logout command removes the ccache data from the session
- # data to invalidate the session credentials.
-
- if 'ccache_data' in session_data:
- session_data['ccache_data'] = load_ccache_data(ipa_ccache_name)
-
- # The request is finished with the ccache, destroy it.
- release_ipa_ccache(ipa_ccache_name)
- # Store the session data.
- session_mgr.store_session_data(session_data)
destroy_context()
return response
diff --git a/ipaserver/session.py b/ipaserver/session.py
index 0f3a9ad58..cdf8995bc 100644
--- a/ipaserver/session.py
+++ b/ipaserver/session.py
@@ -16,1204 +16,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import memcache
-import random
import os
-import re
-import time
-import io
-# pylint: disable=import-error
-from six.moves.urllib.parse import urlparse
-# pylint: enable=import-error
-
-from ipalib import errors
-from ipalib.text import _
-from ipapython.ipa_log_manager import root_logger, log_mgr
-from ipalib import api
-from ipaplatform.paths import paths
+from ipalib.request import context
from ipalib.krb_utils import (
krb5_parse_ccache,
- krb5_unparse_ccache)
-from ipapython.cookie import Cookie
-
-__doc__ = '''
-Session Support for IPA
-John Dennis <jdennis@redhat.com>
-
-Goals
-=====
-
-Provide per-user session data caching which persists between
-requests. Desired features are:
-
-* Integrates cleanly with minimum impact on existing infrastructure.
-
-* Provides maximum security balanced against real-world performance
- demands.
-
-* Sessions must be able to be revoked (flushed).
-
-* Should be flexible and easy to use for developers.
-
-* Should leverage existing technology and code to the maximum extent
- possible to avoid re-invention, excessive implementation time and to
- benefit from robustness in field proven components commonly shared
- in the open source community.
-
-* Must support multiple independent processes which share session
- data.
-
-* System must function correctly if session data is available or not.
-
-* Must be high performance.
-
-* Should not be tied to specific web servers or browsers. Should
- integrate with our chosen WSGI model.
-
-Issues
-======
-
-Cookies
--------
-
-Most session implementations are based on the use of cookies. Cookies
-have some inherent problems.
-
-* User has the option to disable cookies.
-
-* User stored cookie data is not secure. Can be mitigated by setting
- flags indicating the cookie is only to be used with SSL secured HTTP
- connections to specific web resources and setting the cookie to
- expire at session termination. Most modern browsers enforce these.
-
-Where to store session data?
-----------------------------
-
-Session data may be stored on either on the client or on the
-server. Storing session data on the client addresses the problem of
-session data availability when requests are serviced by independent web
-servers because the session data travels with the request. However
-there are data size limitations. Storing session data on the client
-also exposes sensitive data but this can be mitigated by encrypting
-the session data such that only the server can decrypt it.
-
-The more conventional approach is to bind session data to a unique
-name, the session ID. The session ID is transmitted to the client and
-the session data is paired with the session ID on the server in a
-associative data store. The session data is retrieved by the server
-using the session ID when the receiving the request. This eliminates
-exposing sensitive session data on the client along with limitations
-on data size. It however introduces the issue of session data
-availability when requests are serviced by more than one server
-process.
-
-Multi-process session data availability
----------------------------------------
-
-Apache (and other web servers) fork child processes to handle requests
-in parallel. Also web servers may be deployed in a farm where requests
-are load balanced in round robin fashion across different nodes. In
-both cases session data cannot be stored in the memory of a server
-process because it is not available to other processes, either sibling
-children of a master server process or server processes on distinct
-nodes.
-
-Typically this is addressed by storing session data in a SQL
-database. When a request is received by a server process containing a
-session ID in it's cookie data the session ID is used to perform a SQL
-query and the resulting data is then attached to the request as it
-proceeds through the request processing pipeline. This of course
-introduces coherency issues.
-
-For IPA the introduction of a SQL database dependency is undesired and
-should be avoided.
-
-Session data may also be shared by independent processes by storing
-the session data in files.
-
-An alternative solution which has gained considerable popularity
-recently is the use of a fast memory based caching server. Data is
-stored in a single process memory and may be queried and set via a
-light weight protocol using standard socket mechanisms, memcached is
-one example. A typical use is to optimize SQL queries by storing a SQL
-result in shared memory cache avoiding the more expensive SQL
-operation. But the memory cache has distinct advantages in non-SQL
-situations as well.
-
-Possible implementations for use by IPA
-=======================================
-
-Apache Sessions
----------------
-
-Apache has 2.3 has implemented session support via these modules:
-
- mod_session
- Overarching session support based on cookies.
-
- See: http://httpd.apache.org/docs/2.3/mod/mod_session.html
-
- mod_session_cookie
- Stores session data in the client.
-
- See: http://httpd.apache.org/docs/2.3/mod/mod_session_cookie.html
-
- mod_session_crypto
- Encrypts session data for security. Encryption key is shared
- configuration parameter visible to all Apache processes and is
- stored in a configuration file.
-
- See: http://httpd.apache.org/docs/2.3/mod/mod_session_crypto.html
-
- mod_session_dbd
- Stores session data in a SQL database permitting multiple
- processes to access and share the same session data.
-
- See: http://httpd.apache.org/docs/2.3/mod/mod_session_dbd.html
-
-Issues with Apache sessions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Although Apache has implemented generic session support and Apache is
-our web server of preference it nonetheless introduces issues for IPA.
-
- * Session support is only available in httpd >= 2.3 which at the
- time of this writing is currently only available as a Beta release
- from upstream. We currently only ship httpd 2.2, the same is true
- for other distributions.
-
- * We could package and ship the sessions modules as a temporary
- package in httpd 2.2 environments. But this has the following
- consequences:
-
- - The code has to be backported. the module API has changed
- slightly between httpd 2.2 and 2.3. The backporting is not
- terribly difficult and a proof of concept has been
- implemented.
-
- - We would then be on the hook to package and maintain a special
- case Apache package. This is maintenance burden as well as a
- distribution packaging burden. Both of which would be best
- avoided if possible.
-
- * The design of the Apache session modules is such that they can
- only be manipulated by other Apache modules. The ability of
- consumers of the session data to control the session data is
- simplistic, constrained and static during the period the request
- is processed. Request handlers which are not native Apache modules
- (e.g. IPA via WSGI) can only examine the session data
- via request headers and reset it in response headers.
-
- * Shared session data is available exclusively via SQL.
-
-However using the 2.3 Apache session modules would give us robust
-session support implemented in C based on standardized Apache
-interfaces which are widely used.
-
-Python Web Frameworks
----------------------
-
-Virtually every Python web framework supports cookie based sessions,
-e.g. Django, Twisted, Zope, Turbogears etc. Early on in IPA we decided
-to avoid the use of these frameworks. Trying to pull in just one part
-of these frameworks just to get session support would be problematic
-because the code does not function outside it's framework.
-
-IPA implemented sessions
-------------------------
-
-Originally it was believed the path of least effort was to utilize
-existing session support, most likely what would be provided by
-Apache. However there are enough basic modular components available in
-native Python and other standard packages it should be possible to
-provide session support meeting the aforementioned goals with a modest
-implementation effort. Because we're leveraging existing components
-the implementation difficulties are subsumed by other components which
-have already been field proven and have community support. This is a
-smart strategy.
-
-Proposed Solution
-=================
-
-Our interface to the web server is via WSGI which invokes a callback
-per request passing us an environmental context for the request. For
-this discussion we'll name the WSGI callback "application()", a
-conventional name in WSGI parlance.
-
-Shared session data will be handled by memcached. We will create one
-instance of memcached on each server node dedicated to IPA
-exclusively. Communication with memcached will be via a UNIX socket
-located in the file system under /var/run/ipa_memcached. It will be
-protected by file permissions and optionally SELinux policy.
-
-In application() we examine the request cookies and if there is an IPA
-session cookie with a session ID we retrieve the session data from our
-memcached instance.
-
-The session data will be a Python dict. IPA components will read or
-write their session information by using a pre-agreed upon name
-(e.g. key) in the dict. This is a very flexible system and consistent
-with how we pass data in most parts of IPA.
-
-If the session data is not available an empty session data dict will
-be created.
-
-How does this session data travel with the request in the IPA
-pipeline? In IPA we use the HTTP request/response to implement RPC. In
-application() we convert the request into a procedure call passing it
-arguments derived from the HTTP request. The passed parameters are
-specific to the RPC method being invoked. The context the RPC call is
-executing in is not passed as an RPC parameter.
-
-How would the contextual information such as session data be bound to
-the request and hence the RPC call?
-
-In IPA when a RPC invocation is being prepared from a request we
-recognize this will only ever be processed serially by one Python
-thread. A thread local dict called "context" is allocated for each
-thread. The context dict is cleared in between requests (e.g. RPC method
-invocations). The per-thread context dict is populated during the
-lifetime of the request and is used as a global data structure unique to
-the request that various IPA component can read from and write to with
-the assurance the data is unique to the current request and/or method
-call.
-
-The session data dict will be written into the context dict under the
-session key before the RPC method begins execution. Thus session data
-can be read and written by any IPA component by accessing
-``context.session``.
-
-When the RPC method finishes execution the session data bound to the
-request/method is retrieved from the context and written back to the
-memcached instance. The session ID is set in the response sent back to
-the client in the ``Set-Cookie`` header along with the flags
-controlling it's usage.
-
-Issues and details
-------------------
-
-IPA code cannot depend on session data being present, however it
-should always update session data with the hope it will be available
-in the future. Session data may not be available because:
-
- * This is the first request from the user and no session data has
- been created yet.
-
- * The user may have cookies disabled.
-
- * The session data may have been flushed. memcached operates with
- a fixed memory allocation and will flush entries on a LRU basis,
- like with any cache there is no guarantee of persistence.
-
- Also we may have have deliberately expired or deleted session
- data, see below.
-
-Cookie manipulation is done via the standard Python Cookie module.
-
-Session cookies will be set to only persist as long as the browser has
-the session open. They will be tagged so the browser only returns
-the session ID on SSL secured HTTP requests. They will not be visible
-to Javascript in the browser.
-
-Session ID's will be created by using 48 bits of random data and
-converted to 12 hexadecimal digits. Newly generated session ID's will
-be checked for prior existence to handle the unlikely case the random
-number repeats.
-
-memcached will have significantly higher performance than a SQL or file
-based storage solution. Communication is effectively though a pipe
-(UNIX socket) using a very simple protocol and the data is held
-entirely in process memory. memcached also scales easily, it is easy
-to add more memcached processes and distribute the load across them.
-At this point in time we don't anticipate the need for this.
-
-A very nice feature of the Python memcached module is that when a data
-item is written to the cache it is done with standard Python pickling
-(pickling is a standard Python mechanism to marshal and unmarshal
-Python objects). We adopt the convention the object written to cache
-will be a dict to meet our internal data handling conventions. The
-pickling code will recursively handle nested objects in the dict. Thus
-we gain a lot of flexibility using standard Python data structures to
-store and retrieve our session data without having to author and debug
-code to marshal and unmarshal the data if some other storage mechanism
-had been used. This is a significant implementation win. Of course
-some common sense limitations need to observed when deciding on what
-is written to the session cache keeping in mind the data is shared
-between processes and it should not be excessively large (a
-configurable option)
-
-We can set an expiration on memcached entries. We may elect to do that
-to force session data to be refreshed periodically. For example we may
-wish the client to present fresh credentials on a periodic basis even
-if the cached credentials are otherwise within their validity period.
-
-We can explicitly delete session data if for some reason we believe it
-is stale, invalid or compromised.
-
-memcached also gives us certain facilities to prevent race conditions
-between different processes utilizing the cache. For example you can
-check of the entry has been modified since you last read it or use CAS
-(Check And Set) semantics. What has to be protected in terms of cache
-coherency will likely have to be determined as the session support is
-utilized and different data items are added to the cache. This is very
-much data and context specific. Fortunately memcached operations are
-atomic.
-
-Controlling the memcached process
----------------------------------
-
-We need a mechanism to start the memcached process and secure it so
-that only IPA components can access it.
-
-Although memcached ships with both an initscript and systemd unit
-files those are for generic instances. We want a memcached instance
-dedicated exclusively to IPA usage. To accomplish this we would install
-a systemd unit file or an SysV initscript to control the IPA specific
-memcached service. ipactl would be extended to know about this
-additional service. systemd's cgroup facility would give us additional
-mechanisms to integrate the IPA memcached service within a larger IPA
-process group.
-
-Protecting the memcached data would be done via file permissions (and
-optionally SELinux policy) on the UNIX domain socket. Although recent
-implementations of memcached support authentication via SASL this
-introduces a performance and complexity burden not warranted when
-cached is dedicated to our exclusive use and access controlled by OS
-mechanisms.
-
-Conventionally daemons are protected by assigning a system uid and/or
-gid to the daemon. A daemon launched by root will drop it's privileges
-by assuming the effective uid:gid assigned to it. File system access
-is controlled by the OS via the effective identity and SELinux policy
-can be crafted based on the identity. Thus the memcached UNIX socket
-would be protected by having it owned by a specific system user and/or
-membership in a restricted system group (discounting for the moment
-SELinux).
-
-Unfortunately we currently do not have an IPA system uid whose
-identity our processes operate under nor do we have an IPA system
-group. IPA does manage a collection of related processes (daemons) and
-historically each has been assigned their own uid. When these
-unrelated processes communicate they mutually authenticate via other
-mechanisms. We do not have much of a history of using shared file
-system objects across identities. When file objects are created they
-are typically assigned the identity of daemon needing to access the
-object and are not accessed by other daemons, or they carry root
-identity.
-
-When our WSGI application runs in Apache it is run as a WSGI
-daemon. This means when Apache starts up it forks off WSGI processes
-for us and we are independent of other Apache processes. When WSGI is
-run in this mode there is the ability to set the uid:gid of the WSGI
-process hosting us, however we currently do not take advantage of this
-option. WSGI can be run in other modes as well, only in daemon mode
-can the uid:gid be independently set from the rest of Apache. All
-processes started by Apache can be set to a common uid:gid specified
-in the global Apache configuration, by default it's
-apache:apache. Thus when our IPA code executes it is running as
-apache:apache.
-
-To protect our memcached UNIX socket we can do one of two things:
-
-1. Assign it's uid:gid as apache:apache. This would limit access to
- our cache only to processes running under httpd. It's somewhat
- restricted but far from ideal. Any code running in the web server
- could potentially access our cache. It's difficult to control what the
- web server runs and admins may not understand the consequences of
- configuring httpd to serve other things besides IPA.
-
-2. Create an IPA specific uid:gid, for example ipa:ipa. We then configure
- our WSGI application to run as the ipa:ipa user and group. We also
- configure our memcached instance to run as the ipa:ipa user and
- group. In this configuration we are now fully protected, only our WSGI
- code can read & write to our memcached UNIX socket.
-
-However there may be unforeseen issues by converting our code to run as
-something other than apache:apache. This would require some
-investigation and testing.
-
-IPA is dependent on other system daemons, specifically Directory
-Server (ds) and Certificate Server (cs). Currently we configure ds to
-run under the dirsrv:dirsrv user and group, an identity of our
-creation. We allow cs to default to it's pkiuser:pkiuser user and
-group. Should these other cooperating daemons also run under the
-common ipa:ipa user and group identities? At first blush there would
-seem to be an advantage to coalescing all process identities under a
-common IPA user and group identity. However these other processes do
-not depend on user and group permissions when working with external
-agents, processes, etc. Rather they are designed to be stand-alone
-network services which authenticate their clients via other
-mechanisms. They do depend on user and group permission to manage
-their own file system objects. If somehow the ipa user and/or group
-were compromised or malicious code somehow executed under the ipa
-identity there would be an advantage in having the cooperating
-processes cordoned off under their own identities providing one extra
-layer of protection. (Note, these cooperating daemons may not even be
-co-located on the same node in which case the issue is moot)
-
-The UNIX socket behavior (ldapi) with Directory Server is as follows:
-
- * The socket ownership is: root:root
-
- * The socket permissions are: 0666
-
- * When connecting via ldapi you must authenticate as you would
- normally with a TCP socket, except ...
-
- * If autobind is enabled and the uid:gid is available via
- SO_PEERCRED and the uid:gid can be found in the set of users known
- to the Directory Server then that connection will be bound as that
- user.
-
- * Otherwise an anonymous bind will occur.
-
-memcached UNIX socket behavior is as follows:
-
- * memcached can be invoked with a user argument, no group may be
- specified. The effective uid is the uid of the user argument and
- the effective gid is the primary group of the user, let's call
- this euid:egid
-
- * The socket ownership is: euid:egid
-
- * The socket permissions are 0700 by default, but this can be
- modified by the -a mask command line arg which sets the umask
- (defaults to 0700).
-
-Overview of authentication in IPA
-=================================
-
-This describes how we currently authenticate and how we plan to
-improve authentication performance. First some definitions.
-
-There are 4 major players:
-
- 1. client
- 2. mod_auth_gssapi (in Apache process)
- 3. wsgi handler (in IPA wsgi python process)
- 4. ds (directory server)
-
-There are several resources:
-
- 1. /ipa/ui (unprotected, web UI static resources)
- 2. /ipa/xml (protected, xmlrpc RPC used by command line clients)
- 3. /ipa/json (protected, json RPC used by javascript in web UI)
- 4. ds (protected, wsgi acts as proxy, our LDAP server)
-
-Current Model
--------------
-
-This describes how things work in our current system for the web UI.
-
- 1. Client requests /ipa/ui, this is unprotected, is static and
- contains no sensitive information. Apache replies with html and
- javascript. The javascript requests /ipa/json.
-
- 2. Client sends post to /ipa/json.
-
- 3. mod_auth_gssapi is configured to protect /ipa/json, replies 401
- authenticate negotiate.
-
- 4. Client resends with credentials
-
- 5. mod_auth_gssapi validates credentials
-
- a. if invalid replies 403 access denied (stops here)
-
- b. if valid creates temporary ccache, adds KRB5CCNAME to request
- headers
-
- 6. Request passed to wsgi handler
-
- a. validates request, KRB5CCNAME must be present, referrer, etc.
-
- b. ccache saved and used to bind to ds
-
- c. routes to specified RPC handler.
-
- 7. wsgi handler replies to client
-
-Proposed new session based optimization
----------------------------------------
-
-The round trip negotiate and credential validation in steps 3,4,5 is
-expensive. This can be avoided if we can cache the client
-credentials. With client sessions we can store the client credentials
-in the session bound to the client.
-
-A few notes about the session implementation.
-
- * based on session cookies, cookies must be enabled
-
- * session cookie is secure, only passed on secure connections, only
- passed to our URL resource, never visible to client javascript
- etc.
-
- * session cookie has a session id which is used by wsgi handler to
- retrieve client session data from shared multi-process cache.
-
-Changes to Apache's resource protection
----------------------------------------
-
- * /ipa/json is no longer protected by mod_auth_gssapi. This is
- necessary to avoid the negotiate expense in steps 3,4,5
- above. Instead the /ipa/json resource will be protected in our wsgi
- handler via the session cookie.
-
- * A new protected URI is introduced, /ipa/login. This resource
- does no serve any data, it is used exclusively for authentication.
-
-The new sequence is:
-
- 1. Client requests /ipa/ui, this is unprotected. Apache replies with
- html and javascript. The javascript requests /ipa/json.
-
- 2. Client sends post to /ipa/json, which is unprotected.
-
- 3. wsgi handler obtains session data from session cookie.
-
- a. if ccache is present in session data and is valid
-
- - request is further validated
-
- - ccache is established for bind to ds
-
- - request is routed to RPC handler
-
- - wsgi handler eventually replies to client
-
- b. if ccache is not present or not valid processing continues ...
-
- 4. wsgi handler replies with 401 Unauthorized
-
- 5. client sends request to /ipa/login to obtain session credentials
-
- 6. mod_auth_gssapi replies 401 negotiate on /ipa/login
-
- 7. client sends credentials to /ipa/login
-
- 8. mod_auth_gssapi validates credentials
-
- a. if valid
-
- - mod_auth_gssapi permits access to /ipa/login. wsgi handler is
- invoked and does the following:
-
- * establishes session for client
-
- * retrieves the ccache from KRB5CCNAME and stores it
-
- a. if invalid
-
- - mod_auth_gssapi sends 403 access denied (processing stops)
-
- 9. client now posts the same data again to /ipa/json including
- session cookie. Processing repeats starting at step 2 and since
- the session data now contains a valid ccache step 3a executes, a
- successful reply is sent to client.
-
-Command line client using xmlrpc
---------------------------------
-
-The above describes the web UI utilizing the json RPC mechanism. The
-IPA command line tools utilize a xmlrpc RPC mechanism on the same
-HTTP server. Access to the xmlrpc is via the /ipa/xml URI. The json
-and xmlrpc API's are the same, they differ only on how their procedure
-calls are marshalled and unmarshalled.
-
-Under the new scheme /ipa/xml will continue to be Kerberos protected
-at all times. Apache's mod_auth_gssapi will continue to require the
-client provides valid Kerberos credentials.
-
-When the WSGI handler routes to /ipa/xml the Kerberos credentials will
-be extracted from the KRB5CCNAME environment variable as provided by
-mod_auth_gssapi. Everything else remains the same.
-
-'''
-
-#-------------------------------------------------------------------------------
-
-default_max_session_duration = 60*60 # number of seconds
-
-ISO8601_DATETIME_FMT = '%Y-%m-%dT%H:%M:%S' # FIXME, this should be defined elsewhere
-def fmt_time(timestamp):
- return time.strftime(ISO8601_DATETIME_FMT, time.localtime(timestamp))
-
-#-------------------------------------------------------------------------------
-
-class AuthManager(object):
- '''
- This class is an abstract base class and is meant to be subclassed
- to provide actual functionality. The purpose is to encapsulate all
- the callbacks one might need to manage authenticaion. Different
- authentication mechanisms will instantiate a subclass of this and
- register it with the SessionAuthManger. When an authentication
- event occurs the matching method will be called for each
- registered class. This allows the SessionAuthManager to notify
- interested parties.
- '''
-
- def __init__(self, name):
- log_mgr.get_logger(self, True)
- self.name = name
-
-
- def logout(self, session_data):
- '''
- Called when a user requests to be logged out of their session.
-
- :parameters:
- session_data
- The current session data
- :returns:
- None
- '''
- self.debug('AuthManager.logout.%s:', self.name)
-
-class SessionAuthManager(object):
-
- def __init__(self):
- log_mgr.get_logger(self, True)
- self.auth_managers = {}
-
- def register(self, name, auth_mgr):
- self.debug('SessionAuthManager.register: name=%s', name)
-
- existing_mgr = self.auth_managers.get(name)
- if existing_mgr is not None:
- raise KeyError('cannot register auth manager named "%s" one already exists, name="%s" object=%s',
- name, existing_mgr.name, repr(existing_mgr))
-
- if not isinstance(auth_mgr, AuthManager):
- raise TypeError('auth_mgr must be an instance of AuthManager, not %s',
- auth_mgr.__class__.__name__)
-
- self.auth_managers[name] = auth_mgr
-
-
- def unregister(self, name):
- self.debug('SessionAuthManager.unregister: name=%s', name)
-
- if name not in self.auth_managers:
- raise KeyError('cannot unregister auth manager named "%s", does not exist',
- name)
- del self.auth_managers[name]
-
-
- def logout(self, session_data):
- self.debug('SessionAuthManager.logout:')
-
- for auth_mgr in self.auth_managers.values():
- try:
- auth_mgr.logout(session_data)
- except Exception as e:
- self.error('%s auth_mgr logout failed: %s', auth_mgr.name, e)
-
-#-------------------------------------------------------------------------------
-
-class SessionManager(object):
-
- '''
- This class is used to manage a set of sessions. Each client
- connecting to the server is assigned a session id wich is then
- used to store data bound to the client's session in between server
- requests.
- '''
-
- def __init__(self):
- '''
- :returns:
- `SessionManager` object
- '''
-
- log_mgr.get_logger(self, True)
- self.generated_session_ids = set()
- self.auth_mgr = SessionAuthManager()
-
- def generate_session_id(self, n_bits=128):
- '''
- Return a random string to be used as a session id.
-
- This implementation creates a string of hexadecimal digits.
- There is no guarantee of uniqueness, it is the caller's
- responsibility to validate the returned id is not currently in
- use.
-
- :parameters:
- n_bits
- number of bits of random data, will be rounded to next
- highest multiple of 4
- :returns:
- string of random hexadecimal digits
- '''
- # round up to multiple of 4
- n_bits = (n_bits + 3) & ~3
- session_id = '%0*x' % (n_bits >> 2, random.getrandbits(n_bits))
- return session_id
-
- def new_session_id(self, max_retries=5):
- '''
- Returns a new *unique* session id. See `generate_session_id()`
- for how the session id's are formulated.
-
- The scope of the uniqueness of the id is limited to id's
- generated by this instance of the `SessionManager`.
-
- :parameters:
- max_retries
- Maximum number of attempts to produce a unique id.
- :returns:
- Unique session id as a string.
- '''
- n_retries = 0
- while n_retries < max_retries:
- session_id = self.generate_session_id()
- if not session_id in self.generated_session_ids:
- break
- n_retries += 1
- if n_retries >= max_retries:
- self.error('could not allocate unique new session_id, %d retries exhausted', n_retries)
- raise errors.ExecutionError(message=_('could not allocate unique new session_id'))
- self.generated_session_ids.add(session_id)
- return session_id
-
-
-class MemcacheSessionManager(SessionManager):
- '''
-
- This class is used to assign a session id to a HTTP server client
- and then store client specific data associated with the session in
- a memcached memory cache instance. Multiple processes may share
- the memory cache permitting session data to be shared between
- forked HTTP server children handling server requests.
-
- The session id is guaranteed to be unique.
-
- The session id is set into a session cookie returned to the client
- and is secure (see `generate_cookie()`). Future requests from the
- client will send the session id which is then used to retrieve the
- session data (see `load_session_data()`)
- '''
-
- memcached_socket_path = paths.VAR_RUN_IPA_MEMCACHED
- session_cookie_name = 'ipa_session'
- mc_server_stat_name_re = re.compile(r'(.+)\s+\((\d+)\)')
-
- def __init__(self):
- '''
- :returns:
- `MemcacheSessionManager` object.
- '''
-
- super(MemcacheSessionManager, self).__init__()
- self.servers = ['unix:%s' % self.memcached_socket_path]
- self.mc = memcache.Client(self.servers, debug=0)
-
- if not self.servers_running():
- self.warning("session memcached servers not running")
-
- def get_server_statistics(self):
- '''
- Return memcached server statistics.
-
- Return value is a dict whose keys are server names and whose
- value is a dict of key/value statistics as returned by the
- memcached server.
-
- :returns:
- dict of server names, each value is dict of key/value server
- statistics.
-
- '''
- result = {}
- stats = self.mc.get_stats()
- for server in stats:
- match = self.mc_server_stat_name_re.search(server[0].decode())
- if match:
- name = match.group(1)
- result[name] = server[1]
- else:
- self.warning('unparseable memcached server name "%s"', server[0])
- return result
-
- def servers_running(self):
- '''
- Check if all configured memcached servers are running and can
- be communicated with.
-
- :returns:
- True if at least one server is configured and all servers
- can respond, False otherwise.
-
- '''
-
- if len(self.servers) == 0:
- return False
- stats = self.get_server_statistics()
- return len(self.servers) == len(stats)
-
- def new_session_id(self, max_retries=5):
- '''
- Returns a new *unique* session id. See `generate_session_id()`
- for how the session id's are formulated.
-
- The scope of the uniqueness of the id is limited to id's
- generated by this instance of the `SessionManager` and session
- id's currently stored in the memcache instance.
-
- :parameters:
- max_retries
- Maximum number of attempts to produce a unique id.
- :returns:
- Unique session id as a string.
- '''
- n_retries = 0
- while n_retries < max_retries:
- session_id = super(MemcacheSessionManager, self).new_session_id(max_retries)
- session_data = self.get_session_data(session_id)
- if session_data is None:
- break
- n_retries += 1
- if n_retries >= max_retries:
- self.error('could not allocate unique new session_id, %d retries exhausted', n_retries)
- raise errors.ExecutionError(message=_('could not allocate unique new session_id'))
- return session_id
-
- def new_session_data(self, session_id):
- '''
- Return a new session data dict. The session data will be
- associated with it's session id. The dict will be
- pre-populated with:
-
- session_id
- The session ID used to identify this session data.
- session_start_timestamp
- Timestamp when this session was created.
- session_access_timestamp
- Timestamp when the session was last accessed.
- session_expiration_timestamp
- Timestamp when session expires. Defaults to zero which
- implies no expiration. See `set_session_expiration_time()`.
-
- :parameters:
- session_id
- The session id used to look up this session data.
- :returns:
- Session data dict populated with a session_id key.
- '''
-
- now = time.time()
- return {'session_id' : session_id,
- 'session_start_timestamp' : now,
- 'session_access_timestamp' : now,
- 'session_expiration_timestamp' : 0,
- }
-
- def session_key(self, session_id):
- '''
- Given a session id return a memcache key used to look up the
- session data in the memcache.
-
- :parameters:
- session_id
- The session id from which the memcache key will be derived.
- :returns:
- A key (string) used to look up the session data in the memcache.
- '''
- return 'ipa.session.%s' % (session_id)
-
- def get_session_data(self, session_id):
- '''
- Given a session id retrieve the session data associated with it.
- If no session data exists for the session id return None.
-
- :parameters:
- session_id
- The session id whose session data is desired.
- :returns:
- Session data if found, None otherwise.
- '''
- session_key = self.session_key(session_id)
- session_data = self.mc.get(session_key)
-
- if session_data is not None:
- # update the access timestamp
- now = time.time()
- session_data['session_access_timestamp'] = now
-
- return session_data
-
- def get_session_id_from_http_cookie(self, cookie_header):
- '''
- Parse an HTTP cookie header and search for our session
- id. Return the session id if found, return None if not
- found.
-
- :parameters:
- cookie_header
- An HTTP cookie header. May be None, if None return None.
- :returns:
- Session id as string or None if not found.
- '''
-
- if cookie_header is None:
- return None
-
- session_id = None
-
- try:
- session_cookie = Cookie.get_named_cookie_from_string(cookie_header, self.session_cookie_name)
- except Exception:
- session_cookie = None
- if session_cookie:
- session_id = session_cookie.value
-
- if session_id is None:
- self.debug('no session cookie found')
- else:
- self.debug('found session cookie_id = %s', session_id)
-
- return session_id
-
-
- def load_session_data(self, cookie_header):
- '''
- Parse an HTTP cookie header looking for our session
- information.
-
- * If no session id is found then a new session id and new
- session data dict will be generated, stored in the memcache
- and returned. The new session data dict will contain the new
- session id.
-
- * If the session id is found in the cookie an attempt is made
- to retrieve the session data from the memcache using the
- session id.
-
- - If existing session data is found in the memcache it is
- returned.
-
- - If no session data is found in the memcache then a new
- session data dict will be generated, stored in the
- memcache and returned. The new session data dict will
- contain the session id found in the cookie header.
-
- :parameters:
- cookie_header
- An HTTP cookie header. May be None.
- :returns:
- Session data dict containing at a minimum the session id it
- is bound to.
- '''
-
- session_id = self.get_session_id_from_http_cookie(cookie_header)
- if session_id is None:
- session_id = self.new_session_id()
- self.debug('no session id in request, generating empty session data with id=%s', session_id)
- session_data = self.new_session_data(session_id)
- self.store_session_data(session_data)
- return session_data
- else:
- session_data = self.get_session_data(session_id)
- if session_data is None:
- self.debug('no session data in cache with id=%s, generating empty session data', session_id)
- session_data = self.new_session_data(session_id)
- self.store_session_data(session_data)
- return session_data
- else:
- self.debug('found session data in cache with id=%s', session_id)
- return session_data
-
- def store_session_data(self, session_data):
- '''
- Store the supplied session_data dict in the memcached instance.
-
- The session_expiration_timestamp is always passed to memcached
- when the session data is written back to the memcache. This is
- because otherwise the memcache expiration will default to zero
- if it's not specified which implies no expiration. Thus a
- failure to specify an exiration time when writing an item to
- memcached will cause a previously set expiration time for the
- item to be discarded and the item will no longer expire.
-
- :parameters:
- session_data
- Session data dict, must contain session_id key.
-
- :returns:
- session_id
- '''
- session_id = session_data['session_id']
- session_key = self.session_key(session_id)
-
- # update the access timestamp
- now = time.time()
- session_data['session_access_timestamp'] = now
-
- session_expiration_timestamp = session_data['session_expiration_timestamp']
-
- self.debug('store session: session_id=%s start_timestamp=%s access_timestamp=%s expiration_timestamp=%s',
- session_id,
- fmt_time(session_data['session_start_timestamp']),
- fmt_time(session_data['session_access_timestamp']),
- fmt_time(session_data['session_expiration_timestamp']))
-
- self.mc.set(session_key, session_data, time=session_expiration_timestamp)
- return session_id
-
- def generate_cookie(self, url_path, session_id, expiration=None, add_header=False):
- '''
- Return a session cookie containing the session id. The cookie
- will be contrainted to the url path, defined for use
- with HTTP only, and only returned on secure connections (SSL).
-
- :parameters:
- url_path
- The cookie will be returned in a request if it begins
- with this url path.
- session_id
- The session id identified by the session cookie
- add_header
- If true format cookie string with Set-Cookie: header
-
- :returns:
- cookie string
- '''
-
- if not expiration: # Catch zero unix timestamps
- expiration = None
-
- cookie = Cookie(self.session_cookie_name, session_id,
- domain=urlparse(api.env.xmlrpc_uri).netloc,
- path=url_path, httponly=True, secure=True,
- expires=expiration)
- if add_header:
- result = 'Set-Cookie: %s' % cookie
- else:
- result = str(cookie)
-
- return result
-
- def set_session_expiration_time(self, session_data,
- duration=default_max_session_duration,
- max_age=None, duration_type='inactivity_timeout'):
- '''
- memcached permits setting an expiration time on entries. The
- expiration time may either be Unix time (number of seconds since
- January 1, 1970, as a 32-bit value), or a number of seconds starting
- from current time. In the latter case, this number of seconds may
- not exceed 60*60*24*30 (number of seconds in 30 days); if the number
- sent by a client is larger than that, the server will consider it to
- be real Unix time value rather than an offset from current time.
-
- We never use the duration value (< 30 days), we always use a
- timestamp, this makes it easier to integrate with other time
- constraints.
-
- When a session is created it's start time is recorded in the
- session data as the session_start_timestamp value.
-
- There are two ways the expiration timestamp can be computed:
-
- from_start
- A session has a fixed duration beginning with the start of
- the session. The session expires when the duration
- interval has elapsed relative to the start of the session.
- inactivity_timeout
- A session times out after a period of inactivity. The
- expiration time is advanced by the value of the duration
- interval everytime the session is updated.
-
- After the expiration is computed it may be capped at a maximum
- value due to other constraints (e.g. authentication credential
- expiration). If the optional max_age parameter is specified
- then expiration is constrained to be not greater than the
- max_age.
-
- The final computed expiration is then written into the
- session_data as the session_expiration_timestamp value. The
- session_expiration_timestamp is always passed to memcached
- when the session data is written back to the memcache. This is
- because otherwise the memcache expiration will default to zero
- if it's not specified which implies no expiration. Thus a
- failure to specify an exiration time when writing an item to
- memcached will cause a previously set expiration time for the
- item to be discarded and the item will no longer expire.
-
-
- :parameters:
- session_data
- Session data dict, must contain session_id key.
- duration
- Number of seconds cache entry should live. This is a
- duration value, not a timestamp. Zero implies no
- expiration.
- max_age
- Unix time value when cache entry must expire by.
-
- :returns:
- expiration timestamp, zero implies no expiration
- '''
-
- if duration == 0 and max_age is None:
- # No expiration
- expiration = 0
- session_data['session_expiration_timestamp'] = expiration
- return expiration
-
- if duration_type == 'inactivity_timeout':
- now = time.time()
- session_data['session_access_timestamp'] = now
- expiration = now + duration
- elif duration_type == 'from_start':
- session_start_timestamp = session_data['session_start_timestamp']
- expiration = session_start_timestamp + duration
- else:
- # Don't throw an exception, it's critical the session be
- # given some expiration, instead log the error and execute
- # a default action of expiring the session 5 minutes after
- # it was initiated (similar to from_start but with
- # hardcoded duration)
- default = 60*5
- self.warning('unknown session duration_type (%s), defaulting to %s seconds from session start',
- duration_type, default)
- session_start_timestamp = session_data['session_start_timestamp']
- expiration = session_start_timestamp + default
-
- # Cap the expiration if max_age is specified
- if max_age is not None:
- expiration = min(expiration, max_age)
-
- session_data['session_expiration_timestamp'] = expiration
-
- self.debug('set_session_expiration_time: duration_type=%s duration=%s max_age=%s expiration=%s (%s)',
- duration_type, duration, max_age, expiration, fmt_time(expiration))
-
- return expiration
-
- def delete_session_data(self, session_id):
- '''
- Given a session id removed the session data bound to the id from the memcache.
-
- :parameters:
- session_id
- The ID of the session which should be removed from the cache.
- :returns:
- None
- '''
- session_key = self.session_key(session_id)
-
- self.debug('delete session data from memcache, session_id=%s', session_id)
- self.mc.delete(session_key)
+ krb5_unparse_ccache
+)
+from ipaplatform.paths import paths
-#-------------------------------------------------------------------------------
-krbccache_dir =paths.IPA_MEMCACHED_DIR
+krbccache_dir = paths.IPA_HTTPD_DIR
krbccache_prefix = 'krbcc_'
-def _get_krbccache_pathname():
- return os.path.join(krbccache_dir, '%s%s' % (krbccache_prefix, os.getpid()))
def get_ipa_ccache_name(scheme='FILE'):
if scheme == 'FILE':
@@ -1225,62 +40,11 @@ def get_ipa_ccache_name(scheme='FILE'):
return ccache_name
-def load_ccache_data(ccache_name):
- scheme, name = krb5_parse_ccache(ccache_name)
- if scheme == 'FILE':
- root_logger.debug('reading ccache data from file "%s"', name)
- with io.open(name, "rb") as src:
- ccache_data = src.read()
- return ccache_data
- else:
- raise ValueError('ccache scheme "%s" unsupported (%s)', scheme, ccache_name)
-
-def bind_ipa_ccache(ccache_data, scheme='FILE'):
- if scheme == 'FILE':
- name = _get_krbccache_pathname()
- root_logger.debug('storing ccache data into file "%s"', name)
- with io.open(name, 'wb') as dst:
- dst.write(ccache_data)
- else:
- raise ValueError('ccache scheme "%s" unsupported', scheme)
-
- ccache_name = krb5_unparse_ccache(scheme, name)
- os.environ['KRB5CCNAME'] = ccache_name
- return ccache_name
-
-def release_ipa_ccache(ccache_name):
- '''
- Stop using the current request's ccache.
- * Remove KRB5CCNAME from the enviroment
- * Remove the ccache file from the file system
-
- Note, we do not demand any of these elements exist, but if they
- do we'll remove them.
- '''
-
- if 'KRB5CCNAME' in os.environ:
- if ccache_name != os.environ['KRB5CCNAME']:
- root_logger.error('release_ipa_ccache: ccache_name (%s) != KRB5CCNAME environment variable (%s)',
- ccache_name, os.environ['KRB5CCNAME'])
- del os.environ['KRB5CCNAME']
- else:
- root_logger.debug('release_ipa_ccache: KRB5CCNAME environment variable not set')
-
- scheme, name = krb5_parse_ccache(ccache_name)
- if scheme == 'FILE':
- if os.path.exists(name):
- try:
- os.unlink(name)
- except Exception as e:
- root_logger.error('unable to delete session ccache file "%s", %s', name, e)
- else:
- raise ValueError('ccache scheme "%s" unsupported (%s)', scheme, ccache_name)
-
-_session_mgr = None
-
-
-def get_session_mgr():
- global _session_mgr
- if _session_mgr is None:
- _session_mgr = MemcacheSessionManager()
- return _session_mgr
+def logout(ccache_name=None):
+ if ccache_name is None:
+ ccache_name = getattr(context, 'ccache_name', None)
+ if ccache_name is not None:
+ scheme, name = krb5_parse_ccache(ccache_name)
+ if scheme == 'FILE':
+ os.unlink(name)
+ setattr(context, 'logout_cookie', '')
diff --git a/ipaserver/setup.py b/ipaserver/setup.py
index 1f1b424d2..95ba5ebfc 100755
--- a/ipaserver/setup.py
+++ b/ipaserver/setup.py
@@ -58,7 +58,6 @@ if __name__ == '__main__':
"netaddr",
"pyasn1",
"pyldap",
- "python-memcached",
"python-nss",
"six",
# not available on PyPI
diff --git a/pylint_plugins.py b/pylint_plugins.py
index fc2ce9bb3..45adf7132 100644
--- a/pylint_plugins.py
+++ b/pylint_plugins.py
@@ -209,9 +209,6 @@ ipa_class_members = {
'ipaserver.rpcserver.KerberosSession': [
fake_api,
] + LOGGING_ATTRS,
- 'ipaserver.session.AuthManager': LOGGING_ATTRS,
- 'ipaserver.session.SessionAuthManager': LOGGING_ATTRS,
- 'ipaserver.session.SessionManager': LOGGING_ATTRS,
'ipatests.test_integration.base.IntegrationTest': [
'domain',
{'master': [