URL: https://github.com/freeipa/freeipa/pull/234 Author: martbab Title: #234: Always use GSSAPI to set up initial replication Action: synchronized
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 d2bedfe398005f276196bb833a23d258522922dc 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] Turn replication manager group into ReplicationManager class member https://fedorahosted.org/freeipa/ticket/6406 --- ipalib/constants.py | 1 + ipaserver/install/replication.py | 12 ++++++------ 2 files changed, 7 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..2e3b12f 100644 --- a/ipaserver/install/replication.py +++ b/ipaserver/install/replication.py @@ -50,6 +50,7 @@ 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_CN = DN(('cn', 'replication managers')) IPA_REPLICA = 1 WINSYNC = 2 @@ -232,6 +233,8 @@ def __init__(self, realm, hostname, dirman_passwd, port=PORT, starttls=False, co # at runtime if you really want self.repl_man_dn = REPL_MAN_DN self.repl_man_cn = "replication manager" + self.repl_man_group_dn = DN( + REPL_MANAGERS_CN, api.env.container_sysaccounts, api.env.basedn) def _get_replica_id(self, conn, master_conn): """ @@ -438,9 +441,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 +454,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 self.repl_man_group_dn not in binddn_groups: mod.append((ldap.MOD_ADD, 'nsds5replicabinddngroup', - replica_groupdn)) + self.repl_man_group_dn)) if mod: conn.modify_s(dn, mod) @@ -476,7 +476,7 @@ def replica_config(self, conn, replica_id, replica_binddn): nsds5replicatype=[replica_type], nsds5flags=["1"], nsds5replicabinddn=[replica_binddn], - nsds5replicabinddngroup=[replica_groupdn], + nsds5replicabinddngroup=[self.repl_man_group_dn], nsds5replicabinddngroupcheckinterval=["60"], nsds5replicalegacyconsumer=["off"], ) From 7802160d5953ab5e2590e024b5027f1202c088bf 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: 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 2e3b12f..6209f81 100644 --- a/ipaserver/install/replication.py +++ b/ipaserver/install/replication.py @@ -1602,12 +1602,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 b4d5e9eefae3cd6a049ee3fd7e754267d7332703 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 6209f81..4ad6694 100644 --- a/ipaserver/install/replication.py +++ b/ipaserver/install/replication.py @@ -782,6 +782,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(self.repl_man_group_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 @@ -790,37 +806,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 22e79e3a42906f08191d2ab7f9feab6e6d20a346 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 4ad6694..ba35c49 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 @@ -1534,24 +1534,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( + self.repl_man_group_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_service, 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(self.repl_man_group_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): """ @@ -1616,7 +1632,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 26aaf2966bd1acf869154ccb5ad585d52efde34a 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