URL: https://github.com/freeipa/freeipa/pull/234
Author: martbab
 Title: #234: Always use GSSAPI to set up initial replication
Action: opened

PR body:
"""
This PR makes DS replica use common method to set up initial replication in 
both domain levels, namely GSSAPI. Since the workflow was introduced during 
replica promotion work, I have take a special care to make it work also against 
old (think ipa 3.0.0) masters that may still be in production.

https://fedorahosted.org/freeipa/ticket/6406
"""

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/234/head:pr234
git checkout pr234
From 4b391c830f7a86432af30b02956944044546397e Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Fri, 11 Nov 2016 10:23:49 +0100
Subject: [PATCH 1/5] Introduce constants for replication manager sysaccount
 group

https://fedorahosted.org/freeipa/ticket/6406
---
 ipalib/constants.py              |  1 +
 ipaserver/install/replication.py | 11 +++++------
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/ipalib/constants.py b/ipalib/constants.py
index c423117..719f307 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -125,6 +125,7 @@
     ('container_ca', DN(('cn', 'cas'), ('cn', 'ca'))),
     ('container_dnsservers', DN(('cn', 'servers'), ('cn', 'dns'))),
     ('container_custodia', DN(('cn', 'custodia'), ('cn', 'ipa'), ('cn', 'etc'))),
+    ('container_sysaccounts', DN(('cn', 'sysaccounts'), ('cn', 'etc'))),
 
     # Ports, hosts, and URIs:
     ('xmlrpc_uri', 'http://localhost:8888/ipa/xml'),
diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
index 836be73..d7a57e2 100644
--- a/ipaserver/install/replication.py
+++ b/ipaserver/install/replication.py
@@ -50,6 +50,8 @@
 TIMEOUT = 120
 REPL_MAN_DN = DN(('cn', 'replication manager'), ('cn', 'config'))
 DNA_DN = DN(('cn', 'Posix IDs'), ('cn', 'Distributed Numeric Assignment Plugin'), ('cn', 'plugins'), ('cn', 'config'))
+REPL_MANAGERS_DN = DN(('cn', 'replication managers'),
+                      api.env.container_sysaccounts, api.env.basedn)
 
 IPA_REPLICA = 1
 WINSYNC = 2
@@ -438,9 +440,6 @@ def replica_config(self, conn, replica_id, replica_binddn):
         assert isinstance(replica_binddn, DN)
         dn = self.replica_dn()
         assert isinstance(dn, DN)
-        replica_groupdn = DN(
-            ('cn', 'replication managers'), ('cn', 'sysaccounts'),
-            ('cn', 'etc'), self.suffix)
 
         try:
             entry = conn.get_entry(dn)
@@ -454,9 +453,9 @@ def replica_config(self, conn, replica_id, replica_binddn):
                 mod.append((ldap.MOD_ADD, 'nsDS5ReplicaBindDN',
                             replica_binddn))
 
-            if replica_groupdn not in binddn_groups:
+            if REPL_MANAGERS_DN not in binddn_groups:
                 mod.append((ldap.MOD_ADD, 'nsds5replicabinddngroup',
-                            replica_groupdn))
+                            REPL_MANAGERS_DN))
             if mod:
                 conn.modify_s(dn, mod)
 
@@ -476,7 +475,7 @@ def replica_config(self, conn, replica_id, replica_binddn):
             nsds5replicatype=[replica_type],
             nsds5flags=["1"],
             nsds5replicabinddn=[replica_binddn],
-            nsds5replicabinddngroup=[replica_groupdn],
+            nsds5replicabinddngroup=[REPL_MANAGERS_DN],
             nsds5replicabinddngroupcheckinterval=["60"],
             nsds5replicalegacyconsumer=["off"],
         )

From 0618232cf06f4f0b0f2cc515595e29b581296296 Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Wed, 9 Nov 2016 14:44:05 +0100
Subject: [PATCH 2/5] replication.py: augment setup_promote_replication method

the method that sets up initial GSSAPI replication in DL1 was augmented so
that the specified bind DN/bind password allows simple bind to remote master
using STARTTLS. The CA certificate for the connection is also configurable.

This facilitates the use of this method in DL0 where GSSAPI bind can not be
used during DS bootstrap while DM credentials are available.

https://fedorahosted.org/freeipa/ticket/6406
---
 ipaserver/install/replication.py | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
index d7a57e2..90b867e 100644
--- a/ipaserver/install/replication.py
+++ b/ipaserver/install/replication.py
@@ -1601,12 +1601,16 @@ def remove_temp_replication_user(self, conn, r_hostname):
             entry['nsDS5ReplicaBindDN'].remove(replica_binddn)
         conn.update_entry(entry)
 
-    def setup_promote_replication(self, r_hostname):
+    def setup_promote_replication(self, r_hostname, r_binddn=None,
+                                  r_bindpw=None, cacert=CACERT):
         # note - there appears to be a bug in python-ldap - it does not
         # allow connections using two different CA certs
         ldap_uri = ipaldap.get_ldap_uri(r_hostname)
-        r_conn = ipaldap.LDAPClient(ldap_uri)
-        r_conn.gssapi_bind()
+        r_conn = ipaldap.LDAPClient(ldap_uri, cacert=cacert)
+        if r_bindpw:
+            r_conn.simple_bind(r_binddn, r_bindpw)
+        else:
+            r_conn.gssapi_bind()
 
         # Setup the first half
         l_id = self._get_replica_id(self.conn, r_conn)

From a9c79a923cb8ee8d3880f10ab7f173b566625ea1 Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Thu, 10 Nov 2016 14:37:40 +0100
Subject: [PATCH 3/5] replication: refactor the code setting principals as
 replica bind DNs

In addition to improving the readability of
`setup_krb_princs_as_replica_binddns` method, the re-usable bits were factored
out to separate methods

https://fedorahosted.org/freeipa/ticket/6406
---
 ipaserver/install/replication.py | 47 ++++++++++++++++++----------------------
 1 file changed, 21 insertions(+), 26 deletions(-)

diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
index 90b867e..f2d0f4d 100644
--- a/ipaserver/install/replication.py
+++ b/ipaserver/install/replication.py
@@ -781,6 +781,22 @@ def get_replica_principal_dns(self, a, b, retries):
 
         return (a_entry[0].dn, b_entry[0].dn)
 
+    def _add_replica_bind_dn(self, conn, bind_dn):
+        rep_dn = self.replica_dn()
+        assert isinstance(rep_dn, DN)
+        try:
+            mod = [(ldap.MOD_ADD, "nsds5replicabinddn", bind_dn)]
+            conn.modify_s(rep_dn, mod)
+        except ldap.TYPE_OR_VALUE_EXISTS:
+            pass
+
+    def _add_dn_to_replication_managers(self, conn, bind_dn):
+        try:
+            mod = [(ldap.MOD_ADD, "member", bind_dn)]
+            conn.modify_s(REPL_MANAGERS_DN, mod)
+        except (ldap.TYPE_OR_VALUE_EXISTS, ldap.NO_SUCH_OBJECT):
+            pass
+
     def setup_krb_princs_as_replica_binddns(self, a, b):
         """
         Search the appropriate principal names so we can get
@@ -789,37 +805,16 @@ def setup_krb_princs_as_replica_binddns(self, a, b):
         as replication agents.
         """
 
-        rep_dn = self.replica_dn()
-        group_dn = DN(('cn', 'replication managers'), ('cn', 'sysaccounts'),
-                      ('cn', 'etc'), self.suffix)
-        assert isinstance(rep_dn, DN)
         (a_dn, b_dn) = self.get_replica_principal_dns(a, b, retries=100)
         assert isinstance(a_dn, DN)
         assert isinstance(b_dn, DN)
 
-        # Add kerberos principal DNs as valid bindDNs for replication
-        try:
-            mod = [(ldap.MOD_ADD, "nsds5replicabinddn", b_dn)]
-            a.modify_s(rep_dn, mod)
-        except ldap.TYPE_OR_VALUE_EXISTS:
-            pass
-        try:
-            mod = [(ldap.MOD_ADD, "nsds5replicabinddn", a_dn)]
-            b.modify_s(rep_dn, mod)
-        except ldap.TYPE_OR_VALUE_EXISTS:
-            pass
-        # Add kerberos principal DNs as valid bindDNs to bindDN group
-        try:
-            mod = [(ldap.MOD_ADD, "member", b_dn)]
-            a.modify_s(group_dn, mod)
-        except (ldap.TYPE_OR_VALUE_EXISTS, ldap.NO_SUCH_OBJECT):
-            pass
-        try:
-            mod = [(ldap.MOD_ADD, "member", a_dn)]
-            b.modify_s(group_dn, mod)
-        except (ldap.TYPE_OR_VALUE_EXISTS, ldap.NO_SUCH_OBJECT):
-            pass
+        for conn, bind_dn in ((a, b_dn), (b, a_dn)):
+            # Add kerberos principal DNs as valid bindDNs for replication
+            self._add_replica_bind_dn(conn, bind_dn)
 
+            # Add kerberos principal DNs as valid bindDNs to bindDN group
+            self._add_dn_to_replication_managers(conn, bind_dn)
 
     def gssapi_update_agreements(self, a, b):
 

From 7b3226eff26f6076c4305dcf8e35dc0544a5b62e Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Thu, 10 Nov 2016 14:42:01 +0100
Subject: [PATCH 4/5] ensure that the initial sync using GSSAPI works agains
 old masters

IPA 3.x masters neither have 'cn=replication managers' sysaccount groups set,
nor do they support adding nsds5ReplicaBinddnGroup attribute to the replica
config objects.

In order for common replication mechanism to work against
them, the replica must be ready to supply the required information to the old
master.

https://fedorahosted.org/freeipa/ticket/6406
---
 ipaserver/install/replication.py | 46 +++++++++++++++++++++++++++-------------
 1 file changed, 31 insertions(+), 15 deletions(-)

diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
index f2d0f4d..afd1dd8 100644
--- a/ipaserver/install/replication.py
+++ b/ipaserver/install/replication.py
@@ -32,7 +32,7 @@
 from ipalib.cli import textui
 from ipalib.constants import CACERT
 from ipapython.ipa_log_manager import root_logger
-from ipapython import ipautil, ipaldap
+from ipapython import ipautil, ipaldap, kerberos
 from ipapython.admintool import ScriptError
 from ipapython.dn import DN
 from ipaplatform.paths import paths
@@ -1533,24 +1533,40 @@ def enable_agreement(self, hostname):
         except errors.EmptyModlist:
             pass
 
-    def join_replication_managers(self, conn):
+    def _add_replication_managers(self, conn):
+        entry = conn.make_entry(
+            REPL_MANAGERS_DN,
+            objectclass=['top', 'groupofnames'],
+            cn=['replication managers']
+        )
+        conn.add_entry(entry)
+
+    def ensure_replication_managers(self, conn, r_hostname):
         """
-        Create a pseudo user to use for replication.
+        Ensure that the 'cn=replication managers,cn=sysaccounts' group exists
+        and contains the principals for master and remote replica
+
+        On FreeIPA 3.x masters lacking support for nsds5ReplicaBinddnGroup
+        attribute, add replica bind DN directly into the replica entry.
         """
-        dn = DN(('cn', 'replication managers'), ('cn', 'sysaccounts'),
-                ('cn', 'etc'), self.suffix)
-        mydn = DN(('krbprincipalname', 'ldap/%s@%s' % (self.hostname,
-                                                       self.realm)),
-                  ('cn', 'services'), ('cn', 'accounts'), self.suffix)
+        my_princ = kerberos.Principal((u'ldap', unicode(self.hostname)),
+                                      realm=self.realm)
+        remote_princ = kerberos.Principal((u'ldap', unicode(r_hostname)),
+                                          realm=self.realm)
+        services_dn = DN(api.env.container_services, api.env.basedn)
 
-        entry = conn.get_entry(dn)
-        if mydn not in entry['member']:
-            entry['member'].append(mydn)
+        mydn, remote_dn = tuple(
+            DN(('krbprincipalname', unicode(p)), services_dn) for p in (
+                my_princ, remote_princ))
 
         try:
-            conn.update_entry(entry)
-        except errors.EmptyModlist:
-            pass
+            conn.get_entry(REPL_MANAGERS_DN)
+        except errors.NotFound:
+            self._add_replica_bind_dn(conn, mydn)
+            self._add_replication_managers(conn)
+
+        self._add_dn_to_replication_managers(conn, mydn)
+        self._add_dn_to_replication_managers(conn, remote_dn)
 
     def add_temp_sasl_mapping(self, conn, r_hostname):
         """
@@ -1615,7 +1631,7 @@ def setup_promote_replication(self, r_hostname, r_binddn=None,
         # Now setup the other half
         r_id = self._get_replica_id(r_conn, r_conn)
         self.basic_replication_setup(r_conn, r_id, self.repl_man_dn, None)
-        self.join_replication_managers(r_conn)
+        self.ensure_replication_managers(r_conn, r_hostname)
 
         self.setup_agreement(r_conn, self.hostname, isgssapi=True)
         self.setup_agreement(self.conn, r_hostname, isgssapi=True)

From 9019d8dfc03d4253d6306293fad14c04d6b81450 Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Wed, 9 Nov 2016 14:48:56 +0100
Subject: [PATCH 5/5] Use common procedure to setup initial replication in both
 domain levels

Set up initial replication using GSSAPI also in domin level 0. For this to
work, the supplied DM password is used to connect to remote master and set up
agreements. The workflow is unchanged in DL1 where GSSAPI bind as host or
admin is used.

This obsoletes the conversion of replication agreements to GSSAPI made in DL0
during KDC installation.

https://fedorahosted.org/freeipa/ticket/6406
---
 ipaserver/install/dsinstance.py  | 25 ++++++++++++++++++++-----
 ipaserver/install/krbinstance.py |  3 ---
 2 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index a604010..f76378e 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -410,6 +410,16 @@ def create_replica(self, realm_name, master_fqdn, fqdn,
 
 
     def __setup_replica(self):
+        """
+        Setup initial replication between replica and remote master.
+        GSSAPI is always used as a replication bind method. Note, however,
+        that the bind method for the replication differs between domain levels:
+            * in domain level 0, Directory Manager credentials are used to bind
+              to remote master
+            * in domain level 1, GSSAPI using admin/privileged host credentials
+              is used (we do not have access to masters' DM password in this
+              stage)
+        """
         replication.enable_replication_version_checking(
             self.realm,
             self.dm_password)
@@ -421,12 +431,17 @@ def __setup_replica(self):
         repl = replication.ReplicationManager(self.realm,
                                               self.fqdn,
                                               self.dm_password, conn=conn)
-        if self.promote:
-            repl.setup_promote_replication(self.master_fqdn)
+
+        if self.dm_password is not None and not self.promote:
+            bind_dn = DN(('cn', 'Directory Manager'))
+            bind_pw = self.dm_password
         else:
-            repl.setup_replication(self.master_fqdn,
-                                   r_binddn=DN(('cn', 'Directory Manager')),
-                                   r_bindpw=self.dm_password)
+            bind_dn = bind_pw = None
+
+        repl.setup_promote_replication(self.master_fqdn,
+                                       r_binddn=bind_dn,
+                                       r_bindpw=bind_pw,
+                                       cacert=self.ca_file)
         self.run_init_memberof = repl.needs_memberof_fixup()
 
     def __configure_sasl_mappings(self):
diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py
index b7ae38f..b5cfd79 100644
--- a/ipaserver/install/krbinstance.py
+++ b/ipaserver/install/krbinstance.py
@@ -180,9 +180,6 @@ def create_replica(self, realm_name,
         self.step("adding the password extension to the directory", self.__add_pwd_extop_module)
         if setup_pkinit:
             self.step("installing X509 Certificate for PKINIT", self.__setup_pkinit)
-        if not promote:
-            self.step("enable GSSAPI for replication",
-                      self.__convert_to_gssapi_replication)
 
         self.__common_post_setup()
 
-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to