From aab181f0a952a56034278b13b1fdc224132d66f4 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Tue, 7 Feb 2012 15:26:00 -0500
Subject: [PATCH] Add tool to add memberOf to replication attribute exclusion
list
We were creating replication agreements without excluding memberOf
which could cause unnecessary replication traffic.
https://fedorahosted.org/freeipa/ticket/2218
---
install/tools/Makefile.am | 1 +
install/tools/ipa-fixreplica | 129 ++++++++++++++++++++++++++++++++++++
install/tools/man/Makefile.am | 1 +
install/tools/man/ipa-fixreplica.1 | 51 ++++++++++++++
ipaserver/install/replication.py | 29 +++++----
6 files changed, 203 insertions(+), 12 deletions(-)
create mode 100755 install/tools/ipa-fixreplica
create mode 100644 install/tools/man/ipa-fixreplica.1
diff --git a/install/tools/Makefile.am b/install/tools/Makefile.am
index 7e071af..cf4d7fd 100644
--- a/install/tools/Makefile.am
+++ b/install/tools/Makefile.am
@@ -12,6 +12,7 @@ sbin_SCRIPTS = \
ipa-replica-install \
ipa-replica-prepare \
ipa-replica-manage \
+ ipa-fixreplica \
ipa-csreplica-manage \
ipa-server-certinstall \
ipactl \
diff --git a/install/tools/ipa-fixreplica b/install/tools/ipa-fixreplica
new file mode 100755
index 0000000..e444853
--- /dev/null
+++ b/install/tools/ipa-fixreplica
@@ -0,0 +1,129 @@
+#!/usr/bin/python
+# Authors: Rob Crittenden <rcritten@redhat.com>
+#
+# Copyright (C) 2012 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/>.
+#
+
+import sys
+import os
+import pwd
+try:
+ from optparse import OptionParser
+ from ipapython import ipautil, config
+ from ipaserver import ipaldap
+ from ipaserver.install import installutils, replication
+ from ipaserver.plugins.ldap2 import ldap2
+ from ipalib import api, errors
+except ImportError:
+ print >> sys.stderr, """\
+There was a problem importing one of the required Python modules. The
+error was:
+
+ %s
+""" % sys.exc_value
+ sys.exit(1)
+
+def parse_options():
+ usage = "%prog [options]\n"
+ parser = OptionParser(usage=usage, formatter=config.IPAFormatter())
+
+ parser.add_option("-t", "--test", action="store_true", dest="test",
+ help="Run in test mode, no changes are applied")
+ parser.add_option("-d", "--debug", action="store_true", dest="debug",
+ help="Display debugging information about the update(s)")
+ parser.add_option("-y", dest="password",
+ help="File containing the Directory Manager password")
+
+ options, args = parser.parse_args()
+
+ return options, args
+
+def get_dirman_password():
+ """Prompt the user for the Directory Manager password and verify its
+ correctness.
+ """
+ password = installutils.read_password("Directory Manager", confirm=False, validate=False)
+
+ return password
+
+def main():
+ retval = 0
+
+ options, args = parse_options()
+
+ if os.getegid() == 0:
+ installutils.standard_logging_setup("/var/log/ipaserver-fixreplica.log", options.debug, filemode='a')
+
+ api.bootstrap(context='cli', debug=options.debug)
+ api.finalize()
+
+ conn = None
+ dirman_password = ""
+ if os.getegid() == 0:
+ conn = ipaldap.IPAdmin(api.env.host, ldapi=True, realm=api.env.realm)
+ conn.do_external_bind(pwd.getpwuid(os.geteuid()).pw_name)
+ else:
+ if options.password:
+ pw = ipautil.template_file(options.password, [])
+ dirman_password = pw.strip()
+ else:
+ dirman_password = get_dirman_password()
+ if dirman_password is None:
+ sys.exit("\nDirectory Manager password required")
+
+ repl = replication.ReplicationManager(api.env.realm, api.env.host,
+ dirman_password, conn=conn)
+ entries = repl.find_replication_agreements()
+ print "Found %d agreement(s)" % len(entries)
+ for replica in entries:
+ print "%s: " % replica.description
+ if 'memberof' not in replica.nsDS5ReplicatedAttributeList:
+ print " Attribute list needs updating"
+ current = replica.toDict()
+ replica.setValue('nsDS5ReplicatedAttributeList',
+ replica.nsDS5ReplicatedAttributeList + ' memberof')
+ if not options.test:
+ try:
+ repl.conn.updateEntry(replica.dn, current, replica.toDict())
+ print " Updated"
+ except Exception, e:
+ print "Error caught updating replica: %s" % str(e)
+ retval = 1
+ else:
+ print " Test mode, not updating"
+ retval = 2
+ else:
+ print " Attribute list ok"
+
+ return retval
+
+try:
+ if __name__ == "__main__":
+ sys.exit(main())
+except RuntimeError, e:
+ print "%s" % e
+ sys.exit(1)
+except SystemExit, e:
+ sys.exit(e)
+except KeyboardInterrupt, e:
+ sys.exit(1)
+except config.IPAConfigError, e:
+ print "IPA replica not configured."
+ sys.exit(0)
+except errors.LDAPError, e:
+ print "An error occurred while performing operations: %s" % e
+ sys.exit(1)
diff --git a/install/tools/man/Makefile.am b/install/tools/man/Makefile.am
index 43dc57f..fd219f1 100644
--- a/install/tools/man/Makefile.am
+++ b/install/tools/man/Makefile.am
@@ -6,6 +6,7 @@ NULL=
man1_MANS = \
ipa-replica-conncheck.1 \
+ ipa-fixreplica.1 \
ipa-replica-install.1 \
ipa-replica-manage.1 \
ipa-csreplica-manage.1 \
diff --git a/install/tools/man/ipa-fixreplica.1 b/install/tools/man/ipa-fixreplica.1
new file mode 100644
index 0000000..9af72cc
--- /dev/null
+++ b/install/tools/man/ipa-fixreplica.1
@@ -0,0 +1,51 @@
+.\" A man page for ipa-fixreplica
+.\" Copyright (C) 2012 Red Hat, Inc.
+.\"
+.\" 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/>.
+.\"
+.\" Author: Rob Crittenden <rcritten@redhat.com>
+.\"
+.TH "ipa-fixreplica" "1" "Jan 6 2012" "FreeIPA" "FreeIPA Manual Pages"
+.SH "NAME"
+ipa\-fixreplica \- Add memberOf as excluded attribute to replication agreements
+.SH "SYNOPSIS"
+ipa\-fixreplica [options]
+.SH "DESCRIPTION"
+The memberOf attribute was not properly added to some replication agreements
+causing unnecessary replication. This tool will update all replication
+agreements for the current host and add memberOf to the exclusion list.
+
+When executed as root no password is required, otherwise it will prompt
+for the Directory Manager password.
+
+This is executed when the package is updated in rpm so should not need
+to be run by an end-user. It is not harmful to run multiple times.
+
+This needs to be run on all replicas to be truly effective.
+.SH "OPTIONS"
+.TP
+\fB\-d\fR, \fB\-\-debug\fR
+Enable debug logging when more verbose output is needed
+.TP
+\fB\-t\fR, \fB\-\-test\fR
+Run in test mode, no changes are applied
+.TP
+\fB\-y\fR \fIfile\fR
+File containing the Directory Manager password
+.SH "EXIT STATUS"
+0 if the command was successful or IPA is not configured
+
+1 if an error occurred
+
+2 If during test mode it determines that there are agreements that need updating
diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
index 8c8502e..79c0fe7 100644
--- a/ipaserver/install/replication.py
+++ b/ipaserver/install/replication.py
@@ -99,7 +99,7 @@ def enable_replication_version_checking(hostname, realm, dirman_passwd):
class ReplicationManager(object):
"""Manage replication agreements between DS servers, and sync
agreements with Windows servers"""
- def __init__(self, realm, hostname, dirman_passwd, port=PORT, starttls=False):
+ def __init__(self, realm, hostname, dirman_passwd, port=PORT, starttls=False, conn=None):
self.hostname = hostname
self.port = port
self.dirman_passwd = dirman_passwd
@@ -108,18 +108,23 @@ class ReplicationManager(object):
tmp = util.realm_to_suffix(realm)
self.suffix = str(DN(tmp)).lower()
- # If we are passed a password we'll use it as the DM password
- # otherwise we'll do a GSSAPI bind.
- if starttls:
- self.conn = ipaldap.IPAdmin(hostname, port=port)
- ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, CACERT)
- self.conn.start_tls_s()
- else:
- self.conn = ipaldap.IPAdmin(hostname, port=port, cacert=CACERT)
- if dirman_passwd:
- self.conn.do_simple_bind(bindpw=dirman_passwd)
+ # The caller is allowed to pass in an existing IPAdmin connection.
+ # Open a new one if not provided
+ if conn is None:
+ # If we are passed a password we'll use it as the DM password
+ # otherwise we'll do a GSSAPI bind.
+ if starttls:
+ self.conn = ipaldap.IPAdmin(hostname, port=port)
+ ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, CACERT)
+ self.conn.start_tls_s()
+ else:
+ self.conn = ipaldap.IPAdmin(hostname, port=port, cacert=CACERT)
+ if dirman_passwd:
+ self.conn.do_simple_bind(bindpw=dirman_passwd)
+ else:
+ self.conn.do_sasl_gssapi_bind()
else:
- self.conn.do_sasl_gssapi_bind()
+ self.conn = conn
self.repl_man_passwd = dirman_passwd
--
1.7.6