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.
|
||||
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)
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user