mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-23 07:33:27 -06:00
851f6d48ac
Pylint 2.9 introduced new check: > New checker consider-using-dict-items. Emitted when iterating over dictionary keys and then indexing the same dictionary with the key within loop body. Fixes: https://pagure.io/freeipa/issue/9117 Signed-off-by: Stanislav Levin <slev@altlinux.org> Reviewed-By: Rob Crittenden <rcritten@redhat.com>
498 lines
18 KiB
Python
498 lines
18 KiB
Python
#!/usr/bin/python3
|
|
# Authors: Rob Crittenden <rcritten@redhat.com>
|
|
#
|
|
# Based on ipa-replica-manage by Karl MacMillan <kmacmillan@mentalrootkit.com>
|
|
#
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
from __future__ import print_function
|
|
|
|
import logging
|
|
import sys
|
|
import os
|
|
|
|
from ipaplatform.paths import paths
|
|
from ipaserver.install import (replication, installutils, bindinstance,
|
|
cainstance)
|
|
from ipalib import api, errors
|
|
from ipalib.constants import FQDN
|
|
from ipalib.util import has_managed_topology, print_replication_status
|
|
from ipapython import ipautil, ipaldap, version
|
|
from ipapython.admintool import ScriptError
|
|
from ipapython.dn import DN
|
|
|
|
logger = logging.getLogger(os.path.basename(__file__))
|
|
|
|
# dict of command name and tuples of min/max num of args needed
|
|
commands = {
|
|
"list": (0, 1, "[master fqdn]", ""),
|
|
"connect": (1, 2, "<master fqdn> [other master fqdn]",
|
|
"must provide the name of the servers to connect"),
|
|
"disconnect": (1, 2, "<master fqdn> [other master fqdn]",
|
|
"must provide the name of the server to disconnect"),
|
|
"del": (1, 1, "<master fqdn>",
|
|
"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 # pylint: disable=deprecated-module
|
|
|
|
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
|
|
for cmd, args_info in commands.items():
|
|
if cmd == args[0]:
|
|
err = None
|
|
if n < args_info[0]:
|
|
err = args_info[3]
|
|
elif n > args_info[1]:
|
|
err = "too many arguments"
|
|
else:
|
|
valid_syntax = True
|
|
if err:
|
|
parser.error(
|
|
"Invalid syntax: %s\nUsage: %s [options] %s" % (
|
|
err, cmd, args_info[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
|
|
ldap_uri = ipaldap.get_ldap_uri(host, 636, cacert=paths.IPA_CA_CRT)
|
|
conn = ipaldap.LDAPClient(ldap_uri, cacert=paths.IPA_CA_CRT)
|
|
conn.simple_bind(bind_dn=ipaldap.DIRMAN_DN,
|
|
bind_password=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 as 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.items():
|
|
print('%s: %s' % (k, p[0]))
|
|
return
|
|
|
|
try:
|
|
repl = replication.get_cs_replication_manager(realm, replica, dirman_passwd)
|
|
except Exception as e:
|
|
sys.exit(str(e))
|
|
|
|
entries = repl.find_replication_agreements()
|
|
|
|
for entry in entries:
|
|
print('%s' % entry.single_value.get('nsds5replicahost'))
|
|
print_replication_status(entry, verbose)
|
|
|
|
|
|
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 as e:
|
|
sys.exit("Unable to connect to %s: %s" % (replica1, e))
|
|
except Exception as 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 entry in repl_list:
|
|
if entry.single_value.get('nsDS5ReplicaHost') == replica1:
|
|
replica2_dn = entry.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 as exc:
|
|
print("Failed to get data from '%s': %s" % (replica2, exc))
|
|
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 as exc:
|
|
print("Unable to remove agreement on %s: %s" % (replica2, exc))
|
|
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):
|
|
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 as 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 as 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)
|
|
|
|
# 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 as e:
|
|
sys.exit("There were issues removing a connection: %s" % e)
|
|
|
|
# 6. Pick CA renewal master
|
|
ca = cainstance.CAInstance(api.env.realm)
|
|
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(api.env.basedn):
|
|
bind = bindinstance.BindInstance()
|
|
bind.update_system_records()
|
|
except Exception as 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 as e:
|
|
sys.exit(str(e))
|
|
try:
|
|
ldap_uri = ipaldap.get_ldap_uri(replica2, 636, cacert=paths.IPA_CA_CRT)
|
|
conn = ipaldap.LDAPClient(ldap_uri, cacert=paths.IPA_CA_CRT)
|
|
conn.simple_bind(bind_dn=ipaldap.DIRMAN_DN,
|
|
bind_password=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 as e:
|
|
sys.exit("Unable to connect to %s: %s" % (ipautil.format_netloc(replica2, 636), str(e)))
|
|
except Exception as 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 as e:
|
|
sys.exit("Unable to connect to %s: %s" % (replica1, e))
|
|
except Exception as 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 <host name>")
|
|
|
|
thishost = 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 as 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:
|
|
logger.error("Unable to find %s -> %s replication agreement",
|
|
options.fromhost, thishost)
|
|
sys.exit(1)
|
|
if len(entry) > 1:
|
|
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 as e:
|
|
sys.exit(str(e))
|
|
|
|
def set_renewal_master(realm, replica):
|
|
if not replica:
|
|
replica = FQDN
|
|
|
|
ca = cainstance.CAInstance(realm)
|
|
if ca.is_renewal_master(replica):
|
|
sys.exit("%s is already the renewal master" % replica)
|
|
|
|
try:
|
|
ca.set_renewal_master(replica)
|
|
except Exception as e:
|
|
sys.exit("Failed to set renewal master to %s: %s" % (replica, e))
|
|
|
|
print("%s is now the renewal master" % replica)
|
|
|
|
|
|
def exit_on_managed_topology(what, hint="topologysegment"):
|
|
if hint == "topologysegment":
|
|
hinttext = ("Please use `ipa topologysegment-*` commands to manage "
|
|
"the topology.")
|
|
elif hint == "ipa-replica-manage-del":
|
|
hinttext = ("Please use the `ipa-replica-manage del` command.")
|
|
else:
|
|
assert False, "Unexpected value"
|
|
sys.exit("{0} is deprecated with managed IPA replication topology. {1}"
|
|
.format(what, hinttext))
|
|
|
|
|
|
def main():
|
|
installutils.check_server_configuration()
|
|
options, args = parse_options()
|
|
|
|
# Just initialize the environment. This is so the installer can have
|
|
# access to the plugin environment
|
|
api_env = {}
|
|
if os.getegid() != 0:
|
|
api_env['log'] = None # turn off logging for non-root
|
|
|
|
api.bootstrap(
|
|
context='cli',
|
|
in_server=True,
|
|
verbose=options.verbose,
|
|
confdir=paths.ETC_IPA,
|
|
**api_env
|
|
)
|
|
api.finalize()
|
|
|
|
dirman_passwd = None
|
|
realm = api.env.realm
|
|
|
|
if options.host:
|
|
host = options.host
|
|
else:
|
|
host = 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
|
|
|
|
api.Backend.ldap2.connect(bind_pw=options.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":
|
|
if has_managed_topology(api):
|
|
exit_on_managed_topology(
|
|
"Removal of IPA CS replication agreement and replication data",
|
|
hint="ipa-replica-manage-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 <host name>")
|
|
force_sync(realm, host, options.fromhost, options.dirman_passwd)
|
|
elif args[0] == "connect":
|
|
if has_managed_topology(api):
|
|
exit_on_managed_topology("Creation of IPA CS replication agreement")
|
|
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 has_managed_topology(api):
|
|
exit_on_managed_topology("Removal of IPA CS replication agreement")
|
|
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)
|
|
|
|
api.Backend.ldap2.disconnect()
|
|
|
|
try:
|
|
main()
|
|
except KeyboardInterrupt:
|
|
sys.exit(1)
|
|
except (SystemExit, ScriptError) as e:
|
|
sys.exit(e)
|
|
except Exception as e:
|
|
sys.exit("unexpected error: %s" % e)
|