mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-23 23:50:03 -06:00
45fa43540f
Write a new krb5.conf in case any values changed finding the right server to configure against (e.g. for CA, KRA) and ensure the API connection is to the remote server that will be installed against. When finding a CA or KRA during initial replica installation set the remote master as well. The order is: - existing server value in /etc/ipa/default.conf - the chosen CA host if the server doesn't provide one - the chosen KRA host if the server doesn't provide one This is more or less heirarchical. If a server is provided then that is considered first. If it provides all the optional services needed (CA and/or KRA) then it will be used. Otherwise it will fall back to a server that provides all the required services. In short, providing --server either at client install or with ipa-replica-install is no guarantee that it will define all topology. This may be unexpected behavior. For the case of adding a CA or KRA things are effectively unchanged. This type of install does not appear to be impacted by affinity issues. Fixes: https://pagure.io/freeipa/issue/9289 Signed-off-by: Rob Crittenden <rcritten@redhat.com> Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
205 lines
6.2 KiB
Python
205 lines
6.2 KiB
Python
#
|
|
# Copyright (C) 2018 FreeIPA Contributors see COPYING for license
|
|
#
|
|
"""Helpers services in for cn=masters,cn=ipa,cn=etc
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import collections
|
|
import logging
|
|
import random
|
|
|
|
from ipapython.dn import DN
|
|
from ipalib import api
|
|
from ipalib import errors
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# constants for ipaConfigString
|
|
CONFIGURED_SERVICE = u'configuredService'
|
|
ENABLED_SERVICE = u'enabledService'
|
|
HIDDEN_SERVICE = u'hiddenService'
|
|
PAC_TKT_SIGN_SUPPORTED = u'pacTktSignSupported'
|
|
PKINIT_ENABLED = u'pkinitEnabled'
|
|
|
|
# The service name as stored in cn=masters,cn=ipa,cn=etc. The values are:
|
|
# 0: systemd service name
|
|
# 1: start order for system service
|
|
# 2: LDAP server entry CN, also used as SERVICE_LIST key
|
|
service_definition = collections.namedtuple(
|
|
"service_definition",
|
|
"systemd_name startorder service_entry"
|
|
)
|
|
|
|
SERVICES = [
|
|
service_definition('krb5kdc', 10, 'KDC'),
|
|
service_definition('kadmin', 20, 'KPASSWD'),
|
|
service_definition('named', 30, 'DNS'),
|
|
service_definition('httpd', 40, 'HTTP'),
|
|
service_definition('ipa-custodia', 41, 'KEYS'),
|
|
service_definition('pki-tomcatd', 50, 'CA'),
|
|
service_definition('pki-tomcatd', 51, 'KRA'),
|
|
service_definition('smb', 60, 'ADTRUST'),
|
|
service_definition('winbind', 70, 'EXTID'),
|
|
service_definition('ipa-otpd', 80, 'OTPD'),
|
|
service_definition('ipa-ods-exporter', 90, 'DNSKeyExporter'),
|
|
service_definition('ods-enforcerd', 100, 'DNSSEC'),
|
|
service_definition('ipa-dnskeysyncd', 110, 'DNSKeySync'),
|
|
]
|
|
|
|
SERVICE_LIST = {s.service_entry: s for s in SERVICES}
|
|
|
|
|
|
def find_providing_servers(svcname, conn=None, preferred_hosts=(), api=api):
|
|
"""Find servers that provide the given service.
|
|
|
|
:param svcname: The service to find
|
|
:param preferred_hosts: preferred servers
|
|
:param conn: a connection to the LDAP server
|
|
:param api: ipalib.API instance
|
|
:return: list of host names in randomized order (possibly empty)
|
|
|
|
Preferred servers are moved to the front of the list if and only if they
|
|
are found as providing servers.
|
|
"""
|
|
assert isinstance(preferred_hosts, (tuple, list))
|
|
if svcname not in SERVICE_LIST:
|
|
raise ValueError("Unknown service '{}'.".format(svcname))
|
|
if conn is None:
|
|
conn = api.Backend.ldap2
|
|
|
|
dn = DN(api.env.container_masters, api.env.basedn)
|
|
|
|
query_filter = conn.combine_filters(
|
|
[
|
|
conn.make_filter(
|
|
{
|
|
'objectClass': 'ipaConfigObject',
|
|
'cn': svcname
|
|
},
|
|
rules=conn.MATCH_ALL,
|
|
),
|
|
conn.make_filter(
|
|
{
|
|
'ipaConfigString': [ENABLED_SERVICE, HIDDEN_SERVICE]
|
|
},
|
|
rules=conn.MATCH_ANY
|
|
),
|
|
],
|
|
rules=conn.MATCH_ALL
|
|
)
|
|
|
|
try:
|
|
entries, _trunc = conn.find_entries(
|
|
filter=query_filter,
|
|
attrs_list=['ipaConfigString'],
|
|
base_dn=dn
|
|
)
|
|
except errors.NotFound:
|
|
return []
|
|
|
|
# DNS is case insensitive
|
|
preferred_hosts = list(host_name.lower() for host_name in preferred_hosts)
|
|
servers = []
|
|
for entry in entries:
|
|
servername = entry.dn[1].value.lower()
|
|
cfgstrings = entry.get('ipaConfigString', [])
|
|
# always consider enabled services
|
|
if ENABLED_SERVICE in cfgstrings:
|
|
servers.append(servername)
|
|
# use hidden services on preferred hosts
|
|
elif HIDDEN_SERVICE in cfgstrings and servername in preferred_hosts:
|
|
servers.append(servername)
|
|
# unique list of host names
|
|
servers = list(set(servers))
|
|
# shuffle the list like DNS SRV would randomize it
|
|
random.shuffle(servers)
|
|
# Move preferred hosts to front
|
|
for host_name in reversed(preferred_hosts):
|
|
try:
|
|
servers.remove(host_name)
|
|
except ValueError:
|
|
# preferred server not found, log and ignore
|
|
logger.warning(
|
|
"Lookup failed: Preferred host %s does not provide %s.",
|
|
host_name, svcname
|
|
)
|
|
else:
|
|
servers.insert(0, host_name)
|
|
logger.debug("Discovery: available servers for service '%s' are %s",
|
|
svcname, ', '.join(servers))
|
|
return servers
|
|
|
|
|
|
def find_providing_server(svcname, conn=None, preferred_hosts=(), api=api):
|
|
"""Find a server that provides the given service.
|
|
|
|
:param svcname: The service to find
|
|
:param conn: a connection to the LDAP server
|
|
:param host_name: the preferred server
|
|
:param api: ipalib.API instance
|
|
:return: the selected host name or None
|
|
"""
|
|
servers = find_providing_servers(
|
|
svcname, conn=conn, preferred_hosts=preferred_hosts, api=api
|
|
)
|
|
if not servers:
|
|
logger.debug("Discovery: no '%s' service found.", svcname)
|
|
return None
|
|
else:
|
|
logger.debug("Discovery: using %s for '%s' service",
|
|
servers[0], svcname)
|
|
return servers[0]
|
|
|
|
|
|
def get_masters(conn=None, api=api):
|
|
"""Get all master hostnames
|
|
|
|
:param conn: a connection to the LDAP server
|
|
:param api: ipalib.API instance
|
|
:return: list of hostnames
|
|
"""
|
|
if conn is None:
|
|
conn = api.Backend.ldap2
|
|
|
|
dn = DN(api.env.container_masters, api.env.basedn)
|
|
entries = conn.get_entries(dn, conn.SCOPE_ONELEVEL, None, ['cn'])
|
|
return list(e['cn'][0] for e in entries)
|
|
|
|
|
|
def is_service_enabled(svcname, conn=None, api=api):
|
|
"""Check if service is enabled on any master
|
|
|
|
The check function only looks for presence of service entries. It
|
|
ignores enabled/hidden flags.
|
|
|
|
:param svcname: The service to find
|
|
:param conn: a connection to the LDAP server
|
|
:param api: ipalib.API instance
|
|
:return: True/False
|
|
"""
|
|
if svcname not in SERVICE_LIST:
|
|
raise ValueError("Unknown service '{}'.".format(svcname))
|
|
if conn is None:
|
|
conn = api.Backend.ldap2
|
|
|
|
dn = DN(api.env.container_masters, api.env.basedn)
|
|
query_filter = conn.make_filter(
|
|
{
|
|
'objectClass': 'ipaConfigObject',
|
|
'cn': svcname
|
|
},
|
|
rules='&'
|
|
)
|
|
try:
|
|
conn.find_entries(
|
|
filter=query_filter,
|
|
attrs_list=[],
|
|
base_dn=dn
|
|
)
|
|
except errors.NotFound:
|
|
return False
|
|
else:
|
|
return True
|