mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-27 09:21:59 -06:00
025facb85c
A hidden replica is a replica that does not advertise its services via DNS SRV records, ipa-ca DNS entry, or LDAP. Clients do not auto-select a hidden replica, but are still free to explicitly connect to it. Fixes: https://pagure.io/freeipa/issue/7892 Co-authored-by: Francois Cami <fcami@redhat.com>: Signed-off-by: Christian Heimes <cheimes@redhat.com> Reviewed-By: Francois Cami <fcami@redhat.com> Reviewed-By: Thomas Woerner <twoerner@redhat.com>
198 lines
5.9 KiB
Python
198 lines
5.9 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'
|
|
|
|
# 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)
|
|
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:
|
|
return None
|
|
else:
|
|
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
|