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