From 1555dadc186d862eb4c42885575ebeedcdf35084 Mon Sep 17 00:00:00 2001 From: Rich Megginson Date: Thu, 11 Sep 2008 10:56:55 -0600 Subject: Added support to IPA server install to install the winsync plugin configuration entry Added support to ipa-replica-manage to add winsync agreements. I mostly used the existing code for setting up replication agreements since replication and winsync are quite similar in their configuration. I just had to add some extra attributes to the sync agreement configuration. The tricky part was importing the Windows CA cert. --- ipa-server/ipa-install/ipa-replica-manage | 36 ++++++++-- .../ipa-winsync/ipa-winsync-conf.ldif | 7 +- ipa-server/ipaserver/dsinstance.py | 59 +++++++++++++++ ipa-server/ipaserver/ipaldap.py | 2 + ipa-server/ipaserver/replication.py | 84 +++++++++++++++++----- 5 files changed, 164 insertions(+), 24 deletions(-) (limited to 'ipa-server') diff --git a/ipa-server/ipa-install/ipa-replica-manage b/ipa-server/ipa-install/ipa-replica-manage index 78cfe88e5..2021eab68 100644 --- a/ipa-server/ipa-install/ipa-replica-manage +++ b/ipa-server/ipa-install/ipa-replica-manage @@ -34,11 +34,21 @@ def parse_options(): parser.add_option("-p", "--password", dest="dirman_passwd", help="Directory Manager password") parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False, help="provide additional information") + parser.add_option("--port", type="int", dest="port", + help="port number of other server") + parser.add_option("--binddn", dest="binddn", + help="Bind DN to use with remote server") + parser.add_option("--bindpw", dest="bindpw", + help="Password for Bind DN to use with remote server") + parser.add_option("--winsync", dest="winsync", action="store_true", default=False, + help="This is a Windows Sync Agreement") + parser.add_option("--cacert", dest="cacert", + help="Full path and filename of CA certificate to use with TLS/SSL to the remote server") options, args = parser.parse_args() if not len(args) or not ("list" in args[0] or "add" in args[0] or "del" in args[0] or "init" in args[0] or "synch" in args[0]): - parser.error("must provide a comment [list | add | del | init | synch]") + parser.error("must provide a command [list | add | del | init | synch]") return options, args @@ -81,8 +91,26 @@ def del_master(replman, hostname): replman.delete_agreement(other_replman.conn) other_replman.delete_agreement(replman.conn) -def add_master(replman, hostname): - replman.setup_replication(hostname, get_realm_name()) +def add_master(replman, hostname, options): + other_args = {} + if options.winsync: + # these are the parameters required to create a winsync agreement + other_args['winsync'] = True + if options.port: + other_args['port'] = options.port + other_args['binddn'] = options.binddn + other_args['bindpw'] = options.bindpw + other_args['cacert'] = options.cacert + # have to install the windows ca cert before doing anything else + ds = dsinstance.DsInstance(realm_name = get_realm_name(), + dm_password = replman.dirman_passwd) + if not ds.add_ca_cert(options.cacert): + logging.error("Could not load the required CA certificate file [%s] - cannot add winsync agreement" % + options.cacert) + sys.exit(1) + # have to reconnect replman connection since the directory server was restarted + replman = replication.ReplicationManager(replman.hostname, replman.dirman_passwd) + replman.setup_replication(hostname, get_realm_name(), **other_args) def init_master(replman, dirman_passwd, hostname): filter = "(&(nsDS5ReplicaHost=%s)(objectclass=nsds5ReplicationAgreement))" % hostname @@ -133,7 +161,7 @@ def main(): if len(args) != 2: print "must provide hostname of master to add" sys.exit(1) - add_master(r, args[1]) + add_master(r, args[1], options) elif args[0] == "init": if len(args) != 2: print "hostname of supplier to initialize from is required." diff --git a/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif b/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif index 3fdcc5452..8bf8e27c7 100644 --- a/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif +++ b/ipa-server/ipa-slapi-plugins/ipa-winsync/ipa-winsync-conf.ldif @@ -1,10 +1,15 @@ dn: cn=ipa-winsync,cn=plugins,cn=config +changetype: add objectclass: top objectclass: nsSlapdPlugin objectclass: extensibleObject cn: ipa-winsync -nsslapd-pluginpath: libipa-winsync +nsslapd-pluginpath: libipa_winsync nsslapd-plugininitfunc: ipa_winsync_plugin_init +nsslapd-pluginDescription: Allows IPA to work with the DS windows sync feature +nsslapd-pluginid: ipa-winsync +nsslapd-pluginversion: 1.0 +nsslapd-pluginvendor: Red Hat nsslapd-plugintype: preoperation nsslapd-pluginenabled: on nsslapd-plugin-depends-on-type: database diff --git a/ipa-server/ipaserver/dsinstance.py b/ipa-server/ipaserver/dsinstance.py index 93cc457a4..2d3f78ace 100644 --- a/ipa-server/ipaserver/dsinstance.py +++ b/ipa-server/ipaserver/dsinstance.py @@ -27,6 +27,7 @@ import os import re import time import tempfile +import stat from ipa import ipautil @@ -68,6 +69,10 @@ def erase_ds_instance_data(serverid): shutil.rmtree("/usr/lib/dirsrv/slapd-%s" % serverid) except: pass + try: + shutil.rmtree("/usr/lib64/dirsrv/slapd-%s" % serverid) + except: + pass try: shutil.rmtree("/var/lib/dirsrv/slapd-%s" % serverid) except: @@ -76,6 +81,10 @@ def erase_ds_instance_data(serverid): shutil.rmtree("/var/lock/dirsrv/slapd-%s" % serverid) except: pass +# try: +# shutil.rmtree("/var/log/dirsrv/slapd-%s" % serverid) +# except: +# pass def check_existing_installation(): dirs = glob.glob("/etc/dirsrv/slapd-*") @@ -165,6 +174,7 @@ class DsInstance(service.Service): self.step("enabling memberof plugin", self.__add_memberof_module) self.step("enabling referential integrity plugin", self.__add_referint_module) self.step("enabling distributed numeric assignment plugin", self.__add_dna_module) + self.step("enabling winsync plugin", self.__add_winsync_module) self.step("configuring uniqueness plugin", self.__set_unique_attrs) self.step("creating indices", self.__create_indices) self.step("configuring ssl for ds instance", self.__enable_ssl) @@ -325,6 +335,9 @@ class DsInstance(service.Service): def __add_master_entry_first_master(self): self.__ldap_mod("master-entry.ldif", self.sub_dict) + def __add_winsync_module(self): + self.__ldap_mod("ipa-winsync-conf.ldif") + def __enable_ssl(self): dirname = config_dirname(self.serverid) ca = certs.CertDB(dirname) @@ -421,3 +434,49 @@ class DsInstance(service.Service): if self.restore_state("running"): self.start() + + # we could probably move this function into the service.Service + # class - it's very generic - all we need is a way to get an + # instance of a particular Service + def add_ca_cert(self, cacert_fname, cacert_name=''): + """Add a CA certificate to the directory server cert db. We + first have to shut down the directory server in case it has + opened the cert db read-only. Then we use the CertDB class + to add the CA cert. We have to provide a nickname, and we + do not use 'CA certificate' since that's the default, so + we use 'Imported CA' if none specified. Then we restart + the server.""" + # first make sure we have a valid cacert_fname + try: + if not os.access(cacert_fname, os.R_OK): + logging.critical("The given CA cert file named [%s] could not be read" % + cacert_fname) + return False + except OSError, e: + logging.critical("The given CA cert file named [%s] could not be read: %s" % + (cacert_fname, str(e))) + return False + # ok - ca cert file can be read + # shutdown the server + running = self.restore_state("running") + + if not running is None: + self.stop() + + dirname = config_dirname(realm_to_serverid(self.realm_name)) + certdb = certs.CertDB(dirname) + if not cacert_name or len(cacert_name) == 0: + cacert_name = "Imported CA" + # we can't pass in the nickname, so we set the instance variable + certdb.cacert_name = cacert_name + status = True + try: + certdb.load_cacert(cacert_fname) + except CalledProcessError, e: + logging.critical("Error importaing CA cert file named [%s]: %s" % + (cacert_fname, str(e))) + status = False + # restart the directory server + self.start() + + return status diff --git a/ipa-server/ipaserver/ipaldap.py b/ipa-server/ipaserver/ipaldap.py index 3006d479b..c2dbe4e2d 100644 --- a/ipa-server/ipaserver/ipaldap.py +++ b/ipa-server/ipaserver/ipaldap.py @@ -243,6 +243,8 @@ class IPAdmin(SimpleLDAPObject): self.dbdir = os.path.dirname(ent.getValue('nsslapd-directory')) except (ldap.INSUFFICIENT_ACCESS, ldap.CONNECT_ERROR): pass # usually means + except ldap.OPERATIONS_ERROR, e: + pass # usually means this is Active Directory except ldap.LDAPError, e: print "caught exception ", e raise diff --git a/ipa-server/ipaserver/replication.py b/ipa-server/ipaserver/replication.py index d3b1551c8..3ab2e6b17 100644 --- a/ipa-server/ipaserver/replication.py +++ b/ipa-server/ipaserver/replication.py @@ -25,11 +25,15 @@ from ipa import ipaerror DIRMAN_CN = "cn=directory manager" CACERT="/usr/share/ipa/html/ca.crt" +# the default container used by AD for user entries +WIN_USER_CONTAINER="cn=Users" +# the default container used by IPA for user entries +IPA_USER_CONTAINER="cn=users,cn=accounts" PORT = 636 TIMEOUT = 120 - class ReplicationManager: - """Manage replicatin agreements between DS servers""" + """Manage replication agreements between DS servers, and sync + agreements with Windows servers""" def __init__(self, hostname, dirman_passwd): self.hostname = hostname self.dirman_passwd = dirman_passwd @@ -197,6 +201,23 @@ class ReplicationManager: chainbe = self.setup_chaining_backend(other_conn) self.enable_chain_on_update(chainbe) + def setup_winsync_agmt(self, entry, **kargs): + entry.setValues("objectclass", "nsDSWindowsReplicationAgreement") + entry.setValues("nsds7WindowsReplicaSubtree", + kargs.get("win_subtree", + WIN_USER_CONTAINER + "," + self.suffix)) + entry.setValues("nsds7DirectoryReplicaSubtree", + kargs.get("ds_subtree", + IPA_USER_CONTAINER + "," + self.suffix)) + # for now, just sync users and ignore groups + entry.setValues("nsds7NewWinUserSyncEnabled", kargs.get('newwinusers', 'true')) + entry.setValues("nsds7NewWinGroupSyncEnabled", kargs.get('newwingroups', 'false')) + windomain = '' + if kargs.has_key('windomain'): + windomain = kargs['windomain'] + else: + windomain = '.'.join(ldap.explode_dn(self.suffix, 1)) + entry.setValues("nsds7WindowsDomain", windomain) def agreement_dn(self, conn): cn = "meTo%s%d" % (conn.host, PORT) @@ -204,7 +225,7 @@ class ReplicationManager: return (cn, dn) - def setup_agreement(self, a, b): + def setup_agreement(self, a, b, **kargs): cn, dn = self.agreement_dn(b) try: a.getEntry(dn, ldap.SCOPE_BASE) @@ -212,20 +233,27 @@ class ReplicationManager: except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): pass + iswinsync = kargs.get("winsync", False) + repl_man_dn = kargs.get("binddn", self.repl_man_dn) + repl_man_passwd = kargs.get("bindpw", self.repl_man_passwd) + port = kargs.get("port", PORT) + entry = ipaldap.Entry(dn) - entry.setValues('objectclass', "top", "nsds5replicationagreement") + entry.setValues('objectclass', "nsds5replicationagreement") entry.setValues('cn', cn) entry.setValues('nsds5replicahost', b.host) - entry.setValues('nsds5replicaport', str(PORT)) + entry.setValues('nsds5replicaport', str(port)) entry.setValues('nsds5replicatimeout', str(TIMEOUT)) - entry.setValues('nsds5replicabinddn', self.repl_man_dn) - entry.setValues('nsds5replicacredentials', self.repl_man_passwd) + 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 memberOf') - entry.setValues('description', "me to %s%d" % (b.host, PORT)) + entry.setValues('description', "me to %s%d" % (b.host, port)) + if iswinsync: + self.setup_winsync_agmt(entry, **kargs) a.add_s(entry) @@ -278,9 +306,11 @@ class ReplicationManager: done, haserror = self.check_repl_init(conn, agmtdn) return haserror - def start_replication(self, other_conn): + def start_replication(self, other_conn, conn=None): print "Starting replication, please wait until this has completed." - cn, dn = self.agreement_dn(self.conn) + if conn == None: + conn = self.conn + cn, dn = self.agreement_dn(conn) mod = [(ldap.MOD_ADD, 'nsds5BeginReplicaRefresh', 'start')] other_conn.modify_s(dn, mod) @@ -292,24 +322,40 @@ class ReplicationManager: self.local_replica_config(conn, replica_id) self.setup_changelog(conn) - def setup_replication(self, other_hostname, realm_name): + def setup_replication(self, other_hostname, realm_name, **kargs): """ NOTES: - the directory manager password needs to be the same on - both directories. + both directories. Or use the optional binddn and bindpw """ - other_conn = ipaldap.IPAdmin(other_hostname, port=PORT, cacert=CACERT) - other_conn.do_simple_bind(bindpw=self.dirman_passwd) + iswinsync = kargs.get("winsync", False) + oth_port = kargs.get("port", PORT) + oth_cacert = kargs.get("cacert", CACERT) + oth_binddn = kargs.get("binddn", DIRMAN_CN) + oth_bindpw = kargs.get("bindpw", self.dirman_passwd) + # note - there appears to be a bug in python-ldap - it does not + # allow connections using two different CA certs + other_conn = ipaldap.IPAdmin(other_hostname, port=oth_port, cacert=oth_cacert) + try: + other_conn.do_simple_bind(binddn=oth_binddn, bindpw=oth_bindpw) + except Exception, e: + if iswinsync: + logging.info("Could not validate connection to remote server %s:%d - continuing" % + (other_hostname, oth_port)) + else: + raise e self.suffix = ipaldap.IPAdmin.normalizeDN(dsinstance.realm_to_suffix(realm_name)) self.basic_replication_setup(self.conn, 1) - self.basic_replication_setup(other_conn, 2) - - self.setup_agreement(other_conn, self.conn) - self.setup_agreement(self.conn, other_conn) - return self.start_replication(other_conn) + if not iswinsync: + self.basic_replication_setup(other_conn, 2) + self.setup_agreement(other_conn, self.conn) + return self.start_replication(other_conn) + else: + self.setup_agreement(self.conn, other_conn, **kargs) + return self.start_replication(self.conn, other_conn) def initialize_replication(self, dn, conn): mod = [(ldap.MOD_ADD, 'nsds5BeginReplicaRefresh', 'start')] -- cgit