Skip to content

Commit

Permalink
Unify and simplify LDAP service discovery
Browse files Browse the repository at this point in the history
Move LDAP service discovery and service definitions from
ipaserver.install to ipaserver. Simplify and unify different
implementations in favor of a single implementation.

Signed-off-by: Christian Heimes <cheimes@redhat.com>
  • Loading branch information
tiran committed Nov 13, 2018
1 parent 14ad844 commit a5152ba
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 161 deletions.
9 changes: 6 additions & 3 deletions install/tools/ipa-ca-install.in
Expand Up @@ -35,6 +35,7 @@ from ipaserver.install.installutils import check_creds, ReplicaConfig
from ipaserver.install import dsinstance, ca
from ipaserver.install import cainstance, service
from ipaserver.install import custodiainstance
from ipaserver.masters import find_providing_server
from ipapython import version
from ipalib import api
from ipalib.constants import DOMAIN_LEVEL_1
Expand Down Expand Up @@ -183,8 +184,9 @@ def install_replica(safe_options, options):
config.subject_base = attrs.get('ipacertificatesubjectbase')[0]

if config.ca_host_name is None:
config.ca_host_name = \
service.find_providing_server('CA', api.Backend.ldap2, api.env.ca_host)
config.ca_host_name = find_providing_server(
'CA', api.Backend.ldap2, [api.env.ca_host]
)

options.realm_name = config.realm_name
options.domain_name = config.domain_name
Expand Down Expand Up @@ -258,7 +260,8 @@ def install(safe_options, options):
paths.KRB5_KEYTAB,
ccache)

ca_host = service.find_providing_server('CA', api.Backend.ldap2)
ca_host = find_providing_server('CA', api.Backend.ldap2)

if ca_host is None:
install_master(safe_options, options)
else:
Expand Down
4 changes: 2 additions & 2 deletions install/tools/ipactl.in
Expand Up @@ -224,9 +224,9 @@ def get_config(dirsrv):
svc_list.append([order, name])

ordered_list = []
for (order, svc) in sorted(svc_list):
for order, svc in sorted(svc_list):
if svc in service.SERVICE_LIST:
ordered_list.append(service.SERVICE_LIST[svc][0])
ordered_list.append(service.SERVICE_LIST[svc].systemd_name)
return ordered_list

def get_config_from_file():
Expand Down
3 changes: 2 additions & 1 deletion ipaserver/install/cainstance.py
Expand Up @@ -68,6 +68,7 @@
from ipaserver.install import sysupgrade
from ipaserver.install.dogtaginstance import DogtagInstance
from ipaserver.plugins import ldap2
from ipaserver.masters import ENABLED_SERVICE

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -1300,7 +1301,7 @@ def __expose_ca_in_ldap(self):
config = ['caRenewalMaster']
else:
config = []
self._ldap_enable(u'enabledService', "CA", self.fqdn, basedn, config)
self._ldap_enable(ENABLED_SERVICE, "CA", self.fqdn, basedn, config)

def setup_lightweight_ca_key_retrieval(self):
# Important: there is a typo in the below string, which is known
Expand Down
11 changes: 9 additions & 2 deletions ipaserver/install/ipa_kra_install.py
Expand Up @@ -38,6 +38,7 @@
from ipaserver.install import dogtaginstance
from ipaserver.install import kra
from ipaserver.install.installutils import ReplicaConfig
from ipaserver.masters import find_providing_server

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -187,8 +188,14 @@ def run(self):
config.subject_base = attrs.get('ipacertificatesubjectbase')[0]

if config.kra_host_name is None:
config.kra_host_name = service.find_providing_server(
'KRA', api.Backend.ldap2, api.env.ca_host)
config.kra_host_name = find_providing_server(
'KRA', api.Backend.ldap2, [api.env.ca_host]
)
if config.kra_host_name is None:
# all CA/KRA servers are down or unreachable.
raise admintool.ScriptError(
"Failed to find an active KRA server!"
)
custodia = custodiainstance.get_custodia_instance(
config, custodiainstance.CustodiaModes.KRA_PEER)
else:
Expand Down
3 changes: 2 additions & 1 deletion ipaserver/install/opendnssecinstance.py
Expand Up @@ -14,6 +14,7 @@

from ipalib.install import sysrestore
from ipaserver.install import service
from ipaserver.masters import ENABLED_SERVICE
from ipapython.dn import DN
from ipapython import directivesetter
from ipapython import ipautil
Expand Down Expand Up @@ -45,7 +46,7 @@ def get_dnssec_key_masters(conn):
filter_attrs = {
u'cn': u'DNSSEC',
u'objectclass': u'ipaConfigObject',
u'ipaConfigString': [KEYMASTER, u'enabledService'],
u'ipaConfigString': [KEYMASTER, ENABLED_SERVICE],
}
only_masters_f = conn.make_filter(filter_attrs, rules=conn.MATCH_ALL)

Expand Down
17 changes: 10 additions & 7 deletions ipaserver/install/server/replicainstall.py
Expand Up @@ -44,6 +44,7 @@
ReplicaConfig, load_pkcs12, is_ipa_configured)
from ipaserver.install.replication import (
ReplicationManager, replica_conn_check)
from ipaserver.masters import find_providing_servers, find_providing_server
import SSSDConfig
from subprocess import CalledProcessError

Expand Down Expand Up @@ -1021,9 +1022,10 @@ def promote_check(installer):
if subject_base is not None:
config.subject_base = DN(subject_base)

# Find if any server has a CA
ca_host = service.find_providing_server(
'CA', conn, config.ca_host_name)
# Find if any running server with a CA
ca_host = find_providing_server(
'CA', conn, [config.ca_host_name]
)
if ca_host is not None:
config.ca_host_name = ca_host
ca_enabled = True
Expand All @@ -1044,14 +1046,15 @@ def promote_check(installer):
"custom certificates.")
raise ScriptError(rval=3)

kra_host = service.find_providing_server(
'KRA', conn, config.kra_host_name)
kra_host = find_providing_server(
'KRA', conn, [config.kra_host_name]
)
if kra_host is not None:
config.kra_host_name = kra_host
kra_enabled = True
else:
if options.setup_kra:
logger.error("There is no KRA server in the domain, "
logger.error("There is no active KRA server in the domain, "
"can't setup a KRA clone")
raise ScriptError(rval=3)
kra_enabled = False
Expand Down Expand Up @@ -1285,7 +1288,7 @@ def install(installer):
# Enable configured services and update DNS SRV records
service.enable_services(config.host_name)
api.Command.dns_update_system_records()
ca_servers = service.find_providing_servers('CA', api.Backend.ldap2, api)
ca_servers = find_providing_servers('CA', api.Backend.ldap2, api)
api.Backend.ldap2.disconnect()

# Everything installed properly, activate ipa service.
Expand Down
64 changes: 4 additions & 60 deletions ipaserver/install/service.py
Expand Up @@ -38,33 +38,15 @@
from ipalib import api, errors, x509
from ipaplatform import services
from ipaplatform.paths import paths
from ipaserver.masters import (
CONFIGURED_SERVICE, ENABLED_SERVICE, SERVICE_LIST
)

logger = logging.getLogger(__name__)

if six.PY3:
unicode = str

# The service name as stored in cn=masters,cn=ipa,cn=etc. In the tuple
# the first value is the *nix service name, the second the start order.
SERVICE_LIST = {
'KDC': ('krb5kdc', 10),
'KPASSWD': ('kadmin', 20),
'DNS': ('named', 30),
'HTTP': ('httpd', 40),
'KEYS': ('ipa-custodia', 41),
'CA': ('pki-tomcatd', 50),
'KRA': ('pki-tomcatd', 51),
'ADTRUST': ('smb', 60),
'EXTID': ('winbind', 70),
'OTPD': ('ipa-otpd', 80),
'DNSKeyExporter': ('ipa-ods-exporter', 90),
'DNSSEC': ('ods-enforcerd', 100),
'DNSKeySync': ('ipa-dnskeysyncd', 110),
}

CONFIGURED_SERVICE = u'configuredService'
ENABLED_SERVICE = u'enabledService'


def print_msg(message, output_fd=sys.stdout):
logger.debug("%s", message)
Expand Down Expand Up @@ -116,44 +98,6 @@ def add_principals_to_group(admin_conn, group, member_attr, principals):
pass


def find_providing_servers(svcname, conn, api):
"""
Find servers that provide the given service.
:param svcname: The service to find
:param conn: a connection to the LDAP server
:return: list of host names (possibly empty)
"""
dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn)
query_filter = conn.make_filter({'objectClass': 'ipaConfigObject',
'ipaConfigString': ENABLED_SERVICE,
'cn': svcname}, rules='&')
try:
entries, _trunc = conn.find_entries(filter=query_filter, base_dn=dn)
except errors.NotFound:
return []
else:
return [entry.dn[1].value for entry in entries]


def find_providing_server(svcname, conn, host_name=None, 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
:return: the selected host name
"""
servers = find_providing_servers(svcname, conn, api)
if len(servers) == 0:
return None
if host_name in servers:
return host_name
return servers[0]


def case_insensitive_attr_has_value(attr, value):
"""
Expand Down Expand Up @@ -658,7 +602,7 @@ def ldap_configure(self, name, fqdn, dm_password=None, ldap_suffix='',

def _ldap_enable(self, value, name, fqdn, ldap_suffix, config):
extra_config_opts = [
' '.join([u'startOrder', unicode(SERVICE_LIST[name][1])])
u'startOrder {}'.format(SERVICE_LIST[name].startorder),
]
extra_config_opts.extend(config)

Expand Down
119 changes: 119 additions & 0 deletions ipaserver/masters.py
@@ -0,0 +1,119 @@
#
# 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'

# 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.make_filter(
{
'objectClass': 'ipaConfigObject',
'ipaConfigString': ENABLED_SERVICE,
'cn': svcname
},
rules='&'
)
try:
entries, _trunc = conn.find_entries(
filter=query_filter,
attrs_list=[],
base_dn=dn
)
except errors.NotFound:
return []

# unique list of host names, DNS is case insensitive
servers = list(set(entry.dn[1].value.lower() for entry in entries))
# shuffle the list like DNS SRV would randomize it
random.shuffle(servers)
# Move preferred hosts to front
for host_name in reversed(preferred_hosts):
host_name = host_name.lower()
try:
servers.remove(host_name)
except ValueError:
# preferred server not found, ignore
pass
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]
7 changes: 3 additions & 4 deletions ipaserver/plugins/cert.py
Expand Up @@ -51,6 +51,7 @@
from ipapython import kerberos
from ipapython.dn import DN
from ipaserver.plugins.service import normalize_principal, validate_realm
from ipaserver.masters import ENABLED_SERVICE, CONFIGURED_SERVICE

try:
import pyhbac
Expand Down Expand Up @@ -298,10 +299,8 @@ def ca_kdc_check(api_instance, hostname):
kdc_entry = api_instance.Backend.ldap2.get_entry(
kdc_dn, ['ipaConfigString'])

ipaconfigstring = {val.lower() for val in kdc_entry['ipaConfigString']}

if 'enabledservice' not in ipaconfigstring \
and 'configuredservice' not in ipaconfigstring:
wanted = {ENABLED_SERVICE, CONFIGURED_SERVICE}
if wanted.intersection(kdc_entry['ipaConfigString']):
raise errors.NotFound(
reason=_("enabledService/configuredService not in "
"ipaConfigString kdc entry"))
Expand Down

0 comments on commit a5152ba

Please sign in to comment.