Provide Kerberos over HTTP (MS-KKDCP)

Add integration of python-kdcproxy into FreeIPA to support the MS
Kerberos KDC proxy protocol (MS-KKDCP), to allow KDC and KPASSWD
client requests over HTTP and HTTPS.

- freeipa-server now depends on python-kdcproxy >= 0.3. All kdcproxy
  dependencies are already satisfied.
- The service's state is configured in cn=KDC,cn=$FQDN,cn=masters,cn=ipa,
  cn=etc,$SUFFIX. It's enabled, when ipaConfigString=kdcProxyEnabled is
  present.
- The installers and update create a new Apache config file
  /etc/ipa/kdcproxy/ipa-kdc-proxy.conf that mounts a WSGI app on
  /KdcProxy. The app is run inside its own WSGI daemon group with
  a different uid and gid than the webui.
- A ExecStartPre script in httpd.service symlinks the config file to
  /etc/httpd/conf.d/ iff ipaConfigString=kdcProxyEnabled is present.
- The httpd.service also sets KDCPROXY_CONFIG=/etc/ipa/kdcproxy.conf,
  so that an existing config is not used. SetEnv from Apache config does
  not work here, because it doesn't set an OS env var.
- python-kdcproxy is configured to *not* use DNS SRV lookups. The
  location of KDC and KPASSWD servers are read from /etc/krb5.conf.
- The state of the service can be modified with two ldif files for
  ipa-ldap-updater. No CLI script is offered yet.

https://www.freeipa.org/page/V4/KDC_Proxy

https://fedorahosted.org/freeipa/ticket/4801

Reviewed-By: Nathaniel McCallum <npmccallum@redhat.com>
Reviewed-By: Simo Sorce <ssorce@redhat.com>
This commit is contained in:
Christian Heimes
2015-06-23 17:01:00 +02:00
committed by Petr Vobornik
parent 49d708f00f
commit 495da412f1
15 changed files with 335 additions and 5 deletions

View File

@@ -22,6 +22,10 @@
%define _hardened_build 1
%define kdcproxy_user kdcproxy
%define kdcproxy_group kdcproxy
%define kdcproxy_home %{_sharedstatedir}/kdcproxy
Name: freeipa
Version: __VERSION__
Release: __RELEASE__%{?dist}
@@ -95,6 +99,7 @@ BuildRequires: p11-kit-devel
BuildRequires: pki-base >= 10.2.4-1
BuildRequires: python-pytest-multihost >= 0.5
BuildRequires: python-pytest-sourceorder
BuildRequires: python-kdcproxy >= 0.3
%description
IPA is an integrated solution to provide centrally managed Identity (machine,
@@ -130,6 +135,7 @@ Requires: memcached
Requires: python-memcached
Requires: dbus-python
Requires: systemd-units >= 38
Requires(pre): shadow-utils
Requires(pre): systemd-units
Requires(post): systemd-units
Requires: selinux-policy >= %{selinux_policy_version}
@@ -140,6 +146,7 @@ Requires: pki-kra >= 10.2.4-1
Requires(preun): python systemd-units
Requires(postun): python systemd-units
Requires: python-dns >= 1.11.1
Requires: python-kdcproxy >= 0.3
Requires: zip
Requires: policycoreutils >= 2.1.12-5
Requires: tar
@@ -429,6 +436,7 @@ ln -s ../../../..%{_sysconfdir}/ipa/html/browserconfig.html \
# So we can own our Apache configuration
mkdir -p %{buildroot}%{_sysconfdir}/httpd/conf.d/
/bin/touch %{buildroot}%{_sysconfdir}/httpd/conf.d/ipa.conf
/bin/touch %{buildroot}%{_sysconfdir}/httpd/conf.d/ipa-kdc-proxy.conf
/bin/touch %{buildroot}%{_sysconfdir}/httpd/conf.d/ipa-pki-proxy.conf
/bin/touch %{buildroot}%{_sysconfdir}/httpd/conf.d/ipa-rewrite.conf
mkdir -p %{buildroot}%{_usr}/share/ipa/html/
@@ -458,6 +466,10 @@ install daemons/dnssec/ipa-ods-exporter %{buildroot}%{_libexecdir}/ipa/ipa-ods-e
# Web UI plugin dir
mkdir -p %{buildroot}%{_usr}/share/ipa/ui/js/plugins
# KDC proxy config (Apache config sets KDCPROXY_CONFIG to load this file)
mkdir -p %{buildroot}%{_sysconfdir}/ipa/kdcproxy/
install -m 644 install/share/kdcproxy.conf %{buildroot}%{_sysconfdir}/ipa/kdcproxy/kdcproxy.conf
# NOTE: systemd specific section
mkdir -p %{buildroot}%{_tmpfilesdir}
install -m 0644 init/systemd/ipa.conf.tmpfiles %{buildroot}%{_tmpfilesdir}/%{name}.conf
@@ -551,6 +563,13 @@ if [ -e /usr/sbin/ipa_kpasswd ]; then
# END
fi
# create kdcproxy user
getent group %{kdcproxy_group} >/dev/null || groupadd -r %{kdcproxy_group}
getent passwd %{kdcproxy_user} >/dev/null || \
/usr/sbin/useradd -r -m -c "IPA KDC Proxy User" -s /sbin/nologin \
-g %{kdcproxy_group} -d %{kdcproxy_home} %{kdcproxy_user}
exit 0
%postun server-trust-ad
if [ "$1" -ge "1" ]; then
if [ "`readlink %{_sysconfdir}/alternatives/winbind_krb5_locator.so`" == "/dev/null" ]; then
@@ -683,9 +702,11 @@ fi
%{_libexecdir}/ipa/ipa-dnskeysyncd
%{_libexecdir}/ipa/ipa-dnskeysync-replica
%{_libexecdir}/ipa/ipa-ods-exporter
%{_libexecdir}/ipa/ipa-httpd-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/
@@ -777,10 +798,13 @@ fi
%config(noreplace) %{_sysconfdir}/ipa/html/browserconfig.html
%ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa-rewrite.conf
%ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa.conf
%ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa-kdc-proxy.conf
%ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/httpd/conf.d/ipa-pki-proxy.conf
%ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/kdcproxy/ipa-kdc-proxy.conf
%{_usr}/share/ipa/ipa.conf
%{_usr}/share/ipa/ipa-rewrite.conf
%{_usr}/share/ipa/ipa-pki-proxy.conf
%{_usr}/share/ipa/kdcproxy.conf
%ghost %attr(0644,root,apache) %config(noreplace) %{_usr}/share/ipa/html/ca.crt
%ghost %attr(0644,root,apache) %{_usr}/share/ipa/html/configure.jar
%ghost %attr(0644,root,apache) %{_usr}/share/ipa/html/kerberosauth.xpi
@@ -903,6 +927,7 @@ fi
%ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/ca.crt
%dir %attr(0755,root,root) %{_sysconfdir}/ipa/nssdb
%dir %attr(0755,root,root) %{_sysconfdir}/ipa/dnssec
%dir %attr(0755,root,root) %{_sysconfdir}/ipa/kdcproxy
%ghost %config(noreplace) %{_sysconfdir}/ipa/nssdb/cert8.db
%ghost %config(noreplace) %{_sysconfdir}/ipa/nssdb/key3.db
%ghost %config(noreplace) %{_sysconfdir}/ipa/nssdb/secmod.db

View File

@@ -2,3 +2,5 @@
[Service]
Environment=KRB5CCNAME=/var/run/httpd/ipa/krbcache/krb5ccache
Environment=KDCPROXY_CONFIG=/etc/ipa/kdcproxy/kdcproxy.conf
ExecStartPre=/usr/libexec/ipa/ipa-httpd-kdcproxy

View File

@@ -3,6 +3,7 @@ NULL =
appdir = $(IPA_DATA_DIR)
app_DATA = \
ipa.conf \
ipa-kdc-proxy.conf.template \
ipa-pki-proxy.conf \
ipa-rewrite.conf \
$(NULL)

View File

@@ -0,0 +1,30 @@
# Kerberos over HTTP / MS-KKDCP support (Kerberos KDC Proxy)
#
# The symlink from /etc/ipa/kdcproxy/ to /etc/httpd/conf.d/ is maintained
# by the ExecStartPre script /usr/libexec/ipa/ipa-httpd-kdcproxy in
# httpd.service. The service also sets the environment variable
# KDCPROXY_CONFIG to $KDCPROXY_CONFIG.
#
# Disable KDC Proxy on the current host:
# # ipa-ldap-updater /usr/share/ipa/kdcproxy-disable.uldif
# # systemctl restart httpd.service
#
# Enable KDC Proxy on the current host:
# # ipa-ldap-updater /usr/share/ipa/kdcproxy-enable.uldif
# # systemctl restart httpd.service
#
WSGIDaemonProcess kdcproxy processes=2 threads=15 maximum-requests=5000 \
user=kdcproxy group=kdcproxy display-name=%{GROUP}
WSGIImportScript /usr/lib/python2.7/site-packages/kdcproxy/__init__.py \
process-group=kdcproxy application-group=kdcproxy
WSGIScriptAlias /KdcProxy /usr/lib/python2.7/site-packages/kdcproxy/__init__.py
WSGIScriptReloading Off
<Location "/KdcProxy">
Satisfy Any
Order Deny,Allow
Allow from all
WSGIProcessGroup kdcproxy
WSGIApplicationGroup kdcproxy
</Location>

View File

@@ -41,9 +41,7 @@ WSGISocketPrefix /run/httpd/wsgi
# Configure mod_wsgi handler for /ipa
WSGIDaemonProcess ipa processes=2 threads=1 maximum-requests=500
WSGIProcessGroup ipa
WSGIApplicationGroup ipa
WSGIDaemonProcess ipa processes=2 threads=1 maximum-requests=500 display-name=%{GROUP}
WSGIImportScript /usr/share/ipa/wsgi.py process-group=ipa application-group=ipa
WSGIScriptAlias /ipa /usr/share/ipa/wsgi.py
WSGIScriptReloading Off
@@ -70,6 +68,8 @@ WSGIScriptReloading Off
GssapiUseS4U2Proxy on
Require valid-user
ErrorDocument 401 /ipa/errors/unauthorized.html
WSGIProcessGroup ipa
WSGIApplicationGroup ipa
</Location>
# Turn off Apache authentication for sessions

View File

@@ -84,6 +84,9 @@ app_DATA = \
sasl-mapping-fallback.ldif \
schema-update.ldif \
vault.update \
kdcproxy.conf \
kdcproxy-enable.uldif \
kdcproxy-disable.uldif \
$(NULL)
EXTRA_DIST = \

View File

@@ -0,0 +1,3 @@
# Disable MS-KKDCP protocol for the current host
dn: cn=KDC,cn=$FQDN,cn=masters,cn=ipa,cn=etc,$SUFFIX
remove:ipaConfigString:kdcProxyEnabled

View File

@@ -0,0 +1,6 @@
# Enable MS-KKDCP protocol for the current host
dn: cn=KDC,cn=$FQDN,cn=masters,cn=ipa,cn=etc,$SUFFIX
default:objectClass: nsContainer
default:objectClass: ipaConfigObject
default:cn: KDC
add: ipaConfigString: kdcProxyEnabled

View File

@@ -0,0 +1,4 @@
[global]
configs = mit
use_dns = false

View File

@@ -35,6 +35,11 @@ EXTRA_DIST = \
$(sbin_SCRIPTS) \
$(NULL)
appdir = $(libexecdir)/ipa/
app_SCRIPTS = \
ipa-httpd-kdcproxy \
$(NULL)
MAINTAINERCLEANFILES = \
*~ \
Makefile.in

180
install/tools/ipa-httpd-kdcproxy Executable file
View File

@@ -0,0 +1,180 @@
#!/usr/bin/python2
# Authors:
# Christian Heimes <cheimes@redhat.com>
#
# Copyright (C) 2015 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/>.
#
"""ipa-httpd-kdproxy
This script creates or removes the symlink from /etc/ipa/ipa-kdc-proxy.conf
to /etc/httpd/conf.d/. It's called from ExecStartPre hook in httpd.service.
"""
import os
import sys
from ipalib import api, errors
from ipapython.ipa_log_manager import standard_logging_setup
from ipapython.ipaldap import IPAdmin
from ipapython.dn import DN
from ipaplatform.paths import paths
DEBUG = False
TIME_LIMIT = 2
class CheckError(Exception):
"""An unrecoverable error has occured"""
class KDCProxyConfig(object):
ipaconfig_flag = 'ipaKDCProxyEnabled'
def __init__(self, time_limit=TIME_LIMIT):
self.time_limit = time_limit
self.con = None
self.log = api.log
self.ldap_uri = api.env.ldap_uri
self.kdc_dn = DN(('cn', 'KDC'), ('cn', api.env.host),
('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
api.env.basedn)
self.conf = paths.HTTPD_IPA_KDCPROXY_CONF
self.conflink = paths.HTTPD_IPA_KDCPROXY_CONF_SYMLINK
def _ldap_con(self):
"""Establish LDAP connection"""
self.log.debug('ldap_uri: %s', self.ldap_uri)
try:
self.con = IPAdmin(ldap_uri=self.ldap_uri)
# EXTERNAL bind as root user
self.con.ldapi = True
self.con.do_bind(timeout=self.time_limit)
except errors.NetworkError as e:
msg = 'Failed to get setting from dirsrv: %s' % e
self.log.exception(msg)
raise CheckError(msg)
except Exception as e:
msg = ('Unknown error while retrieving setting from %s: %s' %
(self.ldap_uri, e))
self.log.exception(msg)
raise CheckError(msg)
def _find_entry(self, dn, attrs, filter, scope=IPAdmin.SCOPE_BASE):
"""Find an LDAP entry, handles NotFound and Limit"""
try:
entries, truncated = self.con.find_entries(
filter, attrs, dn, scope, time_limit=self.time_limit)
if truncated:
raise errors.LimitsExceeded()
except errors.NotFound:
self.log.debug('Entry not found: %s', dn)
return None
except Exception as e:
msg = ('Unknown error while retrieving setting from %s: %s' %
(self.ldap_uri, e))
self.log.exception(msg)
raise CheckError(msg)
return entries[0]
def is_host_enabled(self):
"""Check replica specific flag"""
self.log.debug('Read settings from dn: %s', self.kdc_dn)
srcfilter = self.con.make_filter(
{'ipaConfigString': u'kdcProxyEnabled'}
)
entry = self._find_entry(self.kdc_dn, ['cn'], srcfilter)
self.log.debug('%s ipaConfigString: %s', self.kdc_dn, entry)
return entry is not None
def validate_symlink(self):
"""Validate symlink in Apache conf.d"""
if not os.path.exists(self.conflink):
return False
if not os.path.islink(self.conflink):
raise CheckError("'%s' already exists, but it is not a symlink" %
self.conflink)
dest = os.readlink(self.conflink)
if dest != self.conf:
raise CheckError("'%s' points to '%s', expected '%s'"
% (self.conflink, dest, self.conf))
return True
def create_symlink(self):
"""Create symlink to enable KDC proxy support"""
try:
valid = self.validate_symlink()
except CheckError as e:
self.log.warn("Cannot enable KDC proxy: %s " % e)
return False
if valid:
self.log.debug("Symlink exists and is valid")
return True
if not os.path.isfile(self.conf):
self.log.warn("'%s' does not exist", self.conf)
return False
# create the symbolic link
self.log.debug("Creating symlink from '%s' to '%s'",
self.conf, self.conflink)
os.symlink(self.conf, self.conflink)
return True
def remove_symlink(self):
"""Delete symlink to disable KDC proxy support"""
try:
valid = self.validate_symlink()
except CheckError as e:
self.log.warn("Cannot disable KDC proxy: %s " % e)
return False
if valid:
self.log.debug("Removing symlink '%s'", self.conflink)
os.unlink(self.conflink)
else:
self.log.debug("Symlink '%s' has already been removed.",
self.conflink)
return True
def __enter__(self):
self._ldap_con()
return self
def __exit__(self, exc_type, exc_value, traceback):
if self.con is not None:
self.con.unbind()
self.con = None
def main(debug=DEBUG, time_limit=TIME_LIMIT):
# initialize API without file logging
if not api.isdone('bootstrap'):
api.bootstrap(context='kdcproxyshim', log=None, debug=debug)
standard_logging_setup(verbose=True, debug=debug)
with KDCProxyConfig(time_limit) as cfg:
if cfg.is_host_enabled():
if cfg.create_symlink():
api.log.info('KDC proxy enabled')
else:
if cfg.remove_symlink():
api.log.info('KDC proxy disabled')
if __name__ == '__main__':
main()

View File

@@ -49,6 +49,8 @@ class BasePathNamespace(object):
ALIAS_CACERT_ASC = "/etc/httpd/alias/cacert.asc"
ALIAS_PWDFILE_TXT = "/etc/httpd/alias/pwdfile.txt"
HTTPD_CONF_D_DIR = "/etc/httpd/conf.d/"
HTTPD_IPA_KDCPROXY_CONF = "/etc/ipa/kdcproxy/ipa-kdc-proxy.conf"
HTTPD_IPA_KDCPROXY_CONF_SYMLINK = "/etc/httpd/conf.d/ipa-kdc-proxy.conf"
HTTPD_IPA_PKI_PROXY_CONF = "/etc/httpd/conf.d/ipa-pki-proxy.conf"
HTTPD_IPA_REWRITE_CONF = "/etc/httpd/conf.d/ipa-rewrite.conf"
HTTPD_IPA_CONF = "/etc/httpd/conf.d/ipa.conf"
@@ -342,7 +344,7 @@ class BasePathNamespace(object):
DB2LDIF = '/usr/sbin/db2ldif'
BAK2DB = '/usr/sbin/bak2db'
DB2BAK = '/usr/sbin/db2bak'
KDCPROXY_CONFIG = '/etc/ipa/kdcproxy/kdcproxy.conf'
path_namespace = BasePathNamespace

View File

@@ -33,15 +33,16 @@ import installutils
from ipapython import sysrestore
from ipapython import ipautil
from ipapython import dogtag
from ipapython.dn import DN
from ipapython.ipa_log_manager import root_logger
import ipapython.errors
from ipaserver.install import sysupgrade
from ipalib import api
from ipalib import errors
from ipaplatform.tasks import tasks
from ipaplatform.paths import paths
from ipaplatform import services
SELINUX_BOOLEAN_SETTINGS = dict(
httpd_can_network_connect='on',
httpd_manage_ipa='on',
@@ -136,6 +137,9 @@ class HTTPInstance(service.Service):
self.step("creating a keytab for httpd", self.__create_http_keytab)
self.step("clean up any existing httpd ccache", self.remove_httpd_ccache)
self.step("configuring SELinux for httpd", self.configure_selinux_for_httpd)
if not self.is_kdcproxy_configured():
self.step("create KDC proxy config", self.create_kdcproxy_conf)
self.step("enable KDC proxy", self.enable_kdcproxy)
self.step("restarting httpd", self.__start)
self.step("configuring httpd to start on boot", self.__enable)
@@ -381,6 +385,63 @@ class HTTPInstance(service.Service):
ca_db = certs.CertDB(self.realm)
ca_db.publish_ca_cert(paths.CA_CRT)
def is_kdcproxy_configured(self):
"""Check if KDC proxy has already been configured in the past"""
return os.path.isfile(paths.HTTPD_IPA_KDCPROXY_CONF)
def enable_kdcproxy(self):
"""Add ipaConfigString=kdcProxyEnabled to cn=KDC"""
entry_name = DN(('cn', 'KDC'), ('cn', self.fqdn), ('cn', 'masters'),
('cn', 'ipa'), ('cn', 'etc'), self.suffix)
attr_name = 'kdcProxyEnabled'
try:
entry = self.admin_conn.get_entry(entry_name, ['ipaConfigString'])
except errors.NotFound:
pass
else:
if any(attr_name.lower() == val.lower()
for val in entry.get('ipaConfigString', [])):
root_logger.debug("service KDCPROXY already enabled")
return
entry.setdefault('ipaConfigString', []).append(attr_name)
try:
self.admin_conn.update_entry(entry)
except errors.EmptyModlist:
root_logger.debug("service KDCPROXY already enabled")
return
except:
root_logger.debug("failed to enable service KDCPROXY")
raise
root_logger.debug("service KDCPROXY enabled")
return
entry = self.admin_conn.make_entry(
entry_name,
objectclass=["nsContainer", "ipaConfigObject"],
cn=['KDC'],
ipaconfigstring=[attr_name]
)
try:
self.admin_conn.add_entry(entry)
except errors.DuplicateEntry:
root_logger.debug("failed to add service KDCPROXY entry")
raise
def create_kdcproxy_conf(self):
"""Create ipa-kdc-proxy.conf in /etc/ipa/kdcproxy"""
target_fname = paths.HTTPD_IPA_KDCPROXY_CONF
sub_dict = dict(KDCPROXY_CONFIG=paths.KDCPROXY_CONFIG)
http_txt = ipautil.template_file(
ipautil.SHARE_DIR + "ipa-kdc-proxy.conf.template", sub_dict)
self.fstore.backup_file(target_fname)
with open(target_fname, 'w') as f:
f.write(http_txt)
os.chmod(target_fname, 0644)
def uninstall(self):
if self.is_configured():
self.print_msg("Unconfiguring web server")
@@ -420,6 +481,8 @@ class HTTPInstance(service.Service):
installutils.remove_file(paths.HTTPD_IPA_REWRITE_CONF)
installutils.remove_file(paths.HTTPD_IPA_CONF)
installutils.remove_file(paths.HTTPD_IPA_PKI_PROXY_CONF)
installutils.remove_file(paths.HTTPD_IPA_KDCPROXY_CONF)
installutils.remove_file(paths.HTTPD_IPA_KDCPROXY_CONF_SYMLINK)
# Restore SELinux boolean states
boolean_states = {name: self.restore_state(name)

View File

@@ -146,6 +146,7 @@ class Backup(admintool.AdminTool):
paths.LIMITS_CONF,
paths.HTTPD_PASSWORD_CONF,
paths.IPA_KEYTAB,
paths.HTTPD_IPA_KDCPROXY_CONF,
paths.HTTPD_IPA_PKI_PROXY_CONF,
paths.HTTPD_IPA_REWRITE_CONF,
paths.HTTPD_NSS_CONF,

View File

@@ -1361,6 +1361,11 @@ def upgrade_configuration():
http.change_mod_nss_port_from_http()
http.configure_certmonger_renewal_guard()
if not http.is_kdcproxy_configured():
root_logger.info('[Enabling KDC Proxy]')
http.create_kdcproxy_conf()
http.enable_kdcproxy()
http.stop()
update_mod_nss_protocol(http)
fix_trust_flags()