pgreco / rpms / ipa

Forked from forks/areguera/rpms/ipa 4 years ago
Clone

Blame SOURCES/0060-Fix-replication-races-in-Dogtag-admin-code.patch

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