#! /usr/bin/python2 -E # Authors: Rob Crittenden # # Based on ipa-replica-manage by Karl MacMillan # # Copyright (C) 2011 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # import sys import os import krbV from ipapython.ipa_log_manager import * from ipaserver.install import (replication, installutils, bindinstance, cainstance, certs) from ipalib import api, errors, util from ipalib.constants import CACERT from ipapython import ipautil, ipaldap, version, dogtag from ipapython.dn import DN # dict of command name and tuples of min/max num of args needed commands = { "list": (0, 1, "[master fqdn]", ""), "connect": (1, 2, " [other master fqdn]", "must provide the name of the servers to connect"), "disconnect": (1, 2, " [other master fqdn]", "must provide the name of the server to disconnect"), "del": (1, 1, "", "must provide hostname of master to delete"), "re-initialize": (0, 0, "", ""), "force-sync": (0, 0, "", ""), "set-renewal-master": (0, 1, "[master fqdn]", "") } def parse_options(): from optparse import OptionParser parser = OptionParser(version=version.VERSION) parser.add_option("-H", "--host", dest="host", help="starting host") 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("-f", "--force", dest="force", action="store_true", default=False, help="ignore some types of errors") parser.add_option("--from", dest="fromhost", help="Host to get data from") options, args = parser.parse_args() valid_syntax = False if len(args): n = len(args) - 1 k = commands.keys() for cmd in k: if cmd == args[0]: v = commands[cmd] err = None if n < v[0]: err = v[3] elif n > v[1]: err = "too many arguments" else: valid_syntax = True if err: parser.error("Invalid syntax: %s\nUsage: %s [options] %s" % (err, cmd, v[2])) if not valid_syntax: cmdstr = " | ".join(commands.keys()) parser.error("must provide a command [%s]" % cmdstr) return options, args def list_replicas(realm, host, replica, dirman_passwd, verbose): peers = {} try: # connect to main IPA LDAP server conn = ipaldap.IPAdmin(host, 636, cacert=CACERT) conn.do_simple_bind(bindpw=dirman_passwd) dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), ipautil.realm_to_suffix(realm)) entries = conn.get_entries(dn, conn.SCOPE_ONELEVEL) for ent in entries: try: cadn = DN(('cn', 'CA'), DN(ent.dn)) entry = conn.get_entry(cadn) peers[ent.single_value['cn']] = ['master', ''] except errors.NotFound: peers[ent.single_value['cn']] = ['CA not configured', ''] except Exception, e: sys.exit( "Failed to get data from '%s' while trying to list replicas: %s" % (host, e)) finally: conn.unbind() if not replica: for k, p in peers.iteritems(): print '%s: %s' % (k, p[0]) return try: repl = replication.get_cs_replication_manager(realm, replica, dirman_passwd) except Exception, e: sys.exit(str(e)) entries = repl.find_replication_agreements() for entry in entries: print '%s' % entry.single_value.get('nsds5replicahost') if verbose: print " last init status: %s" % entry.single_value.get( 'nsds5replicalastinitstatus') print " last init ended: %s" % str( ipautil.parse_generalized_time( entry.single_value['nsds5replicalastinitend'])) print " last update status: %s" % entry.single_value.get( 'nsds5replicalastupdatestatus') print " last update ended: %s" % str( ipautil.parse_generalized_time( entry.single_value['nsds5replicalastupdateend'])) def del_link(realm, replica1, replica2, dirman_passwd, force=False): repl2 = None try: repl1 = replication.get_cs_replication_manager(realm, replica1, dirman_passwd) repl1.hostnames = [replica1, replica2] repl_list1 = repl1.find_replication_agreements() # Find the DN of the replication agreement to remove replica1_dn = None for e in repl_list1: if e.single_value.get('nsDS5ReplicaHost') == replica2: replica1_dn = e.dn break if replica1_dn is None: sys.exit("'%s' has no replication agreement for '%s'" % (replica1, replica2)) repl1.hostnames = [replica1, replica2] except errors.NetworkError, e: sys.exit("Unable to connect to %s: %s" % (replica1, e)) except Exception, e: sys.exit("Failed to get data from '%s': %s" % (replica1, e)) try: repl2 = replication.get_cs_replication_manager(realm, replica2, dirman_passwd) repl2.hostnames = [replica1, replica2] repl_list = repl2.find_replication_agreements() # Now that we've confirmed that both hostnames are vaild, make sure # that we aren't removing the last link from either side. if not force and len(repl_list) <= 1: print "Cannot remove the last replication link of '%s'" % replica2 print "Please use the 'del' command to remove it from the domain" sys.exit(1) if not force and len(repl_list1) <= 1: print "Cannot remove the last replication link of '%s'" % replica1 print "Please use the 'del' command to remove it from the domain" sys.exit(1) # Find the DN of the replication agreement to remove replica2_dn = None for e in repl_list: if e.single_value.get('nsDS5ReplicaHost') == replica1: replica2_dn = e.dn break # This should never happen if replica2_dn is None: sys.exit("'%s' has no replication agreement for '%s'" % (replica1, replica2)) except errors.NotFound: print "'%s' has no replication agreement for '%s'" % (replica2, replica1) if not force: return except Exception, e: print "Failed to get data from '%s': %s" % (replica2, e) if not force: sys.exit(1) if repl2: failed = False try: repl2.delete_agreement(replica1, replica2_dn) repl2.delete_referral(replica1, repl1.port) except Exception, e: print "Unable to remove agreement on %s: %s" % (replica2, e) failed = True if failed: if force: print "Forcing removal on '%s'" % replica1 else: sys.exit(1) if not repl2 and force: print "Forcing removal on '%s'" % replica1 repl1.delete_agreement(replica2, replica1_dn) repl1.delete_referral(replica2, repl2.port) print "Deleted replication agreement from '%s' to '%s'" % (replica1, replica2) def del_master(realm, hostname, options): force_del = False delrepl = None # 1. Connect to the local dogtag DS server try: thisrepl = replication.get_cs_replication_manager(realm, options.host, options.dirman_passwd) except Exception, e: sys.exit("Failed to connect to server %s: %s" % (options.host, e)) # 2. Ensure we have an agreement with the master if thisrepl.get_replication_agreement(hostname) is None: sys.exit("'%s' has no replication agreement for '%s'" % (options.host, hostname)) # 3. Connect to the dogtag DS to be removed. try: delrepl = replication.get_cs_replication_manager(realm, hostname, options.dirman_passwd) except Exception, e: if not options.force: print "Unable to delete replica %s: %s" % (hostname, e) sys.exit(1) else: print "Unable to connect to replica %s, forcing removal" % hostname force_del = True # 4. Get list of agreements. if delrepl is None: # server not up, just remove it from this server replica_names = [options.host] else: replica_entries = delrepl.find_ipa_replication_agreements() replica_names = [rep.single_value.get('nsds5replicahost') for rep in replica_entries] # 5. Remove each agreement for r in replica_names: try: del_link(realm, r, hostname, options.dirman_passwd, force=True) except Exception, e: sys.exit("There were issues removing a connection: %s" % e) # 6. Pick CA renewal master ca = cainstance.CAInstance(api.env.realm, certs.NSS_DIR) if ca.is_renewal_master(hostname): ca.set_renewal_master(options.host) # 7. And clean up the removed replica DNS entries if any. try: if bindinstance.dns_container_exists(options.host, api.env.basedn, dm_password=options.dirman_passwd): api.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=options.dirman_passwd) bind = bindinstance.BindInstance() bind.remove_ipa_ca_dns_records(hostname, realm.lower()) except Exception, e: print "Failed to cleanup %s DNS entries: %s" % (hostname, e) print "You may need to manually remove them from the tree" def add_link(realm, replica1, replica2, dirman_passwd, options): try: repl2 = replication.get_cs_replication_manager(realm, replica2, dirman_passwd) except Exception, e: sys.exit(str(e)) try: conn = ipaldap.IPAdmin(replica2, 636, cacert=CACERT) conn.do_simple_bind(bindpw=dirman_passwd) dn = DN(('cn', 'CA'), ('cn', replica2), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), ipautil.realm_to_suffix(realm)) conn.get_entries(dn, conn.SCOPE_BASE) conn.unbind() except errors.NotFound: sys.exit('%s does not have a CA configured.' % replica2) except errors.NetworkError, e: sys.exit("Unable to connect to %s: %s" % (ipautil.format_netloc(replica2, 636), str(e))) except Exception, e: sys.exit("Failed to get data while trying to bind to '%s': %s" % (replica1, str(e))) try: repl1 = replication.get_cs_replication_manager(realm, replica1, dirman_passwd) entries = repl1.find_replication_agreements() for e in entries: if e.single_value.get('nsDS5ReplicaHost') == replica2: sys.exit('This replication agreement already exists.') repl1.hostnames = [replica1, replica2] except errors.NotFound: sys.exit("Cannot find replica '%s'" % replica1) except errors.NetworkError, e: sys.exit("Unable to connect to %s: %s" % (replica1, e)) except Exception, e: sys.exit( "Failed to get data from '%s' while trying to get current " "agreements: %s" % (replica1, e)) repl1.setup_replication( replica2, repl2.port, 0, DN(('cn', 'Directory Manager')), dirman_passwd, is_cs_replica=True, local_port=repl1.port) print "Connected '%s' to '%s'" % (replica1, replica2) def re_initialize(realm, options): if not options.fromhost: sys.exit("re-initialize requires the option --from ") thishost = installutils.get_fqdn() try: repl = replication.get_cs_replication_manager(realm, options.fromhost, options.dirman_passwd) thisrepl = replication.get_cs_replication_manager(realm, thishost, options.dirman_passwd) except Exception, e: sys.exit(str(e)) filter = repl.get_agreement_filter(host=thishost) try: entry = repl.conn.get_entries( DN(('cn', 'config')), repl.conn.SCOPE_SUBTREE, filter) except errors.NotFound: root_logger.error("Unable to find %s -> %s replication agreement" % (options.fromhost, thishost)) sys.exit(1) if len(entry) > 1: root_logger.error("Found multiple agreements for %s. Only initializing the first one returned: %s" % (thishost, entry[0].dn)) repl.hostnames = thisrepl.hostnames = [thishost, options.fromhost] thisrepl.enable_agreement(options.fromhost) repl.enable_agreement(thishost) repl.initialize_replication(entry[0].dn, repl.conn) repl.wait_for_repl_init(repl.conn, entry[0].dn) def force_sync(realm, thishost, fromhost, dirman_passwd): try: repl = replication.get_cs_replication_manager(realm, fromhost, dirman_passwd) repl.force_sync(repl.conn, thishost) except Exception, e: sys.exit(str(e)) def set_renewal_master(realm, replica): if not replica: replica = installutils.get_fqdn() ca = cainstance.CAInstance(realm, certs.NSS_DIR) if ca.is_renewal_master(replica): sys.exit("%s is already the renewal master" % replica) try: ca.set_renewal_master(replica) except Exception, e: sys.exit("Failed to set renewal master to %s: %s" % (replica, e)) print "%s is now the renewal master" % replica def main(): options, args = parse_options() # Just initialize the environment. This is so the installer can have # access to the plugin environment api_env = {'in_server' : True, 'verbose' : options.verbose, } if os.getegid() != 0: api_env['log'] = None # turn off logging for non-root api.bootstrap(**api_env) api.finalize() dirman_passwd = None realm = krbV.default_context().default_realm if options.host: host = options.host else: host = installutils.get_fqdn() options.host = host if options.dirman_passwd: dirman_passwd = options.dirman_passwd else: dirman_passwd = installutils.read_password("Directory Manager", confirm=False, validate=False, retry=False) if dirman_passwd is None: sys.exit("Directory Manager password required") options.dirman_passwd = dirman_passwd if args[0] == "list": replica = None if len(args) == 2: replica = args[1] list_replicas(realm, host, replica, dirman_passwd, options.verbose) elif args[0] == "del": del_master(realm, args[1], options) elif args[0] == "re-initialize": re_initialize(realm, options) elif args[0] == "force-sync": if not options.fromhost: sys.exit("force-sync requires the option --from ") force_sync(realm, host, options.fromhost, options.dirman_passwd) elif args[0] == "connect": if len(args) == 3: replica1 = args[1] replica2 = args[2] elif len(args) == 2: replica1 = host replica2 = args[1] add_link(realm, replica1, replica2, dirman_passwd, options) elif args[0] == "disconnect": if len(args) == 3: replica1 = args[1] replica2 = args[2] elif len(args) == 2: replica1 = host replica2 = args[1] del_link(realm, replica1, replica2, dirman_passwd, options.force) elif args[0] == 'set-renewal-master': replica = None if len(args) > 1: replica = args[1] set_renewal_master(realm, replica) try: main() except KeyboardInterrupt: sys.exit(1) except SystemExit, e: sys.exit(e) except Exception, e: sys.exit("unexpected error: %s" % e)