示例#1
0
    def process(self):
        is_system_efi = False
        has_efibootmgr = os.path.exists('/sbin/efibootmgr')
        for fact in self.consume(FirmwareFacts):
            if fact.firmware == 'efi':
                is_system_efi = True
                break

        if is_system_efi and not has_efibootmgr:
            reporting.create_report([
                reporting.Title(
                    'efibootmgr package is required on EFI systems'),
                reporting.Summary(
                    'efibootmgr is required so that we can can set proper boot options in between restarts'
                ),
                reporting.Remediation(
                    commands=[['yum', '-y', 'install', 'efibootmgr']]),
                reporting.RelatedResource('package', 'efibootmgr'),
                reporting.Flags([reporting.Flags.INHIBITOR]),
                reporting.Severity(reporting.Severity.HIGH),
                reporting.Tags([reporting.Tags.BOOT])
            ])
示例#2
0
 def process(self):
     if has_package(InstalledRedHatSignedRPM, 'wireshark'):
         create_report([
             reporting.Title('tshark: CLI options and output changes'),
             reporting.Summary(
                 'The -C suboption for -N option for asynchronous DNS name resolution '
                 'has been completely removed from tshark. The reason for this is that '
                 'the asynchronous DNS resolution is now the only resolution available '
                 'so there is no need for -C. If you are using -NC with tshark in any '
                 'of your scripts, please remove it.'
                 '\n\n'
                 'When using -H option with capinfos, the output no longer shows MD5 hashes. '
                 'Now it shows SHA256 instead. SHA1 might get removed very soon as well. '
                 'If you use these output values, please change your scripts.'
             ),
             reporting.Severity(reporting.Severity.LOW),
             reporting.Tags([
                 reporting.Tags.MONITORING, reporting.Tags.SANITY,
                 reporting.Tags.TOOLS
             ]),
             reporting.RelatedResource('package', 'wireshark'),
         ])
示例#3
0
def check_include_directive(facts, report_func):
    """
    Checks if the data model tells include directives are used
    and produces a report.

    :param obj facts: model object containing info about CUPS configuration
    :param func report_func: creates report
    """
    title = ('CUPS no longer supports usage of Include directive')
    summary = ('Include directive was removed due to security reasons. '
               'Contents of found included files will be appended to '
               'cupsd.conf')
    if facts.include:
        args = [
            reporting.Title(title),
            reporting.Summary(summary),
            reporting.Tags([reporting.Tags.SERVICES]),
            reporting.Severity(reporting.Severity.MEDIUM),
        ] + [
            reporting.RelatedResource('file', f) for f in facts.include_files
        ]

        report_func(args)
示例#4
0
 def process(self):
     for fact in self.consume(ActiveKernelModulesFacts):
         for active_module in fact.kernel_modules:
             if active_module.filename == 'btrfs':
                 create_report([
                     reporting.Title('Btrfs has been removed from RHEL8'),
                     reporting.Summary(
                         'The Btrfs file system was introduced as Technology Preview with the '
                         'initial release of Red Hat Enterprise Linux 6 and Red Hat Enterprise Linux 7. As of '
                         'versions 6.6 and 7.4 this technology has been deprecated and removed in RHEL8.'
                     ),
                     reporting.ExternalLink(
                         title=
                         'Considerations in adopting RHEL 8 - btrfs has been removed.',
                         url=
                         'https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/considerations_in_adopting_rhel_8/file-systems-and-storage_considerations-in-adopting-rhel-8#btrfs-has-been-removed_file-systems-and-storage'  # noqa: E501; pylint: disable=line-too-long
                     ),
                     reporting.Severity(reporting.Severity.HIGH),
                     reporting.Flags([reporting.Flags.INHIBITOR]),
                     reporting.Tags([reporting.Tags.FILESYSTEM]),
                     reporting.RelatedResource('kernel-driver', 'btrfs')
                 ])
                 break
示例#5
0
def process():
    if not rhsm.skip_rhsm():
        for info in api.consume(RHSMInfo):
            if not info.attached_skus:
                create_report([
                    reporting.Title(
                        'The system is not registered or subscribed.'),
                    reporting.Summary(
                        'The system has to be registered and subscribed to be able to proceed'
                        ' with the upgrade, unless the --no-rhsm option is specified when'
                        ' executing leapp.'),
                    reporting.Severity(reporting.Severity.HIGH),
                    reporting.Tags([reporting.Tags.SANITY]),
                    reporting.Flags([reporting.Flags.INHIBITOR]),
                    reporting.Remediation(
                        hint=
                        'Register your system with the subscription-manager tool and attach'
                        ' proper SKUs to be able to proceed the upgrade or use the --no-rhsm'
                        ' leapp option if you want to provide target repositories by yourself.'
                    ),
                    reporting.RelatedResource('package',
                                              'subscription-manager')
                ])
示例#6
0
 def produce_inhibitor(self, module):
     create_report([
         reporting.Title(
             'Upgrade process was interrupted because {0} is enabled in '
             'PAM configuration and SA user refused to disable it '
             'automatically.'.format(module)),
         reporting.Summary(
             'Module {0} was surpassed by SSSD and therefore it was '
             'removed from RHEL-8. Keeping it in PAM configuration may '
             'lock out the system thus it is necessary to disable it '
             'before the upgrade process can continue.'.format(module)
         ),
         reporting.Severity(reporting.Severity.HIGH),
         reporting.Tags([
                 reporting.Tags.AUTHENTICATION,
                 reporting.Tags.SECURITY,
                 reporting.Tags.TOOLS
         ]),
         reporting.Flags([reporting.Flags.INHIBITOR]),
         reporting.Remediation(
             hint='Disable {0} module and switch to SSSD to recover its functionality.'.format(module)),
         reporting.RelatedResource('package', 'sssd')
     ])
def report_skipped_packages(title, message, package_repo_pairs, remediation=None):
    """Generate report message about skipped packages"""
    package_repo_pairs = sorted(package_repo_pairs)
    summary = '{} {}\n{}'.format(
        len(package_repo_pairs), message, '\n'.join(
            [
                '- {pkg} (repoid: {repo})'.format(pkg=pkg, repo=repo)
                for pkg, repo in package_repo_pairs
            ]
        )
    )
    report_content = [
        reporting.Title(title),
        reporting.Summary(summary),
        reporting.Severity(reporting.Severity.HIGH),
        reporting.Tags([reporting.Tags.REPOSITORY]),
    ]
    if remediation:
        report_content += [reporting.Remediation(hint=remediation)]
    report_content += [reporting.RelatedResource('package', p) for p, _ in package_repo_pairs]
    reporting.create_report(report_content)
    if is_verbose():
        api.current_logger().info(summary)
def _report_unhandled_release():
    # TODO: set the POST group after it's created.
    target_version = api.current_actor().configuration.version.target
    hint_command = 'subscription-manager release --set {}'.format(
        target_version)
    # FIXME: This should use Dialogs and Answers to offer post-upgrade remediation
    # so that users can choose whether to --set or --unset the release number
    hint = 'Set the new release (or unset it) after the upgrade using subscription-manager: ' + hint_command
    reporting.create_report([
        reporting.Title(
            'The subscription-manager release is going to be kept as it is during the upgrade'
        ),
        reporting.Summary(
            'The upgrade is executed with the --no-rhsm option (or with'
            ' the LEAPP_NO_RHSM environment variable). In this case, the subscription-manager'
            ' will not be configured during the upgrade. If the system is subscribed and release'
            ' is set already, you could encounter issues to get RHEL content using DNF/YUM'
            ' after the upgrade.'),
        reporting.Severity(reporting.Severity.LOW),
        reporting.Remediation(hint=hint),
        reporting.Tags([reporting.Tags.UPGRADE_PROCESS]),
        reporting.RelatedResource('package', 'subscription-manager')
    ])
示例#9
0
def ipa_inhibit_upgrade(ipainfo):
    """
    Create upgrade inhibitor for configured ipa-server
    """
    entries = [
        reporting.Title(
            "ipa-server does not support in-place upgrade from RHEL 7 to 8"),
        reporting.Summary(
            "An IdM server installation was detected on the system. IdM "
            "does not support in-place upgrade to RHEL 8. Please follow "
            "the migration guide lines."),
        reporting.Remediation(
            hint="Please follow the IdM RHEL 7 to 8 migration guide lines."),
        reporting.ExternalLink(
            url=MIGRATION_GUIDE,
            title="Migrating IdM from RHEL 7 to 8",
        ),
        reporting.Severity(reporting.Severity.HIGH),
        reporting.Flags([reporting.Flags.INHIBITOR]),
        reporting.Tags([reporting.Tags.SERVICES]),
        reporting.RelatedResource("package", "ipa-server"),
    ]
    return reporting.create_report(entries)
示例#10
0
def process():
    pkgs = get_kernel_devel_rpms()
    if len(pkgs) > 1:
        title = 'Multiple devel kernels installed'
        summary = ('DNF cannot produce a valid upgrade transaction when'
                   ' multiple kernel-devel packages are installed.')
        hint = (
            'Remove all but one kernel-devel packages before running Leapp again.'
        )
        all_but_latest_kernel_devel = pkgs[:-1]
        packages = [
            '{n}-{v}-{r}'.format(n=pkg.name, v=pkg.version, r=pkg.release)
            for pkg in all_but_latest_kernel_devel
        ]
        commands = [['yum', '-y', 'remove'] + packages]
        reporting.create_report([
            reporting.Title(title),
            reporting.Summary(summary),
            reporting.Severity(reporting.Severity.HIGH),
            reporting.Tags([reporting.Tags.KERNEL]),
            reporting.Flags([reporting.Flags.INHIBITOR]),
            reporting.Remediation(hint=hint, commands=commands),
            reporting.RelatedResource('package', 'kernel')
        ])
示例#11
0
def get_os_release(path):
    """Retrieve data about System OS release from provided file."""
    try:
        with open(path) as f:
            data = dict(l.strip().split('=', 1) for l in f.readlines() if '=' in l)
            return OSRelease(
                release_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
            )
    except IOError as e:
        reporting.create_report([
            reporting.Title('Error while collecting system OS facts'),
            reporting.Summary(str(e)),
            reporting.Severity(reporting.Severity.HIGH),
            reporting.Tags([reporting.Tags.OS_FACTS, reporting.Tags.SANITY]),
            reporting.Flags([reporting.Flags.INHIBITOR]),
            reporting.RelatedResource('file', path)
        ])
        return None
示例#12
0
from leapp.actors import Actor
from leapp.libraries.actor import checksendmail
from leapp.libraries.common.rpms import has_package
from leapp.libraries.common.tcpwrappersutils import config_applies_to_daemon
from leapp.models import InstalledRedHatSignedRPM, SendmailMigrationDecision, TcpWrappersFacts
from leapp.reporting import Report, create_report
from leapp import reporting
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag

COMMON_REPORT_TAGS = [reporting.Tags.SERVICES, reporting.Tags.EMAIL]

related = [
    reporting.RelatedResource('file', f)
    for f in checksendmail.get_conf_files()
] + [reporting.RelatedResource('package', 'sendmail')]


class CheckSendmail(Actor):
    """
    Check if sendmail is installed, check whether configuration update is needed, inhibit upgrade if TCP wrappers
    are used.
    """

    name = 'check_sendmail'
    consumes = (
        InstalledRedHatSignedRPM,
        TcpWrappersFacts,
    )
    produces = (
        Report,
        SendmailMigrationDecision,
示例#13
0
from leapp.actors import Actor
from leapp.models import FirewalldFacts
from leapp.libraries.actor import private
from leapp.reporting import Report, create_report
from leapp import reporting
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag

related = [reporting.RelatedResource('package', 'firewalld')]


class CheckFirewalld(Actor):
    """
    Check for certain firewalld configuration that may prevent an upgrade.
    """

    name = 'check_firewalld'
    consumes = (FirewalldFacts, )
    produces = (Report, )
    tags = (ChecksPhaseTag, IPUWorkflowTag)

    def process(self):
        unsupported_tables = []
        unsupported_ipset_types = []
        list_separator_fmt = '\n    -'
        for facts in self.consume(FirewalldFacts):
            for table in facts.ebtablesTablesInUse:
                if not private.isEbtablesTableSupported(table):
                    unsupported_tables.append(table)
            for ipset_type in facts.ipsetTypesInUse:
                if not private.isIpsetTypeSupportedByNftables(ipset_type):
                    unsupported_ipset_types.append(ipset_type)
def process():

    rhui_info = next(api.consume(RHUIInfo), None)

    if not rhsm.skip_rhsm() or rhui_info:
        # getting RH repositories through RHSM or RHUI; resolved by seatbelts
        # implemented in other actors
        return

    # rhsm skipped; take your seatbelts please
    is_ctr = _any_custom_repo_defined()
    is_ctrf = _the_custom_repofile_defined()
    is_re = _the_enablerepo_option_used()
    if not is_ctr:
        # no rhsm, no custom repositories.. this will really not work :)
        # TODO: add link to the RH article about use of custom repositories!!
        # NOTE: we can put here now the link to the main document, as this
        # will be described there or at least the link to the right document
        # will be delivered here.
        if is_ctrf:
            summary_ctrf = '\n\nThe custom repository file has been detected. Maybe it is empty?'
        else:
            summary_ctrf = ''
        reporting.create_report([
            reporting.Title(
                'Using RHSM has been skipped but no custom or RHUI repositories have been delivered.'
            ),
            reporting.Summary(
                'Leapp is run in the mode when the Red Hat Subscription Manager'
                ' is not used (the --no-rhsm option or the LEAPP_NO_RHSM=1'
                ' environment variable has been set) so leapp is not able to'
                ' obtain YUM/DNF repositories with the content for the target'
                ' system in the standard way. The content has to be delivered'
                ' either by user manually or, in case of public clouds, by a'
                ' special Leapp package for RHUI environments.'),
            reporting.Remediation(hint=(
                'Create the repository file according to instructions in the'
                ' referred document on the following path with all'
                ' repositories that should be used during the upgrade: "{}".'
                '\n\n{}'.format(CUSTOM_REPO_PATH, summary_ctrf))),
            reporting.Severity(reporting.Severity.HIGH),
            reporting.Tags([reporting.Tags.SANITY]),
            reporting.Flags([reporting.Flags.INHIBITOR]),
            reporting.ExternalLink(url=_IPU_DOC_URL,
                                   title='UPGRADING TO RHEL 8'),
            reporting.RelatedResource('file', CUSTOM_REPO_PATH),
        ])
    elif not (is_ctrf or is_re):
        # Some custom repositories have been discovered, but the custom repo
        # file not - neither the --enablerepo option is used. Inform about
        # the official recommended way.
        reporting.create_report([
            reporting.Title(
                'Detected "CustomTargetRepositories" without using new provided mechanisms used.'
            ),
            reporting.Summary(
                'Red Hat now provides an official way for using custom'
                ' repositories during the in-place upgrade through'
                ' the referred custom repository file or through the'
                ' --enablerepo option for leapp. The CustomTargetRepositories'
                ' have been produced from custom (own) actors?'),
            reporting.Remediation(hint=(
                'Follow the new simple way to enable custom repositories'
                ' during the upgrade (see the referred document) or create'
                ' the empty custom repository file to acknowledge this report'
                ' message.')),
            reporting.Severity(reporting.Severity.INFO),
            reporting.ExternalLink(url=_IPU_DOC_URL,
                                   title='UPGRADING TO RHEL 8'),
            reporting.RelatedResource('file', CUSTOM_REPO_PATH),
        ])
示例#15
0
def gather_target_repositories(context, indata):
    """
    Get available required target repositories and inhibit or raise error if basic checks do not pass.

    In case of repositories provided by Red Hat, it's checked whether the basic
    required repositories are available (or at least defined) in the given
    context. If not, raise StopActorExecutionError.

    For the custom target repositories we expect all of them have to be defined.
    If any custom target repository is missing, raise StopActorExecutionError.

    If any repository is defined multiple times, produce the inhibitor Report
    msg.

    :param context: An instance of a mounting.IsolatedActions class
    :type context: mounting.IsolatedActions class
    :return: List of target system repoids
    :rtype: List(string)
    """
    rh_available_repoids = _get_rh_available_repoids(context, indata)
    all_available_repoids = _get_all_available_repoids(context)

    target_repoids = []
    missing_custom_repoids = []
    for target_repo in api.consume(TargetRepositories):
        for rhel_repo in target_repo.rhel_repos:
            if rhel_repo.repoid in rh_available_repoids:
                target_repoids.append(rhel_repo.repoid)
            else:
                # TODO: We shall report that the RHEL repos that we deem necessary for
                # the upgrade are not available; but currently it would just print bunch of
                # data everytime as we maps EUS and other repositories as well. But these
                # do not have to be necessary available on the target system in the time
                # of the upgrade. Let's skip it for now until it's clear how we will deal
                # with it.
                pass
        for custom_repo in target_repo.custom_repos:
            if custom_repo.repoid in all_available_repoids:
                target_repoids.append(custom_repo.repoid)
            else:
                missing_custom_repoids.append(custom_repo.repoid)
    api.current_logger().debug("Gathered target repositories: {}".format(
        ', '.join(target_repoids)))
    if not target_repoids:
        reporting.create_report([
            reporting.Title('There are no enabled target repositories'),
            reporting.Summary(
                'This can happen when a system is not correctly registered with the subscription manager'
                ' or, when the leapp --no-rhsm option has been used, no custom repositories have been'
                ' passed on the command line.'),
            reporting.Tags([reporting.Tags.REPOSITORY]),
            reporting.Flags([reporting.Flags.INHIBITOR]),
            reporting.Severity(reporting.Severity.HIGH),
            reporting.Remediation(hint=(
                'Ensure the system is correctly registered with the subscription manager and that'
                ' the current subscription is entitled to install the requested target version {version}.'
                ' If you used the --no-rhsm option (or the LEAPP_NO_RHSM=1 environment variable is set),'
                ' ensure the custom repository file is provided with'
                ' properly defined repositories and that the --enablerepo option for leapp is set if the'
                ' repositories are defined in any repofiles under the /etc/yum.repos.d/ directory.'
                ' For more information on custom repository files, see the documentation.'
                ' Finally, verify that the "/etc/leapp/files/repomap.json" file is up-to-date.'
            ).format(
                version=api.current_actor().configuration.version.target)),
            reporting.ExternalLink(
                # TODO: How to handle different documentation links for each version?
                url='https://red.ht/preparing-for-upgrade-to-rhel8',
                title='Preparing for the upgrade'),
            reporting.RelatedResource("file", "/etc/leapp/files/repomap.json"),
            reporting.RelatedResource("file", "/etc/yum.repos.d/")
        ])
        raise StopActorExecution()
    if missing_custom_repoids:
        reporting.create_report([
            reporting.Title(
                'Some required custom target repositories have not been found'
            ),
            reporting.Summary(
                'This can happen when a repository ID was entered incorrectly either'
                ' while using the --enablerepo option of leapp, or in a third party actor that produces a'
                ' CustomTargetRepositoryMessage.\n'
                'The following repositories IDs could not be found in the target configuration:\n'
                '- {}\n'.format('\n- '.join(missing_custom_repoids))),
            reporting.Tags([reporting.Tags.REPOSITORY]),
            reporting.Flags([reporting.Flags.INHIBITOR]),
            reporting.Severity(reporting.Severity.HIGH),
            reporting.ExternalLink(
                # TODO: How to handle different documentation links for each version?
                url='https://access.redhat.com/articles/4977891',
                title=
                'Customizing your Red Hat Enterprise Linux in-place upgrade'),
            reporting.Remediation(hint=(
                'Consider using the custom repository file, which is documented in the official'
                ' upgrade documentation. Check whether a repository ID has been'
                ' entered incorrectly with the --enablerepo option of leapp.'
                ' Check the leapp logs to see the list of all available repositories.'
            ))
        ])
        raise StopActorExecution()
    return set(target_repoids)
示例#16
0
    def process(self):
        removed_ciphers = [
            "blowfish-cbc",
            "cast128-cbc",
            "arcfour",
            "arcfour128",
            "arcfour256",
        ]
        removed_macs = [
            "hmac-ripemd160",
        ]
        found_ciphers = []
        found_macs = []
        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.'})

        for cipher in removed_ciphers:
            if config.ciphers and cipher in config.ciphers:
                found_ciphers.append(cipher)
        for mac in removed_macs:
            if config.macs and mac in config.macs:
                found_macs.append(mac)

        resources = [
            reporting.RelatedResource('package', 'openssh-server'),
            reporting.RelatedResource('file', '/etc/ssh/sshd_config')
        ]
        if found_ciphers:
            create_report([
                reporting.Title('OpenSSH configured to use removed ciphers'),
                reporting.Summary(
                    'OpenSSH is configured to use removed ciphers {}. '
                    'These ciphers were removed from OpenSSH and if '
                    'present the sshd daemon will not start in RHEL 8'
                    ''.format(','.join(found_ciphers))),
                reporting.Severity(reporting.Severity.HIGH),
                reporting.Tags([
                    reporting.Tags.AUTHENTICATION, reporting.Tags.SECURITY,
                    reporting.Tags.NETWORK, reporting.Tags.SERVICES
                ]),
                reporting.Remediation(
                    hint='Remove the following ciphers from sshd_config: '
                    '{}'.format(','.join(found_ciphers))),
                reporting.Flags([reporting.Flags.INHIBITOR])
            ] + resources)

        if found_macs:
            create_report([
                reporting.Title('OpenSSH configured to use removed mac'),
                reporting.Summary(
                    'OpenSSH is configured to use removed mac {}. '
                    'This MAC was removed from OpenSSH and if present '
                    'the sshd daemon will not start in RHEL 8'
                    ''.format(','.join(found_macs))),
                reporting.Severity(reporting.Severity.HIGH),
                reporting.Tags([
                    reporting.Tags.AUTHENTICATION, reporting.Tags.SECURITY,
                    reporting.Tags.NETWORK, reporting.Tags.SERVICES
                ]),
                reporting.Remediation(
                    hint='Remove the following MACs from sshd_config: {}'.
                    format(','.join(found_macs))),
                reporting.Flags([reporting.Flags.INHIBITOR])
            ] + resources)
示例#17
0
    def process(self):
        with open('files/removed_drivers.txt', 'r') as removed:
            removed_drivers = []
            whitelisted_modules = set()
            collected_drivers = set()
            drivers_to_report = set()

            # Extracting kernel drivers from the files/removed_drivers.txt.
            for line in removed.readlines():
                token = line.strip()
                if token.startswith('#') or not token:
                    # We do not want comments or empty lines.
                    continue
                removed_drivers.append(token)

            # Consuming whitelisted kernel modules.
            for fact in self.consume(WhitelistedKernelModules):
                whitelisted_modules.update(fact.whitelisted_modules)

            # Collecting only non-whitelisted drivers that are part of the
            # files/removed_drivers.txt.
            for fact in self.consume(ActiveKernelModulesFacts):
                for active_module in fact.kernel_modules:
                    if active_module.filename in whitelisted_modules:
                        continue
                    if active_module.filename in removed_drivers:
                        collected_drivers.add(active_module.filename)

            # Going over the collected drivers and considering for reporting only
            # those drivers that are currently used by some device.
            udevadm_db = ''
            for fact in self.consume(UdevAdmInfoData):
                udevadm_db += fact.db
            for line in udevadm_db.split('\n'):
                if 'E: DRIVER=' in line:
                    _, driver = line.split('=')
                    if driver in collected_drivers:
                        drivers_to_report.add(driver)

            # In the end, we are only going to report drivers that are:
            # - removed in the RHEL8 (are part of files/removed_drivers.txt)
            # - not whitelisted
            # - currently being used by some device
            if drivers_to_report:
                title = (
                    'Detected loaded kernel drivers which have been removed '
                    'in RHEL 8. Upgrade cannot proceed.')
                URL = (
                    'https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html-single/'
                    'considerations_in_adopting_rhel_8/index#removed-device-drivers_hardware-enablement'
                )
                summary = (
                    'Support for the following currently loaded RHEL 7 '
                    'device drivers has been removed in RHEL 8: \n     - {}'
                    '\nPlease see {} for details.'.format(
                        '\n     - '.join(drivers_to_report), URL))
                remediation = (
                    'Please disable detected kernel drivers in '
                    'order to proceed with the upgrade process using the rmmod tool.'
                )
                create_report([
                    reporting.Title(title),
                    reporting.Summary(summary),
                    reporting.Severity(reporting.Severity.HIGH),
                    reporting.Tags(
                        [reporting.Tags.KERNEL, reporting.Tags.DRIVERS]),
                    reporting.Flags([reporting.Flags.INHIBITOR]),
                    reporting.Remediation(hint=remediation)
                ] + [
                    reporting.RelatedResource('kernel-driver', km)
                    for km in drivers_to_report
                ])
示例#18
0
from leapp import reporting
from leapp.libraries.stdlib import api, run

related = [
    reporting.RelatedResource('package', 'ntpd'),
    reporting.RelatedResource('package', 'chrony'),
    reporting.RelatedResource('file', '/etc/chrony.conf'),
]


def is_config_default():
    """Check if the chrony config file was not modified since installation."""
    try:
        result = run(['rpm', '-V', '--nomtime', 'chrony'], checked=False)
        return '/etc/chrony.conf' not in result['stdout']
    except OSError as e:
        api.current_logger().warn("rpm verification failed: %s", str(e))
        return True


def check_chrony(chrony_installed):
    """Report potential issues in chrony configuration."""
    if not chrony_installed:
        api.current_logger().info('chrony package is not installed')
        return

    if is_config_default():
        reporting.create_report([
            reporting.Title('chrony using default configuration'),
            reporting.Summary(
                'default chrony configuration in RHEL8 uses leapsectz directive, which cannot be used with '
示例#19
0
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)

    config_resources = [
        reporting.RelatedResource('file', mc)
        for mc in migrate_configs + [ntp_conf]
    ]
    package_resources = [
        reporting.RelatedResource('package', p) for p in ['ntpd', 'chrony']
    ]

    if not ignored_lines:
        reporting.create_report([
            reporting.Title('{} configuration migrated to chrony'.format(
                ' and '.join(migrate_configs))),
            reporting.Summary('ntp2chrony executed successfully'),
            reporting.Severity(reporting.Severity.INFO),
            reporting.Tags(COMMON_REPORT_TAGS)
        ] + config_resources + package_resources)

    else:
        reporting.create_report([
            reporting.Title('{} configuration partially migrated to chrony'.
                            format(' and '.join(migrate_configs))),
            reporting.Summary(
                'Some lines in /etc/ntp.conf were ignored in migration (check /etc/chrony.conf)'
            ),
            reporting.Severity(reporting.Severity.MEDIUM),
            reporting.Tags(COMMON_REPORT_TAGS)
        ] + config_resources + package_resources)
示例#20
0
def process():
    facts = next(api.consume(SELinuxFacts), None)
    if not facts:
        return

    enabled = facts.enabled
    conf_status = facts.static_mode

    if conf_status == 'disabled':
        if get_target_major_version() == '9':
            api.produce(KernelCmdlineArg(key='selinux', value='0'))
            reporting.create_report([
                reporting.Title('LEAPP detected SELinux disabled in "/etc/selinux/config"'),
                reporting.Summary(
                    'On RHEL 9, disabling SELinux in "/etc/selinux/config" is no longer possible. '
                    'This way, the system starts with SELinux enabled but with no policy loaded. LEAPP '
                    'will automatically disable SELinux using "SELINUX=0" kernel command line parameter. '
                    'However, Red Hat strongly recommends to have SELinux enabled'
                ),
                reporting.Severity(reporting.Severity.INFO),
                reporting.Tags([reporting.Tags.SELINUX]),
                reporting.RelatedResource('file', '/etc/selinux/config'),
                reporting.ExternalLink(url=DOC_URL, title='Disabling SELinux'),
            ])

        if enabled:
            reporting.create_report([
                reporting.Title('SElinux should be disabled based on the configuration file but it is enabled'),
                reporting.Summary(
                    'This message is to inform user about non-standard SElinux configuration. Please check '
                    '"/etc/selinux/config" to see whether the configuration is set as expected.'
                ),
                reporting.Severity(reporting.Severity.LOW),
                reporting.Tags([reporting.Tags.SELINUX, reporting.Tags.SECURITY])
            ])
        reporting.create_report([
            reporting.Title('SElinux disabled'),
            reporting.Summary('SElinux disabled, continuing...'),
            reporting.Tags([reporting.Tags.SELINUX, reporting.Tags.SECURITY])
        ])
        return

    if conf_status in ('enforcing', 'permissive'):
        api.produce(SelinuxRelabelDecision(set_relabel=True))
        reporting.create_report([
            reporting.Title('SElinux relabeling will be scheduled'),
            reporting.Summary('SElinux relabeling will be scheduled as the status is permissive/enforcing.'),
            reporting.Severity(reporting.Severity.INFO),
            reporting.Tags([reporting.Tags.SELINUX, reporting.Tags.SECURITY])
        ])

    if conf_status == 'enforcing':
        api.produce(SelinuxPermissiveDecision(
            set_permissive=True))
        reporting.create_report([
            reporting.Title('SElinux will be set to permissive mode'),
            reporting.Summary(
                'SElinux will be set to permissive mode. Current mode: enforcing. This action is '
                'required by the upgrade process to make sure the upgraded system can boot without '
                'beinig blocked by SElinux rules.'
            ),
            reporting.Severity(reporting.Severity.LOW),
            reporting.Remediation(hint=(
                'Make sure there are no SElinux related warnings after the upgrade and enable SElinux '
                'manually afterwards. Notice: You can ignore the "/root/tmp_leapp_py3" SElinux warnings.'
                )
            ),
            reporting.Tags([reporting.Tags.SELINUX, reporting.Tags.SECURITY])
        ])
示例#21
0
def render_report(
    restricted_driver_names_on_host,
    restricted_pci_ids_on_host,
    restricted_devices_drivers,
    restricted_devices_pcis,
    inhibit_upgrade=False,
):
    """
    Render the report for restricted PCI devices in use.

    :param inhibit_upgrade: if True, the report will have Inhibitor flag and
        the upgrade will be inhibited.
    """
    # Prefilter needed data for the report
    unavailable_driver_names = tuple(
        driver for driver in restricted_driver_names_on_host
        if restricted_devices_drivers[driver].available_rhel8 == 0)
    unavailable_pci_ids = tuple(
        pci_id for pci_id in restricted_pci_ids_on_host
        if restricted_devices_pcis[pci_id].available_rhel8 == 0)
    unsupported_driver_names = tuple(
        driver for driver in restricted_driver_names_on_host
        if restricted_devices_drivers[driver].supported_rhel8 == 0)
    unsupported_pci_ids = tuple(
        pci_id for pci_id in restricted_pci_ids_on_host
        if restricted_devices_pcis[pci_id].supported_rhel8 == 0)

    # Render the report entities
    title = "Detected PCI drivers that are restricted on RHEL8."

    # Prepare summary
    summary_partial = "The following drivers are restricted on RHEL 8 system:"
    resources = []
    # Process unavailable devices
    if unavailable_driver_names or unavailable_pci_ids:
        summary_partial += "\n\t Unavailable in RHEL8:"
        if unavailable_driver_names:
            summary_partial += "\n\t\t- driver names:\n"
            summary_partial += "\t\t\t- " + "\n\t\t\t- ".join(
                unavailable_driver_names)
            resources += [
                reporting.RelatedResource('kernel-driver', i)
                for i in unavailable_driver_names
            ]
        if unavailable_pci_ids:
            summary_partial += "\n\t\t- pci ids:\n"
            summary_partial += "\t\t\t- " + "\n\t\t\t- ".join(
                unavailable_pci_ids)

    # Process unsupported devices
    if unsupported_driver_names or unsupported_pci_ids:
        summary_partial += "\n\t Unsupported in RHEL8:"
        if unsupported_driver_names:
            summary_partial += "\n\t\t- driver names:\n"
            summary_partial += "\t\t\t- " + "\n\t\t\t- ".join(
                unsupported_driver_names)
            resources += [
                reporting.RelatedResource('kernel-driver', i)
                for i in unsupported_driver_names
            ]
        if unsupported_pci_ids:
            summary_partial += "\n\t\t- pci ids:\n"
            summary_partial += "\t\t\t- " + "\n\t\t\t- ".join(
                unsupported_pci_ids)

    reports = [
        reporting.Title(title),
        reporting.Summary(summary_partial),
        reporting.Severity(reporting.Severity.HIGH),
        reporting.Tags([reporting.Tags.KERNEL, reporting.Tags.DRIVERS]),
    ]

    if resources:
        reports += resources
    if inhibit_upgrade:
        reports += [reporting.Flags([reporting.Flags.INHIBITOR])]
    return reports
示例#22
0
import re

from leapp import reporting
from leapp.libraries.stdlib import api, run


COMMON_REPORT_TAGS = [reporting.Tags.SERVICES]


sysconfig_path = '/etc/sysconfig/memcached'

related = [
    reporting.RelatedResource('package', 'memcached'),
    reporting.RelatedResource('file', sysconfig_path)
]


def is_sysconfig_default():
    """Check if the memcached sysconfig file was not modified since installation."""
    try:
        result = run(['rpm', '-V', '--nomtime', 'memcached'], checked=False)
        return sysconfig_path not in result['stdout']
    except OSError as e:
        api.current_logger().warning("rpm verification failed: %s", str(e))
        return True


def is_udp_disabled():
    """Check if UDP port is disabled in the sysconfig file."""
    with open(sysconfig_path) as f:
        for line in f:
示例#23
0
from leapp.actors import Actor
from leapp.libraries.actor import library
from leapp.libraries.common.rpms import has_package
from leapp.models import InstalledRedHatSignedRPM, BrlttyMigrationDecision
from leapp.reporting import Report, create_report
from leapp import reporting
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag

related = [reporting.RelatedResource('package', 'brltty')]


class CheckBrltty(Actor):
    """
    Check if brltty is installed, check whether configuration update is needed.
    """

    name = 'check_brltty'
    consumes = (InstalledRedHatSignedRPM, )
    produces = (
        Report,
        BrlttyMigrationDecision,
    )
    tags = (ChecksPhaseTag, IPUWorkflowTag)

    def process(self):
        if has_package(InstalledRedHatSignedRPM, 'brltty'):
            create_report([
                reporting.Title(
                    'Brltty has incompatible changes in the next major version'
                ),
                reporting.Summary(
示例#24
0
    def process(self):
        arch = self.configuration.architecture
        for provider, info in rhui.RHUI_CLOUD_MAP[arch].items():
            if has_package(InstalledRPM, info['el7_pkg']):
                is_azure_sap = False
                azure_sap_pkg = rhui.RHUI_CLOUD_MAP[arch]['azure-sap'][
                    'el7_pkg']
                azure_nonsap_pkg = rhui.RHUI_CLOUD_MAP[arch]['azure'][
                    'el7_pkg']
                # we need to do this workaround in order to overcome our RHUI handling limitation
                # in case there are more client packages on the source system
                if 'azure' in info['el7_pkg'] and has_package(
                        InstalledRPM, azure_sap_pkg):
                    is_azure_sap = True
                    provider = 'azure-sap'
                    info = rhui.RHUI_CLOUD_MAP[arch]['azure-sap']
                if not rhsm.skip_rhsm():
                    create_report([
                        reporting.Title(
                            'Upgrade initiated with RHSM on public cloud with RHUI infrastructure'
                        ),
                        reporting.Summary(
                            'Leapp detected this system is on public cloud with RHUI infrastructure '
                            'but the process was initiated without "--no-rhsm" command line option '
                            'which implies RHSM usage (valid subscription is needed).'
                        ),
                        reporting.Severity(reporting.Severity.INFO),
                        reporting.Tags([reporting.Tags.PUBLIC_CLOUD]),
                    ])
                    return
                # AWS RHUI package is provided and signed by RH but the Azure one not
                if not has_package(InstalledRPM, info['leapp_pkg']):
                    create_report([
                        reporting.Title('Package "{}" is missing'.format(
                            info['leapp_pkg'])),
                        reporting.Summary(
                            'On {} using RHUI infrastructure, a package "{}" is needed for'
                            'in-place upgrade'.format(provider.upper(),
                                                      info['leapp_pkg'])),
                        reporting.Severity(reporting.Severity.HIGH),
                        reporting.RelatedResource('package',
                                                  info['leapp_pkg']),
                        reporting.Flags([reporting.Flags.INHIBITOR]),
                        reporting.Tags(
                            [reporting.Tags.PUBLIC_CLOUD,
                             reporting.Tags.RHUI]),
                        reporting.Remediation(commands=[[
                            'yum', 'install', '-y', info['leapp_pkg']
                        ]])
                    ])
                    return
                # there are several "variants" related to the *AWS* provider (aws, aws-sap)
                if provider.startswith('aws'):
                    # We have to disable Amazon-id plugin in the initramdisk phase as the network
                    # is down at the time
                    self.produce(
                        DNFPluginTask(name='amazon-id',
                                      disable_in=['upgrade']))
                # if RHEL7 and RHEL8 packages differ, we cannot rely on simply updating them
                if info['el7_pkg'] != info['el8_pkg']:
                    self.produce(
                        RpmTransactionTasks(to_install=[info['el8_pkg']]))
                    self.produce(
                        RpmTransactionTasks(to_remove=[info['el7_pkg']]))
                    if is_azure_sap:
                        self.produce(
                            RpmTransactionTasks(to_remove=[azure_nonsap_pkg]))

                self.produce(RHUIInfo(provider=provider))
                self.produce(
                    RequiredTargetUserspacePackages(
                        packages=[info['el8_pkg']]))
                return
示例#25
0
from leapp.actors import Actor
from leapp.tags import FinalizationPhaseTag, IPUWorkflowTag
from leapp.models import SelinuxRelabelDecision
from leapp.reporting import Report, create_report
from leapp import reporting

COMMON_REPORT_TAGS = [reporting.Tags.SELINUX]

related = [reporting.RelatedResource('file', '/.autorelabel')]


class ScheduleSeLinuxRelabeling(Actor):
    """
    Schedule SELinux relabelling.

    If SELinux status was set to permissive or enforcing, a relabelling is necessary.
    """

    name = 'schedule_se_linux_relabelling'
    consumes = (SelinuxRelabelDecision, )
    produces = (Report, )
    tags = (FinalizationPhaseTag, IPUWorkflowTag)

    def process(self):
        for decision in self.consume(SelinuxRelabelDecision):
            if decision.set_relabel:
                try:
                    with open('/.autorelabel', 'w'):
                        pass
                    create_report([
                        reporting.Title('SElinux scheduled for relabelling'),
示例#26
0
import os

from leapp import reporting
from leapp.libraries.common.config import version

COMMON_REPORT_TAGS = [reporting.Tags.SANITY]

related = [reporting.RelatedResource('file', '/etc/os-release')]


def skip_check():
    """ Check if an environment variable was used to skip this actor """
    if os.getenv('LEAPP_DEVEL_SKIP_CHECK_OS_RELEASE'):
        reporting.create_report([
            reporting.Title('Skipped OS release check'),
            reporting.Summary(
                'Source RHEL release check skipped via LEAPP_DEVEL_SKIP_CHECK_OS_RELEASE env var.'
            ),
            reporting.Severity(reporting.Severity.HIGH),
            reporting.Tags(COMMON_REPORT_TAGS)
        ] + related)

        return True
    return False


def check_os_version():
    """ Check the RHEL minor version and inhibit the upgrade if it does not match the supported ones """
    if not version.is_supported_version():
        supported_releases = []
        for rel in version.SUPPORTED_VERSIONS:
示例#27
0
    def process(self):
        # save progress for repoting purposes
        failed_modules = []
        failed_custom = []

        # clear working directory
        shutil.rmtree(WORKING_DIRECTORY, ignore_errors=True)

        try:
            os.mkdir(WORKING_DIRECTORY)
        except OSError:
            self.log.warning('Failed to create working directory! Aborting.')
            return

        # get list of policy modules after the upgrade
        installed_modules = set([
            module[0] for module in selinuxapplycustom.list_selinux_modules()
        ])

        # import custom SElinux modules
        for semodules in self.consume(SELinuxModules):
            self.log.info(
                'Processing custom SELinux policy modules. Count: {}.'.format(
                    len(semodules.modules)))
            # check for presence of udica templates and make sure to install their latest versions
            selinuxapplycustom.install_udica_templates(semodules.templates)

            if not semodules.modules:
                continue

            command = ['semodule']
            for module in semodules.modules:
                # Skip modules that are already installed. This prevents DSP modules installed with wrong
                # priority (usually 400) from being overwritten by an older version
                if module.name in installed_modules:
                    self.log.info(
                        'Skipping module {} on priority {} because it is already installed.'
                        .format(module.name, module.priority))
                    continue

                # cil module files need to be extracted to disk in order to be installed
                cil_filename = os.path.join(WORKING_DIRECTORY,
                                            '{}.cil'.format(module.name))
                self.log.info('Installing module {} on priority {}.'.format(
                    module.name, module.priority))
                if module.removed:
                    self.log.warning(
                        '{}: The following lines where removed because of incompatibility:\n{}'
                        .format(module.name, '\n'.join(module.removed)))
                # write module content to disk
                try:
                    with open(cil_filename, 'w') as cil_file:
                        cil_file.write(module.content)
                except OSError as e:
                    self.log.warning('Error writing {} : {}'.format(
                        cil_filename, e))
                    continue

                command.extend(
                    ['-X', str(module.priority), '-i', cil_filename])

            try:
                run(command)
            except CalledProcessError as e:
                self.log.warning(
                    'Error installing modules in a single transaction:'
                    '{}\nRetrying -- now each module will be installed separately.'
                    .format(e.stderr))
                # Retry, but install each module separately
                for module in semodules.modules:
                    cil_filename = os.path.join(WORKING_DIRECTORY,
                                                '{}.cil'.format(module.name))
                    self.log.info(
                        'Installing module {} on priority {}.'.format(
                            module.name, module.priority))
                    try:
                        run([
                            'semodule', '-X',
                            str(module.priority), '-i', cil_filename
                        ])
                    except CalledProcessError as e:
                        self.log.warning('Error installing module: {}'.format(
                            e.stderr))
                        failed_modules.append(module.name)
                        selinuxapplycustom.back_up_failed(cil_filename)
                        continue

        # import SELinux customizations collected by "semanage export"
        for custom in self.consume(SELinuxCustom):
            self.log.info(
                'Importing the following SELinux customizations collected by "semanage export":\n{}'
                .format('\n'.join(custom.commands)))
            semanage_filename = os.path.join(WORKING_DIRECTORY, 'semanage')
            # save SELinux customizations to disk
            try:
                with open(semanage_filename, 'w') as s_file:
                    s_file.write('\n'.join(custom.commands))
            except OSError as e:
                self.log.warning(
                    'Error writing SELinux customizations to disk: {}'.format(
                        e))
                failed_custom.extend(custom.commands)
            # import customizations
            try:
                run(['semanage', 'import', '-f', semanage_filename])
            except CalledProcessError as e:
                self.log.warning(
                    'Failed to import SELinux customizations: {}'.format(
                        e.stderr))
                failed_custom.extend(custom.commands)
                continue
            # clean-up
            try:
                os.remove(semanage_filename)
            except OSError as e:
                self.log.warning(
                    'Failed to remove temporary file {}: {}'.format(
                        semanage_filename, e))
                continue

        # clean-up
        shutil.rmtree(WORKING_DIRECTORY, ignore_errors=True)

        if failed_modules or failed_custom:
            summary = ''
            if failed_modules:
                summary = (
                    'The following policy modules couldn\'t be installed: {}.\n'
                    'You can review their content in {}.'.format(
                        ', '.join(failed_modules), BACKUP_DIRECTORY))
            if failed_custom:
                if summary:
                    summary = '{}\n\n'.format(summary)
                summary = '{}The following commands couldn\'t be applied:\n{}'.format(
                    summary,
                    '\n'.join(['semanage {}'.format(x)
                               for x in failed_custom]))

            reporting.create_report([
                reporting.Title(
                    'SELinux failed to reapply some customizations after the upgrade.'
                ),
                reporting.Summary(summary),
                reporting.Severity(reporting.Severity.MEDIUM),
                reporting.Tags(
                    [reporting.Tags.SECURITY, reporting.Tags.SELINUX]),
            ] + [
                reporting.RelatedResource(
                    'file', os.path.join(BACKUP_DIRECTORY, '{}.cil'.format(x)))
                for x in failed_modules
            ])
示例#28
0
from leapp.actors import Actor
from leapp.models import SSSDConfig8to9
from leapp import reporting
from leapp.reporting import Report, create_report
from leapp.tags import IPUWorkflowTag, ChecksPhaseTag


COMMON_REPORT_TAGS = [reporting.Tags.AUTHENTICATION, reporting.Tags.SECURITY]

related = [
    reporting.RelatedResource('package', 'sssd'),
    reporting.RelatedResource('file', '/etc/sssd/sssd.conf')
]


class SSSDCheck8to9(Actor):
    """
    Check SSSD configuration for changes in RHEL9 and report them in model.

    Implicit files domain is disabled by default. This may affect local
    smartcard authentication if there is not explicit files domain created.

    If there is no files domain and smartcard authentication is enabled,
    we will notify the administrator.
    """

    name = 'sssd_check_8to9'
    consumes = (SSSDConfig8to9,)
    produces = (Report,)
    tags = (IPUWorkflowTag, ChecksPhaseTag)
示例#29
0
from leapp.actors import Actor
from leapp.dialogs import Dialog
from leapp.dialogs.components import BooleanComponent
from leapp.models import Authselect, AuthselectDecision
from leapp.reporting import Report, create_report
from leapp import reporting
from leapp.tags import IPUWorkflowTag, ChecksPhaseTag


resources = [
    reporting.RelatedResource('package', 'authselect'),
    reporting.RelatedResource('package', 'authconfig'),
    reporting.RelatedResource('file', '/etc/nsswitch.conf')
]


class AuthselectCheck(Actor):
    """
    Confirm suggested authselect call from AuthselectScanner.

    AuthselectScanner produces an Authselect model that contains changes
    that are suggested based on current configuration. This actor will
    ask administrator for confirmation and will report the result.
    """

    name = 'authselect_check'
    consumes = (Authselect,)
    produces = (AuthselectDecision, Report,)
    tags = (IPUWorkflowTag, ChecksPhaseTag)
    dialogs = (
        Dialog(
示例#30
0
import io
import os
import tarfile

from leapp import reporting
from leapp.libraries.stdlib import CalledProcessError, api, run
from leapp.models import NtpMigrationDecision


files = [
    '/etc/ntp.conf', '/etc/ntp/keys',
    '/etc/ntp/crypto/pw', '/etc/ntp/step-tickers'
]

related = [
    reporting.RelatedResource('package', 'ntpd'),
    reporting.RelatedResource('package', 'chrony'),
] + [reporting.RelatedResource('file', f) for f in files]


# Check if a service is active and enabled
def check_service(name):
    for state in ['active', 'enabled']:
        try:
            run(['systemctl', 'is-{}'.format(state), name])
            api.current_logger().debug('{} is {}'.format(name, state))
        except CalledProcessError:
            api.current_logger().debug('{} is not {}'.format(name, state))
            return False

    return True