mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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:
@@ -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,-)
|
||||
|
||||
@@ -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' >$@
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
SOCKET_PATH=@localstatedir@/run/ipa_memcached/ipa_memcached
|
||||
USER=apache
|
||||
MAXCONN=1024
|
||||
CACHESIZE=64
|
||||
OPTIONS=
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = \
|
||||
|
||||
0
install/share/gssapi.login
Normal file
0
install/share/gssapi.login
Normal file
1
install/share/memcache-remove.uldif
Normal file
1
install/share/memcache-remove.uldif
Normal file
@@ -0,0 +1 @@
|
||||
deleteentry: cn=MEMCACHE,cn=$FQDN,cn=masters,cn=ipa,cn=etc,$SUFFIX
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)))
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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")
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
1262
ipaserver/session.py
1262
ipaserver/session.py
File diff suppressed because it is too large
Load Diff
@@ -58,7 +58,6 @@ if __name__ == '__main__':
|
||||
"netaddr",
|
||||
"pyasn1",
|
||||
"pyldap",
|
||||
"python-memcached",
|
||||
"python-nss",
|
||||
"six",
|
||||
# not available on PyPI
|
||||
|
||||
@@ -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': [
|
||||
|
||||
Reference in New Issue
Block a user