From 027515230a93a7a60983d3eca26a97a0d9c3610e Mon Sep 17 00:00:00 2001 From: Martin Basti Date: May 25 2015 16:34:44 +0000 Subject: Server Upgrade: Move code from ipa-upgradeconfig to separate module This also prevent the script ipa-upgradeconfig execute upgrading. Upgrade of services is called from ipa-server-upgrade https://fedorahosted.org/freeipa/ticket/4904 Reviewed-By: Jan Cholasta --- diff --git a/install/tools/ipa-upgradeconfig b/install/tools/ipa-upgradeconfig index dfef1e0..4329296 100755 --- a/install/tools/ipa-upgradeconfig +++ b/install/tools/ipa-upgradeconfig @@ -19,1417 +19,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -""" -Upgrade configuration files to a newer template. -""" - import sys -import re -import os -import shutil -import pwd -import fileinput -import ConfigParser -import grp - -from ipalib import api -import SSSDConfig -import ipalib.util -import ipalib.errors -from ipaplatform import services -from ipaplatform.tasks import tasks -from ipapython import ipautil, sysrestore, version, certdb -from ipapython.config import IPAOptionParser -from ipapython.ipa_log_manager import * -from ipapython import certmonger -from ipapython import dogtag -from ipaplatform.paths import paths -from ipaserver.install import installutils -from ipaserver.install import dsinstance -from ipaserver.install import httpinstance -from ipaserver.install import memcacheinstance -from ipaserver.install import bindinstance -from ipaserver.install import service -from ipaserver.install import cainstance -from ipaserver.install import certs -from ipaserver.install import otpdinstance -from ipaserver.install import sysupgrade -from ipaserver.install import dnskeysyncinstance - - -def parse_options(): - parser = IPAOptionParser(version=version.VERSION) - parser.add_option("-d", "--debug", dest="debug", action="store_true", - default=False, help="print debugging information") - parser.add_option("-q", "--quiet", dest="quiet", - action="store_true", - default=False, help="Output only errors") - - options, args = parser.parse_args() - safe_options = parser.get_safe_opts(options) - - return safe_options, options - -class KpasswdInstance(service.SimpleServiceInstance): - def __init__(self): - service.SimpleServiceInstance.__init__(self, "ipa_kpasswd") - -def uninstall_ipa_kpasswd(): - """ - We can't use the full service uninstaller because that will attempt - to stop and disable the service which by now doesn't exist. We just - want to clean up sysrestore.state to remove all references to - ipa_kpasswd. - """ - ipa_kpasswd = KpasswdInstance() - - running = ipa_kpasswd.restore_state("running") - enabled = not ipa_kpasswd.restore_state("enabled") - - if enabled is not None and not enabled: - ipa_kpasswd.remove() - -def backup_file(filename, ext): - """Make a backup of filename using ext as the extension. Do not overwrite - previous backups.""" - if not os.path.isabs(filename): - raise ValueError("Absolute path required") - - backupfile = filename + ".bak" - (reldir, file) = os.path.split(filename) - - while os.path.exists(backupfile): - backupfile = backupfile + "." + str(ext) - - try: - shutil.copy2(filename, backupfile) - except IOError, e: - if e.errno == 2: # No such file or directory - pass - else: - raise e - -def update_conf(sub_dict, filename, template_filename): - template = ipautil.template_file(template_filename, sub_dict) - fd = open(filename, "w") - fd.write(template) - fd.close() - -def find_hostname(): - """Find the hostname currently configured in ipa-rewrite.conf""" - filename=paths.HTTPD_IPA_REWRITE_CONF - - if not ipautil.file_exists(filename): - return None - - pattern = "^[\s#]*.*https:\/\/([A-Za-z0-9\.\-]*)\/.*" - p = re.compile(pattern) - for line in fileinput.input(filename): - if p.search(line): - fileinput.close() - return p.search(line).group(1) - fileinput.close() - - raise RuntimeError("Unable to determine the fully qualified hostname from %s" % filename) - -def find_autoredirect(fqdn): - """ - When upgrading ipa-rewrite.conf we need to see if the automatic redirect - was disabled during install time (or afterward). So sift through the - configuration file and see if we can determine the status. - - Returns True if autoredirect is enabled, False otherwise - """ - filename = paths.HTTPD_IPA_REWRITE_CONF - if os.path.exists(filename): - pattern = "^RewriteRule \^/\$ https://%s/ipa/ui \[L,NC,R=301\]" % fqdn - p = re.compile(pattern) - for line in fileinput.input(filename): - if p.search(line): - fileinput.close() - return True - fileinput.close() - return False - return True - -def find_version(filename): - """Find the version of a configuration file - - If no VERSION entry exists in the file, returns 0. - If the file does not exist, returns -1. - """ - if os.path.exists(filename): - pattern = "^[\s#]*VERSION\s+([0-9]+)\s+.*" - p = re.compile(pattern) - for line in fileinput.input(filename): - if p.search(line): - fileinput.close() - return p.search(line).group(1) - fileinput.close() - - # no VERSION found - return 0 - else: - return -1 - -def upgrade(sub_dict, filename, template, add=False): - """ - Get the version from the current and template files and update the - installed configuration file if there is a new template. - - If add is True then create a new configuration file. - """ - old = int(find_version(filename)) - new = int(find_version(template)) - - if old < 0 and not add: - root_logger.error("%s not found." % filename) - sys.exit(1) - - if new < 0: - root_logger.error("%s not found." % template) - - if old == 0: - # The original file does not have a VERSION entry. This means it's now - # managed by IPA, but previously was not. - root_logger.warning("%s is now managed by IPA. It will be " - "overwritten. A backup of the original will be made.", filename) - - if old < new or (add and old == 0): - backup_file(filename, new) - update_conf(sub_dict, filename, template) - root_logger.info("Upgraded %s to version %d", filename, new) - -def check_certs(): - """Check ca.crt is in the right place, and try to fix if not""" - root_logger.info('[Verifying that root certificate is published]') - if not os.path.exists(paths.CA_CRT): - ca_file = paths.ALIAS_CACERT_ASC - if os.path.exists(ca_file): - old_umask = os.umask(022) # make sure its readable by httpd - try: - shutil.copyfile(ca_file, paths.CA_CRT) - finally: - os.umask(old_umask) - else: - root_logger.error("Missing Certification Authority file.") - root_logger.error("You should place a copy of the CA certificate in /usr/share/ipa/html/ca.crt") - else: - root_logger.debug('Certificate file exists') - -def upgrade_pki(ca, fstore): - """ - Update/add the dogtag proxy configuration. The IPA side of this is - handled in ipa-pki-proxy.conf. - - This requires enabling SSL renegotiation. - """ - configured_constants = dogtag.configured_constants() - root_logger.info('[Verifying that CA proxy configuration is correct]') - if not ca.is_configured(): - root_logger.info('CA is not configured') - return - - http = httpinstance.HTTPInstance(fstore) - http.enable_mod_nss_renegotiate() - if not installutils.get_directive(configured_constants.CS_CFG_PATH, - 'proxy.securePort', '=') and \ - os.path.exists(paths.PKI_SETUP_PROXY): - # update proxy configuration with stopped dogtag to prevent corruption - # of CS.cfg - ipautil.run([paths.PKI_SETUP_PROXY, '-pki_instance_root=/var/lib', - '-pki_instance_name=pki-ca','-subsystem_type=ca']) - root_logger.debug('Proxy configuration updated') - else: - root_logger.debug('Proxy configuration up-to-date') - -def update_dbmodules(realm, filename=paths.KRB5_CONF): - newfile = [] - found_dbrealm = False - found_realm = False - prefix = '' - - root_logger.info('[Verifying that KDC configuration is using ipa-kdb backend]') - st = os.stat(filename) - fd = open(filename) - - lines = fd.readlines() - fd.close() - - if ' db_library = ipadb.so\n' in lines: - root_logger.debug('dbmodules already updated in %s', filename) - return - - for line in lines: - if line.startswith('[dbmodules]'): - found_dbrealm = True - if found_dbrealm and line.find(realm) > -1: - found_realm = True - prefix = '#' - if found_dbrealm and line.find('}') > -1 and found_realm: - found_realm = False - newfile.append('#%s' % line) - prefix = '' - continue - - newfile.append('%s%s' % (prefix, line)) - - # Append updated dbmodules information - newfile.append(' %s = {\n' % realm) - newfile.append(' db_library = ipadb.so\n') - newfile.append(' }\n') - - # Write out new file - fd = open(filename, 'w') - fd.write("".join(newfile)) - fd.close() - root_logger.debug('%s updated', filename) - -def cleanup_kdc(fstore): - """ - Clean up old KDC files if they exist. We need to remove the actual - file and any references in the uninstall configuration. - """ - root_logger.info('[Checking for deprecated KDC configuration files]') - for file in ['kpasswd.keytab', 'ldappwd']: - filename = os.path.join(paths.VAR_KERBEROS_KRB5KDC_DIR, file) - installutils.remove_file(filename) - if fstore.has_file(filename): - fstore.untrack_file(filename) - root_logger.debug('Uninstalling %s', filename) - -def cleanup_adtrust(fstore): - """ - Clean up any old Samba backup files that were deprecated. - """ - - root_logger.info('[Checking for deprecated backups of Samba ' - 'configuration files]') - - for backed_up_file in [paths.SMB_CONF]: - if fstore.has_file(backed_up_file): - fstore.untrack_file(backed_up_file) - root_logger.debug('Removing %s from backup', backed_up_file) - - -def setup_firefox_extension(fstore): - """Set up the Firefox configuration extension, if it's not set up yet - """ - root_logger.info('[Setting up Firefox extension]') - http = httpinstance.HTTPInstance(fstore) - realm = api.env.realm - domain = api.env.domain - http.setup_firefox_extension(realm, domain) - - -def upgrade_ipa_profile(ca, domain, fqdn): - """ - Update the IPA Profile provided by dogtag - - Returns True if restart is needed, False otherwise. - """ - root_logger.info('[Verifying that CA service certificate profile is updated]') - if ca.is_configured(): - ski = ca.enable_subject_key_identifier() - if ski: - root_logger.debug('Subject Key Identifier updated.') - else: - root_logger.debug('Subject Key Identifier already set.') - san = ca.enable_subject_alternative_name() - if san: - root_logger.debug('Subject Alternative Name updated.') - else: - root_logger.debug('Subject Alternative Name already set.') - audit = ca.set_audit_renewal() - uri = ca.set_crl_ocsp_extensions(domain, fqdn) - if audit or ski or san or uri: - return True - else: - root_logger.info('CA is not configured') - - return False - - -def named_remove_deprecated_options(): - """ - From IPA 3.3, persistent search is a default mechanism for new DNS zone - detection. - - Remove psearch, zone_refresh and cache_ttl options, as they have been - deprecated in bind-dyndb-ldap configuration file. - - When some change in named.conf is done, this functions returns True. - """ - - root_logger.info('[Removing deprecated DNS configuration options]') - - if not bindinstance.named_conf_exists(): - # DNS service may not be configured - root_logger.info('DNS is not configured') - return False - - deprecated_options = ['zone_refresh', 'psearch', 'cache_ttl'] - removed_options = [] - - try: - # Remove all the deprecated options - for option in deprecated_options: - value = bindinstance.named_conf_get_directive(option) - - if value is not None: - bindinstance.named_conf_set_directive(option, None) - removed_options.append(option) - - except IOError, e: - root_logger.error('Cannot modify DNS configuration in %s: %s', - bindinstance.NAMED_CONF, e) - - # Log only the changed options - if not removed_options: - root_logger.debug('No changes made') - return False - - root_logger.debug('The following configuration options have been removed: ' - '{options}'.format(options = ', '.join(removed_options))) - return True - - -def named_set_minimum_connections(): - """ - Sets the minimal number of connections. - - When some change in named.conf is done, this functions returns True. - """ - - changed = False - - root_logger.info('[Ensuring minimal number of connections]') - - if not bindinstance.named_conf_exists(): - # DNS service may not be configured - root_logger.info('DNS is not configured') - return changed - - # make sure number of connections is right - minimum_connections = 4 - - try: - connections = bindinstance.named_conf_get_directive('connections') - except IOError, e: - root_logger.debug('Cannot retrieve connections option from %s: %s', - bindinstance.NAMED_CONF, e) - return changed - - try: - if connections is not None: - connections = int(connections) - except ValueError: - # this should not happend, but there is some bad value in - # "connections" option, bail out - pass - else: - if connections is not None and connections < minimum_connections: - try: - bindinstance.named_conf_set_directive('connections', - minimum_connections) - root_logger.debug('Connections set to %d', minimum_connections) - except IOError, e: - root_logger.error('Cannot update connections in %s: %s', - bindinstance.NAMED_CONF, e) - else: - changed = True - - if not changed: - root_logger.debug('No changes made') - - return changed - - -def named_enable_serial_autoincrement(): - """ - Serial autoincrement is a requirement for zone transfers or DNSSEC. It - should be enabled both for new installs and upgraded servers. - - When some change in named.conf is done, this functions returns True - """ - changed = False - - root_logger.info('[Enabling serial autoincrement in DNS]') - - if not bindinstance.named_conf_exists(): - # DNS service may not be configured - root_logger.info('DNS is not configured') - return changed - - try: - serial_autoincrement = bindinstance.named_conf_get_directive( - 'serial_autoincrement') - except IOError, e: - root_logger.debug('Cannot retrieve psearch option from %s: %s', - bindinstance.NAMED_CONF, e) - return changed - else: - serial_autoincrement = None if serial_autoincrement is None \ - else serial_autoincrement.lower() - - # enable SOA serial autoincrement - if not sysupgrade.get_upgrade_state('named.conf', 'autoincrement_enabled'): - if serial_autoincrement != 'yes': - try: - bindinstance.named_conf_set_directive('serial_autoincrement', 'yes') - except IOError, e: - root_logger.error('Cannot enable serial_autoincrement in %s: %s', - bindinstance.NAMED_CONF, e) - return changed - else: - root_logger.debug('Serial autoincrement enabled') - changed = True - else: - root_logger.debug('Serial autoincrement is alredy enabled') - sysupgrade.set_upgrade_state('named.conf', 'autoincrement_enabled', True) - else: - root_logger.debug('Skip serial autoincrement check') - - return changed - -def named_update_gssapi_configuration(): - """ - Update GSSAPI configuration in named.conf to a recent API. - tkey-gssapi-credential and tkey-domain is replaced with tkey-gssapi-keytab. - Details can be found in https://fedorahosted.org/freeipa/ticket/3429. - - When some change in named.conf is done, this functions returns True - """ - - root_logger.info('[Updating GSSAPI configuration in DNS]') - - if not bindinstance.named_conf_exists(): - # DNS service may not be configured - root_logger.info('DNS is not configured') - return False - - if sysupgrade.get_upgrade_state('named.conf', 'gssapi_updated'): - root_logger.debug('Skip GSSAPI configuration check') - return False - - try: - gssapi_keytab = bindinstance.named_conf_get_directive('tkey-gssapi-keytab', - bindinstance.NAMED_SECTION_OPTIONS) - except IOError, e: - root_logger.error('Cannot retrieve tkey-gssapi-keytab option from %s: %s', - bindinstance.NAMED_CONF, e) - return False - else: - if gssapi_keytab: - root_logger.debug('GSSAPI configuration already updated') - sysupgrade.set_upgrade_state('named.conf', 'gssapi_updated', True) - return False - - try: - tkey_credential = bindinstance.named_conf_get_directive('tkey-gssapi-credential', - bindinstance.NAMED_SECTION_OPTIONS) - tkey_domain = bindinstance.named_conf_get_directive('tkey-domain', - bindinstance.NAMED_SECTION_OPTIONS) - except IOError, e: - root_logger.error('Cannot retrieve tkey-gssapi-credential option from %s: %s', - bindinstance.NAMED_CONF, e) - return False - - if not tkey_credential or not tkey_domain: - root_logger.error('Either tkey-gssapi-credential or tkey-domain is missing in %s. ' - 'Skip update.', bindinstance.NAMED_CONF) - return False - - try: - bindinstance.named_conf_set_directive( - 'tkey-gssapi-credential', None, - bindinstance.NAMED_SECTION_OPTIONS) - bindinstance.named_conf_set_directive( - 'tkey-domain', None, - bindinstance.NAMED_SECTION_OPTIONS) - bindinstance.named_conf_set_directive( - 'tkey-gssapi-keytab', paths.NAMED_KEYTAB, - bindinstance.NAMED_SECTION_OPTIONS) - except IOError, e: - root_logger.error('Cannot update GSSAPI configuration in %s: %s', - bindinstance.NAMED_CONF, e) - return False - else: - root_logger.debug('GSSAPI configuration updated') - - sysupgrade.set_upgrade_state('named.conf', 'gssapi_updated', True) - return True - - -def named_update_pid_file(): - """ - Make sure that named reads the pid file from the right file - """ - root_logger.info('[Updating pid-file configuration in DNS]') - - if not bindinstance.named_conf_exists(): - # DNS service may not be configured - root_logger.info('DNS is not configured') - return False - - if sysupgrade.get_upgrade_state('named.conf', 'pid-file_updated'): - root_logger.debug('Skip pid-file configuration check') - return False - - try: - pid_file = bindinstance.named_conf_get_directive('pid-file', - bindinstance.NAMED_SECTION_OPTIONS) - except IOError, e: - root_logger.error('Cannot retrieve pid-file option from %s: %s', - bindinstance.NAMED_CONF, e) - return False - else: - if pid_file: - root_logger.debug('pid-file configuration already updated') - sysupgrade.set_upgrade_state('named.conf', 'pid-file_updated', True) - return False - - try: - bindinstance.named_conf_set_directive('pid-file', paths.NAMED_PID, - bindinstance.NAMED_SECTION_OPTIONS) - except IOError, e: - root_logger.error('Cannot update pid-file configuration in %s: %s', - bindinstance.NAMED_CONF, e) - return False - else: - root_logger.debug('pid-file configuration updated') - - sysupgrade.set_upgrade_state('named.conf', 'pid-file_updated', True) - return True - -def named_enable_dnssec(): - """ - Enable dnssec in named.conf - """ - if not bindinstance.named_conf_exists(): - # DNS service may not be configured - root_logger.info('DNS is not configured') - return False - - if not sysupgrade.get_upgrade_state('named.conf', 'dnssec_enabled'): - root_logger.info('[Enabling "dnssec-enable" configuration in DNS]') - try: - bindinstance.named_conf_set_directive('dnssec-enable', 'yes', - bindinstance.NAMED_SECTION_OPTIONS, - str_val=False) - except IOError, e: - root_logger.error('Cannot update dnssec-enable configuration in %s: %s', - bindinstance.NAMED_CONF, e) - return False - else: - root_logger.debug('dnssec-enabled in %s' % bindinstance.NAMED_CONF) - - sysupgrade.set_upgrade_state('named.conf', 'dnssec_enabled', True) - return True - -def named_validate_dnssec(): - """ - Disable dnssec validation in named.conf - - We can't let enable it by default, there can be non-valid dns forwarders - which breaks DNSSEC validation - """ - if not bindinstance.named_conf_exists(): - # DNS service may not be configured - root_logger.info('DNS is not configured') - return False - - if (not sysupgrade.get_upgrade_state('named.conf', 'dnssec_validation_upgraded') - and bindinstance.named_conf_get_directive( - 'dnssec-validation', bindinstance.NAMED_SECTION_OPTIONS, - str_val=False) is None): - # dnssec-validation is not configured, disable it - root_logger.info('[Disabling "dnssec-validate" configuration in DNS]') - try: - bindinstance.named_conf_set_directive('dnssec-validation', 'no', - bindinstance.NAMED_SECTION_OPTIONS, - str_val=False) - except IOError, e: - root_logger.error('Cannot update dnssec-validate configuration in %s: %s', - bindinstance.NAMED_CONF, e) - return False - else: - root_logger.debug('dnssec-validate already configured in %s' % bindinstance.NAMED_CONF) - - sysupgrade.set_upgrade_state('named.conf', 'dnssec_validation_upgraded', True) - return True - -def named_bindkey_file_option(): - """ - Add options bindkey_file to named.conf - """ - if not bindinstance.named_conf_exists(): - # DNS service may not be configured - root_logger.info('DNS is not configured') - return False - - if sysupgrade.get_upgrade_state('named.conf', 'bindkey-file_updated'): - root_logger.debug('Skip bindkey-file configuration check') - return False - - try: - bindkey_file = bindinstance.named_conf_get_directive('bindkey-file', - bindinstance.NAMED_SECTION_OPTIONS) - except IOError, e: - root_logger.error('Cannot retrieve bindkey-file option from %s: %s', - bindinstance.NAMED_CONF, e) - return False - else: - if bindkey_file: - root_logger.debug('bindkey-file configuration already updated') - sysupgrade.set_upgrade_state('named.conf', 'bindkey-file_updated', True) - return False - - root_logger.info('[Setting "bindkeys-file" option in named.conf]') - try: - bindinstance.named_conf_set_directive('bindkeys-file', - paths.NAMED_BINDKEYS_FILE, - bindinstance.NAMED_SECTION_OPTIONS) - except IOError, e: - root_logger.error('Cannot update bindkeys-file configuration in %s: %s', - bindinstance.NAMED_CONF, e) - return False - - - sysupgrade.set_upgrade_state('named.conf', 'bindkey-file_updated', True) - return True - -def named_managed_keys_dir_option(): - """ - Add options managed_keys_directory to named.conf - """ - if not bindinstance.named_conf_exists(): - # DNS service may not be configured - root_logger.info('DNS is not configured') - return False - - if sysupgrade.get_upgrade_state('named.conf', 'managed-keys-directory_updated'): - root_logger.debug('Skip managed-keys-directory configuration check') - return False - - try: - managed_keys = bindinstance.named_conf_get_directive('managed-keys-directory', - bindinstance.NAMED_SECTION_OPTIONS) - except IOError, e: - root_logger.error('Cannot retrieve managed-keys-directory option from %s: %s', - bindinstance.NAMED_CONF, e) - return False - else: - if managed_keys: - root_logger.debug('managed_keys_directory configuration already updated') - sysupgrade.set_upgrade_state('named.conf', 'managed-keys-directory_updated', True) - return False - - root_logger.info('[Setting "managed-keys-directory" option in named.conf]') - try: - bindinstance.named_conf_set_directive('managed-keys-directory', - paths.NAMED_MANAGED_KEYS_DIR, - bindinstance.NAMED_SECTION_OPTIONS) - except IOError, e: - root_logger.error('Cannot update managed-keys-directory configuration in %s: %s', - bindinstance.NAMED_CONF, e) - return False - - - sysupgrade.set_upgrade_state('named.conf', 'managed-keys-directory_updated', True) - return True - -def named_root_key_include(): - """ - Add options managed_keys_directory to named.conf - """ - if not bindinstance.named_conf_exists(): - # DNS service may not be configured - root_logger.info('DNS is not configured') - return False - - if sysupgrade.get_upgrade_state('named.conf', 'root_key_updated'): - root_logger.debug('Skip root key configuration check') - return False - - try: - root_key = bindinstance.named_conf_include_exists(paths.NAMED_ROOT_KEY) - except IOError, e: - root_logger.error('Cannot check root key include in %s: %s', - bindinstance.NAMED_CONF, e) - return False - else: - if root_key: - root_logger.debug('root keys configuration already updated') - sysupgrade.set_upgrade_state('named.conf', 'root_key_updated', True) - return False - - root_logger.info('[Including named root key in named.conf]') - try: - bindinstance.named_conf_add_include(paths.NAMED_ROOT_KEY) - except IOError, e: - root_logger.error('Cannot update named root key include in %s: %s', - bindinstance.NAMED_CONF, e) - return False - - - sysupgrade.set_upgrade_state('named.conf', 'root_key_updated', True) - return True - -def certificate_renewal_update(ca): - """ - Update certmonger certificate renewal configuration. - """ - dogtag_constants = dogtag.configured_constants() - - # bump version when requests is changed - version = 3 - requests = ( - ( - dogtag_constants.ALIAS_DIR, - 'auditSigningCert cert-pki-ca', - 'dogtag-ipa-ca-renew-agent', - 'stop_pkicad', - 'renew_ca_cert', - None, - ), - ( - dogtag_constants.ALIAS_DIR, - 'ocspSigningCert cert-pki-ca', - 'dogtag-ipa-ca-renew-agent', - 'stop_pkicad', - 'renew_ca_cert', - None, - ), - ( - dogtag_constants.ALIAS_DIR, - 'subsystemCert cert-pki-ca', - 'dogtag-ipa-ca-renew-agent', - 'stop_pkicad', - 'renew_ca_cert', - None, - ), - ( - dogtag_constants.ALIAS_DIR, - 'caSigningCert cert-pki-ca', - 'dogtag-ipa-ca-renew-agent', - 'stop_pkicad', - 'renew_ca_cert', - 'ipaCACertRenewal', - ), - ( - paths.HTTPD_ALIAS_DIR, - 'ipaCert', - 'dogtag-ipa-ca-renew-agent', - None, - 'renew_ra_cert', - None, - ), - ( - dogtag_constants.ALIAS_DIR, - 'Server-Cert cert-pki-ca', - 'dogtag-ipa-renew-agent', - 'stop_pkicad', - 'renew_ca_cert', - None, - ), - ) - - root_logger.info("[Update certmonger certificate renewal configuration to " - "version %d]" % version) - if not ca.is_configured(): - root_logger.info('CA is not configured') - return False - - state = 'certificate_renewal_update_%d' % version - if sysupgrade.get_upgrade_state('dogtag', state): - return False - - # State not set, lets see if we are already configured - for request in requests: - nss_dir, nickname, ca_name, pre_command, post_command, profile = request - criteria = { - 'cert-database': nss_dir, - 'cert-nickname': nickname, - 'ca-name': ca_name, - 'template-profile': profile, - } - request_id = certmonger.get_request_id(criteria) - if request_id is None: - break - - val = certmonger.get_request_value(request_id, 'cert-presave-command') - if val is not None: - val = val.split(' ', 1)[0] - val = os.path.basename(val) - if pre_command != val: - break - - val = certmonger.get_request_value(request_id, 'cert-postsave-command') - if val is not None: - val = val.split(' ', 1)[0] - val = os.path.basename(val) - if post_command != val: - break - else: - sysupgrade.set_upgrade_state('dogtag', state, True) - root_logger.info("Certmonger certificate renewal configuration is " - "already at version %d" % version) - return False - - # Ok, now we need to stop tracking, then we can start tracking them - # again with new configuration: - ca.stop_tracking_certificates() - - if not sysupgrade.get_upgrade_state('dogtag', - 'certificate_renewal_update_1'): - filename = paths.CERTMONGER_CAS_CA_RENEWAL - if os.path.exists(filename): - with installutils.stopped_service('certmonger'): - root_logger.info("Removing %s" % filename) - installutils.remove_file(filename) - - ca.configure_certmonger_renewal() - ca.configure_renewal() - ca.configure_agent_renewal() - ca.track_servercert() - - sysupgrade.set_upgrade_state('dogtag', state, True) - root_logger.info("Certmonger certificate renewal configuration updated to " - "version %d" % version) - return True - -def copy_crl_file(old_path, new_path=None): - """ - Copy CRL to new location, update permissions and SELinux context - """ - if new_path is None: - filename = os.path.basename(old_path) - new_path = os.path.join(dogtag.configured_constants().CRL_PUBLISH_PATH, - filename) - root_logger.debug('copy_crl_file: %s -> %s', old_path, new_path) - - if os.path.islink(old_path): - # update symlink to the most most recent CRL file - filename = os.path.basename(os.readlink(old_path)) - realpath = os.path.join(dogtag.configured_constants().CRL_PUBLISH_PATH, - filename) - root_logger.debug('copy_crl_file: Create symlink %s -> %s', - new_path, realpath) - os.symlink(realpath, new_path) - else: - shutil.copy2(old_path, new_path) - pent = pwd.getpwnam(cainstance.PKI_USER) - os.chown(new_path, pent.pw_uid, pent.pw_gid) - - tasks.restore_context(new_path) - -def migrate_crl_publish_dir(ca): - """ - Move CRL publish dir from /var/lib/pki-ca/publish to IPA controlled tree: - /var/lib/ipa/pki-ca/publish - """ - root_logger.info('[Migrate CRL publish directory]') - if sysupgrade.get_upgrade_state('dogtag', 'moved_crl_publish_dir'): - root_logger.info('CRL tree already moved') - return False - - if not ca.is_configured(): - root_logger.info('CA is not configured') - return False - - caconfig = dogtag.configured_constants() - - try: - old_publish_dir = installutils.get_directive(caconfig.CS_CFG_PATH, - 'ca.publish.publisher.instance.FileBaseCRLPublisher.directory', - separator='=') - except OSError, e: - root_logger.error('Cannot read CA configuration file "%s": %s', - caconfig.CS_CFG_PATH, e) - return False - - # Prepare target publish dir (creation, permissions, SELinux context) - # Run this every update to ensure proper values - publishdir = ca.prepare_crl_publish_dir() - - if old_publish_dir == caconfig.CRL_PUBLISH_PATH: - # publish dir is already updated - root_logger.info('Publish directory already set to new location') - sysupgrade.set_upgrade_state('dogtag', 'moved_crl_publish_dir', True) - return False - - # Copy all CRLs to new directory - root_logger.info('Copy all CRLs to new publish directory') - try: - crl_files_unsorted = cainstance.get_crl_files(old_publish_dir) - except OSError, e: - root_logger.error('Cannot move CRL files to new directory: %s', e) - else: - # Move CRL files at the end of the list to make sure that the actual - # CRL files are copied first - crl_files = sorted(crl_files_unsorted, - key=lambda f: os.path.islink(f)) - for f in crl_files: - try: - copy_crl_file(f) - except Exception, e: - root_logger.error('Cannot move CRL file to new directory: %s', e) - - try: - installutils.set_directive(caconfig.CS_CFG_PATH, - 'ca.publish.publisher.instance.FileBaseCRLPublisher.directory', - publishdir, quotes=False, separator='=') - except OSError, e: - root_logger.error('Cannot update CA configuration file "%s": %s', - caconfig.CS_CFG_PATH, e) - return False - sysupgrade.set_upgrade_state('dogtag', 'moved_crl_publish_dir', True) - root_logger.info('CRL publish directory has been migrated, ' - 'request pki-ca restart') - return True - - -def ca_enable_pkix(ca): - root_logger.info('[Enable PKIX certificate path discovery and validation]') - if sysupgrade.get_upgrade_state('dogtag', 'pkix_enabled'): - root_logger.info('PKIX already enabled') - return False - - if not ca.is_configured(): - root_logger.info('CA is not configured') - return False - - ca.enable_pkix() - sysupgrade.set_upgrade_state('dogtag', 'pkix_enabled', True) - - return True - - -def add_ca_dns_records(): - root_logger.info('[Add missing CA DNS records]') - - if sysupgrade.get_upgrade_state('dns', 'ipa_ca_records'): - root_logger.info('IPA CA DNS records already processed') - return - - if not api.Backend.ldap2.isconnected(): - try: - api.Backend.ldap2.connect(autobind=True) - except ipalib.errors.PublicError, e: - root_logger.error( - "Cannot connect to LDAP to add DNS records: %s", e) - return - - ret = api.Command['dns_is_enabled']() - if not ret['result']: - root_logger.info('DNS is not configured') - sysupgrade.set_upgrade_state('dns', 'ipa_ca_records', True) - return - - bind = bindinstance.BindInstance() - - bind.convert_ipa_ca_cnames(api.env.domain) - - # DNS is enabled, so let bindinstance find out if CA is enabled - # and let it add the record in that case - bind.add_ipa_ca_dns_records(api.env.host, api.env.domain, - ca_configured=None) - - sysupgrade.set_upgrade_state('dns', 'ipa_ca_records', True) - - -def find_subject_base(): - """ - Try to find the current value of certificate subject base. - See the docstring in dsinstance.DsInstance for details. - """ - subject_base = dsinstance.DsInstance().find_subject_base() - - if subject_base: - sysupgrade.set_upgrade_state( - 'certmap.conf', - 'subject_base', - subject_base - ) - return subject_base - - root_logger.error('Unable to determine certificate subject base. ' - 'certmap.conf will not be updated.') - - -def uninstall_selfsign(ds, http): - root_logger.info('[Removing self-signed CA]') - """Replace self-signed CA by a CA-less install""" - if api.env.ra_plugin != 'selfsign': - root_logger.debug('Self-signed CA is not installed') - return - - root_logger.warning( - 'Removing self-signed CA. Certificates will need to managed manually.') - p = ConfigParser.SafeConfigParser() - p.read(paths.IPA_DEFAULT_CONF) - p.set('global', 'enable_ra', 'False') - p.set('global', 'ra_plugin', 'none') - with open(paths.IPA_DEFAULT_CONF, 'w') as f: - p.write(f) - - ds.stop_tracking_certificates() - http.stop_tracking_certificates() - - -def mask_named_regular(): - """Disable named, we need to run only named-pkcs11, running both named and - named-pkcs can cause unexpected errors""" - if sysupgrade.get_upgrade_state('dns', 'regular_named_masked'): - return False - - sysupgrade.set_upgrade_state('dns', 'regular_named_masked', True) - - if bindinstance.named_conf_exists(): - root_logger.info('[Masking named]') - named = services.service('named-regular') - try: - named.stop() - except Exception as e: - root_logger.warning('Unable to stop named service (%s)', e) - - try: - named.mask() - except Exception as e: - root_logger.warning('Unable to mask named service (%s)', e) - - return True - - return False - - -def fix_dyndb_ldap_workdir_permissions(): - """Fix dyndb-ldap working dir permissions. DNSSEC daemons requires it""" - if sysupgrade.get_upgrade_state('dns', 'dyndb_ipa_workdir_perm'): - return - - if bindinstance.named_conf_exists(): - root_logger.info('[Fix bind-dyndb-ldap IPA working directory]') - dnskeysync = dnskeysyncinstance.DNSKeySyncInstance() - dnskeysync.set_dyndb_ldap_workdir_permissions() - - sysupgrade.set_upgrade_state('dns', 'dyndb_ipa_workdir_perm', True) - - -def fix_schema_file_syntax(): - """Fix syntax errors in schema files - - https://fedorahosted.org/freeipa/ticket/3578 - """ - root_logger.info('[Fix DS schema file syntax]') - - # This is not handled by normal schema updates, because pre-1.3.2 DS will - # ignore (auto-fix) these syntax errors, and 1.3.2 and above will choke on - # them before checking dynamic schema updates. - - if sysupgrade.get_upgrade_state('ds', 'fix_schema_syntax'): - root_logger.info('Syntax already fixed') - return - - serverid = installutils.realm_to_serverid(api.env.realm) - ds_dir = dsinstance.config_dirname(serverid) - - # 1. 60ipadns.ldif: Add parenthesis to idnsRecord - - filename = os.path.join(ds_dir, 'schema', '60ipadns.ldif') - result_lines = [] - with open(filename) as file: - for line in file: - line = line.strip('\n') - if (line.startswith('objectClasses:') and - "NAME 'idnsRecord'" in line and - line.count('(') == 2 and - line.count(')') == 1): - root_logger.debug('Add closing parenthesis in idnsRecord') - line += ' )' - result_lines.append(line) - - with open(filename, 'w') as file: - file.write('\n'.join(result_lines)) - - # 2. 65ipasudo.ldif: Remove extra dollar from ipaSudoRule - - filename = os.path.join(ds_dir, 'schema', '65ipasudo.ldif') - result_lines = [] - with open(filename) as file: - for line in file: - line = line.strip('\n') - if (line.startswith('objectClasses:') and - "NAME 'ipaSudoRule'" in line): - root_logger.debug('Remove extra dollar sign in ipaSudoRule') - line = line.replace('$$', '$') - result_lines.append(line) - - with open(filename, 'w') as file: - file.write('\n'.join(result_lines)) - - # Done - - sysupgrade.set_upgrade_state('ds', 'fix_schema_syntax', True) - - -def set_sssd_domain_option(option, value): - sssdconfig = SSSDConfig.SSSDConfig() - sssdconfig.import_config() - domain = sssdconfig.get_domain(str(api.env.domain)) - domain.set_option(option, value) - sssdconfig.save_domain(domain) - sssdconfig.write(paths.SSSD_CONF) - - -def remove_ds_ra_cert(subject_base): - root_logger.info('[Removing RA cert from DS NSS database]') - - if sysupgrade.get_upgrade_state('ds', 'remove_ra_cert'): - root_logger.info('RA cert already removed') - return - - dbdir = dsinstance.config_dirname( - installutils.realm_to_serverid(api.env.realm)) - dsdb = certs.CertDB(api.env.realm, nssdir=dbdir, subject_base=subject_base) - - nickname = 'CN=IPA RA,%s' % subject_base - cert = dsdb.get_cert_from_db(nickname) - if cert: - dsdb.delete_cert(nickname) - - sysupgrade.set_upgrade_state('ds', 'remove_ra_cert', True) - - -def fix_trust_flags(): - root_logger.info('[Fixing trust flags in %s]' % paths.HTTPD_ALIAS_DIR) - - if sysupgrade.get_upgrade_state('http', 'fix_trust_flags'): - root_logger.info("Trust flags already processed") - return - - if not api.Backend.ldap2.isconnected(): - try: - api.Backend.ldap2.connect(autobind=True) - except ipalib.errors.PublicError, e: - root_logger.error("Cannot connect to LDAP: %s", e) - return - - if not api.Command.ca_is_enabled()['result']: - root_logger.info("CA is not enabled") - return - - db = certs.CertDB(api.env.realm) - nickname = certdb.get_ca_nickname(api.env.realm) - cert = db.get_cert_from_db(nickname) - if cert: - db.trust_root_cert(nickname, 'CT,C,C') - - sysupgrade.set_upgrade_state('http', 'fix_trust_flags', True) - - -def update_mod_nss_protocol(http): - root_logger.info('[Updating mod_nss protocol versions]') - - if sysupgrade.get_upgrade_state('nss.conf', 'protocol_updated_tls12'): - root_logger.info("Protocol versions already updated") - return - - http.set_mod_nss_protocol() - - sysupgrade.set_upgrade_state('nss.conf', 'protocol_updated_tls12', True) - - -def main(): - """ - Get some basics about the system. If getting those basics fail then - this is likely because the machine isn't currently an IPA server so - exit gracefully. - """ - - if not os.geteuid()==0: - sys.exit("\nYou must be root to run this script.\n") - - if not installutils.is_ipa_configured(): - sys.exit(0) - - safe_options, options = parse_options() - - verbose = not options.quiet - if options.debug: - console_format = '%(levelname)s: %(message)s' - else: - console_format = '%(message)s' - - standard_logging_setup(paths.IPAUPGRADE_LOG, debug=options.debug, - verbose=verbose, console_format=console_format, filemode='a') - root_logger.debug('%s was invoked with options: %s' % (sys.argv[0], safe_options)) - root_logger.debug('IPA version %s' % version.VENDOR_VERSION) - - fstore = sysrestore.FileStore(paths.SYSRESTORE) - - api.bootstrap(context='restart', in_server=True) - api.finalize() - - fqdn = find_hostname() - if fqdn is None: - # ipa-rewrite.conf doesn't exist, nothing to do - sys.exit(0) - - # Ok, we are an IPA server, do the additional tests - - check_certs() - - auto_redirect = find_autoredirect(fqdn) - configured_constants = dogtag.configured_constants() - sub_dict = dict( - REALM=api.env.realm, - FQDN=fqdn, - AUTOREDIR='' if auto_redirect else '#', - CRL_PUBLISH_PATH=configured_constants.CRL_PUBLISH_PATH, - DOGTAG_PORT=configured_constants.AJP_PORT, - CLONE='#' - ) - - subject_base = find_subject_base() - if subject_base: - sub_dict['SUBJECT_BASE'] = subject_base - - ca = cainstance.CAInstance(api.env.realm, certs.NSS_DIR) - ca.backup_config() - - with installutils.stopped_service(configured_constants.SERVICE_NAME, - configured_constants.PKI_INSTANCE_NAME): - # migrate CRL publish dir before the location in ipa.conf is updated - ca_restart = migrate_crl_publish_dir(ca) - - if ca.is_configured(): - crl = installutils.get_directive(configured_constants.CS_CFG_PATH, - 'ca.crl.MasterCRL.enableCRLUpdates', '=') - sub_dict['CLONE']='#' if crl.lower() == 'true' else '' - - ds_serverid = installutils.realm_to_serverid(api.env.realm) - ds_dirname = dsinstance.config_dirname(ds_serverid) - - upgrade(sub_dict, paths.HTTPD_IPA_CONF, ipautil.SHARE_DIR + "ipa.conf") - upgrade(sub_dict, paths.HTTPD_IPA_REWRITE_CONF, ipautil.SHARE_DIR + "ipa-rewrite.conf") - if ca.is_configured(): - upgrade(sub_dict, paths.HTTPD_IPA_PKI_PROXY_CONF, ipautil.SHARE_DIR + "ipa-pki-proxy.conf", add=True) - else: - if ipautil.file_exists(paths.HTTPD_IPA_PKI_PROXY_CONF): - os.remove(paths.HTTPD_IPA_PKI_PROXY_CONF) - if subject_base: - upgrade( - sub_dict, - os.path.join(ds_dirname, "certmap.conf"), - os.path.join(ipautil.SHARE_DIR, "certmap.conf.template") - ) - upgrade_pki(ca, fstore) - - ca.configure_certmonger_renewal_guard() - - update_dbmodules(api.env.realm) - uninstall_ipa_kpasswd() - - removed_sysconfig_file = paths.SYSCONFIG_HTTPD - if fstore.has_file(removed_sysconfig_file): - root_logger.info('Restoring %s as it is no longer required', - removed_sysconfig_file) - fstore.restore_file(removed_sysconfig_file) - - http = httpinstance.HTTPInstance(fstore) - http.configure_selinux_for_httpd() - http.change_mod_nss_port_from_http() - http.configure_certmonger_renewal_guard() - - http.stop() - update_mod_nss_protocol(http) - fix_trust_flags() - http.start() - - ds = dsinstance.DsInstance() - ds.configure_dirsrv_ccache() - - ds.stop(ds_serverid) - fix_schema_file_syntax() - remove_ds_ra_cert(subject_base) - ds.start(ds_serverid) - - uninstall_selfsign(ds, http) - - simple_service_list = ( - (memcacheinstance.MemcacheInstance(), 'MEMCACHE'), - (otpdinstance.OtpdInstance(), 'OTPD'), - ) - - for service, ldap_name in simple_service_list: - service.ldapi = True - try: - if not service.is_configured(): - # 389-ds needs to be running to create the memcache instance - # because we record the new service in cn=masters. - ds.start() - service.create_instance(ldap_name, fqdn, None, - ipautil.realm_to_suffix(api.env.realm), - realm=api.env.realm) - except ipalib.errors.DuplicateEntry: - pass - - # install DNSKeySync service only if DNS is configured on server - if bindinstance.named_conf_exists(): - dnskeysyncd = dnskeysyncinstance.DNSKeySyncInstance(fstore, ldapi=True) - if not dnskeysyncd.is_configured(): - ds.start() - dnskeysyncd.create_instance(fqdn, api.env.realm) - dnskeysyncd.start_dnskeysyncd() - - cleanup_kdc(fstore) - cleanup_adtrust(fstore) - setup_firefox_extension(fstore) - add_ca_dns_records() - - # Any of the following functions returns True iff the named.conf file - # has been altered - named_conf_changes = ( - named_remove_deprecated_options(), - named_set_minimum_connections(), - named_enable_serial_autoincrement(), - named_update_gssapi_configuration(), - named_update_pid_file(), - named_enable_dnssec(), - named_validate_dnssec(), - named_bindkey_file_option(), - named_managed_keys_dir_option(), - named_root_key_include(), - mask_named_regular(), - fix_dyndb_ldap_workdir_permissions(), - ) - - if any(named_conf_changes): - # configuration has changed, restart the name server - root_logger.info('Changes to named.conf have been made, restart named') - bind = bindinstance.BindInstance(fstore) - try: - bind.restart() - except ipautil.CalledProcessError, e: - root_logger.error("Failed to restart %s: %s", bind.service_name, e) - - ca_restart = any([ - ca_restart, - upgrade_ipa_profile(ca, api.env.domain, fqdn), - certificate_renewal_update(ca), - ca_enable_pkix(ca), - ]) - - if ca_restart: - root_logger.info('pki-ca configuration changed, restart pki-ca') - try: - ca.restart(dogtag.configured_constants().PKI_INSTANCE_NAME) - except ipautil.CalledProcessError, e: - root_logger.error("Failed to restart %s: %s", ca.service_name, e) - set_sssd_domain_option('ipa_server_mode', 'True') if __name__ == '__main__': - installutils.run_script(main, operation_name='ipa-upgradeconfig') + sys.exit("Please run the 'ipa-server-upgrade' command to upgrade the " + "IPA server.") diff --git a/ipaserver/install/ipa_server_upgrade.py b/ipaserver/install/ipa_server_upgrade.py index 6236e2e..31772dc 100644 --- a/ipaserver/install/ipa_server_upgrade.py +++ b/ipaserver/install/ipa_server_upgrade.py @@ -12,6 +12,7 @@ from ipaplatform.paths import paths from ipapython import admintool, ipautil from ipaserver.install import dsinstance from ipaserver.install import installutils +from ipaserver.install.server import upgrade_configuration from ipaserver.install.upgradeinstance import IPAUpgrade from ipaserver.install.ldapupdate import BadSyntax @@ -95,16 +96,10 @@ class ServerUpgrade(admintool.AdminTool): # store new data version after upgrade installutils.store_version() - # FIXME: remove this when new installer will be ready - # execute upgrade of configuration - cmd = ['ipa-upgradeconfig', ] - if options.verbose: - cmd.append('--debug') - if options.quiet: - cmd.append('--quiet') - - self.log.info('Executing ipa-upgradeconfig, please wait') - ipautil.run(cmd) + print 'Upgrading IPA services' + self.log.info('Upgrading the configuration of the IPA services') + upgrade_configuration() + self.log.info('The IPA services were upgraded') def handle_error(self, exception): return installutils.handle_error(exception, self.log_file_name) diff --git a/ipaserver/install/server.py b/ipaserver/install/server.py new file mode 100644 index 0000000..c08b748 --- /dev/null +++ b/ipaserver/install/server.py @@ -0,0 +1,1376 @@ +# +# Copyright (C) 2015 FreeIPA Contributors see COPYING for license +# + +import re +import os +import shutil +import pwd +import fileinput +import ConfigParser + +from ipalib import api +import SSSDConfig +import ipalib.util +import ipalib.errors +from ipaplatform import services +from ipaplatform.tasks import tasks +from ipapython import ipautil, sysrestore, version, certdb +from ipapython.ipa_log_manager import * +from ipapython import certmonger +from ipapython import dogtag +from ipaplatform.paths import paths +from ipaserver.install import installutils +from ipaserver.install import dsinstance +from ipaserver.install import httpinstance +from ipaserver.install import memcacheinstance +from ipaserver.install import bindinstance +from ipaserver.install import service +from ipaserver.install import cainstance +from ipaserver.install import certs +from ipaserver.install import otpdinstance +from ipaserver.install import sysupgrade +from ipaserver.install import dnskeysyncinstance + +class KpasswdInstance(service.SimpleServiceInstance): + def __init__(self): + service.SimpleServiceInstance.__init__(self, "ipa_kpasswd") + +def uninstall_ipa_kpasswd(): + """ + We can't use the full service uninstaller because that will attempt + to stop and disable the service which by now doesn't exist. We just + want to clean up sysrestore.state to remove all references to + ipa_kpasswd. + """ + ipa_kpasswd = KpasswdInstance() + + running = ipa_kpasswd.restore_state("running") + enabled = not ipa_kpasswd.restore_state("enabled") + + if enabled is not None and not enabled: + ipa_kpasswd.remove() + +def backup_file(filename, ext): + """Make a backup of filename using ext as the extension. Do not overwrite + previous backups.""" + if not os.path.isabs(filename): + raise ValueError("Absolute path required") + + backupfile = filename + ".bak" + (reldir, file) = os.path.split(filename) + + while os.path.exists(backupfile): + backupfile = backupfile + "." + str(ext) + + try: + shutil.copy2(filename, backupfile) + except IOError as e: + if e.errno == 2: # No such file or directory + pass + else: + raise e + +def update_conf(sub_dict, filename, template_filename): + template = ipautil.template_file(template_filename, sub_dict) + fd = open(filename, "w") + fd.write(template) + fd.close() + +def find_hostname(): + """Find the hostname currently configured in ipa-rewrite.conf""" + filename=paths.HTTPD_IPA_REWRITE_CONF + + if not ipautil.file_exists(filename): + return None + + pattern = "^[\s#]*.*https:\/\/([A-Za-z0-9\.\-]*)\/.*" + p = re.compile(pattern) + for line in fileinput.input(filename): + if p.search(line): + fileinput.close() + return p.search(line).group(1) + fileinput.close() + + raise RuntimeError("Unable to determine the fully qualified hostname from %s" % filename) + +def find_autoredirect(fqdn): + """ + When upgrading ipa-rewrite.conf we need to see if the automatic redirect + was disabled during install time (or afterward). So sift through the + configuration file and see if we can determine the status. + + Returns True if autoredirect is enabled, False otherwise + """ + filename = paths.HTTPD_IPA_REWRITE_CONF + if os.path.exists(filename): + pattern = "^RewriteRule \^/\$ https://%s/ipa/ui \[L,NC,R=301\]" % fqdn + p = re.compile(pattern) + for line in fileinput.input(filename): + if p.search(line): + fileinput.close() + return True + fileinput.close() + return False + return True + +def find_version(filename): + """Find the version of a configuration file + + If no VERSION entry exists in the file, returns 0. + If the file does not exist, returns -1. + """ + if os.path.exists(filename): + pattern = "^[\s#]*VERSION\s+([0-9]+)\s+.*" + p = re.compile(pattern) + for line in fileinput.input(filename): + if p.search(line): + fileinput.close() + return p.search(line).group(1) + fileinput.close() + + # no VERSION found + return 0 + else: + return -1 + +def upgrade(sub_dict, filename, template, add=False): + """ + Get the version from the current and template files and update the + installed configuration file if there is a new template. + + If add is True then create a new configuration file. + """ + old = int(find_version(filename)) + new = int(find_version(template)) + + if old < 0 and not add: + root_logger.error("%s not found." % filename) + raise RuntimeError("%s not found." % filename) + + if new < 0: + root_logger.error("%s not found." % template) + + if old == 0: + # The original file does not have a VERSION entry. This means it's now + # managed by IPA, but previously was not. + root_logger.warning("%s is now managed by IPA. It will be " + "overwritten. A backup of the original will be made.", filename) + + if old < new or (add and old == 0): + backup_file(filename, new) + update_conf(sub_dict, filename, template) + root_logger.info("Upgraded %s to version %d", filename, new) + +def check_certs(): + """Check ca.crt is in the right place, and try to fix if not""" + root_logger.info('[Verifying that root certificate is published]') + if not os.path.exists(paths.CA_CRT): + ca_file = paths.ALIAS_CACERT_ASC + if os.path.exists(ca_file): + old_umask = os.umask(022) # make sure its readable by httpd + try: + shutil.copyfile(ca_file, paths.CA_CRT) + finally: + os.umask(old_umask) + else: + root_logger.error("Missing Certification Authority file.") + root_logger.error("You should place a copy of the CA certificate in /usr/share/ipa/html/ca.crt") + else: + root_logger.debug('Certificate file exists') + +def upgrade_pki(ca, fstore): + """ + Update/add the dogtag proxy configuration. The IPA side of this is + handled in ipa-pki-proxy.conf. + + This requires enabling SSL renegotiation. + """ + configured_constants = dogtag.configured_constants() + root_logger.info('[Verifying that CA proxy configuration is correct]') + if not ca.is_configured(): + root_logger.info('CA is not configured') + return + + http = httpinstance.HTTPInstance(fstore) + http.enable_mod_nss_renegotiate() + if not installutils.get_directive(configured_constants.CS_CFG_PATH, + 'proxy.securePort', '=') and \ + os.path.exists(paths.PKI_SETUP_PROXY): + # update proxy configuration with stopped dogtag to prevent corruption + # of CS.cfg + ipautil.run([paths.PKI_SETUP_PROXY, '-pki_instance_root=/var/lib', + '-pki_instance_name=pki-ca','-subsystem_type=ca']) + root_logger.debug('Proxy configuration updated') + else: + root_logger.debug('Proxy configuration up-to-date') + +def update_dbmodules(realm, filename=paths.KRB5_CONF): + newfile = [] + found_dbrealm = False + found_realm = False + prefix = '' + + root_logger.info('[Verifying that KDC configuration is using ipa-kdb backend]') + st = os.stat(filename) + fd = open(filename) + + lines = fd.readlines() + fd.close() + + if ' db_library = ipadb.so\n' in lines: + root_logger.debug('dbmodules already updated in %s', filename) + return + + for line in lines: + if line.startswith('[dbmodules]'): + found_dbrealm = True + if found_dbrealm and line.find(realm) > -1: + found_realm = True + prefix = '#' + if found_dbrealm and line.find('}') > -1 and found_realm: + found_realm = False + newfile.append('#%s' % line) + prefix = '' + continue + + newfile.append('%s%s' % (prefix, line)) + + # Append updated dbmodules information + newfile.append(' %s = {\n' % realm) + newfile.append(' db_library = ipadb.so\n') + newfile.append(' }\n') + + # Write out new file + fd = open(filename, 'w') + fd.write("".join(newfile)) + fd.close() + root_logger.debug('%s updated', filename) + +def cleanup_kdc(fstore): + """ + Clean up old KDC files if they exist. We need to remove the actual + file and any references in the uninstall configuration. + """ + root_logger.info('[Checking for deprecated KDC configuration files]') + for file in ['kpasswd.keytab', 'ldappwd']: + filename = os.path.join(paths.VAR_KERBEROS_KRB5KDC_DIR, file) + installutils.remove_file(filename) + if fstore.has_file(filename): + fstore.untrack_file(filename) + root_logger.debug('Uninstalling %s', filename) + +def cleanup_adtrust(fstore): + """ + Clean up any old Samba backup files that were deprecated. + """ + + root_logger.info('[Checking for deprecated backups of Samba ' + 'configuration files]') + + for backed_up_file in [paths.SMB_CONF]: + if fstore.has_file(backed_up_file): + fstore.untrack_file(backed_up_file) + root_logger.debug('Removing %s from backup', backed_up_file) + + +def setup_firefox_extension(fstore): + """Set up the Firefox configuration extension, if it's not set up yet + """ + root_logger.info('[Setting up Firefox extension]') + http = httpinstance.HTTPInstance(fstore) + realm = api.env.realm + domain = api.env.domain + http.setup_firefox_extension(realm, domain) + + +def upgrade_ipa_profile(ca, domain, fqdn): + """ + Update the IPA Profile provided by dogtag + + Returns True if restart is needed, False otherwise. + """ + root_logger.info('[Verifying that CA service certificate profile is updated]') + if ca.is_configured(): + ski = ca.enable_subject_key_identifier() + if ski: + root_logger.debug('Subject Key Identifier updated.') + else: + root_logger.debug('Subject Key Identifier already set.') + san = ca.enable_subject_alternative_name() + if san: + root_logger.debug('Subject Alternative Name updated.') + else: + root_logger.debug('Subject Alternative Name already set.') + audit = ca.set_audit_renewal() + uri = ca.set_crl_ocsp_extensions(domain, fqdn) + if audit or ski or san or uri: + return True + else: + root_logger.info('CA is not configured') + + return False + + +def named_remove_deprecated_options(): + """ + From IPA 3.3, persistent search is a default mechanism for new DNS zone + detection. + + Remove psearch, zone_refresh and cache_ttl options, as they have been + deprecated in bind-dyndb-ldap configuration file. + + When some change in named.conf is done, this functions returns True. + """ + + root_logger.info('[Removing deprecated DNS configuration options]') + + if not bindinstance.named_conf_exists(): + # DNS service may not be configured + root_logger.info('DNS is not configured') + return False + + deprecated_options = ['zone_refresh', 'psearch', 'cache_ttl'] + removed_options = [] + + try: + # Remove all the deprecated options + for option in deprecated_options: + value = bindinstance.named_conf_get_directive(option) + + if value is not None: + bindinstance.named_conf_set_directive(option, None) + removed_options.append(option) + + except IOError as e: + root_logger.error('Cannot modify DNS configuration in %s: %s', + bindinstance.NAMED_CONF, e) + + # Log only the changed options + if not removed_options: + root_logger.debug('No changes made') + return False + + root_logger.debug('The following configuration options have been removed: ' + '{options}'.format(options = ', '.join(removed_options))) + return True + + +def named_set_minimum_connections(): + """ + Sets the minimal number of connections. + + When some change in named.conf is done, this functions returns True. + """ + + changed = False + + root_logger.info('[Ensuring minimal number of connections]') + + if not bindinstance.named_conf_exists(): + # DNS service may not be configured + root_logger.info('DNS is not configured') + return changed + + # make sure number of connections is right + minimum_connections = 4 + + try: + connections = bindinstance.named_conf_get_directive('connections') + except IOError as e: + root_logger.debug('Cannot retrieve connections option from %s: %s', + bindinstance.NAMED_CONF, e) + return changed + + try: + if connections is not None: + connections = int(connections) + except ValueError: + # this should not happend, but there is some bad value in + # "connections" option, bail out + pass + else: + if connections is not None and connections < minimum_connections: + try: + bindinstance.named_conf_set_directive('connections', + minimum_connections) + root_logger.debug('Connections set to %d', minimum_connections) + except IOError, e: + root_logger.error('Cannot update connections in %s: %s', + bindinstance.NAMED_CONF, e) + else: + changed = True + + if not changed: + root_logger.debug('No changes made') + + return changed + + +def named_enable_serial_autoincrement(): + """ + Serial autoincrement is a requirement for zone transfers or DNSSEC. It + should be enabled both for new installs and upgraded servers. + + When some change in named.conf is done, this functions returns True + """ + changed = False + + root_logger.info('[Enabling serial autoincrement in DNS]') + + if not bindinstance.named_conf_exists(): + # DNS service may not be configured + root_logger.info('DNS is not configured') + return changed + + try: + serial_autoincrement = bindinstance.named_conf_get_directive( + 'serial_autoincrement') + except IOError as e: + root_logger.debug('Cannot retrieve psearch option from %s: %s', + bindinstance.NAMED_CONF, e) + return changed + else: + serial_autoincrement = None if serial_autoincrement is None \ + else serial_autoincrement.lower() + + # enable SOA serial autoincrement + if not sysupgrade.get_upgrade_state('named.conf', 'autoincrement_enabled'): + if serial_autoincrement != 'yes': + try: + bindinstance.named_conf_set_directive('serial_autoincrement', + 'yes') + except IOError, e: + root_logger.error('Cannot enable serial_autoincrement in %s: %s', + bindinstance.NAMED_CONF, e) + return changed + else: + root_logger.debug('Serial autoincrement enabled') + changed = True + else: + root_logger.debug('Serial autoincrement is alredy enabled') + sysupgrade.set_upgrade_state('named.conf', 'autoincrement_enabled', True) + else: + root_logger.debug('Skip serial autoincrement check') + + return changed + +def named_update_gssapi_configuration(): + """ + Update GSSAPI configuration in named.conf to a recent API. + tkey-gssapi-credential and tkey-domain is replaced with tkey-gssapi-keytab. + Details can be found in https://fedorahosted.org/freeipa/ticket/3429. + + When some change in named.conf is done, this functions returns True + """ + + root_logger.info('[Updating GSSAPI configuration in DNS]') + + if not bindinstance.named_conf_exists(): + # DNS service may not be configured + root_logger.info('DNS is not configured') + return False + + if sysupgrade.get_upgrade_state('named.conf', 'gssapi_updated'): + root_logger.debug('Skip GSSAPI configuration check') + return False + + try: + gssapi_keytab = bindinstance.named_conf_get_directive('tkey-gssapi-keytab', + bindinstance.NAMED_SECTION_OPTIONS) + except IOError, e: + root_logger.error('Cannot retrieve tkey-gssapi-keytab option from %s: %s', + bindinstance.NAMED_CONF, e) + return False + else: + if gssapi_keytab: + root_logger.debug('GSSAPI configuration already updated') + sysupgrade.set_upgrade_state('named.conf', 'gssapi_updated', True) + return False + + try: + tkey_credential = bindinstance.named_conf_get_directive('tkey-gssapi-credential', + bindinstance.NAMED_SECTION_OPTIONS) + tkey_domain = bindinstance.named_conf_get_directive('tkey-domain', + bindinstance.NAMED_SECTION_OPTIONS) + except IOError, e: + root_logger.error('Cannot retrieve tkey-gssapi-credential option from %s: %s', + bindinstance.NAMED_CONF, e) + return False + + if not tkey_credential or not tkey_domain: + root_logger.error('Either tkey-gssapi-credential or tkey-domain is missing in %s. ' + 'Skip update.', bindinstance.NAMED_CONF) + return False + + try: + bindinstance.named_conf_set_directive( + 'tkey-gssapi-credential', None, + bindinstance.NAMED_SECTION_OPTIONS) + bindinstance.named_conf_set_directive( + 'tkey-domain', None, + bindinstance.NAMED_SECTION_OPTIONS) + bindinstance.named_conf_set_directive( + 'tkey-gssapi-keytab', paths.NAMED_KEYTAB, + bindinstance.NAMED_SECTION_OPTIONS) + except IOError, e: + root_logger.error('Cannot update GSSAPI configuration in %s: %s', + bindinstance.NAMED_CONF, e) + return False + else: + root_logger.debug('GSSAPI configuration updated') + + sysupgrade.set_upgrade_state('named.conf', 'gssapi_updated', True) + return True + + +def named_update_pid_file(): + """ + Make sure that named reads the pid file from the right file + """ + root_logger.info('[Updating pid-file configuration in DNS]') + + if not bindinstance.named_conf_exists(): + # DNS service may not be configured + root_logger.info('DNS is not configured') + return False + + if sysupgrade.get_upgrade_state('named.conf', 'pid-file_updated'): + root_logger.debug('Skip pid-file configuration check') + return False + + try: + pid_file = bindinstance.named_conf_get_directive('pid-file', + bindinstance.NAMED_SECTION_OPTIONS) + except IOError, e: + root_logger.error('Cannot retrieve pid-file option from %s: %s', + bindinstance.NAMED_CONF, e) + return False + else: + if pid_file: + root_logger.debug('pid-file configuration already updated') + sysupgrade.set_upgrade_state('named.conf', 'pid-file_updated', True) + return False + + try: + bindinstance.named_conf_set_directive('pid-file', paths.NAMED_PID, + bindinstance.NAMED_SECTION_OPTIONS) + except IOError, e: + root_logger.error('Cannot update pid-file configuration in %s: %s', + bindinstance.NAMED_CONF, e) + return False + else: + root_logger.debug('pid-file configuration updated') + + sysupgrade.set_upgrade_state('named.conf', 'pid-file_updated', True) + return True + +def named_enable_dnssec(): + """ + Enable dnssec in named.conf + """ + if not bindinstance.named_conf_exists(): + # DNS service may not be configured + root_logger.info('DNS is not configured') + return False + + if not sysupgrade.get_upgrade_state('named.conf', 'dnssec_enabled'): + root_logger.info('[Enabling "dnssec-enable" configuration in DNS]') + try: + bindinstance.named_conf_set_directive('dnssec-enable', 'yes', + bindinstance.NAMED_SECTION_OPTIONS, + str_val=False) + except IOError, e: + root_logger.error('Cannot update dnssec-enable configuration in %s: %s', + bindinstance.NAMED_CONF, e) + return False + else: + root_logger.debug('dnssec-enabled in %s' % bindinstance.NAMED_CONF) + + sysupgrade.set_upgrade_state('named.conf', 'dnssec_enabled', True) + return True + +def named_validate_dnssec(): + """ + Disable dnssec validation in named.conf + + We can't let enable it by default, there can be non-valid dns forwarders + which breaks DNSSEC validation + """ + if not bindinstance.named_conf_exists(): + # DNS service may not be configured + root_logger.info('DNS is not configured') + return False + + if (not sysupgrade.get_upgrade_state('named.conf', 'dnssec_validation_upgraded') + and bindinstance.named_conf_get_directive( + 'dnssec-validation', bindinstance.NAMED_SECTION_OPTIONS, + str_val=False) is None): + # dnssec-validation is not configured, disable it + root_logger.info('[Disabling "dnssec-validate" configuration in DNS]') + try: + bindinstance.named_conf_set_directive('dnssec-validation', 'no', + bindinstance.NAMED_SECTION_OPTIONS, + str_val=False) + except IOError, e: + root_logger.error('Cannot update dnssec-validate configuration in %s: %s', + bindinstance.NAMED_CONF, e) + return False + else: + root_logger.debug('dnssec-validate already configured in %s' % bindinstance.NAMED_CONF) + + sysupgrade.set_upgrade_state('named.conf', 'dnssec_validation_upgraded', True) + return True + +def named_bindkey_file_option(): + """ + Add options bindkey_file to named.conf + """ + if not bindinstance.named_conf_exists(): + # DNS service may not be configured + root_logger.info('DNS is not configured') + return False + + if sysupgrade.get_upgrade_state('named.conf', 'bindkey-file_updated'): + root_logger.debug('Skip bindkey-file configuration check') + return False + + try: + bindkey_file = bindinstance.named_conf_get_directive('bindkey-file', + bindinstance.NAMED_SECTION_OPTIONS) + except IOError, e: + root_logger.error('Cannot retrieve bindkey-file option from %s: %s', + bindinstance.NAMED_CONF, e) + return False + else: + if bindkey_file: + root_logger.debug('bindkey-file configuration already updated') + sysupgrade.set_upgrade_state('named.conf', 'bindkey-file_updated', True) + return False + + root_logger.info('[Setting "bindkeys-file" option in named.conf]') + try: + bindinstance.named_conf_set_directive('bindkeys-file', + paths.NAMED_BINDKEYS_FILE, + bindinstance.NAMED_SECTION_OPTIONS) + except IOError, e: + root_logger.error('Cannot update bindkeys-file configuration in %s: %s', + bindinstance.NAMED_CONF, e) + return False + + + sysupgrade.set_upgrade_state('named.conf', 'bindkey-file_updated', True) + return True + +def named_managed_keys_dir_option(): + """ + Add options managed_keys_directory to named.conf + """ + if not bindinstance.named_conf_exists(): + # DNS service may not be configured + root_logger.info('DNS is not configured') + return False + + if sysupgrade.get_upgrade_state('named.conf', 'managed-keys-directory_updated'): + root_logger.debug('Skip managed-keys-directory configuration check') + return False + + try: + managed_keys = bindinstance.named_conf_get_directive('managed-keys-directory', + bindinstance.NAMED_SECTION_OPTIONS) + except IOError, e: + root_logger.error('Cannot retrieve managed-keys-directory option from %s: %s', + bindinstance.NAMED_CONF, e) + return False + else: + if managed_keys: + root_logger.debug('managed_keys_directory configuration already updated') + sysupgrade.set_upgrade_state('named.conf', 'managed-keys-directory_updated', True) + return False + + root_logger.info('[Setting "managed-keys-directory" option in named.conf]') + try: + bindinstance.named_conf_set_directive('managed-keys-directory', + paths.NAMED_MANAGED_KEYS_DIR, + bindinstance.NAMED_SECTION_OPTIONS) + except IOError, e: + root_logger.error('Cannot update managed-keys-directory configuration in %s: %s', + bindinstance.NAMED_CONF, e) + return False + + + sysupgrade.set_upgrade_state('named.conf', 'managed-keys-directory_updated', True) + return True + +def named_root_key_include(): + """ + Add options managed_keys_directory to named.conf + """ + if not bindinstance.named_conf_exists(): + # DNS service may not be configured + root_logger.info('DNS is not configured') + return False + + if sysupgrade.get_upgrade_state('named.conf', 'root_key_updated'): + root_logger.debug('Skip root key configuration check') + return False + + try: + root_key = bindinstance.named_conf_include_exists(paths.NAMED_ROOT_KEY) + except IOError, e: + root_logger.error('Cannot check root key include in %s: %s', + bindinstance.NAMED_CONF, e) + return False + else: + if root_key: + root_logger.debug('root keys configuration already updated') + sysupgrade.set_upgrade_state('named.conf', 'root_key_updated', True) + return False + + root_logger.info('[Including named root key in named.conf]') + try: + bindinstance.named_conf_add_include(paths.NAMED_ROOT_KEY) + except IOError, e: + root_logger.error('Cannot update named root key include in %s: %s', + bindinstance.NAMED_CONF, e) + return False + + + sysupgrade.set_upgrade_state('named.conf', 'root_key_updated', True) + return True + +def certificate_renewal_update(ca): + """ + Update certmonger certificate renewal configuration. + """ + dogtag_constants = dogtag.configured_constants() + + # bump version when requests is changed + version = 3 + requests = ( + ( + dogtag_constants.ALIAS_DIR, + 'auditSigningCert cert-pki-ca', + 'dogtag-ipa-ca-renew-agent', + 'stop_pkicad', + 'renew_ca_cert', + None, + ), + ( + dogtag_constants.ALIAS_DIR, + 'ocspSigningCert cert-pki-ca', + 'dogtag-ipa-ca-renew-agent', + 'stop_pkicad', + 'renew_ca_cert', + None, + ), + ( + dogtag_constants.ALIAS_DIR, + 'subsystemCert cert-pki-ca', + 'dogtag-ipa-ca-renew-agent', + 'stop_pkicad', + 'renew_ca_cert', + None, + ), + ( + dogtag_constants.ALIAS_DIR, + 'caSigningCert cert-pki-ca', + 'dogtag-ipa-ca-renew-agent', + 'stop_pkicad', + 'renew_ca_cert', + 'ipaCACertRenewal', + ), + ( + paths.HTTPD_ALIAS_DIR, + 'ipaCert', + 'dogtag-ipa-ca-renew-agent', + None, + 'renew_ra_cert', + None, + ), + ( + dogtag_constants.ALIAS_DIR, + 'Server-Cert cert-pki-ca', + 'dogtag-ipa-renew-agent', + 'stop_pkicad', + 'renew_ca_cert', + None, + ), + ) + + root_logger.info("[Update certmonger certificate renewal configuration to " + "version %d]" % version) + if not ca.is_configured(): + root_logger.info('CA is not configured') + return False + + state = 'certificate_renewal_update_%d' % version + if sysupgrade.get_upgrade_state('dogtag', state): + return False + + # State not set, lets see if we are already configured + for request in requests: + nss_dir, nickname, ca_name, pre_command, post_command, profile = request + criteria = { + 'cert-database': nss_dir, + 'cert-nickname': nickname, + 'ca-name': ca_name, + 'template-profile': profile, + } + request_id = certmonger.get_request_id(criteria) + if request_id is None: + break + + val = certmonger.get_request_value(request_id, 'cert-presave-command') + if val is not None: + val = val.split(' ', 1)[0] + val = os.path.basename(val) + if pre_command != val: + break + + val = certmonger.get_request_value(request_id, 'cert-postsave-command') + if val is not None: + val = val.split(' ', 1)[0] + val = os.path.basename(val) + if post_command != val: + break + else: + sysupgrade.set_upgrade_state('dogtag', state, True) + root_logger.info("Certmonger certificate renewal configuration is " + "already at version %d" % version) + return False + + # Ok, now we need to stop tracking, then we can start tracking them + # again with new configuration: + ca.stop_tracking_certificates() + + if not sysupgrade.get_upgrade_state('dogtag', + 'certificate_renewal_update_1'): + filename = paths.CERTMONGER_CAS_CA_RENEWAL + if os.path.exists(filename): + with installutils.stopped_service('certmonger'): + root_logger.info("Removing %s" % filename) + installutils.remove_file(filename) + + ca.configure_certmonger_renewal() + ca.configure_renewal() + ca.configure_agent_renewal() + ca.track_servercert() + + sysupgrade.set_upgrade_state('dogtag', state, True) + root_logger.info("Certmonger certificate renewal configuration updated to " + "version %d" % version) + return True + +def copy_crl_file(old_path, new_path=None): + """ + Copy CRL to new location, update permissions and SELinux context + """ + if new_path is None: + filename = os.path.basename(old_path) + new_path = os.path.join(dogtag.configured_constants().CRL_PUBLISH_PATH, + filename) + root_logger.debug('copy_crl_file: %s -> %s', old_path, new_path) + + if os.path.islink(old_path): + # update symlink to the most most recent CRL file + filename = os.path.basename(os.readlink(old_path)) + realpath = os.path.join(dogtag.configured_constants().CRL_PUBLISH_PATH, + filename) + root_logger.debug('copy_crl_file: Create symlink %s -> %s', + new_path, realpath) + os.symlink(realpath, new_path) + else: + shutil.copy2(old_path, new_path) + pent = pwd.getpwnam(cainstance.PKI_USER) + os.chown(new_path, pent.pw_uid, pent.pw_gid) + + tasks.restore_context(new_path) + +def migrate_crl_publish_dir(ca): + """ + Move CRL publish dir from /var/lib/pki-ca/publish to IPA controlled tree: + /var/lib/ipa/pki-ca/publish + """ + root_logger.info('[Migrate CRL publish directory]') + if sysupgrade.get_upgrade_state('dogtag', 'moved_crl_publish_dir'): + root_logger.info('CRL tree already moved') + return False + + if not ca.is_configured(): + root_logger.info('CA is not configured') + return False + + caconfig = dogtag.configured_constants() + + try: + old_publish_dir = installutils.get_directive(caconfig.CS_CFG_PATH, + 'ca.publish.publisher.instance.FileBaseCRLPublisher.directory', + separator='=') + except OSError, e: + root_logger.error('Cannot read CA configuration file "%s": %s', + caconfig.CS_CFG_PATH, e) + return False + + # Prepare target publish dir (creation, permissions, SELinux context) + # Run this every update to ensure proper values + publishdir = ca.prepare_crl_publish_dir() + + if old_publish_dir == caconfig.CRL_PUBLISH_PATH: + # publish dir is already updated + root_logger.info('Publish directory already set to new location') + sysupgrade.set_upgrade_state('dogtag', 'moved_crl_publish_dir', True) + return False + + # Copy all CRLs to new directory + root_logger.info('Copy all CRLs to new publish directory') + try: + crl_files_unsorted = cainstance.get_crl_files(old_publish_dir) + except OSError, e: + root_logger.error('Cannot move CRL files to new directory: %s', e) + else: + # Move CRL files at the end of the list to make sure that the actual + # CRL files are copied first + crl_files = sorted(crl_files_unsorted, + key=lambda f: os.path.islink(f)) + for f in crl_files: + try: + copy_crl_file(f) + except Exception as e: + root_logger.error('Cannot move CRL file to new directory: %s', e) + + try: + installutils.set_directive(caconfig.CS_CFG_PATH, + 'ca.publish.publisher.instance.FileBaseCRLPublisher.directory', + publishdir, quotes=False, separator='=') + except OSError as e: + root_logger.error('Cannot update CA configuration file "%s": %s', + caconfig.CS_CFG_PATH, e) + return False + sysupgrade.set_upgrade_state('dogtag', 'moved_crl_publish_dir', True) + root_logger.info('CRL publish directory has been migrated, ' + 'request pki-ca restart') + return True + + +def ca_enable_pkix(ca): + root_logger.info('[Enable PKIX certificate path discovery and validation]') + if sysupgrade.get_upgrade_state('dogtag', 'pkix_enabled'): + root_logger.info('PKIX already enabled') + return False + + if not ca.is_configured(): + root_logger.info('CA is not configured') + return False + + ca.enable_pkix() + sysupgrade.set_upgrade_state('dogtag', 'pkix_enabled', True) + + return True + + +def add_ca_dns_records(): + root_logger.info('[Add missing CA DNS records]') + + if sysupgrade.get_upgrade_state('dns', 'ipa_ca_records'): + root_logger.info('IPA CA DNS records already processed') + return + + if not api.Backend.ldap2.isconnected(): + try: + api.Backend.ldap2.connect(autobind=True) + except ipalib.errors.PublicError, e: + root_logger.error( + "Cannot connect to LDAP to add DNS records: %s", e) + return + + ret = api.Command['dns_is_enabled']() + if not ret['result']: + root_logger.info('DNS is not configured') + sysupgrade.set_upgrade_state('dns', 'ipa_ca_records', True) + return + + bind = bindinstance.BindInstance() + + bind.convert_ipa_ca_cnames(api.env.domain) + + # DNS is enabled, so let bindinstance find out if CA is enabled + # and let it add the record in that case + bind.add_ipa_ca_dns_records(api.env.host, api.env.domain, + ca_configured=None) + + sysupgrade.set_upgrade_state('dns', 'ipa_ca_records', True) + + +def find_subject_base(): + """ + Try to find the current value of certificate subject base. + See the docstring in dsinstance.DsInstance for details. + """ + subject_base = dsinstance.DsInstance().find_subject_base() + + if subject_base: + sysupgrade.set_upgrade_state( + 'certmap.conf', + 'subject_base', + subject_base + ) + return subject_base + + root_logger.error('Unable to determine certificate subject base. ' + 'certmap.conf will not be updated.') + + +def uninstall_selfsign(ds, http): + root_logger.info('[Removing self-signed CA]') + """Replace self-signed CA by a CA-less install""" + if api.env.ra_plugin != 'selfsign': + root_logger.debug('Self-signed CA is not installed') + return + + root_logger.warning( + 'Removing self-signed CA. Certificates will need to managed manually.') + p = ConfigParser.SafeConfigParser() + p.read(paths.IPA_DEFAULT_CONF) + p.set('global', 'enable_ra', 'False') + p.set('global', 'ra_plugin', 'none') + with open(paths.IPA_DEFAULT_CONF, 'w') as f: + p.write(f) + + ds.stop_tracking_certificates() + http.stop_tracking_certificates() + + +def mask_named_regular(): + """Disable named, we need to run only named-pkcs11, running both named and + named-pkcs can cause unexpected errors""" + if sysupgrade.get_upgrade_state('dns', 'regular_named_masked'): + return False + + sysupgrade.set_upgrade_state('dns', 'regular_named_masked', True) + + if bindinstance.named_conf_exists(): + root_logger.info('[Masking named]') + named = services.service('named-regular') + try: + named.stop() + except Exception as e: + root_logger.warning('Unable to stop named service (%s)', e) + + try: + named.mask() + except Exception as e: + root_logger.warning('Unable to mask named service (%s)', e) + + return True + + return False + + +def fix_dyndb_ldap_workdir_permissions(): + """Fix dyndb-ldap working dir permissions. DNSSEC daemons requires it""" + if sysupgrade.get_upgrade_state('dns', 'dyndb_ipa_workdir_perm'): + return + + if bindinstance.named_conf_exists(): + root_logger.info('[Fix bind-dyndb-ldap IPA working directory]') + dnskeysync = dnskeysyncinstance.DNSKeySyncInstance() + dnskeysync.set_dyndb_ldap_workdir_permissions() + + sysupgrade.set_upgrade_state('dns', 'dyndb_ipa_workdir_perm', True) + + +def fix_schema_file_syntax(): + """Fix syntax errors in schema files + + https://fedorahosted.org/freeipa/ticket/3578 + """ + root_logger.info('[Fix DS schema file syntax]') + + # This is not handled by normal schema updates, because pre-1.3.2 DS will + # ignore (auto-fix) these syntax errors, and 1.3.2 and above will choke on + # them before checking dynamic schema updates. + + if sysupgrade.get_upgrade_state('ds', 'fix_schema_syntax'): + root_logger.info('Syntax already fixed') + return + + serverid = installutils.realm_to_serverid(api.env.realm) + ds_dir = dsinstance.config_dirname(serverid) + + # 1. 60ipadns.ldif: Add parenthesis to idnsRecord + + filename = os.path.join(ds_dir, 'schema', '60ipadns.ldif') + result_lines = [] + with open(filename) as file: + for line in file: + line = line.strip('\n') + if (line.startswith('objectClasses:') and + "NAME 'idnsRecord'" in line and + line.count('(') == 2 and + line.count(')') == 1): + root_logger.debug('Add closing parenthesis in idnsRecord') + line += ' )' + result_lines.append(line) + + with open(filename, 'w') as file: + file.write('\n'.join(result_lines)) + + # 2. 65ipasudo.ldif: Remove extra dollar from ipaSudoRule + + filename = os.path.join(ds_dir, 'schema', '65ipasudo.ldif') + result_lines = [] + with open(filename) as file: + for line in file: + line = line.strip('\n') + if (line.startswith('objectClasses:') and + "NAME 'ipaSudoRule'" in line): + root_logger.debug('Remove extra dollar sign in ipaSudoRule') + line = line.replace('$$', '$') + result_lines.append(line) + + with open(filename, 'w') as file: + file.write('\n'.join(result_lines)) + + # Done + + sysupgrade.set_upgrade_state('ds', 'fix_schema_syntax', True) + + +def set_sssd_domain_option(option, value): + sssdconfig = SSSDConfig.SSSDConfig() + sssdconfig.import_config() + domain = sssdconfig.get_domain(str(api.env.domain)) + domain.set_option(option, value) + sssdconfig.save_domain(domain) + sssdconfig.write(paths.SSSD_CONF) + + +def remove_ds_ra_cert(subject_base): + root_logger.info('[Removing RA cert from DS NSS database]') + + if sysupgrade.get_upgrade_state('ds', 'remove_ra_cert'): + root_logger.info('RA cert already removed') + return + + dbdir = dsinstance.config_dirname( + installutils.realm_to_serverid(api.env.realm)) + dsdb = certs.CertDB(api.env.realm, nssdir=dbdir, subject_base=subject_base) + + nickname = 'CN=IPA RA,%s' % subject_base + cert = dsdb.get_cert_from_db(nickname) + if cert: + dsdb.delete_cert(nickname) + + sysupgrade.set_upgrade_state('ds', 'remove_ra_cert', True) + + +def fix_trust_flags(): + root_logger.info('[Fixing trust flags in %s]' % paths.HTTPD_ALIAS_DIR) + + if sysupgrade.get_upgrade_state('http', 'fix_trust_flags'): + root_logger.info("Trust flags already processed") + return + + if not api.Backend.ldap2.isconnected(): + try: + api.Backend.ldap2.connect(autobind=True) + except ipalib.errors.PublicError, e: + root_logger.error("Cannot connect to LDAP: %s", e) + return + + if not api.Command.ca_is_enabled()['result']: + root_logger.info("CA is not enabled") + return + + db = certs.CertDB(api.env.realm) + nickname = certdb.get_ca_nickname(api.env.realm) + cert = db.get_cert_from_db(nickname) + if cert: + db.trust_root_cert(nickname, 'CT,C,C') + + sysupgrade.set_upgrade_state('http', 'fix_trust_flags', True) + + +def update_mod_nss_protocol(http): + root_logger.info('[Updating mod_nss protocol versions]') + + if sysupgrade.get_upgrade_state('nss.conf', 'protocol_updated_tls12'): + root_logger.info("Protocol versions already updated") + return + + http.set_mod_nss_protocol() + + sysupgrade.set_upgrade_state('nss.conf', 'protocol_updated_tls12', True) + + +def upgrade_configuration(): + """ + Execute configuration upgrade of the IPA services + """ + + root_logger.debug('IPA version %s' % version.VENDOR_VERSION) + + fstore = sysrestore.FileStore(paths.SYSRESTORE) + + fqdn = find_hostname() + if fqdn is None: + # ipa-rewrite.conf doesn't exist, nothing to do + raise RuntimeError("ipa-rewrite.conf doesn't exists (is this server?)") + + # Ok, we are an IPA server, do the additional tests + + check_certs() + + auto_redirect = find_autoredirect(fqdn) + configured_constants = dogtag.configured_constants() + sub_dict = dict( + REALM=api.env.realm, + FQDN=fqdn, + AUTOREDIR='' if auto_redirect else '#', + CRL_PUBLISH_PATH=configured_constants.CRL_PUBLISH_PATH, + DOGTAG_PORT=configured_constants.AJP_PORT, + CLONE='#' + ) + + subject_base = find_subject_base() + if subject_base: + sub_dict['SUBJECT_BASE'] = subject_base + + ca = cainstance.CAInstance(api.env.realm, certs.NSS_DIR) + ca.backup_config() + + with installutils.stopped_service(configured_constants.SERVICE_NAME, + configured_constants.PKI_INSTANCE_NAME): + # migrate CRL publish dir before the location in ipa.conf is updated + ca_restart = migrate_crl_publish_dir(ca) + + if ca.is_configured(): + crl = installutils.get_directive(configured_constants.CS_CFG_PATH, + 'ca.crl.MasterCRL.enableCRLUpdates', '=') + sub_dict['CLONE']='#' if crl.lower() == 'true' else '' + + ds_serverid = installutils.realm_to_serverid(api.env.realm) + ds_dirname = dsinstance.config_dirname(ds_serverid) + + upgrade(sub_dict, paths.HTTPD_IPA_CONF, ipautil.SHARE_DIR + "ipa.conf") + upgrade(sub_dict, paths.HTTPD_IPA_REWRITE_CONF, + ipautil.SHARE_DIR + "ipa-rewrite.conf") + if ca.is_configured(): + upgrade(sub_dict, paths.HTTPD_IPA_PKI_PROXY_CONF, + ipautil.SHARE_DIR + "ipa-pki-proxy.conf", add=True) + else: + if ipautil.file_exists(paths.HTTPD_IPA_PKI_PROXY_CONF): + os.remove(paths.HTTPD_IPA_PKI_PROXY_CONF) + if subject_base: + upgrade( + sub_dict, + os.path.join(ds_dirname, "certmap.conf"), + os.path.join(ipautil.SHARE_DIR, "certmap.conf.template") + ) + upgrade_pki(ca, fstore) + + ca.configure_certmonger_renewal_guard() + + update_dbmodules(api.env.realm) + uninstall_ipa_kpasswd() + + removed_sysconfig_file = paths.SYSCONFIG_HTTPD + if fstore.has_file(removed_sysconfig_file): + root_logger.info('Restoring %s as it is no longer required', + removed_sysconfig_file) + fstore.restore_file(removed_sysconfig_file) + + http = httpinstance.HTTPInstance(fstore) + http.configure_selinux_for_httpd() + http.change_mod_nss_port_from_http() + http.configure_certmonger_renewal_guard() + + http.stop() + update_mod_nss_protocol(http) + fix_trust_flags() + http.start() + + ds = dsinstance.DsInstance() + ds.configure_dirsrv_ccache() + + ds.stop(ds_serverid) + fix_schema_file_syntax() + remove_ds_ra_cert(subject_base) + ds.start(ds_serverid) + + uninstall_selfsign(ds, http) + + simple_service_list = ( + (memcacheinstance.MemcacheInstance(), 'MEMCACHE'), + (otpdinstance.OtpdInstance(), 'OTPD'), + ) + + for service, ldap_name in simple_service_list: + service.ldapi = True + try: + if not service.is_configured(): + # 389-ds needs to be running to create the memcache instance + # because we record the new service in cn=masters. + ds.start() + service.create_instance(ldap_name, fqdn, None, + ipautil.realm_to_suffix(api.env.realm), + realm=api.env.realm) + except ipalib.errors.DuplicateEntry: + pass + + # install DNSKeySync service only if DNS is configured on server + if bindinstance.named_conf_exists(): + dnskeysyncd = dnskeysyncinstance.DNSKeySyncInstance(fstore, + ldapi=True) + if not dnskeysyncd.is_configured(): + ds.start() + dnskeysyncd.create_instance(fqdn, api.env.realm) + dnskeysyncd.start_dnskeysyncd() + + cleanup_kdc(fstore) + cleanup_adtrust(fstore) + setup_firefox_extension(fstore) + add_ca_dns_records() + + # Any of the following functions returns True iff the named.conf file + # has been altered + named_conf_changes = ( + named_remove_deprecated_options(), + named_set_minimum_connections(), + named_enable_serial_autoincrement(), + named_update_gssapi_configuration(), + named_update_pid_file(), + named_enable_dnssec(), + named_validate_dnssec(), + named_bindkey_file_option(), + named_managed_keys_dir_option(), + named_root_key_include(), + mask_named_regular(), + fix_dyndb_ldap_workdir_permissions(), + ) + + if any(named_conf_changes): + # configuration has changed, restart the name server + root_logger.info('Changes to named.conf have been made, restart named') + bind = bindinstance.BindInstance(fstore) + try: + bind.restart() + except ipautil.CalledProcessError as e: + root_logger.error("Failed to restart %s: %s", bind.service_name, e) + + ca_restart = any([ + ca_restart, + upgrade_ipa_profile(ca, api.env.domain, fqdn), + certificate_renewal_update(ca), + ca_enable_pkix(ca), + ]) + + if ca_restart: + root_logger.info('pki-ca configuration changed, restart pki-ca') + try: + ca.restart(dogtag.configured_constants().PKI_INSTANCE_NAME) + except ipautil.CalledProcessError as e: + root_logger.error("Failed to restart %s: %s", ca.service_name, e) + + set_sssd_domain_option('ipa_server_mode', 'True')