mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Add constructors to ldap client
Add LDAPClient.from_realm(), LDAPClient.from_hostname_secure(), and LDAPClient.from_hostname_plain() constructors. The simple_bind() method now also refuses to transmit a password over a plain, unencrypted line. LDAPClient.from_hostname_secure() uses start_tls and FreeIPA's CA cert by default. The constructor also automatically disables start_tls for ldaps and ldapi connections. Signed-off-by: Christian Heimes <cheimes@redhat.com> Reviewed-By: Rob Crittenden <rcritten@redhat.com>
This commit is contained in:
parent
d5d9233b7c
commit
5be9341fba
@ -1218,7 +1218,6 @@ def re_initialize(realm, thishost, fromhost, dirman_passwd, nolookup=False):
|
|||||||
# we did not replicate memberOf, do so now.
|
# we did not replicate memberOf, do so now.
|
||||||
if not agreement.single_value.get('nsDS5ReplicatedAttributeListTotal'):
|
if not agreement.single_value.get('nsDS5ReplicatedAttributeListTotal'):
|
||||||
ds = dsinstance.DsInstance(realm_name=realm)
|
ds = dsinstance.DsInstance(realm_name=realm)
|
||||||
ds.ldapi = os.getegid() == 0
|
|
||||||
ds.init_memberof()
|
ds.init_memberof()
|
||||||
|
|
||||||
def force_sync(realm, thishost, fromhost, dirman_passwd, nolookup=False):
|
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)
|
repl.force_sync(repl.conn, fromhost)
|
||||||
else:
|
else:
|
||||||
ds = dsinstance.DsInstance(realm_name=realm)
|
ds = dsinstance.DsInstance(realm_name=realm)
|
||||||
ds.ldapi = os.getegid() == 0
|
|
||||||
ds.replica_manage_time_skew(prevent=False)
|
ds.replica_manage_time_skew(prevent=False)
|
||||||
repl = replication.ReplicationManager(realm, fromhost, dirman_passwd)
|
repl = replication.ReplicationManager(realm, fromhost, dirman_passwd)
|
||||||
repl.force_sync(repl.conn, thishost)
|
repl.force_sync(repl.conn, thishost)
|
||||||
|
@ -29,7 +29,6 @@ from copy import deepcopy
|
|||||||
import contextlib
|
import contextlib
|
||||||
import os
|
import os
|
||||||
import pwd
|
import pwd
|
||||||
from urllib.parse import urlparse
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from cryptography import x509 as crypto_x509
|
from cryptography import x509 as crypto_x509
|
||||||
@ -778,15 +777,9 @@ class LDAPClient:
|
|||||||
syntax.
|
syntax.
|
||||||
"""
|
"""
|
||||||
if ldap_uri is not None:
|
if ldap_uri is not None:
|
||||||
|
# special case for ldap2 server plugin
|
||||||
self.ldap_uri = ldap_uri
|
self.ldap_uri = ldap_uri
|
||||||
self.host = 'localhost'
|
assert self.protocol in {'ldaps', 'ldapi', 'ldap'}
|
||||||
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
|
|
||||||
|
|
||||||
self._start_tls = start_tls
|
self._start_tls = start_tls
|
||||||
self._force_schema_updates = force_schema_updates
|
self._force_schema_updates = force_schema_updates
|
||||||
self._no_schema = no_schema
|
self._no_schema = no_schema
|
||||||
@ -797,7 +790,50 @@ class LDAPClient:
|
|||||||
self._has_schema = False
|
self._has_schema = False
|
||||||
self._schema = None
|
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):
|
def __str__(self):
|
||||||
return self.ldap_uri
|
return self.ldap_uri
|
||||||
@ -813,6 +849,13 @@ class LDAPClient:
|
|||||||
def conn(self):
|
def conn(self):
|
||||||
return self._conn
|
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):
|
def _get_schema(self):
|
||||||
if self._no_schema:
|
if self._no_schema:
|
||||||
return None
|
return None
|
||||||
@ -1156,7 +1199,8 @@ class LDAPClient:
|
|||||||
if not self._sasl_nocanon:
|
if not self._sasl_nocanon:
|
||||||
conn.set_option(ldap.OPT_X_SASL_NOCANON, ldap.OPT_OFF)
|
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()
|
conn.start_tls_s()
|
||||||
|
|
||||||
return conn
|
return conn
|
||||||
@ -1166,6 +1210,9 @@ class LDAPClient:
|
|||||||
"""
|
"""
|
||||||
Perform simple bind operation.
|
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():
|
with self.error_handler():
|
||||||
self._flush_schema()
|
self._flush_schema()
|
||||||
assert isinstance(bind_dn, DN)
|
assert isinstance(bind_dn, DN)
|
||||||
@ -1190,7 +1237,7 @@ class LDAPClient:
|
|||||||
Perform SASL bind operation using the SASL GSSAPI mechanism.
|
Perform SASL bind operation using the SASL GSSAPI mechanism.
|
||||||
"""
|
"""
|
||||||
with self.error_handler():
|
with self.error_handler():
|
||||||
if self._protocol == 'ldapi':
|
if self.protocol == 'ldapi':
|
||||||
auth_tokens = SASL_GSS_SPNEGO
|
auth_tokens = SASL_GSS_SPNEGO
|
||||||
else:
|
else:
|
||||||
auth_tokens = SASL_GSSAPI
|
auth_tokens = SASL_GSSAPI
|
||||||
|
@ -400,7 +400,7 @@ class Backup(admintool.AdminTool):
|
|||||||
self._conn.external_bind()
|
self._conn.external_bind()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Unable to bind to LDAP server %s: %s",
|
logger.error("Unable to bind to LDAP server %s: %s",
|
||||||
self._conn.host, e)
|
self._conn.ldap_uri, e)
|
||||||
|
|
||||||
return self._conn
|
return self._conn
|
||||||
|
|
||||||
@ -594,7 +594,7 @@ class Backup(admintool.AdminTool):
|
|||||||
"Unable to obtain list of master services, continuing anyway")
|
"Unable to obtain list of master services, continuing anyway")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Failed to read services from '%s': %s",
|
logger.error("Failed to read services from '%s': %s",
|
||||||
conn.host, e)
|
conn.ldap_uri, e)
|
||||||
else:
|
else:
|
||||||
services_cns = [s.single_value['cn'] for s in services]
|
services_cns = [s.single_value['cn'] for s in services]
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ def generate_aci_lines(api):
|
|||||||
"""Yields ACI lines as they appear in ACI.txt, with trailing newline"""
|
"""Yields ACI lines as they appear in ACI.txt, with trailing newline"""
|
||||||
update_plugin = api.Updater['update_managed_permissions']
|
update_plugin = api.Updater['update_managed_permissions']
|
||||||
perm_plugin = api.Object['permission']
|
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():
|
for name, template, obj in update_plugin.get_templates():
|
||||||
dn = perm_plugin.get_dn(name)
|
dn = perm_plugin.get_dn(name)
|
||||||
entry = fake_ldap.make_entry(dn)
|
entry = fake_ldap.make_entry(dn)
|
||||||
|
Loading…
Reference in New Issue
Block a user