mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-26 17:01:14 -06:00
Use GSSAPI for replication
Uses a temporary simple replication agreement over SSL to init the tree. Then once all principals have been created switches replication to GSSAPI. Fixes: https://fedorahosted.org/freeipa/ticket/690
This commit is contained in:
parent
05055870c9
commit
a0bfbec19f
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user