def process(self): if has_package(InstalledRedHatSignedRPM, 'brltty'): report_with_remediation( title='Brltty has incompatible changes in the next major version', summary='The --message-delay brltty option has been renamed to --message-timeout.\n' 'The -U [--update-interval=] brltty option has been removed.', remediation='Please update your scripts to be compatible with the changes.', severity='low') (migrate_file, migrate_bt, migrate_espeak) = library.check_for_unsupported_cfg() report_summary = '' if migrate_bt: report_summary = 'Unsupported aliases for bluetooth devices (\'bth:\' and \'bluez:\') will be ' report_summary += 'renamed to \'bluetooth:\'.' if migrate_espeak: if report_summary: report_summary += '\n' report_summary += 'eSpeak speech driver is no longer supported, it will be switched to eSpeak-NG.' if report_summary: report_generic( title='brltty configuration will be migrated', summary=report_summary, severity='low') self.produce(BrlttyMigrationDecision(migrate_file=migrate_file, migrate_bt=migrate_bt, migrate_espeak=migrate_espeak)) else: report_generic( title='brltty configuration will be not migrated', summary='brltty configuration seems to be compatible', severity='low')
def check_ntp(installed_packages): service_data = [('ntpd', 'ntp', '/etc/ntp.conf'), ('ntpdate', 'ntpdate', '/etc/ntp/step-tickers'), ('ntp-wait', 'ntp-perl', None)] migrate_services = [] migrate_configs = [] for service, package, main_config in service_data: if package in installed_packages and \ check_service('{}.service'.format(service)) and \ (not main_config or is_file(main_config)): migrate_services.append(service) if main_config: migrate_configs.append(service) if migrate_configs: reporting.report_generic( title='{} configuration will be migrated'.format( ' and '.join(migrate_configs)), summary='{} service(s) detected to be enabled and active'.format( ', '.join(migrate_services)), severity='low') # Save configuration files that will be renamed in the upgrade config_tgz64 = get_tgz64([ '/etc/ntp.conf', '/etc/ntp/keys', '/etc/ntp/crypto/pw', '/etc/ntp/step-tickers' ]) else: api.current_logger().info( 'ntpd/ntpdate configuration will not be migrated') migrate_services = [] config_tgz64 = '' return NtpMigrationDecision(migrate_services=migrate_services, config_tgz64=config_tgz64)
def process(self): if platform.machine() != 'x86_64': report_generic( title='Unsupported arch', summary='Upgrade process is only supported on x86_64 systems.', severity='high', flags=['inhibitor'])
def process(self): if not has_package(InstalledRedHatSignedRPM, 'sendmail'): return if config_applies_to_daemon(next(self.consume(TcpWrappersFacts)), 'sendmail'): report_with_remediation( title='TCP wrappers support removed in the next major version', summary= 'TCP wrappers are legacy host-based ACL (Access Control List) system ' 'which has been removed in the next major version of RHEL.', remediation= 'Please migrate from TCP wrappers to some other access control mechanism and delete ' 'sendmail from the /etc/hosts.[allow|deny].', severity='high', flags=['inhibitor']) return migrate_files = library.check_files_for_compressed_ipv6() if migrate_files: report_generic( title='sendmail configuration will be migrated', summary= 'IPv6 addresses will be uncompressed, check all IPv6 addresses in all sendmail ' 'configuration files for correctness.', severity='low') self.produce( SendmailMigrationDecision(migrate_files=migrate_files)) else: self.log.info( 'The sendmail configuration seems compatible - it won\'t be migrated.' )
def report_skipped_packages(message, packages): """Generate report message about skipped packages""" title = 'Packages will not be installed' summary = '{} {}\n{}'.format(len(packages), message, '\n'.join(['- ' + p for p in packages])) reporting.report_generic(title=title, summary=summary, severity='high') if is_verbose(): api.show_message(summary)
def inhibit_upgrade(title): summary = 'Read documentation at: https://access.redhat.com/articles/3664871 for more information ' \ 'about how to retrieve the files' reporting.report_generic(title=title, summary=summary, severity='high', flags=['inhibitor']) raise StopActorExecution()
def inhibit_upgrade(avail_bytes): additional_mib_needed = (MIN_AVAIL_BYTES_FOR_BOOT - avail_bytes) / 2**20 # we use "reporting.report_generic" to allow mocking in the tests reporting.report_generic( title='Not enough space on /boot', summary='/boot needs additional {0} MiB to be able to accomodate the upgrade initramfs and new kernel.'.format( additional_mib_needed), severity='high', flags=['inhibitor'])
def inhibit(node_type): report_generic( title="Use of HA cluster detected. Upgrade can't proceed.", summary=("HA cluster is not supported by the inplace upgrade.\n" "HA cluster configuration file(s) found." " It seems to be a cluster {0}.".format(node_type)), severity="high", flags=["inhibitor"], )
def skip_check(): """ Check if has environment variable to skip this actor checks """ if os.getenv('LEAPP_SKIP_CHECK_SIGNED_PACKAGES'): reporting.report_generic( title='Skipped signed packages check', severity='low', summary= 'Signed packages check skipped via LEAPP_SKIP_CHECK_SIGNED_PACKAGES env var' ) raise StopActorExecution()
def skip_check(): """ Check if an environment variable was used to skip this actor """ if os.getenv('LEAPP_SKIP_CHECK_OS_RELEASE'): reporting.report_generic( title='Skipped OS release check', severity='high', summary= 'Source RHEL release check skipped via LEAPP_SKIP_CHECK_OS_RELEASE env var.', ) return True return False
def process(self): for config in self.consume(OpenSshConfig): if config.use_privilege_separation is not None and \ config.use_privilege_separation != "sandbox": report_generic( title= 'OpenSSH configured not to use privilege separation sandbox', summary='OpenSSH is configured to disable privilege ' 'separation sandbox, which is decreasing security ' 'and is no longer supported in RHEL 8', severity='low')
def process(self): for config in self.consume(OpenSshConfig): if not config.protocol: continue report_generic( title='OpenSSH configured with removed configuration Protocol', summary='OpenSSH is configured with removed configuration ' 'option Protocol. If this used to be for enabling ' 'SSHv1, this is no longer supported in RHEL 8. ' 'Otherwise this option can be simply removed.', severity='low')
def process(self): error_detected = detect_config_error('/etc/default/grub') if error_detected: report_generic( title='Syntax error detected in grub configuration', summary= 'Syntax error was detected in GRUB_CMDLINE_LINUX value of grub configuration. ' 'This error is causing booting and other issues. ' 'Error is automatically fixed by add_upgrade_boot_entry actor.', severity='low') self.produce(GrubConfigError(error_detected=error_detected))
def migrate_ntp(migrate_services, config_tgz64): # Map of ntp->chrony services and flag if using configuration service_map = { 'ntpd': ('chronyd', True), 'ntpdate': ('chronyd', True), 'ntp-wait': ('chrony-wait', False) } # Minimal secure ntp.conf with no sources to migrate ntpdate only no_sources_directives = ( '# This file was created to migrate ntpdate configuration to chrony\n' '# without ntp configuration (ntpd service was disabled)\n' 'driftfile /var/lib/ntp/drift\n' 'restrict default ignore nomodify notrap nopeer noquery\n') if not migrate_services: # Nothing to migrate return migrate_configs = [] for service in migrate_services: if service not in service_map: raise StopActorExecutionError('Unknown service {}'.format(service)) enable_service(service_map[service][0]) if service_map[service][1]: migrate_configs.append(service) # Unpack archive with configuration files extract_tgz64(config_tgz64) if 'ntpd' in migrate_configs: ntp_conf = '/etc/ntp.conf' else: ntp_conf = '/etc/ntp.conf.nosources' write_file(ntp_conf, no_sources_directives) step_tickers = '/etc/ntp/step-tickers' if 'ntpdate' in migrate_configs else '' ignored_lines = ntp2chrony('/', ntp_conf, step_tickers) if not ignored_lines: reporting.report_generic( title='{} configuration migrated to chrony'.format( ' and '.join(migrate_configs)), summary='ntp2chrony executed successfully', severity='low') else: reporting.report_generic( title='{} configuration partially migrated to chrony'.format( ' and '.join(migrate_configs)), summary= 'Some lines in /etc/ntp.conf were ignored in migration (check /etc/chrony.conf)', severity='medium')
def process(self): decision = next(self.consume(SendmailMigrationDecision), None) if not decision or not decision.migrate_files: return for f in decision.migrate_files: library.migrate_file(f) list_separator_fmt = '\n - ' report_generic(title='sendmail configuration files migrated', summary='Uncompressed IPv6 addresses in {}'.format( list_separator_fmt.join(decision.migrate_files)), severity='low')
def process(self): interfaces = next(self.consume(PersistentNetNamesFacts)).interfaces if self.single_eth0(interfaces): self.disable_persistent_naming() elif self.ethX_count(interfaces) > 1: reporting.report_generic( title='Unsupported network configuration', summary='Detected multiple network interfaces using unstable kernel names (e.g. eth0, eth1). ' 'Upgrade process can not continue because stability of names can not be guaranteed. ' 'Please read the article at https://access.redhat.com/solutions/4067471 for more information.', severity='high', flags=['inhibitor'])
def scan_events(path): """ Scan JSON file containing PES events """ try: events = parse_file(path) except (ValueError, KeyError): title = 'Missing/Invalid PES data file ({})'.format(path) summary = 'Read documentation at: https://access.redhat.com/articles/3664871 for more information ' \ 'about how to retrieve the files' reporting.report_generic(title=title, summary=summary, severity='high', flags=['inhibitor']) raise StopActorExecution() process_events(filter_events(events))
def check_chrony(chrony_installed): if not chrony_installed: api.current_logger().info('chrony package is not installed') return if is_config_default(): reporting.report_generic( title='chrony using default configuration', summary= 'default chrony configuration in RHEL8 uses leapsectz directive, which cannot be used with leap smearing NTP servers, and uses a single pool directive instead of four server directives', severity='medium') else: reporting.report_generic( title='chrony using non-default configuration', summary='chrony behavior will not change in RHEL8', severity='low')
def process(self): # if NIS is not used for domainname resolution, everything is OK if not self.nis_in_nsswitch_hosts(): return hostnames = self.hostnames_in_yp_conf() if len(hostnames) > 0: title = "Unsupported NIS configuration found" summary = "NIS may be used for domain name resolution only if NIS "\ "server is specified by IP. NIS servers specified by "\ "host name: {}".format(", ".join(hostnames)) severity = "medium" report_generic(title=title, severity=severity, summary=summary)
def process(self): service_name = 'leapp_resume.service' if os.path.isfile('/etc/systemd/system/{}'.format(service_name)): run(['systemctl', 'disable', service_name]) try: os.unlink('/etc/systemd/system/{}'.format(service_name)) os.unlink('/etc/systemd/system/default.target.wants/{}'.format(service_name)) except OSError as e: if e.errno != errno.ENOENT: raise report_generic( title='"{}" service deleted'.format(service_name), summary='"{}" was taking care of resuming upgrade process ' 'after the first reboot.'.format(service_name), severity='low' )
def process(self): openssh_messages = self.consume(OpenSshConfig) config = next(openssh_messages, None) if list(openssh_messages): api.current_logger().warning('Unexpectedly received more than one OpenSshConfig message.') if not config: raise StopActorExecutionError( 'Could not check openssh configuration', details={'details': 'No OpenSshConfig facts found.'} ) if config.protocol: report_generic( title='OpenSSH configured with removed configuration Protocol', summary='OpenSSH is configured with removed configuration ' 'option Protocol. If this used to be for enabling ' 'SSHv1, this is no longer supported in RHEL 8. ' 'Otherwise this option can be simply removed.', severity='low')
def process(self): for decision in self.consume(BrlttyMigrationDecision): report_summary = '' library.migrate_file(decision.migrate_file, decision.migrate_bt, decision.migrate_espeak) if decision.migrate_bt: report_summary = 'Unsupported aliases for bluetooth devices (\'bth:\' and \'bluez:\') was ' report_summary += 'renamed to \'bluetooth:\' in {}' report_summary = report_summary.format(', '.join(decision.migrate_file)) if decision.migrate_espeak: if report_summary: report_summary += '\n' report_summary += 'eSpeak speech driver was switched to eSpeak-NG in {}' report_summary = report_summary.format(', '.join(decision.migrate_file)) if decision.migrate_bt or decision.migrate_espeak: report_generic( title='brltty configuration files migrated', summary=report_summary, severity='low')
def get_events(pes_events_filepath): """ Get all the events from the source JSON file exported from PES. :return: List of Event tuples, where each event contains event type and input/output pkgs """ try: return parse_pes_events_file(pes_events_filepath) except (ValueError, KeyError): title = 'Missing/Invalid PES data file ({})'.format( pes_events_filepath) summary = 'Read documentation at: https://access.redhat.com/articles/3664871 for more information ' \ 'about how to retrieve the files' reporting.report_generic(title=title, summary=summary, severity='high', flags=['inhibitor']) raise StopActorExecution()
def process(self): model = next(self.consume(Authselect)) decision = next(self.consume(AuthselectDecision)) if not decision.confirmed or model.profile is None: return command = ['authselect', 'select', '--force', model.profile ] + model.features try: run(command) except CalledProcessError as err: report_generic(title='Authselect call failed.', summary=str(err)) return report_generic(title='System was converted to authselect.', summary='System was converted to authselect with the ' 'following call: "{}"'.format(' '.join(command)))
def process(self): for fact in self.consume(InstalledRedHatSignedRPM): for rpm in fact.items: if rpm.name == 'postfix': report_generic( title= 'Postfix has incompatible changes in the next major version', summary= 'Postfix 3.x has so called "compatibility safety net" that runs Postfix programs ' 'with backwards-compatible default settings. It will log a warning whenever ' 'backwards-compatible default setting may be required for continuity of service. ' 'Based on this logging the system administrator can decide if any ' 'backwards-compatible settings need to be made permanent in main.cf or master.cf, ' 'before turning off the backwards-compatibility safety net.\n' 'The backward compatibility safety net is by default turned off in Red Hat ' 'Enterprise Linux 8.\n' 'It can be turned on by running: "postconf -e compatibility_level=0\n' 'It can be turned off by running: "postconf -e compatibility_level=2\n\n' 'In the Postfix MySQL database client, the default "option_group" value has changed ' 'to "client", i.e. it now reads options from the [client] group from the MySQL ' 'configuration file. To disable it, set "option_group" to the empty string.\n\n' 'The postqueue command no longer forces all message arrival times to be reported ' 'in UTC. To get the old behavior, set TZ=UTC in main.cf:import_environment.\n\n' 'Postfix 3.2 enables elliptic curve negotiation. This changes the default ' 'smtpd_tls_eecdh_grade setting to "auto", and introduces a new parameter ' '"tls_eecdh_auto_curves" with the names of curves that may be negotiated.\n\n' 'The "master.cf" chroot default value has changed from "y" (yes) to "n" (no). ' 'This applies to master.cf services where chroot field is not explicitly ' 'specified.\n\n' 'The "append_dot_mydomain" default value has changed from "yes" to "no". You may ' 'need changing it to "yes" if senders cannot use complete domain names in e-mail ' 'addresses.\n\n' 'The "relay_domains" default value has changed from "$mydestination" to the empty ' 'value. This could result in unexpected "Relay access denied" errors or ETRN errors, ' 'because now will postfix by default relay only for the localhost.\n\n' 'The "mynetworks_style" default value has changed from "subnet" to "host". ' 'This parameter is used to implement the "permit_mynetworks" feature. The change ' 'could result in unexpected "access denied" errors, because postfix will now by ' 'default trust only the local machine, not the remote SMTP clients on the ' 'same IP subnetwork.\n', severity='low') return
def process(self): openssh_messages = self.consume(OpenSshConfig) config = next(openssh_messages, None) if list(openssh_messages): api.current_logger().warning( 'Unexpectedly received more than one OpenSshConfig message.') if not config: raise StopActorExecutionError( 'Could not check openssh configuration', details={'details': 'No OpenSshConfig facts found.'}) if config.use_privilege_separation is not None and \ config.use_privilege_separation != "sandbox": report_generic( title= 'OpenSSH configured not to use privilege separation sandbox', summary='OpenSSH is configured to disable privilege ' 'separation sandbox, which is decreasing security ' 'and is no longer supported in RHEL 8', severity='low')
def check_memcached(memcached_installed): if not memcached_installed: api.current_logger().info('memcached package is not installed') return default_memcached_conf = is_sysconfig_default() disabled_udp_port = is_udp_disabled() if default_memcached_conf: reporting.report_generic( title='memcached service is using default configuration', summary= 'memcached in RHEL8 listens on loopback only and has UDP port disabled by default', severity='medium') elif not disabled_udp_port: reporting.report_generic( title='memcached has enabled UDP port', summary= 'memcached in RHEL7 has UDP port enabled by default, but it is disabled by default in RHEL8', severity='medium') else: reporting.report_generic( title='memcached has already disabled UDP port', summary='memcached in RHEL8 has UDP port disabled by default', severity='low')
def check_os_version(supported_version): """ Check OS version and inhibit upgrade if not the same as supported ones """ if not isinstance(supported_version, dict): api.current_logger().warning('The supported version value is invalid.') raise StopActorExecution() facts_messages = api.consume(OSReleaseFacts) facts = next(facts_messages, None) if list(facts_messages): api.current_logger().warning( 'Unexpectedly received more than one OSReleaseFacts message.') if not facts: raise StopActorExecutionError( 'Could not check OS version', details={'details': 'No OSReleaseFacts facts found.'}) if facts.release_id not in supported_version: reporting.report_generic( title='Unsupported OS', summary='Only RHEL is supported by the upgrade process', flags=['inhibitor'], ) return if not isinstance(supported_version[facts.release_id], list): raise StopActorExecutionError( 'Invalid versions', details={ 'details': 'OS versions are invalid, please provide a valid list.' }, ) if facts.version_id not in supported_version[facts.release_id]: reporting.report_generic( title='Unsupported OS version', summary='The supported OS versions for the upgrade process: {}'. format(', '.join(supported_version[facts.release_id])), flags=['inhibitor'], )
def get_os_release_info(path): ''' Retrieve data about System OS release from provided file ''' data = {} try: with open(path) as f: data = dict(l.strip().split('=', 1) for l in f.readlines() if '=' in l) except IOError as e: reporting.report_generic( title='Error while collecting system OS facts', summary=str(e), severity='high', flags=['inhibitor']) return None return OSReleaseFacts(id=data.get('ID', '').strip('"'), name=data.get('NAME', '').strip('"'), pretty_name=data.get('PRETTY_NAME', '').strip('"'), version=data.get('VERSION', '').strip('"'), version_id=data.get('VERSION_ID', '').strip('"'), variant=data.get('VARIANT', '').strip('"') or None, variant_id=data.get('VARIANT_ID', '').strip('"') or None)
def process(self): for decision in self.consume(SelinuxRelabelDecision): if decision.set_relabel: try: with open('/.autorelabel', 'w'): pass report_generic( title='SElinux scheduled for relabelling', summary= '/.autorelabel file touched on root in order to schedule SElinux relabelling.', severity='low', ) except OSError as e: # FIXME: add an "action required" flag later report_with_remediation( title='Could not schedule SElinux for relabelling', summary='./autorelabel file could not be created: {}.'. format(e), remediation= 'Please set autorelabelling manually after the upgrade.', severity='high') self.log.critical( 'Could not schedule SElinux for relabelling: %s.' % e)