summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xinstall/tools/ipa-replica-install3
-rwxr-xr-xinstall/tools/ipa-replica-manage4
-rw-r--r--ipaserver/install/krbinstance.py14
-rw-r--r--ipaserver/install/replication.py140
4 files changed, 145 insertions, 16 deletions
diff --git a/install/tools/ipa-replica-install b/install/tools/ipa-replica-install
index 76f7f8c9..3c912a75 100755
--- a/install/tools/ipa-replica-install
+++ b/install/tools/ipa-replica-install
@@ -205,7 +205,8 @@ def install_krb(config, setup_pkinit=False):
pkcs12_info = (config.dir + "/pkinitcert.p12",
config.dir + "/pkinit_pin.txt")
- krb.create_replica(config.ds_user, config.realm_name, config.host_name,
+ krb.create_replica(config.ds_user, config.realm_name,
+ config.master_host_name, config.host_name,
config.domain_name, config.dirman_password,
ldappwd_filename, kpasswd_filename,
setup_pkinit, pkcs12_info)
diff --git a/install/tools/ipa-replica-manage b/install/tools/ipa-replica-manage
index 2400176f..0fd06fd2 100755
--- a/install/tools/ipa-replica-manage
+++ b/install/tools/ipa-replica-manage
@@ -312,7 +312,7 @@ def add_link(realm, replica1, replica2, dirman_passwd, options):
options.passsync, options.win_subtree,
options.cacert)
else:
- repl1.setup_replication(replica2, "cn=Directory Manager", dirman_passwd)
+ repl1.setup_gssapi_replication(replica2, "cn=Directory Manager", dirman_passwd)
print "Connected '%s' to '%s'" % (replica1, replica2)
def re_initialize(realm, options):
@@ -350,7 +350,7 @@ def force_sync(realm, thishost, fromhost, dirman_passwd):
sys.exit(1)
if len(entry) > 1:
logging.error("Found multiple agreements for %s. Only initializing the first one returned: %s" % (thishost, entry[0].dn))
- repl.force_synch(entry[0].dn, entry[0].nsds5replicaupdateschedule, repl.conn)
+ repl.force_synch(entry[0].dn, entry[0].nsds5replicaupdateschedule)
def main():
options, args = parse_options()
diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py
index 4ad2fcec..d89ad0b3 100644
--- a/ipaserver/install/krbinstance.py
+++ b/ipaserver/install/krbinstance.py
@@ -34,6 +34,7 @@ from ipalib import util
from ipalib import errors
from ipaserver import ipaldap
+from ipaserver.install import replication
import ldap
from ldap import LDAPError
@@ -181,7 +182,8 @@ class KrbInstance(service.Service):
self.kpasswd = KpasswdInstance()
self.kpasswd.create_instance('KPASSWD', self.fqdn, self.admin_password, self.suffix)
- def create_replica(self, ds_user, realm_name, host_name,
+ def create_replica(self, ds_user, realm_name,
+ master_fqdn, host_name,
domain_name, admin_password,
ldap_passwd_filename, kpasswd_filename,
setup_pkinit=False, pkcs12_info=None,
@@ -191,6 +193,7 @@ class KrbInstance(service.Service):
self.subject_base = subject_base
self.__copy_ldap_passwd(ldap_passwd_filename)
self.__copy_kpasswd_keytab(kpasswd_filename)
+ self.master_fqdn = master_fqdn
self.__common_setup(ds_user, realm_name, host_name, domain_name, admin_password)
@@ -202,6 +205,7 @@ class KrbInstance(service.Service):
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)
+ self.step("Enable GSSAPI for replication", self.__convert_to_gssapi_replication)
self.__common_post_setup()
@@ -543,6 +547,14 @@ class KrbInstance(service.Service):
dn = "krbprincipalname=%s,cn=%s,cn=kerberos,%s" % (princ_realm, self.realm, self.suffix)
self.admin_conn.inactivateEntry(dn, False)
+ def __convert_to_gssapi_replication(self):
+ repl = replication.ReplicationManager(self.realm,
+ self.fqdn,
+ self.dm_password)
+ repl.convert_to_gssapi_replication(self.master_fqdn,
+ r_binddn="cn=Directory Manager",
+ r_bindpw=self.dm_password)
+
def uninstall(self):
if self.is_configured():
self.print_msg("Unconfiguring %s" % self.service_name)
diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
index 3e467a9e..54774659 100644
--- a/ipaserver/install/replication.py
+++ b/ipaserver/install/replication.py
@@ -59,6 +59,7 @@ class ReplicationManager:
def __init__(self, realm, hostname, dirman_passwd):
self.hostname = hostname
self.dirman_passwd = dirman_passwd
+ self.realm = realm
tmp = util.realm_to_suffix(realm)
self.suffix = ipaldap.IPAdmin.normalizeDN(tmp)
@@ -353,15 +354,15 @@ class ReplicationManager:
entry.setValues("nsds7NewWinGroupSyncEnabled", 'false')
entry.setValues("nsds7WindowsDomain", windomain)
- def agreement_dn(self, hostname, port=PORT):
- cn = "meTo%s%d" % (hostname, port)
+ def agreement_dn(self, hostname):
+ cn = "meTo%s" % (hostname)
dn = "cn=%s, %s" % (cn, self.replica_dn())
return (cn, dn)
def setup_agreement(self, a, b,
repl_man_dn=None, repl_man_passwd=None,
- iswinsync=False, win_subtree=None):
+ iswinsync=False, win_subtree=None, isgssapi=False):
cn, dn = self.agreement_dn(b.host)
try:
a.getEntry(dn, ldap.SCOPE_BASE)
@@ -369,7 +370,7 @@ class ReplicationManager:
except errors.NotFound:
pass
- port = PORT
+ port = 389
if repl_man_dn is None:
repl_man_dn = self.repl_man_dn
if repl_man_passwd is None:
@@ -387,15 +388,20 @@ class ReplicationManager:
entry.setValues('nsds5replicahost', b.host)
entry.setValues('nsds5replicaport', str(port))
entry.setValues('nsds5replicatimeout', str(TIMEOUT))
- entry.setValues('nsds5replicabinddn', repl_man_dn)
- entry.setValues('nsds5replicacredentials', repl_man_passwd)
- entry.setValues('nsds5replicabindmethod', 'simple')
entry.setValues('nsds5replicaroot', self.suffix)
entry.setValues('nsds5replicaupdateschedule', '0000-2359 0123456')
- entry.setValues('nsds5replicatransportinfo', 'SSL')
entry.setValues('nsDS5ReplicatedAttributeList',
'(objectclass=*) $ EXCLUDE %s' % " ".join(excludes))
- entry.setValues('description', "me to %s%d" % (b.host, port))
+ entry.setValues('description', "me to %s" % b.host)
+ entry.setValues('nsds5replicabinddn', repl_man_dn)
+ if isgssapi:
+ entry.setValues('nsds5replicatransportinfo', 'LDAP')
+ entry.setValues('nsds5replicabindmethod', 'SASL/GSSAPI')
+ else:
+ entry.setValues('nsds5replicacredentials', repl_man_passwd)
+ entry.setValues('nsds5replicatransportinfo', 'TLS')
+ entry.setValues('nsds5replicabindmethod', 'simple')
+
if iswinsync:
self.setup_winsync_agmt(entry, win_subtree)
@@ -403,6 +409,64 @@ class ReplicationManager:
entry = a.waitForEntry(entry)
+ def setup_krb_princs_as_replica_binddns(self, a, b):
+ """
+ Search the appropriate principal names so we can get
+ the correct DNs to store in the replication agreements.
+ Then modify the replica object to allow these DNs to act
+ as replication agents.
+ """
+
+ rep_dn = self.replica_dn()
+ filter_a = '(krbprincipalname=ldap/%s@%s)' % (a.host, self.realm)
+ filter_b = '(krbprincipalname=ldap/%s@%s)' % (b.host, self.realm)
+
+ a_pn = b.search_s(self.suffix, ldap.SCOPE_SUBTREE, filterstr=filter_a)
+ b_pn = a.search_s(self.suffix, ldap.SCOPE_SUBTREE, filterstr=filter_b)
+
+ # Add kerberos principal DNs as valid bindDNs for replication
+ try:
+ mod = [(ldap.MOD_ADD, "nsds5replicabinddn", b_pn[0].dn)]
+ a.modify_s(rep_dn, mod)
+ except ldap.TYPE_OR_VALUE_EXISTS:
+ pass
+ try:
+ mod = [(ldap.MOD_ADD, "nsds5replicabinddn", a_pn[0].dn)]
+ b.modify_s(rep_dn, mod)
+ except ldap.TYPE_OR_VALUE_EXISTS:
+ pass
+
+ return (a_pn[0].dn, b_pn[0].dn)
+
+ def gssapi_update_agreements(self, a, b):
+
+ (a_pn_dn, b_pn_dn) = self.setup_krb_princs_as_replica_binddns(a, b)
+
+ #change replication agreements to connect to other host using GSSAPI
+ cn, a_ag_dn = self.agreement_dn(b.host)
+ mod = [(ldap.MOD_REPLACE, "nsds5replicabinddn", a_pn_dn),
+ (ldap.MOD_DELETE, "nsds5replicacredentials", None),
+ (ldap.MOD_REPLACE, "nsds5replicatransportinfo", "LDAP"),
+ (ldap.MOD_REPLACE, "nsds5replicabindmethod", "SASL/GSSAPI")]
+ a.modify_s(a_ag_dn, mod)
+
+ cn, b_ag_dn = self.agreement_dn(a.host)
+ mod = [(ldap.MOD_REPLACE, "nsds5replicabinddn", b_pn_dn),
+ (ldap.MOD_DELETE, "nsds5replicacredentials", None),
+ (ldap.MOD_REPLACE, "nsds5replicatransportinfo", "LDAP"),
+ (ldap.MOD_REPLACE, "nsds5replicabindmethod", "SASL/GSSAPI")]
+ b.modify_s(b_ag_dn, mod)
+
+ # Finally remove the temporary replication manager user
+ try:
+ a.delete_s(self.repl_man_dn)
+ except ldap.NO_SUCH_OBJECT:
+ pass
+ try:
+ b.delete_s(self.repl_man_dn)
+ except ldap.NO_SUCH_OBJECT:
+ pass
+
def delete_agreement(self, hostname):
cn, dn = self.agreement_dn(hostname)
return self.conn.deleteEntry(dn)
@@ -582,6 +646,58 @@ class ReplicationManager:
return self.start_replication(self.conn, ad_conn,
self.repl_man_dn, self.repl_man_passwd)
+ def convert_to_gssapi_replication(self, r_hostname, r_binddn, r_bindpw):
+ r_conn = ipaldap.IPAdmin(r_hostname, port=PORT, cacert=CACERT)
+ if r_bindpw:
+ r_conn.do_simple_bind(binddn=r_binddn, bindpw=r_bindpw)
+ else:
+ r_conn.sasl_interactive_bind_s('', SASL_AUTH)
+
+ # First off make sure servers are in sync so that both KDCs
+ # have all princiapls and their passwords and can release
+ # the right tickets. We do this by force pushing all our changes
+ filter = "(&(nsDS5ReplicaHost=%s)(objectclass=nsds5ReplicationAgreement))" % r_hostname
+ entry = self.conn.search_s("cn=config", ldap.SCOPE_SUBTREE, filter)
+ if len(entry) == 0:
+ raise RuntimeError("Missing %s -> %s replication agreement" %
+ (self.hostname, r_hostname))
+ if len(entry) > 1:
+ logging.info("Found multiple agreements for %s." % r_hostname)
+ logging.info("Syncing only the first one: %s" % entry[0].dn)
+
+ self.force_synch(entry[0].dn, entry[0].nsds5replicaupdateschedule)
+
+ # now wait until we are sure replication has succeeded.
+ cn, dn = self.agreement_dn(r_hostname)
+ self.wait_for_repl_update(self.conn, dn, 30)
+
+ # now that directories are in sync,
+ # change the agreements to use GSSAPI
+ self.gssapi_update_agreements(self.conn, r_conn)
+
+ def setup_gssapi_replication(self, r_hostname, r_binddn=None, r_bindpw=None):
+ """
+ Directly sets up GSSAPI replication.
+ Only usable to connect 2 existing replicas (needs existing kerberos
+ principals)
+ """
+ # note - there appears to be a bug in python-ldap - it does not
+ # allow connections using two different CA certs
+ r_conn = ipaldap.IPAdmin(r_hostname, port=PORT, cacert=CACERT)
+ if r_bindpw:
+ r_conn.do_simple_bind(binddn=r_binddn, bindpw=r_bindpw)
+ else:
+ r_conn.sasl_interactive_bind_s('', SASL_AUTH)
+
+ # Allow krb principals to act as replicas
+ (self_dn, r_dn) = self.setup_krb_princs_as_replica_binddns(self.conn, r_conn)
+
+ # Create mutual replication agreementsausiung SASL/GSSAPI
+ self.setup_agreement(self.conn, r_conn,
+ repl_man_dn=self_dn, isgssapi=True)
+ self.setup_agreement(r_conn, self.conn,
+ repl_man_dn=r_dn, isgssapi=True)
+
def initialize_replication(self, dn, conn):
mod = [(ldap.MOD_ADD, 'nsds5BeginReplicaRefresh', 'start')]
try:
@@ -589,7 +705,7 @@ class ReplicationManager:
except ldap.ALREADY_EXISTS:
return
- def force_synch(self, dn, schedule, conn):
+ def force_synch(self, dn, schedule):
newschedule = '2358-2359 0'
# On the remote chance of a match. We force a synch to happen right
@@ -600,12 +716,12 @@ class ReplicationManager:
logging.info("Changing agreement %s schedule to %s to force synch" %
(dn, newschedule))
mod = [(ldap.MOD_REPLACE, 'nsDS5ReplicaUpdateSchedule', [ newschedule ])]
- conn.modify_s(dn, mod)
+ self.conn.modify_s(dn, mod)
time.sleep(1)
logging.info("Changing agreement %s to restore original schedule %s" %
(dn, schedule))
mod = [(ldap.MOD_REPLACE, 'nsDS5ReplicaUpdateSchedule', [ schedule ])]
- conn.modify_s(dn, mod)
+ self.conn.modify_s(dn, mod)
def get_agreement_type(self, hostname):
cn, dn = self.agreement_dn(hostname)