Use new LDAPClient constructors

Replace get_ldap_uri() + LDAPClient() with new LDAPClient constructors
like LDAPClient.from_realm().

Some places now use LDAPI with external bind instead of LDAP with simple
bind. Although the FQDN *should* resolve to 127.0.0.1 / [::1], there is
no hard guarantee. The draft
https://tools.ietf.org/html/draft-west-let-localhost-be-localhost-04#section-5.1
specifies that applications must verify that the resulting IP is a
loopback API. LDAPI is always local and a bit more efficient, too.

The simple_bind() method also prevents the caller from sending a
password over an insecure line.

Signed-off-by: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
This commit is contained in:
Christian Heimes 2018-11-30 10:28:32 +01:00 committed by Rob Crittenden
parent 5be9341fba
commit a3934a211d
12 changed files with 55 additions and 85 deletions

View File

@ -54,8 +54,11 @@ def bind(ldap_uri, base_dn, username, password):
logger.error('migration unable to get base dn') logger.error('migration unable to get base dn')
raise IOError(errno.EIO, 'Cannot get Base DN') raise IOError(errno.EIO, 'Cannot get Base DN')
bind_dn = DN(('uid', username), ('cn', 'users'), ('cn', 'accounts'), base_dn) bind_dn = DN(('uid', username), ('cn', 'users'), ('cn', 'accounts'), base_dn)
# ldap_uri should be ldapi:// in all common cases. Enforce start_tls just
# in case it's a plain LDAP connection.
start_tls = ldap_uri.startswith('ldap://')
try: try:
conn = ipaldap.LDAPClient(ldap_uri) conn = ipaldap.LDAPClient(ldap_uri, start_tls=start_tls)
conn.simple_bind(bind_dn, password) conn.simple_bind(bind_dn, password)
except (errors.ACIError, errors.DatabaseError, errors.NotFound) as e: except (errors.ACIError, errors.DatabaseError, errors.NotFound) as e:
logger.error( logger.error(

View File

@ -1637,8 +1637,7 @@ def cert_summary(msg, certs, indent=' '):
def get_certs_from_ldap(server, base_dn, realm, ca_enabled): def get_certs_from_ldap(server, base_dn, realm, ca_enabled):
ldap_uri = ipaldap.get_ldap_uri(server) conn = ipaldap.LDAPClient.from_hostname_plain(server)
conn = ipaldap.LDAPClient(ldap_uri)
try: try:
conn.gssapi_bind() conn.gssapi_bind()
certs = certstore.get_ca_certs(conn, base_dn, realm, ca_enabled) certs = certstore.get_ca_certs(conn, base_dn, realm, ca_enabled)

View File

@ -70,8 +70,7 @@ def run_with_args(api):
""" """
server = urlsplit(api.env.jsonrpc_uri).hostname server = urlsplit(api.env.jsonrpc_uri).hostname
ldap_uri = ipaldap.get_ldap_uri(server) ldap = ipaldap.LDAPClient.from_hostname_secure(server)
ldap = ipaldap.LDAPClient(ldap_uri)
tmpdir = tempfile.mkdtemp(prefix="tmp-") tmpdir = tempfile.mkdtemp(prefix="tmp-")
ccache_name = os.path.join(tmpdir, 'ccache') ccache_name = os.path.join(tmpdir, 'ccache')

View File

@ -724,9 +724,10 @@ class DomainValidator:
entries = None entries = None
try: try:
ldap_uri = ipaldap.get_ldap_uri(host) # AD does not support SASL + TLS at the same time
conn = ipaldap.LDAPClient( # https://msdn.microsoft.com/en-us/library/cc223500.aspx
ldap_uri, conn = ipaldap.LDAPClient.from_hostname_plain(
host,
no_schema=True, no_schema=True,
decode_attrs=False decode_attrs=False
) )

View File

@ -386,8 +386,7 @@ class DogtagInstance(service.Service):
conn = None conn = None
try: try:
ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=self.realm) conn = ipaldap.LDAPClient.from_realm(self.realm)
conn = ipaldap.LDAPClient(ldap_uri)
conn.external_bind() conn.external_bind()
entry_attrs = conn.get_entry(self.admin_dn, ['usercertificate']) entry_attrs = conn.get_entry(self.admin_dn, ['usercertificate'])
@ -465,8 +464,9 @@ class DogtagInstance(service.Service):
wait_groups.append(group_dn) wait_groups.append(group_dn)
# Now wait until the other server gets replicated this data # Now wait until the other server gets replicated this data
ldap_uri = ipaldap.get_ldap_uri(self.master_host) master_conn = ipaldap.LDAPClient.from_hostname_secure(
master_conn = ipaldap.LDAPClient(ldap_uri, start_tls=True) self.master_host
)
logger.debug( logger.debug(
"Waiting for %s to appear on %s", self.admin_dn, master_conn "Waiting for %s to appear on %s", self.admin_dn, master_conn
) )

View File

@ -162,18 +162,17 @@ def is_ds_running(server_id=''):
def get_domain_level(api=api): def get_domain_level(api=api):
ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=api.env.realm)
conn = ipaldap.LDAPClient(ldap_uri)
conn.external_bind()
dn = DN(('cn', 'Domain Level'), dn = DN(('cn', 'Domain Level'),
('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn)
try: with ipaldap.LDAPClient.from_realm(api.env.realm) as conn:
entry = conn.get_entry(dn, ['ipaDomainLevel']) conn.external_bind()
except errors.NotFound: try:
return constants.DOMAIN_LEVEL_0 entry = conn.get_entry(dn, ['ipaDomainLevel'])
return int(entry.single_value['ipaDomainLevel']) except errors.NotFound:
return constants.DOMAIN_LEVEL_0
else:
return int(entry.single_value['ipaDomainLevel'])
def get_all_external_schema_files(root): def get_all_external_schema_files(root):
@ -392,8 +391,7 @@ class DsInstance(service.Service):
def _get_replication_manager(self): def _get_replication_manager(self):
# Always connect to self over ldapi # Always connect to self over ldapi
ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=self.realm) conn = ipaldap.LDAPClient.from_realm(self.realm)
conn = ipaldap.LDAPClient(ldap_uri)
conn.external_bind() conn.external_bind()
repl = replication.ReplicationManager( repl = replication.ReplicationManager(
self.realm, self.fqdn, self.dm_password, conn=conn self.realm, self.fqdn, self.dm_password, conn=conn
@ -680,7 +678,6 @@ class DsInstance(service.Service):
self._ldap_mod("memberof-conf.ldif") self._ldap_mod("memberof-conf.ldif")
def init_memberof(self): def init_memberof(self):
if not self.run_init_memberof: if not self.run_init_memberof:
return return
@ -689,15 +686,9 @@ class DsInstance(service.Service):
dn = DN(('cn', 'IPA install %s' % self.sub_dict["TIME"]), ('cn', 'memberof task'), dn = DN(('cn', 'IPA install %s' % self.sub_dict["TIME"]), ('cn', 'memberof task'),
('cn', 'tasks'), ('cn', 'config')) ('cn', 'tasks'), ('cn', 'config'))
logger.debug("Waiting for memberof task to complete.") logger.debug("Waiting for memberof task to complete.")
ldap_uri = ipaldap.get_ldap_uri(self.fqdn) with ipaldap.LDAPClient.from_realm(self.realm) as conn:
conn = ipaldap.LDAPClient(ldap_uri) conn.external_bind()
if self.dm_password: replication.wait_for_task(conn, dn)
conn.simple_bind(bind_dn=ipaldap.DIRMAN_DN,
bind_password=self.dm_password)
else:
conn.gssapi_bind()
replication.wait_for_task(conn, dn)
conn.unbind()
def apply_updates(self): def apply_updates(self):
schema_files = get_all_external_schema_files(paths.EXTERNAL_SCHEMA_DIR) schema_files = get_all_external_schema_files(paths.EXTERNAL_SCHEMA_DIR)
@ -861,10 +852,9 @@ class DsInstance(service.Service):
self.cacert_name = dsdb.cacert_name self.cacert_name = dsdb.cacert_name
ldap_uri = ipaldap.get_ldap_uri(self.fqdn) # use LDAPI?
conn = ipaldap.LDAPClient(ldap_uri) conn = ipaldap.LDAPClient.from_realm(self.realm)
conn.simple_bind(bind_dn=ipaldap.DIRMAN_DN, conn.external_bind()
bind_password=self.dm_password)
encrypt_entry = conn.make_entry( encrypt_entry = conn.make_entry(
DN(('cn', 'encryption'), ('cn', 'config')), DN(('cn', 'encryption'), ('cn', 'config')),
@ -917,10 +907,8 @@ class DsInstance(service.Service):
subject_base=self.subject_base) subject_base=self.subject_base)
trust_flags = dict(reversed(dsdb.list_certs())) trust_flags = dict(reversed(dsdb.list_certs()))
ldap_uri = ipaldap.get_ldap_uri(self.fqdn) conn = ipaldap.LDAPClient.from_realm(self.realm)
conn = ipaldap.LDAPClient(ldap_uri) conn.external_bind()
conn.simple_bind(bind_dn=ipaldap.DIRMAN_DN,
bind_password=self.dm_password)
nicknames = dsdb.find_root_cert(self.cacert_name)[:-1] nicknames = dsdb.find_root_cert(self.cacert_name)[:-1]
for nickname in nicknames: for nickname in nicknames:
@ -951,14 +939,9 @@ class DsInstance(service.Service):
dsdb = certs.CertDB(self.realm, nssdir=dirname, dsdb = certs.CertDB(self.realm, nssdir=dirname,
subject_base=self.subject_base) subject_base=self.subject_base)
ldap_uri = ipaldap.get_ldap_uri(self.fqdn) with ipaldap.LDAPClient.from_realm(self.realm) as conn:
conn = ipaldap.LDAPClient(ldap_uri) conn.external_bind()
conn.simple_bind(bind_dn=ipaldap.DIRMAN_DN, self.export_ca_certs_nssdb(dsdb, self.ca_is_configured, conn)
bind_password=self.dm_password)
self.export_ca_certs_nssdb(dsdb, self.ca_is_configured, conn)
conn.unbind()
def __add_default_layout(self): def __add_default_layout(self):
self._ldap_mod("bootstrap-template.ldif", self.sub_dict) self._ldap_mod("bootstrap-template.ldif", self.sub_dict)

View File

@ -393,8 +393,7 @@ class Backup(admintool.AdminTool):
if self._conn is not None: if self._conn is not None:
return self._conn return self._conn
ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=api.env.realm) self._conn = ipaldap.LDAPClient.from_realm(api.env.realm)
self._conn = ipaldap.LDAPClient(ldap_uri)
try: try:
self._conn.external_bind() self._conn.external_bind()

View File

@ -475,8 +475,7 @@ class Restore(admintool.AdminTool):
if self._conn is not None: if self._conn is not None:
return self._conn return self._conn
ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=api.env.realm) self._conn = ipaldap.LDAPClient.from_realm(api.env.realm)
self._conn = ipaldap.LDAPClient(ldap_uri)
try: try:
self._conn.external_bind() self._conn.external_bind()

View File

@ -54,8 +54,12 @@ UPDATE_SEARCH_TIME_LIMIT = 30 # seconds
def connect(ldapi=False, realm=None, fqdn=None, dm_password=None): def connect(ldapi=False, realm=None, fqdn=None, dm_password=None):
"""Create a connection for updates""" """Create a connection for updates"""
ldap_uri = ipaldap.get_ldap_uri(fqdn, ldapi=ldapi, realm=realm) if ldapi:
conn = ipaldap.LDAPClient(ldap_uri, decode_attrs=False) conn = ipaldap.LDAPClient.from_realm(realm, decode_attrs=False)
else:
conn = ipaldap.LDAPClient.from_hostname_secure(
fqdn, decode_attrs=False
)
try: try:
if dm_password: if dm_password:
conn.simple_bind(bind_dn=ipaldap.DIRMAN_DN, conn.simple_bind(bind_dn=ipaldap.DIRMAN_DN,

View File

@ -137,8 +137,7 @@ def enable_replication_version_checking(realm, dirman_passwd):
enabled then enable it and restart 389-ds. If it is enabled enabled then enable it and restart 389-ds. If it is enabled
the do nothing. the do nothing.
""" """
ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=realm) conn = ipaldap.LDAPClient.from_realm(realm)
conn = ipaldap.LDAPClient(ldap_uri)
if dirman_passwd: if dirman_passwd:
conn.simple_bind(bind_dn=ipaldap.DIRMAN_DN, conn.simple_bind(bind_dn=ipaldap.DIRMAN_DN,
bind_password=dirman_passwd) bind_password=dirman_passwd)
@ -619,8 +618,9 @@ class ReplicationManager:
""" """
self._finalize_replica_settings(self.conn) self._finalize_replica_settings(self.conn)
ldap_uri = ipaldap.get_ldap_uri(r_hostname) r_conn = ipaldap.LDAPClient.from_hostname_secure(
r_conn = ipaldap.LDAPClient(ldap_uri, cacert=cacert) r_hostname, cacert=cacert
)
if r_bindpw: if r_bindpw:
r_conn.simple_bind(r_binddn, r_bindpw) r_conn.simple_bind(r_binddn, r_bindpw)
else: else:
@ -1148,12 +1148,7 @@ class ReplicationManager:
local_port = r_port local_port = r_port
# note - there appears to be a bug in python-ldap - it does not # note - there appears to be a bug in python-ldap - it does not
# allow connections using two different CA certs # allow connections using two different CA certs
ldap_uri = ipaldap.get_ldap_uri(r_hostname, r_port, r_conn = ipaldap.LDAPClient.from_hostname_secure(r_hostname)
cacert=paths.IPA_CA_CRT,
protocol='ldap')
r_conn = ipaldap.LDAPClient(ldap_uri,
cacert=paths.IPA_CA_CRT,
start_tls=True)
if r_bindpw: if r_bindpw:
r_conn.simple_bind(r_binddn, r_bindpw) r_conn.simple_bind(r_binddn, r_bindpw)
@ -1259,9 +1254,7 @@ class ReplicationManager:
raise RuntimeError("Failed to start replication") raise RuntimeError("Failed to start replication")
def convert_to_gssapi_replication(self, r_hostname, r_binddn, r_bindpw): def convert_to_gssapi_replication(self, r_hostname, r_binddn, r_bindpw):
ldap_uri = ipaldap.get_ldap_uri(r_hostname, PORT, r_conn = ipaldap.LDAPClient.from_hostname_secure(r_hostname)
cacert=paths.IPA_CA_CRT)
r_conn = ipaldap.LDAPClient(ldap_uri, cacert=paths.IPA_CA_CRT)
if r_bindpw: if r_bindpw:
r_conn.simple_bind(r_binddn, r_bindpw) r_conn.simple_bind(r_binddn, r_bindpw)
else: else:
@ -1289,11 +1282,7 @@ class ReplicationManager:
Only usable to connect 2 existing replicas (needs existing kerberos Only usable to connect 2 existing replicas (needs existing kerberos
principals) principals)
""" """
# note - there appears to be a bug in python-ldap - it does not r_conn = ipaldap.LDAPClient.from_hostname_secure(r_hostname)
# allow connections using two different CA certs
ldap_uri = ipaldap.get_ldap_uri(r_hostname, PORT,
cacert=paths.IPA_CA_CRT)
r_conn = ipaldap.LDAPClient(ldap_uri, cacert=paths.IPA_CA_CRT)
if r_bindpw: if r_bindpw:
r_conn.simple_bind(r_binddn, r_bindpw) r_conn.simple_bind(r_binddn, r_bindpw)
else: else:
@ -1789,10 +1778,8 @@ class ReplicationManager:
def setup_promote_replication(self, r_hostname, r_binddn=None, def setup_promote_replication(self, r_hostname, r_binddn=None,
r_bindpw=None, cacert=paths.IPA_CA_CRT): r_bindpw=None, cacert=paths.IPA_CA_CRT):
# note - there appears to be a bug in python-ldap - it does not r_conn = ipaldap.LDAPClient.from_hostname_secure(
# allow connections using two different CA certs r_hostname, cacert=cacert)
ldap_uri = ipaldap.get_ldap_uri(r_hostname)
r_conn = ipaldap.LDAPClient(ldap_uri, cacert=cacert)
if r_bindpw: if r_bindpw:
r_conn.simple_bind(r_binddn, r_bindpw) r_conn.simple_bind(r_binddn, r_bindpw)
else: else:
@ -1931,8 +1918,7 @@ class CAReplicationManager(ReplicationManager):
def __init__(self, realm, hostname): def __init__(self, realm, hostname):
# Always connect to self over ldapi # Always connect to self over ldapi
ldap_uri = ipaldap.get_ldap_uri(protocol='ldapi', realm=realm) conn = ipaldap.LDAPClient.from_realm(realm)
conn = ipaldap.LDAPClient(ldap_uri)
conn.external_bind() conn.external_bind()
super(CAReplicationManager, self).__init__( super(CAReplicationManager, self).__init__(
realm, hostname, None, port=DEFAULT_PORT, conn=conn) realm, hostname, None, port=DEFAULT_PORT, conn=conn)
@ -1944,8 +1930,7 @@ class CAReplicationManager(ReplicationManager):
Assumes a promote replica with working GSSAPI for replication Assumes a promote replica with working GSSAPI for replication
and unified DS instance. and unified DS instance.
""" """
ldap_uri = ipaldap.get_ldap_uri(r_hostname) r_conn = ipaldap.LDAPClient.from_hostname_secure(r_hostname)
r_conn = ipaldap.LDAPClient(ldap_uri)
r_conn.gssapi_bind() r_conn.gssapi_bind()
# Setup the first half # Setup the first half

View File

@ -45,8 +45,7 @@ class Host(pytest_multihost.host.Host):
"""Return an LDAPClient authenticated to this host as directory manager """Return an LDAPClient authenticated to this host as directory manager
""" """
self.log.info('Connecting to LDAP at %s', self.external_hostname) self.log.info('Connecting to LDAP at %s', self.external_hostname)
ldap_uri = ipaldap.get_ldap_uri(self.external_hostname) ldap = ipaldap.LDAPClient.from_hostname_secure(self.external_hostname)
ldap = ipaldap.LDAPClient(ldap_uri)
binddn = self.config.dirman_dn binddn = self.config.dirman_dn
self.log.info('LDAP bind as %s' % binddn) self.log.info('LDAP bind as %s' % binddn)
ldap.simple_bind(binddn, self.config.dirman_password) ldap.simple_bind(binddn, self.config.dirman_password)

View File

@ -65,8 +65,7 @@ class test_update(unittest.TestCase):
else: else:
raise unittest.SkipTest("No directory manager password") raise unittest.SkipTest("No directory manager password")
self.updater = LDAPUpdate(dm_password=self.dm_password, sub_dict={}) self.updater = LDAPUpdate(dm_password=self.dm_password, sub_dict={})
ldap_uri = ipaldap.get_ldap_uri(fqdn) self.ld = ipaldap.LDAPClient.from_hostname_secure(fqdn)
self.ld = ipaldap.LDAPClient(ldap_uri)
self.ld.simple_bind(bind_dn=ipaldap.DIRMAN_DN, self.ld.simple_bind(bind_dn=ipaldap.DIRMAN_DN,
bind_password=self.dm_password) bind_password=self.dm_password)
self.testdir = os.path.abspath(os.path.dirname(__file__)) self.testdir = os.path.abspath(os.path.dirname(__file__))