diff --git a/install/tools/ipa-replica-manage.in b/install/tools/ipa-replica-manage.in index 35d412c2e..67f678d44 100644 --- a/install/tools/ipa-replica-manage.in +++ b/install/tools/ipa-replica-manage.in @@ -1218,7 +1218,6 @@ def re_initialize(realm, thishost, fromhost, dirman_passwd, nolookup=False): # we did not replicate memberOf, do so now. if not agreement.single_value.get('nsDS5ReplicatedAttributeListTotal'): ds = dsinstance.DsInstance(realm_name=realm) - ds.ldapi = os.getegid() == 0 ds.init_memberof() def force_sync(realm, thishost, fromhost, dirman_passwd, nolookup=False): @@ -1238,7 +1237,6 @@ def force_sync(realm, thishost, fromhost, dirman_passwd, nolookup=False): repl.force_sync(repl.conn, fromhost) else: ds = dsinstance.DsInstance(realm_name=realm) - ds.ldapi = os.getegid() == 0 ds.replica_manage_time_skew(prevent=False) repl = replication.ReplicationManager(realm, fromhost, dirman_passwd) repl.force_sync(repl.conn, thishost) diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py index 78330a4d1..9ff443fe4 100644 --- a/ipapython/ipaldap.py +++ b/ipapython/ipaldap.py @@ -29,7 +29,6 @@ from copy import deepcopy import contextlib import os import pwd -from urllib.parse import urlparse import warnings from cryptography import x509 as crypto_x509 @@ -778,15 +777,9 @@ class LDAPClient: syntax. """ if ldap_uri is not None: + # special case for ldap2 server plugin self.ldap_uri = ldap_uri - self.host = 'localhost' - self.port = None - url_data = urlparse(ldap_uri) - self._protocol = url_data.scheme - if self._protocol in ('ldap', 'ldaps'): - self.host = url_data.hostname - self.port = url_data.port - + assert self.protocol in {'ldaps', 'ldapi', 'ldap'} self._start_tls = start_tls self._force_schema_updates = force_schema_updates self._no_schema = no_schema @@ -797,7 +790,50 @@ class LDAPClient: self._has_schema = False self._schema = None - self._conn = self._connect() + if ldap_uri is not None: + self._conn = self._connect() + + @classmethod + def from_realm(cls, realm_name, **kwargs): + """Create a LDAPI connection to local 389-DS instance + """ + uri = realm_to_ldapi_uri(realm_name) + return cls(uri, start_tls=False, cacert=None, **kwargs) + + @classmethod + def from_hostname_secure(cls, hostname, cacert=paths.IPA_CA_CRT, + start_tls=True, **kwargs): + """Create LDAP or LDAPS connection to a remote 389-DS instance + + This constructor is opinionated and doesn't let you shoot yourself in + the foot. It always creates a secure connection. By default it + returns a LDAP connection to port 389 and performs STARTTLS using the + default CA cert. With start_tls=False, it creates a LDAPS connection + to port 636 instead. + + Note: Microsoft AD does not support SASL encryption and integrity + verification with a TLS connection. For AD, use a plain connection + with GSSAPI and a MIN_SSF >= 56. SASL GSSAPI and SASL GSS SPNEGO + ensure data integrity and confidentiality with SSF > 1. Also see + https://msdn.microsoft.com/en-us/library/cc223500.aspx + """ + if start_tls: + uri = 'ldap://%s' % format_netloc(hostname, 389) + else: + uri = 'ldaps://%s' % format_netloc(hostname, 636) + return cls(uri, start_tls=start_tls, cacert=cacert, **kwargs) + + @classmethod + def from_hostname_plain(cls, hostname, **kwargs): + """Create a plain LDAP connection with TLS/SSL + + Note: A plain TLS connection should only be used in combination with + GSSAPI bind. + """ + assert 'start_tls' not in kwargs + assert 'cacert' not in kwargs + uri = 'ldap://%s' % format_netloc(hostname, 389) + return cls(uri, **kwargs) def __str__(self): return self.ldap_uri @@ -813,6 +849,13 @@ class LDAPClient: def conn(self): return self._conn + @property + def protocol(self): + if self.ldap_uri: + return self.ldap_uri.split('://', 1)[0] + else: + return None + def _get_schema(self): if self._no_schema: return None @@ -1156,7 +1199,8 @@ class LDAPClient: if not self._sasl_nocanon: conn.set_option(ldap.OPT_X_SASL_NOCANON, ldap.OPT_OFF) - if self._start_tls: + if self._start_tls and self.protocol == 'ldap': + # STARTTLS applies only to ldap:// connections conn.start_tls_s() return conn @@ -1166,6 +1210,9 @@ class LDAPClient: """ Perform simple bind operation. """ + if self.protocol == 'ldap' and not self._start_tls and bind_password: + # non-empty bind must use a secure connection + raise ValueError('simple_bind over insecure LDAP connection') with self.error_handler(): self._flush_schema() assert isinstance(bind_dn, DN) @@ -1190,7 +1237,7 @@ class LDAPClient: Perform SASL bind operation using the SASL GSSAPI mechanism. """ with self.error_handler(): - if self._protocol == 'ldapi': + if self.protocol == 'ldapi': auth_tokens = SASL_GSS_SPNEGO else: auth_tokens = SASL_GSSAPI diff --git a/ipaserver/install/ipa_backup.py b/ipaserver/install/ipa_backup.py index 90d16d68f..a6af840f9 100644 --- a/ipaserver/install/ipa_backup.py +++ b/ipaserver/install/ipa_backup.py @@ -400,7 +400,7 @@ class Backup(admintool.AdminTool): self._conn.external_bind() except Exception as e: logger.error("Unable to bind to LDAP server %s: %s", - self._conn.host, e) + self._conn.ldap_uri, e) return self._conn @@ -594,7 +594,7 @@ class Backup(admintool.AdminTool): "Unable to obtain list of master services, continuing anyway") except Exception as e: logger.error("Failed to read services from '%s': %s", - conn.host, e) + conn.ldap_uri, e) else: services_cns = [s.single_value['cn'] for s in services] diff --git a/makeaci.in b/makeaci.in index fe341f6f5..8840ccc53 100644 --- a/makeaci.in +++ b/makeaci.in @@ -50,7 +50,7 @@ def generate_aci_lines(api): """Yields ACI lines as they appear in ACI.txt, with trailing newline""" update_plugin = api.Updater['update_managed_permissions'] perm_plugin = api.Object['permission'] - fake_ldap = LDAPClient('', force_schema_updates=False, no_schema=True) + fake_ldap = LDAPClient(None, force_schema_updates=False, no_schema=True) for name, template, obj in update_plugin.get_templates(): dn = perm_plugin.get_dn(name) entry = fake_ldap.make_entry(dn)