#!/usr/bin/python2 """Copy the IPA schema to the CA directory server instance You need to run this script to prepare a 2.2 or 3.0 IPA master for installation of a 3.1 replica. Once a 3.1 replica is in the domain, every older CA master will emit schema replication errors until this script is run on it. """ # DO NOT TOUCH THIS CODE, IT MUST BE COMPATIBLE WITH RHEL6 # disable pylint because current codebase didn't match RHEL6 code # pylint: disable=all import os import sys import pwd import shutil from hashlib import sha1 from ipapython import ipautil from ipapython.ipa_log_manager import root_logger, standard_logging_setup from ipaserver.install.dsinstance import schema_dirname from ipalib import api # oh dear, this is an old IPA (3.0+) from ipaserver.install.dsinstance import DS_USER from ipaserver.install.cainstance import PKI_USER from ipapython import services # for mod_nss from ipaserver.install.httpinstance import NSS_CONF from ipaserver.install.httpinstance import HTTPInstance from ipaserver.install import installutils from ipapython import sysrestore SERVERID = "PKI-IPA" SCHEMA_FILENAMES = ( "60kerberos.ldif", "60samba.ldif", "60ipaconfig.ldif", "60basev2.ldif", "60basev3.ldif", "60ipadns.ldif", "61kerberos-ipav3.ldif", "65ipacertstore.ldif", "65ipasudo.ldif", "70ipaotp.ldif", "05rfc2247.ldif", ) def _sha1_file(filename): with open(filename, 'rb') as f: return sha1(f.read()).hexdigest() def add_ca_schema(): """Copy IPA schema files into the CA DS instance """ pki_pent = pwd.getpwnam(PKI_USER) ds_pent = pwd.getpwnam(DS_USER) for schema_fname in SCHEMA_FILENAMES: source_fname = os.path.join(ipautil.SHARE_DIR, schema_fname) target_fname = os.path.join(schema_dirname(SERVERID), schema_fname) if not os.path.exists(source_fname): root_logger.debug('File does not exist: %s', source_fname) continue if os.path.exists(target_fname): target_sha1 = _sha1_file(target_fname) source_sha1 = _sha1_file(source_fname) if target_sha1 != source_sha1: target_size = os.stat(target_fname).st_size source_size = os.stat(source_fname).st_size root_logger.info('Target file %s exists but the content is ' 'different', target_fname) root_logger.info('\tTarget file: sha1: %s, size: %s B', target_sha1, target_size) root_logger.info('\tSource file: sha1: %s, size: %s B', source_sha1, source_size) if not ipautil.user_input("Do you want replace %s file?" % target_fname, True): continue else: root_logger.info( 'Target exists, not overwriting: %s', target_fname) continue try: shutil.copyfile(source_fname, target_fname) except IOError as e: root_logger.warning('Could not install %s: %s', target_fname, e) else: root_logger.info('Installed %s', target_fname) os.chmod(target_fname, 0o440) # read access for dirsrv user/group os.chown(target_fname, pki_pent.pw_uid, ds_pent.pw_gid) def restart_pki_ds(): """Restart the CA DS instance to pick up schema changes """ root_logger.info('Restarting CA DS') services.service('dirsrv').restart(SERVERID) # The ipa-3-0 set_directive() has very loose comparision of directive # which would cause multiple NSSCipherSuite to be added so provide # a custom function for it. def set_directive(filename, directive, value, quotes=True, separator=' '): """Set a name/value pair directive in a configuration file. A value of None means to drop the directive. This has only been tested with nss.conf """ valueset = False st = os.stat(filename) fd = open(filename) newfile = [] for line in fd: if line.lstrip().startswith(directive): valueset = True if value is not None: if quotes: newfile.append('%s%s"%s"\n' % (directive, separator, value)) else: newfile.append('%s%s%s\n' % (directive, separator, value)) else: newfile.append(line) fd.close() if not valueset: if value is not None: if quotes: newfile.append('%s%s"%s"\n' % (directive, separator, value)) else: newfile.append('%s%s%s\n' % (directive, separator, value)) fd = open(filename, "w") fd.write("".join(newfile)) fd.close() os.chown(filename, st.st_uid, st.st_gid) # reset perms def update_mod_nss_cipher_suite(): add_ciphers = ['ecdhe_rsa_aes_128_sha', 'ecdhe_rsa_aes_256_sha'] ciphers = installutils.get_directive(NSS_CONF, 'NSSCipherSuite') # Run through once to see if any of the new ciphers are there but # disabled. If they are then enable them. lciphers = ciphers.split(',') new_ciphers = [] for cipher in lciphers: for add in add_ciphers: if cipher.endswith(add): if cipher.startswith('-'): cipher = '+%s' % add new_ciphers.append(cipher) # Run through again and add remaining ciphers as enabled. for add in add_ciphers: if add not in ciphers: new_ciphers.append('+%s' % add) ciphers = ','.join(new_ciphers) set_directive(NSS_CONF, 'NSSCipherSuite', ciphers, False) root_logger.info('Updated Apache cipher list') def restart_http(): root_logger.info('Restarting HTTP') fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') http = HTTPInstance(fstore) http.restart() def main(): if os.getegid() != 0: sys.exit("Must be root to run this script") standard_logging_setup(verbose=True) # In 3.0, restarting needs access to api.env api.bootstrap_with_global_options(context='server') add_ca_schema() restart_pki_ds() update_mod_nss_cipher_suite() restart_http() root_logger.info('Schema updated successfully') if __name__ == '__main__': main()