Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
#!/usr/bin/python # Authors: Rob Crittenden <rcritten@redhat.com # # Copyright (C) 2013 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/>. #
CSReplicationManager, get_cs_replication_manager)
''' Change ownership of all files and directories in a path. ''' for root, dirs, files in os.walk(path): for dir in dirs: os.chown(os.path.join(root, dir), uid, gid) os.chmod(os.path.join(root, dir), 0750) for file in files: os.chown(os.path.join(root, file), uid, gid) os.chmod(os.path.join(root, file), 0640)
source = filename (dest, ext) = os.path.splitext(filename)
if ext != '.gpg': raise admintool.ScriptError('Trying to decrypt a non-gpg file')
dest = os.path.basename(dest) dest = os.path.join(tmpdir, dest)
args = ['/usr/bin/gpg', '--batch', '-o', dest]
if keyring is not None: args.append('--no-default-keyring') args.append('--keyring') args.append(keyring + '.pub') args.append('--secret-keyring') args.append(keyring + '.sec')
args.append('-d') args.append(source)
(stdout, stderr, rc) = run(args, raiseonerr=False) if rc != 0: raise admintool.ScriptError('gpg failed: %s' % stderr)
return dest
super(Restore, self).__init__(options, args) self._conn = None
def add_options(cls, parser): super(Restore, cls).add_options(parser, debug_option=True)
parser.add_option("-p", "--password", dest="password", help="Directory Manager password") parser.add_option("--gpg-keyring", dest="gpg_keyring", help="The gpg key name to be used") parser.add_option("--data", dest="data_only", action="store_true", default=False, help="Restore only the data") parser.add_option("--online", dest="online", action="store_true", default=False, help="Perform the LDAP restores online, for data only.") parser.add_option("--instance", dest="instance", help="The 389-ds instance to restore (defaults to all found)") parser.add_option("--backend", dest="backend", help="The backend to restore within the instance or instances") parser.add_option('--no-logs', dest="no_logs", action="store_true", default=False, help="Do not restore log files from the backup") parser.add_option('-U', '--unattended', dest="unattended", action="store_true", default=False, help="Unattended restoration never prompts the user")
super(Restore, self).setup_logging(log_file_mode='a')
options = self.options super(Restore, self).validate_options(needs_root=True) if options.data_only: installutils.check_server_configuration()
if len(self.args) < 1: self.option_parser.error( "must provide the backup to restore") elif len(self.args) > 1: self.option_parser.error( "must provide exactly one name for the backup")
dirname = self.args[0] if not os.path.isabs(dirname): self.backup_dir = os.path.join(BACKUP_DIR, dirname) else: self.backup_dir = dirname
if options.gpg_keyring: if (not os.path.exists(options.gpg_keyring + '.pub') or not os.path.exists(options.gpg_keyring + '.sec')): raise admintool.ScriptError('No such key %s' % options.gpg_keyring)
options = self.options super(Restore, self).ask_for_options()
# get the directory manager password self.dirman_password = options.password if not options.password: if not options.unattended: self.dirman_password = installutils.read_password( "Directory Manager (existing master)", confirm=False, validate=False) if self.dirman_password is None: raise admintool.ScriptError( "Directory Manager password required")
options = self.options super(Restore, self).run()
api.bootstrap(in_server=False, context='restore') api.finalize()
self.log.info("Preparing restore from %s on %s", self.backup_dir, api.env.host)
if not options.instance: instances = [] for instance in [realm_to_serverid(api.env.realm), 'PKI-IPA']: if os.path.exists('/var/lib/dirsrv/slapd-%s' % instance): instances.append(instance) else: instances = [options.instance] if options.data_only and not instances: raise admintool.ScriptError('No instances to restore to')
pent = pwd.getpwnam(DS_USER)
# Temporary directory for decrypting files before restoring self.top_dir = tempfile.mkdtemp("ipa") os.chown(self.top_dir, pent.pw_uid, pent.pw_gid) os.chmod(self.top_dir, 0750) self.dir = os.path.join(self.top_dir, "ipa") os.mkdir(self.dir, 0750)
os.chown(self.dir, pent.pw_uid, pent.pw_gid)
self.header = os.path.join(self.backup_dir, 'header')
cwd = os.getcwd() try: dirsrv = ipaservices.knownservices.dirsrv
self.read_header() # These two checks would normally be in the validate method but # we need to know the type of backup we're dealing with. if (self.backup_type != 'FULL' and not options.data_only and not instances): raise admintool.ScriptError('Cannot restore a data backup into an empty system') if (self.backup_type == 'FULL' and not options.data_only and (options.instance or options.backend)): raise admintool.ScriptError('Restore must be in data-only mode when restoring a specific instance or backend.') if self.backup_host != api.env.host: self.log.warning('Host name %s does not match backup name %s' % (api.env.host, self.backup_host)) if (not options.unattended and not user_input("Continue to restore?", False)): raise admintool.ScriptError("Aborted") if self.backup_ipa_version != str(version.VERSION): self.log.warning( "Restoring data from a different release of IPA.\n" "Data is version %s.\n" "Server is running %s." % (self.backup_ipa_version, str(version.VERSION))) if (not options.unattended and not user_input("Continue to restore?", False)): raise admintool.ScriptError("Aborted")
# Big fat warning if (not options.unattended and not user_input("Restoring data will overwrite existing live data. Continue to restore?", False)): raise admintool.ScriptError("Aborted")
self.log.info( "Each master will individually need to be re-initialized or") self.log.info( "re-created from this one. The replication agreements on") self.log.info( "masters running IPA 3.1 or earlier will need to be manually") self.log.info( "re-enabled. See the man page for details.")
self.log.info("Disabling all replication.") self.disable_agreements()
self.extract_backup(options.gpg_keyring) if options.data_only: if not options.online: self.log.info('Stopping Directory Server') dirsrv.stop(capture_output=False) else: self.log.info('Starting Directory Server') dirsrv.start(capture_output=False) else: self.log.info('Stopping IPA services') (stdout, stderr, rc) = run(['ipactl', 'stop'], raiseonerr=False) if rc not in [0, 6]: self.log.warn('Stopping IPA failed: %s' % stderr)
# We do either a full file restore or we restore data. if self.backup_type == 'FULL' and not options.data_only: if options.online: raise admintool.ScriptError('File restoration cannot be done online.') self.file_restore(options.no_logs) if 'CA' in self.backup_services: self.__create_dogtag_log_dirs()
# Always restore the data from ldif # If we are restoring PKI-IPA then we need to restore the # userRoot backend in it and the main IPA instance. If we # have a unified instance we need to restore both userRoot and # ipaca. for instance in instances: if os.path.exists('/var/lib/dirsrv/slapd-%s' % instance): if options.backend is None: self.ldif2db(instance, 'userRoot', online=options.online) if os.path.exists('/var/lib/dirsrv/slapd-%s/db/ipaca' % instance): self.ldif2db(instance, 'ipaca', online=options.online) else: self.ldif2db(instance, options.backend, online=options.online) else: raise admintool.ScriptError('389-ds instance %s does not exist' % instance)
if options.data_only: if not options.online: self.log.info('Starting Directory Server') dirsrv.start(capture_output=False) else: # explicitly enable then disable the pki tomcatd service to # re-register its instance. FIXME, this is really wierd. ipaservices.knownservices.pki_tomcatd.enable() ipaservices.knownservices.pki_tomcatd.disable()
self.log.info('Starting IPA services') run(['ipactl', 'start']) self.log.info('Restarting SSSD') sssd = ipaservices.service('sssd') sssd.restart() finally: try: os.chdir(cwd) except Exception, e: self.log.error('Cannot change directory to %s: %s' % (cwd, e)) shutil.rmtree(self.top_dir)
''' Create an ldapi connection and bind to it using autobind as root. ''' if self._conn is not None: return self._conn
self._conn = ipaldap.IPAdmin(host=api.env.host, ldapi=True, protocol='ldapi', realm=api.env.realm)
try: pw_name = pwd.getpwuid(os.geteuid()).pw_name self._conn.do_external_bind(pw_name) except Exception, e: raise admintool.ScriptError('Unable to bind to LDAP server: %s' % e) return self._conn
''' Find all replication agreements on all masters and disable them.
Warn very loudly about any agreements/masters we cannot contact. ''' try: conn = self.get_connection() except Exception, e : self.log.error('Unable to get connection, skipping disabling agreements: %s' % e) return masters = [] dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) try: entries = conn.get_entries(dn, conn.SCOPE_ONELEVEL) except Exception, e: raise admintool.ScriptError( "Failed to read master data: %s" % e) else: masters = [ent.single_value('cn') for ent in entries]
for master in masters: if master == api.env.host: continue
try: repl = ReplicationManager(api.env.realm, master, self.dirman_password) except Exception, e: self.log.critical("Unable to disable agreement on %s: %s" % (master, e))
master_dn = DN(('cn', master), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) try: services = repl.conn.get_entries(master_dn, repl.conn.SCOPE_ONELEVEL) except errors.NotFound: continue
services_cns = [s.single_value('cn') for s in services]
hosts = repl.find_ipa_replication_agreements() for host in hosts: self.log.info('Disabling replication agreement on %s to %s' % (master, host)) repl.disable_agreement(host)
if 'CA' in services_cns: try: repl = get_cs_replication_manager(api.env.realm, master, self.dirman_password) except Exception, e: self.log.critical("Unable to disable agreement on %s: %s" % (master, e))
hosts = repl.find_ipa_replication_agreements() for host in hosts: self.log.info('Disabling CA replication agreement on %s to %s' % (master, host)) repl.hostnames = [master, host] repl.disable_agreement(host)
''' Restore a LDIF backup of the data in this instance.
If executed online create a task and wait for it to complete. ''' self.log.info('Restoring from %s in %s' % (backend, instance))
now = time.localtime() cn = time.strftime('import_%Y_%m_%d_%H_%M_%S') dn = DN(('cn', cn), ('cn', 'import'), ('cn', 'tasks'), ('cn', 'config'))
ldifname = '%s-%s.ldif' % (instance, backend) ldiffile = os.path.join(self.dir, ldifname)
if online: conn = self.get_connection() ent = conn.make_entry( dn, { 'objectClass': ['top', 'extensibleObject'], 'cn': [cn], 'nsFilename': [ldiffile], 'nsUseOneFile': ['true'], } ) ent['nsInstance'] = [backend]
try: conn.add_entry(ent) except Exception, e: raise admintool.ScriptError( 'Unable to bind to LDAP server: %s' % e)
self.log.info("Waiting for LDIF to finish") wait_for_task(conn, dn) else: args = ['%s/ldif2db' % self.__find_scripts_dir(instance), '-i', ldiffile] if backend is not None: args.append('-n') args.append(backend) else: args.append('-n') args.append('userRoot') (stdout, stderr, rc) = run(args, raiseonerr=False) if rc != 0: self.log.critical("ldif2db failed: %s" % stderr)
''' Restore a BAK backup of the data and changelog in this instance.
If backend is None then all backends are restored.
If executed online create a task and wait for it to complete.
instance here is a loaded term. It can mean either a separate 389-ds install instance or a separate 389-ds backend. We only need to treat PKI-IPA and ipaca specially. ''' if backend is not None: self.log.info('Restoring %s in %s' % (backend, instance)) else: self.log.info('Restoring %s' % instance)
cn = time.strftime('restore_%Y_%m_%d_%H_%M_%S')
dn = DN(('cn', cn), ('cn', 'restore'), ('cn', 'tasks'), ('cn', 'config'))
if online: conn = self.get_connection() ent = conn.make_entry( dn, { 'objectClass': ['top', 'extensibleObject'], 'cn': [cn], 'nsArchiveDir': [os.path.join(self.dir, instance)], 'nsDatabaseType': ['ldbm database'], } ) if backend is not None: ent['nsInstance'] = [backend]
try: conn.add_entry(ent) except Exception, e: raise admintool.ScriptError('Unable to bind to LDAP server: %s' % e)
self.log.info("Waiting for restore to finish") wait_for_task(conn, dn) else: args = ['%s/bak2db' % self.__find_scripts_dir(instance), os.path.join(self.dir, instance)] if backend is not None: args.append('-n') args.append(backend) (stdout, stderr, rc) = run(args, raiseonerr=False) if rc != 0: self.log.critical("bak2db failed: %s" % stderr)
''' Restore all the files in the tarball.
This MUST be done offline because we directly backup the 389-ds databases. ''' self.log.info("Restoring files") cwd = os.getcwd() os.chdir('/') args = ['tar', '-xzf', os.path.join(self.dir, 'files.tar') ] if nologs: args.append('--exclude') args.append('var/log')
(stdout, stderr, rc) = run(args, raiseonerr=False) if rc != 0: self.log.critical('Restoring files failed: %s', stderr)
os.chdir(cwd)
''' Read the backup file header that contains the meta data about this particular backup. ''' fd = open(self.header) config = SafeConfigParser() config.readfp(fd)
self.backup_type = config.get('ipa', 'type') self.backup_time = config.get('ipa', 'time') self.backup_host = config.get('ipa', 'host') self.backup_ipa_version = config.get('ipa', 'ipa_version') self.backup_version = config.get('ipa', 'version') self.backup_services = config.get('ipa', 'services')
''' Extract the contents of the tarball backup into a temporary location, decrypting if necessary. '''
encrypt = False filename = None if self.backup_type == 'FULL': filename = os.path.join(self.backup_dir, 'ipa-full.tar') else: filename = os.path.join(self.backup_dir, 'ipa-data.tar') if not os.path.exists(filename): if not os.path.exists(filename + '.gpg'): raise admintool.ScriptError('Unable to find backup file in %s' % self.backup_dir) else: filename = filename + '.gpg' encrypt = True
if encrypt: self.log.info('Decrypting %s' % filename) filename = decrypt_file(self.dir, filename, keyring)
cwd = os.getcwd() os.chdir(self.dir)
args = ['tar', '-xzf', filename, '.' ] run(args)
pent = pwd.getpwnam(DS_USER) os.chown(self.top_dir, pent.pw_uid, pent.pw_gid) recursive_chown(self.dir, pent.pw_uid, pent.pw_gid)
if encrypt: # We can remove the decoded tarball os.unlink(filename)
""" IPA stores its 389-ds scripts in a different directory than dogtag does so we need to probe for it. """ if instance != 'PKI-IPA': return os.path.join('/var/lib/dirsrv', 'scripts-%s' % instance) else: if sys.maxsize > 2**32: libpath = 'lib64' else: libpath = 'lib' return os.path.join('/usr', libpath, 'dirsrv', 'slapd-PKI-IPA')
""" If we are doing a full restore and the dogtag log directories do not exist then tomcat will fail to start.
The directory is different depending on whether we have a d9-based or a d10-based installation. We can tell based on whether there is a PKI-IPA 389-ds instance. """ if os.path.exists('/etc/dirsrv/slapd-PKI-IPA'): # dogtag 9 topdir = '/var/log/pki-ca' dirs = [topdir, '/var/log/pki-ca/signedAudit,'] else: # dogtag 10 topdir = '/var/log/pki/pki-tomcat' dirs = [topdir, '/var/log/pki/pki-tomcat/ca', '/var/log/pki/pki-tomcat/ca/archive', '/var/log/pki/pki-tomcat/ca/signedAudit',]
if os.path.exists(topdir): return
try: pent = pwd.getpwnam(PKI_USER) except KeyError: self.log.debug("No %s user exists, skipping CA directory creation" % PKI_USER) return self.log.debug('Creating log directories for dogtag') for dir in dirs: try: self.log.debug('Creating %s' % dir) os.mkdir(dir, 0770) os.chown(dir, pent.pw_uid, pent.pw_gid) ipaservices.restore_context(dir) except Exception, e: # This isn't so fatal as to side-track the restore self.log.error('Problem with %s: %s' % (dir, e)) |