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>
This commit is contained in:
Simo Sorce
2016-08-19 09:23:55 -04:00
committed by Jan Cholasta
parent 11ef2cacbf
commit c894ebefc5
25 changed files with 172 additions and 1572 deletions

View File

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

View File

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

View File

@@ -1,5 +0,0 @@
SOCKET_PATH=@localstatedir@/run/ipa_memcached/ipa_memcached
USER=apache
MAXCONN=1024
CACHESIZE=64
OPTIONS=

View File

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

View File

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

View File

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

View File

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

View File

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

View File

View File

@@ -0,0 +1 @@
deleteentry: cn=MEMCACHE,cn=$FQDN,cn=masters,cn=ipa,cn=etc,$SUFFIX

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 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.
'''
def __call__(self, request):
self.initial_step(request)
request.register_hook('response', self.handle_response)
return request
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)
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)
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']
# 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')
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)
# 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

File diff suppressed because it is too large Load Diff

View File

@@ -58,7 +58,6 @@ if __name__ == '__main__':
"netaddr",
"pyasn1",
"pyldap",
"python-memcached",
"python-nss",
"six",
# not available on PyPI

View File

@@ -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': [