f65af0
From ecd1fa6b42c6ae76ce620c5ac949e64ba11d375a Mon Sep 17 00:00:00 2001
979ee0
From: Christian Heimes <cheimes@redhat.com>
979ee0
Date: Fri, 22 Jun 2018 10:04:38 +0200
979ee0
Subject: [PATCH] Fix replication races in Dogtag admin code
979ee0
979ee0
DogtagInstance.setup_admin and related methods have multiple LDAP
979ee0
replication race conditions. The bugs can cause parallel
979ee0
ipa-replica-install to fail.
979ee0
979ee0
The code from __add_admin_to_group() has been changed to use MOD_ADD
979ee0
ather than search + MOD_REPLACE. The MOD_REPLACE approach can lead to
979ee0
data loss, when more than one writer changes a group.
979ee0
979ee0
setup_admin() now waits until both admin user and group membership have
979ee0
been replicated to the master peer. The method also adds a new ACI to
979ee0
allow querying group member in the replication check.
979ee0
979ee0
Fixes: https://pagure.io/freeipa/issue/7593
979ee0
Signed-off-by: Christian Heimes <cheimes@redhat.com>
979ee0
Reviewed-By: Fraser Tweedale <ftweedal@redhat.com>
979ee0
---
f65af0
 ipaserver/install/cainstance.py     |  1 +
f65af0
 ipaserver/install/dogtaginstance.py | 96 +++++++++++++++++++++++++++++--------
f65af0
 ipaserver/install/krainstance.py    |  1 +
f65af0
 3 files changed, 77 insertions(+), 21 deletions(-)
979ee0
979ee0
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
f65af0
index 5bdac82a57c2228b1b196337a2ceadb062fdc13c..b58fbb4c881d247d6b5fb661f4085ec82c3cc811 100644
979ee0
--- a/ipaserver/install/cainstance.py
979ee0
+++ b/ipaserver/install/cainstance.py
f65af0
@@ -392,6 +392,7 @@ class CAInstance(DogtagInstance):
979ee0
                 # Setup Database
979ee0
                 self.step("creating certificate server db", self.__create_ds_db)
979ee0
                 self.step("setting up initial replication", self.__setup_replication)
979ee0
+                self.step("creating ACIs for admin", self.add_ipaca_aci)
979ee0
                 self.step("creating installation admin user", self.setup_admin)
979ee0
             self.step("configuring certificate server instance",
979ee0
                       self.__spawn_instance)
979ee0
diff --git a/ipaserver/install/dogtaginstance.py b/ipaserver/install/dogtaginstance.py
f65af0
index 07e63024c6e138269634f0206b14c6a1710df936..5b2c30f8a1b7e932ce1cca3ca38f5962a3d54266 100644
979ee0
--- a/ipaserver/install/dogtaginstance.py
979ee0
+++ b/ipaserver/install/dogtaginstance.py
f65af0
@@ -21,6 +21,7 @@ from __future__ import absolute_import
979ee0
 
979ee0
 import base64
f65af0
 import logging
979ee0
+import time
f65af0
 
979ee0
 import ldap
979ee0
 import os
f65af0
@@ -90,6 +91,16 @@ class DogtagInstance(service.Service):
979ee0
     tracking_reqs = None
979ee0
     server_cert_name = None
979ee0
 
979ee0
+    ipaca_groups = DN(('ou', 'groups'), ('o', 'ipaca'))
979ee0
+    ipaca_people = DN(('ou', 'people'), ('o', 'ipaca'))
979ee0
+    groups_aci = (
979ee0
+        b'(targetfilter="(objectClass=groupOfUniqueNames)")'
979ee0
+        b'(targetattr="cn || description || objectclass || uniquemember")'
979ee0
+        b'(version 3.0; acl "Allow users from o=ipaca to read groups"; '
979ee0
+        b'allow (read, search, compare) '
979ee0
+        b'userdn="ldap:///uid=*,ou=people,o=ipaca";)'
979ee0
+    )
979ee0
+
979ee0
     def __init__(self, realm, subsystem, service_desc, host_name=None,
f65af0
                  nss_db=paths.PKI_TOMCAT_ALIAS_DIR, service_prefix=None,
f65af0
                  config=None):
f65af0
@@ -108,10 +119,11 @@ class DogtagInstance(service.Service):
979ee0
         self.pkcs12_info = None
979ee0
         self.clone = False
979ee0
 
979ee0
-        self.basedn = DN(('o', 'ipa%s' % subsystem.lower()))
979ee0
+        self.basedn = DN(('o', 'ipaca'))
979ee0
         self.admin_user = "admin"
979ee0
-        self.admin_dn = DN(('uid', self.admin_user),
979ee0
-                           ('ou', 'people'), ('o', 'ipaca'))
979ee0
+        self.admin_dn = DN(
979ee0
+            ('uid', self.admin_user), self.ipaca_people
979ee0
+        )
979ee0
         self.admin_groups = None
979ee0
         self.tmp_agent_db = None
979ee0
         self.subsystem = subsystem
f65af0
@@ -393,27 +405,31 @@ class DogtagInstance(service.Service):
979ee0
 
979ee0
         raise RuntimeError("%s configuration failed." % self.subsystem)
979ee0
 
979ee0
-    def __add_admin_to_group(self, group):
979ee0
-        dn = DN(('cn', group), ('ou', 'groups'), ('o', 'ipaca'))
979ee0
-        entry = api.Backend.ldap2.get_entry(dn)
979ee0
-        members = entry.get('uniqueMember', [])
979ee0
-        members.append(self.admin_dn)
979ee0
-        mod = [(ldap.MOD_REPLACE, 'uniqueMember', members)]
979ee0
+    def add_ipaca_aci(self):
979ee0
+        """Add ACI to allow ipaca users to read their own group information
979ee0
+
979ee0
+        Dogtag users aren't allowed to enumerate their own groups. The
979ee0
+        setup_admin() method needs the permission to wait, until all group
979ee0
+        information has been replicated.
979ee0
+        """
979ee0
+        dn = self.ipaca_groups
979ee0
+        mod = [(ldap.MOD_ADD, 'aci', [self.groups_aci])]
979ee0
         try:
979ee0
             api.Backend.ldap2.modify_s(dn, mod)
979ee0
         except ldap.TYPE_OR_VALUE_EXISTS:
979ee0
-            # already there
979ee0
-            pass
f65af0
+            logger.debug("%s already has ACI to read group information", dn)
979ee0
+        else:
f65af0
+            logger.debug("Added ACI to read groups to %s", dn)
979ee0
 
979ee0
     def setup_admin(self):
979ee0
         self.admin_user = "admin-%s" % self.fqdn
979ee0
         self.admin_password = ipautil.ipa_generate_password()
979ee0
-        self.admin_dn = DN(('uid', self.admin_user),
979ee0
-                           ('ou', 'people'), ('o', 'ipaca'))
979ee0
-
979ee0
+        self.admin_dn = DN(
979ee0
+            ('uid', self.admin_user), self.ipaca_people
979ee0
+        )
979ee0
         # remove user if left-over exists
979ee0
         try:
979ee0
-            entry = api.Backend.ldap2.delete_entry(self.admin_dn)
979ee0
+            api.Backend.ldap2.delete_entry(self.admin_dn)
979ee0
         except errors.NotFound:
979ee0
             pass
979ee0
 
f65af0
@@ -432,18 +448,56 @@ class DogtagInstance(service.Service):
979ee0
         )
979ee0
         api.Backend.ldap2.add_entry(entry)
979ee0
 
979ee0
+        wait_groups = []
979ee0
         for group in self.admin_groups:
979ee0
-            self.__add_admin_to_group(group)
979ee0
+            group_dn = DN(('cn', group), self.ipaca_groups)
979ee0
+            mod = [(ldap.MOD_ADD, 'uniqueMember', [self.admin_dn])]
979ee0
+            try:
979ee0
+                api.Backend.ldap2.modify_s(group_dn, mod)
979ee0
+            except ldap.TYPE_OR_VALUE_EXISTS:
979ee0
+                # already there
979ee0
+                return None
979ee0
+            else:
979ee0
+                wait_groups.append(group_dn)
979ee0
 
979ee0
         # Now wait until the other server gets replicated this data
979ee0
         ldap_uri = ipaldap.get_ldap_uri(self.master_host)
979ee0
-        master_conn = ipaldap.LDAPClient(ldap_uri)
979ee0
-        master_conn.gssapi_bind()
979ee0
-        replication.wait_for_entry(master_conn, entry.dn)
979ee0
-        del master_conn
f65af0
+        master_conn = ipaldap.LDAPClient(ldap_uri, start_tls=True)
f65af0
+        logger.debug(
979ee0
+            "Waiting for %s to appear on %s", self.admin_dn, master_conn
979ee0
+        )
979ee0
+        deadline = time.time() + api.env.replication_wait_timeout
979ee0
+        while time.time() < deadline:
979ee0
+            time.sleep(1)
979ee0
+            try:
979ee0
+                master_conn.simple_bind(self.admin_dn, self.admin_password)
979ee0
+            except ldap.INVALID_CREDENTIALS:
979ee0
+                pass
979ee0
+            else:
f65af0
+                logger.debug("Successfully logged in as %s", self.admin_dn)
979ee0
+                break
979ee0
+        else:
f65af0
+            logger.error(
979ee0
+                "Unable to log in as %s on %s", self.admin_dn, master_conn
979ee0
+            )
979ee0
+            raise errors.NotFound(
979ee0
+                reason="{} did not replicate to {}".format(
979ee0
+                    self.admin_dn, master_conn
979ee0
+                )
979ee0
+            )
979ee0
+
979ee0
+        # wait for group membership
979ee0
+        for group_dn in wait_groups:
979ee0
+            replication.wait_for_entry(
979ee0
+                master_conn,
979ee0
+                group_dn,
979ee0
+                timeout=api.env.replication_wait_timeout,
979ee0
+                attr='uniqueMember',
979ee0
+                attrvalue=self.admin_dn
979ee0
+            )
979ee0
 
979ee0
     def __remove_admin_from_group(self, group):
979ee0
-        dn = DN(('cn', group), ('ou', 'groups'), ('o', 'ipaca'))
979ee0
+        dn = DN(('cn', group), self.ipaca_groups)
979ee0
         mod = [(ldap.MOD_DELETE, 'uniqueMember', self.admin_dn)]
979ee0
         try:
979ee0
             api.Backend.ldap2.modify_s(dn, mod)
979ee0
diff --git a/ipaserver/install/krainstance.py b/ipaserver/install/krainstance.py
f65af0
index b959398706d051a2584e722e176a9f2b0a9a0dc7..9483f0ec4edbabea0f7eff0dd5dd223377653536 100644
979ee0
--- a/ipaserver/install/krainstance.py
979ee0
+++ b/ipaserver/install/krainstance.py
f65af0
@@ -115,6 +115,7 @@ class KRAInstance(DogtagInstance):
979ee0
                 "A Dogtag CA must be installed first")
979ee0
 
979ee0
         if promote:
979ee0
+            self.step("creating ACIs for admin", self.add_ipaca_aci)
979ee0
             self.step("creating installation admin user", self.setup_admin)
979ee0
         self.step("configuring KRA instance", self.__spawn_instance)
979ee0
         if not self.clone:
979ee0
-- 
f65af0
2.14.4
979ee0