mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-28 01:41:14 -06:00
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.
This commit is contained in:
parent
2a2bc851bd
commit
5a5bfa2c70
@ -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."
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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')]
|
||||
|
Loading…
Reference in New Issue
Block a user