From ca5332951c68904b0763f79f3612209271206b2a Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Fri, 18 Mar 2011 11:19:53 -0400 Subject: [PATCH] Automatically update IPA LDAP on rpm upgrades Re-enable ldapi code in ipa-ldap-updater and remove the searchbase restriction when run in --upgrade mode. This allows us to autobind giving root Directory Manager powers. This also: * corrects the ipa-ldap-updater man page * remove automatic --realm, --server, --domain options * handle upgrade errors properly * saves a copy of dse.ldif before we change it so it can be recovered * fixes an error discovered by pylint ticket 1087 --- freeipa.spec.in | 8 +++++- install/tools/ipa-ldap-updater | 31 ++++++++++++++------ install/tools/man/ipa-ldap-updater.1 | 31 +++++++++++++------- ipaserver/install/ldapupdate.py | 43 +++++++++++++++++----------- ipaserver/install/upgradeinstance.py | 31 +++++++++++++++++--- 5 files changed, 103 insertions(+), 41 deletions(-) diff --git a/freeipa.spec.in b/freeipa.spec.in index 8962b3f50..53bfb809c 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -279,7 +279,10 @@ if [ $1 = 1 ]; then /sbin/chkconfig --add ipa /sbin/chkconfig --add ipa_kpasswd fi -/usr/sbin/ipa-upgradeconfig || : +if [ $1 -gt 1 ] ; then + /usr/sbin/ipa-upgradeconfig || : + /usr/sbin/ipa-ldap-updater --upgrade >/dev/null 2>&1 || : +fi %preun server if [ $1 = 0 ]; then @@ -478,6 +481,9 @@ fi %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/default.conf %changelog +* Thu Mar 17 2011 Rob Crittenden - 1.99-46 +- Automatically apply updates when the package is upgraded. + * Thu Feb 17 2011 Jakub Hrozek - 1.99-45 - Set minimum version of python-nss to 0.11 to make sure IPv6 support is in diff --git a/install/tools/ipa-ldap-updater b/install/tools/ipa-ldap-updater index 161766e3b..b325e35e9 100755 --- a/install/tools/ipa-ldap-updater +++ b/install/tools/ipa-ldap-updater @@ -23,6 +23,7 @@ # TODO # save undo files? +import os import sys try: from ipapython.config import IPAOptionParser @@ -30,6 +31,7 @@ try: from ipaserver.install import installutils from ipaserver.install.ldapupdate import LDAPUpdate, BadSyntax, UPDATES_DIR from ipaserver.install.upgradeinstance import IPAUpgrade + from ipapython import sysrestore import logging import krbV except ImportError: @@ -57,12 +59,9 @@ def parse_options(): parser.add_option("-u", '--upgrade', action="store_true", dest="upgrade", default=False, help="Upgrade an installed server in offline mode") - config.add_standard_options(parser) options, args = parser.parse_args() safe_options = parser.get_safe_opts(options) - config.init_config(options) - return safe_options, options, args def get_dirman_password(): @@ -75,11 +74,19 @@ def get_dirman_password(): def main(): loglevel = logging.INFO + badsyntax = False safe_options, options, args = parse_options() if options.debug: loglevel = logging.DEBUG + if os.getegid() == 0: + fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + if not fstore.has_files(): + sys.exit("IPA is not configured on this system.") + elif not os.path.exists('/etc/ipa/default.conf'): + sys.exit("IPA is not configured on this system.") + dirman_password = "" if options.password: pw = ipautil.template_file(options.password, []) @@ -93,6 +100,8 @@ def main(): files = args if options.upgrade: + if os.getegid() != 0: + sys.exit('Upgrade can only be done as root') logging.basicConfig(level=loglevel, format='%(levelname)s %(message)s', filename='/var/log/ipaupgrade.log') @@ -101,7 +110,15 @@ def main(): upgrade = IPAUpgrade(realm, files, live_run=not options.test) upgrade.create_instance() modified = upgrade.modified + badsyntax = upgrade.badsyntax else: + if os.getegid() == 0 and options.ldapi: + sys.exit('ldapi cannot be used by root') + # Clear all existing log handlers, this is need to log as root + loggers = logging.getLogger() + if loggers.handlers: + for handler in loggers.handlers: + loggers.removeHandler(handler) logging.basicConfig(level=loglevel, format='%(levelname)s %(message)s') ld = LDAPUpdate(dm_password=dirman_password, sub_dict={}, live_run=not options.test, ldapi=options.ldapi) @@ -109,7 +126,9 @@ def main(): files = ld.get_all_files(UPDATES_DIR) modified = ld.update(files) - if modified and options.test: + if badsyntax: + return 1 + elif modified and options.test: return 2 else: return 0 @@ -128,7 +147,3 @@ except SystemExit, e: sys.exit(e) except KeyboardInterrupt, e: sys.exit(1) -except config.IPAConfigError, e: - print "An IPA server to update cannot be found. Has one been configured yet?" - print "The error was: %s" % e - sys.exit(1) diff --git a/install/tools/man/ipa-ldap-updater.1 b/install/tools/man/ipa-ldap-updater.1 index 795b3681f..9924d2f8e 100644 --- a/install/tools/man/ipa-ldap-updater.1 +++ b/install/tools/man/ipa-ldap-updater.1 @@ -1,21 +1,21 @@ .\" A man page for ipa-ldap-updater .\" Copyright (C) 2008 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 . -.\" +.\" .\" Author: Rob Crittenden -.\" +.\" .TH "ipa-ldap-updater" "1" "Sep 12 2008" "freeipa" "" .SH "NAME" ipa\-ldap\-updater \- Update the IPA LDAP configuration @@ -34,7 +34,9 @@ There are 4 keywords: * default: the starting value * add: add a value (or values) to an attribute * remove: remove a value (or values) from an attribute - * only: set an attribute to this + * only: set an attribute to this + * deleteentry: remove the entry + * replace: replace an existing value, format is old: new Values is a comma\-separated field so multi\-values may be added at one time. Double or single quotes may be put around individual values that contain embedded commas. @@ -48,8 +50,9 @@ The available template variables are: * $FQDN \- the fully\-qualified domain name of the IPA server being updated (ipa.example.com) * $DOMAIN \- the domain name (example.com) * $SUFFIX \- the IPA LDAP suffix (dc=example,dc=com) + * $ESCAPED_SUFFIX \- the ldap-escaped IPA LDAP suffix * $LIBARCH \- set to 64 on x86_64 systems to be used for plugin paths - * $TIME \- an integer representation of current time + * $TIME \- an integer representation of current time A few rules: @@ -59,17 +62,23 @@ A few rules: 4. removing a value that doesn't exist is ok. It is simply ignored. 5. If a DN doesn't exist it is created from the 'default' entry and all updates are applied 6. If a DN does exist the default values are skipped - 7. Only the first rule on a line is respected + 7. Only the first rule on a line is respected .SH "OPTIONS" -.TP +.TP \fB\-d\fR, \fB\-\-debug Enable debug logging when more verbose output is needed -.TP +.TP \fB\-t\fR, \fB\-\-test\fR Run through the update without changing anything. If changes are available then the command returns 2. If no updates are available it returns 0. -.TP +.TP \fB\-y\fR File containing the Directory Manager password +.TP +\fB\-l\fR, \fB\-\-ldapi\fR +Connect to the LDAP server using the ldapi socket +.TP +\fB\-u\fR, \fB\-\-\-upgrade\fR +Upgrade an installed server in offline mode (implies \-\-ldapi) .SH "EXIT STATUS" 0 if the command was successful diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py index a2eebebcc..44a95465f 100644 --- a/ipaserver/install/ldapupdate.py +++ b/ipaserver/install/ldapupdate.py @@ -109,7 +109,7 @@ class LDAPUpdate: if online: # Try out the password - #if not self.ldapi: + if not self.ldapi: try: conn = ipaldap.IPAdmin(fqdn, ldapi=True, realm=self.realm) conn.do_simple_bind(binddn="cn=directory manager", bindpw=self.dm_password) @@ -120,13 +120,16 @@ class LDAPUpdate: raise RuntimeError("Unable to connect to LDAP server %s" % fqdn) except ldap.INVALID_CREDENTIALS: raise RuntimeError("The password provided is incorrect for LDAP server %s" % fqdn) - # THIS IS COMMENTED OUT, BECAUSE: - # external_bind does work, but even as root, you don't always have - # enought power to do everything we need due to strict ACI rules - # - #else: - # conn = ipaldap.IPAdmin(ldapi=True, realm=self.realm) - # conn.do_external_bind(self.pw_name) + else: + conn = ipaldap.IPAdmin(ldapi=True, realm=self.realm) + try: + if os.getegid() == 0: + # autobind + conn.do_external_bind(self.pw_name) + else: + conn.do_sasl_gssapi_bind() + except ldap.LOCAL_ERROR, e: + raise RuntimeError('%s' % e.args[0].get('info', '').strip()) else: raise RuntimeError("Offline updates are not supported.") @@ -476,7 +479,7 @@ class LDAPUpdate: try: (old, new) = v.split(':', 1) except ValueError: - raise BadSyntax, "bad syntax in replace, needs to be in the format old: new in %s" % new_entry.dn + raise BadSyntax, "bad syntax in replace, needs to be in the format old: new in %s" % v try: e.remove(old) e.append(new) @@ -596,6 +599,9 @@ class LDAPUpdate: except errors.DatabaseError, e: logging.error("Update failed: %s", e) updated = False + except errors.ACIError, e: + logging.error("Update failed: %s", e) + updated = False if ("cn=index" in entry.dn and "cn=userRoot" in entry.dn): @@ -654,14 +660,17 @@ class LDAPUpdate: try: if self.online: - # THIS IS COMMENTED OUT, BECAUSE: - # external_bind does work, but even as root, you don't always have - # enought power to do everything we need due to strict ACI rules - # - #if self.ldapi: - # self.conn = ipaldap.IPAdmin(ldapi=True, realm=self.realm) - # self.conn.do_external_bind(self.pw_name) - #else: + if self.ldapi: + self.conn = ipaldap.IPAdmin(ldapi=True, realm=self.realm) + try: + if os.getegid() == 0: + # autobind + self.conn.do_external_bind(self.pw_name) + else: + self.conn.do_sasl_gssapi_bind() + except ldap.LOCAL_ERROR, e: + raise RuntimeError('%s' % e.args[0].get('info', '').strip()) + else: self.conn = ipaldap.IPAdmin(self.sub_dict['FQDN'], ldapi=self.ldapi, realm=self.realm) diff --git a/ipaserver/install/upgradeinstance.py b/ipaserver/install/upgradeinstance.py index 156c4c2db..ad977b745 100644 --- a/ipaserver/install/upgradeinstance.py +++ b/ipaserver/install/upgradeinstance.py @@ -19,6 +19,8 @@ import os import sys +import shutil +import random from ipaserver.install import installutils from ipaserver.install import dsinstance @@ -41,12 +43,19 @@ class IPAUpgrade(service.Service): live_run: boolean that defines if we are in test or live mode. """ + ext = '' + rand = random.Random() + for i in range(8): + h = "%02x" % rand.randint(0,255) + ext += h service.Service.__init__(self, "dirsrv") serverid = dsinstance.realm_to_serverid(realm_name) self.filename = '%s%s/%s' % (DSBASE, serverid, DSE) + self.savefilename = '%s%s/%s.ipa.%s' % (DSBASE, serverid, DSE, ext) self.live_run = live_run self.files = files self.modified = False + self.badsyntax = False def create_instance(self): self.step("stopping directory server", self.stop) @@ -61,21 +70,26 @@ class IPAUpgrade(service.Service): self.start_creation("Upgrading IPA:") def __save_config(self): + shutil.copy2(self.filename, self.savefilename) port = installutils.get_directive(self.filename, 'nsslapd-port', separator=':') security = installutils.get_directive(self.filename, 'nsslapd-security', separator=':') autobind = installutils.get_directive(self.filename, 'nsslapd-ldapiautobind', separator=':') + searchbase = installutils.get_directive(self.filename, + 'nsslapd-ldapientrysearchbase', separator=':') self.backup_state('nsslapd-port', port) self.backup_state('nsslapd-security', security) self.backup_state('nsslapd-ldapiautobind', autobind) + self.backup_state('nsslapd-ldapientrysearchbase', searchbase) def __restore_config(self): port = self.restore_state('nsslapd-port') security = self.restore_state('nsslapd-security') autobind = self.restore_state('nsslapd-ldapiautobind') + searchbase = self.restore_state('nsslapd-ldapientrysearchbase') installutils.set_directive(self.filename, 'nsslapd-port', port, quotes=False, separator=':') @@ -83,6 +97,9 @@ class IPAUpgrade(service.Service): security, quotes=False, separator=':') installutils.set_directive(self.filename, 'nsslapd-ldapiautobind', autobind, quotes=False, separator=':') + installutils.set_directive(self.filename, + 'nsslapd-ldapientrysearchbase', + searchbase, quotes=False, separator=':') def __disable_listeners(self): installutils.set_directive(self.filename, 'nsslapd-port', @@ -91,12 +108,18 @@ class IPAUpgrade(service.Service): 'off', quotes=False, separator=':') installutils.set_directive(self.filename, 'nsslapd-ldapiautobind', 'on', quotes=False, separator=':') + installutils.set_directive(self.filename, 'nsslapd-ldapientrysearchbase', + '', quotes=False, separator=':') def __upgrade(self): - ld = ldapupdate.LDAPUpdate(dm_password='', ldapi=True, live_run=self.live_run) - if len(self.files) == 0: - self.files = ld.get_all_files(ldapupdate.UPDATES_DIR) - self.modified = ld.update(self.files) + try: + ld = ldapupdate.LDAPUpdate(dm_password='', ldapi=True, live_run=self.live_run) + if len(self.files) == 0: + self.files = ld.get_all_files(ldapupdate.UPDATES_DIR) + self.modified = ld.update(self.files) + except ldapupdate.BadSyntax: + self.modified = False + self.badsyntax = True def main(): if os.getegid() != 0: