Пример #1
0
def test_redact_exclude_no_regex(_process_content_redaction):
    '''
    Verify that the _process_content_redaction call is made with
    exclude == list of strings and regex == False when a list
    of pattern strings is defined in rm_conf
    '''
    conf = InsightsConfig()
    arch = InsightsArchive(conf)
    arch.create_archive_dir()

    # put something in the archive to redact
    test_file = os.path.join(arch.archive_dir, 'test.file')
    with open(test_file, 'w') as t:
        t.write(test_file_data)

    dc = DataCollector(conf, arch)
    rm_conf = {'patterns': ['1234', 'abcd']}

    if six.PY3:
        open_name = 'builtins.open'
    else:
        open_name = '__builtin__.open'

    with patch(open_name, create=True):
        dc.redact(rm_conf)
        _process_content_redaction.assert_called_once_with(test_file, ['1234', 'abcd'], False)
Пример #2
0
def test_redact_exclude_none(_process_content_redaction):
    '''
    Verify that the _process_content_redaction call is made with
    exclude == None and regex == False when the patterns key is
    defined but value is an empty dict
    '''
    conf = InsightsConfig()
    arch = InsightsArchive(conf)
    arch.create_archive_dir()

    # put something in the archive to redact
    test_file = os.path.join(arch.archive_dir, 'test.file')
    with open(test_file, 'w') as t:
        t.write(test_file_data)

    dc = DataCollector(conf, arch)
    rm_conf = {'patterns': {}}

    if six.PY3:
        open_name = 'builtins.open'
    else:
        open_name = '__builtin__.open'

    with patch(open_name, create=True):
        dc.redact(rm_conf)
        _process_content_redaction.assert_called_once_with(test_file, None, False)
Пример #3
0
def test_redact_call_process_redaction(_process_content_redaction):
    '''
    Verify that redact() calls _process_content_redaction
    then writes the returned data back to the same file

    Also verifies that the "exclude" parameter is None and the
    "regex" parameter is False in the _process_content_redaction
    call when rm_conf is empty
    '''
    conf = InsightsConfig()
    arch = InsightsArchive(conf)
    arch.create_archive_dir()

    # put something in the archive to redact
    test_file = os.path.join(arch.archive_dir, 'test.file')
    with open(test_file, 'w') as t:
        t.write(test_file_data)

    dc = DataCollector(conf, arch)
    rm_conf = {}

    if six.PY3:
        open_name = 'builtins.open'
    else:
        open_name = '__builtin__.open'

    with patch(open_name, create=True) as mock_open:
        dc.redact(rm_conf)
        _process_content_redaction.assert_called_once_with(test_file, None, False)
        mock_open.assert_called_once_with(test_file, 'wb')
        mock_open.return_value.__enter__.return_value.write.assert_called_once_with(_process_content_redaction.return_value)
def test_dont_archive_when_missing_dep(write_data_to_file):
    """
    If missing dependencies do not archive it
    """
    arch = InsightsArchive(InsightsConfig())

    cmd = MagicMock(spec=InsightsCommand)
    cmd.get_output.return_value = "Missing Dependencies:"
    cmd.archive_path = '/path/to/command'

    arch.add_to_archive(cmd)
    write_data_to_file.assert_not_called()
Пример #5
0
def test_redact_call_walk(walk):
    '''
    Verify that redact() calls os.walk and when an
    an archive structure is present in /var/tmp/**/insights-*
    '''
    conf = InsightsConfig()
    arch = InsightsArchive(conf)
    arch.create_archive_dir()

    dc = DataCollector(conf, arch)
    rm_conf = {}

    dc.redact(rm_conf)
    walk.assert_called_once_with(arch.archive_dir)
Пример #6
0
def test_redact_call_walk_core(walk):
    '''
    Verify that redact() calls os.walk and when an
    an archive structure is present in /var/tmp/**/insights-*
    With core collection, /data is added to the path
    '''
    conf = InsightsConfig(core_collect=True)
    arch = InsightsArchive(conf)
    arch.create_archive_dir()

    dc = DataCollector(conf, arch)
    rm_conf = {}

    dc.redact(rm_conf)
    walk.assert_called_once_with(os.path.join(arch.archive_dir, 'data'))
Пример #7
0
def test_redact_bad_location(_process_content_redaction, walk):
    '''
    Verify that redact() raises a RuntimeError
    if the directory present in InsightsArchive is
    in a location other than /var/tmp/**/insights-*
    '''
    conf = InsightsConfig()
    arch = InsightsArchive(conf)

    for bad_path in ['/', '/home', '/etc', '/var/log/', '/home/test', '/var/tmp/f22D1d/ins2ghts']:
        arch.archive_dir = bad_path
        dc = DataCollector(conf, arch)
        rm_conf = {}
        with pytest.raises(RuntimeError):
            dc.redact(rm_conf)
        walk.assert_not_called()
        _process_content_redaction.assert_not_called()
Пример #8
0
def test_dont_archive_when_command_not_found(write_data_to_file):
    """
    If the command is not found do not archive it
    """
    arch = InsightsArchive(InsightsConfig())
    arch.archive_dir = arch.create_archive_dir()
    arch.cmd_dir = arch.create_command_dir()

    cmd = MagicMock(spec=InsightsCommand)
    cmd.get_output.return_value = 'timeout: failed to run command blah: No such file or directory'
    cmd.archive_path = '/path/to/command'

    arch.add_to_archive(cmd)
    write_data_to_file.assert_not_called()

    cmd.get_output.return_value = '/usr/bin/command -a'

    arch.add_to_archive(cmd)
    write_data_to_file.assert_called_once()
Пример #9
0
def test_delete_archive_internal():
    config = InsightsConfig(keep_archive=True)
    arch = InsightsArchive()
    _delete_archive_internal(config, arch)
    assert os.path.exists(arch.tmp_dir)
    assert os.path.exists(arch.archive_tmp_dir)

    config.keep_archive = False
    _delete_archive_internal(config, arch)
    assert not os.path.exists(arch.tmp_dir)
    assert not os.path.exists(arch.archive_tmp_dir)
Пример #10
0
def test_cleanup_tmp():
    config = InsightsConfig(keep_archive=True)
    arch = InsightsArchive(config)
    arch.tar_file = os.path.join(arch.archive_tmp_dir, 'test.tar.gz')
    arch.cleanup_tmp()
    assert not os.path.exists(arch.tmp_dir)
    assert os.path.exists(arch.archive_tmp_dir)

    config.keep_archive = False
    arch.cleanup_tmp()
    assert not os.path.exists(arch.tmp_dir)
    assert not os.path.exists(arch.archive_tmp_dir)
Пример #11
0
class ComplianceClient:
    def __init__(self, config):
        self.config = config
        self.conn = InsightsConnection(config)
        self.archive = InsightsArchive(config)
        self._ssg_version = None

    def oscap_scan(self):
        self.inventory_id = self._get_inventory_id()
        self._assert_oscap_rpms_exist()
        initial_profiles = self.get_initial_profiles()
        matching_os_profiles = self.get_profiles_matching_os()
        profiles = self.profile_union_by_ref_id(matching_os_profiles,
                                                initial_profiles)
        if not profiles:
            logger.error(
                "System is not associated with any profiles. Assign profiles using the Compliance web UI.\n"
            )
            exit(constants.sig_kill_bad)

        archive_dir = self.archive.create_archive_dir()
        results_need_repair = self.results_need_repair()

        for profile in profiles:
            tailoring_file = self.download_tailoring_file(profile)
            results_file = self._results_file(archive_dir, profile)
            self.run_scan(profile['attributes']['ref_id'],
                          self.find_scap_policy(
                              profile['attributes']['ref_id']),
                          results_file,
                          tailoring_file_path=tailoring_file)
            if results_need_repair:
                self.repair_results(results_file)
            if tailoring_file:
                os.remove(tailoring_file)

        return self.archive.create_tar_file(), COMPLIANCE_CONTENT_TYPE

    def download_tailoring_file(self, profile):
        if ('tailored' not in profile['attributes']
                or profile['attributes']['tailored'] is False
                or ('os_minor_version' in profile['attributes']
                    and profile['attributes']['os_minor_version'] !=
                    self.os_minor_version())):
            return None

        # Download tailoring file to pass as argument to run_scan
        logger.debug(
            "Policy {0} is a tailored policy. Starting tailoring file download..."
            .format(profile['attributes']['ref_id']))
        tailoring_file_path = tempfile.mkstemp(
            prefix='oscap_tailoring_file-{0}.'.format(
                profile['attributes']['ref_id']),
            suffix='.xml',
            dir='/var/tmp')[1]
        response = self.conn.session.get(
            "https://{0}/compliance/profiles/{1}/tailoring_file".format(
                self.config.base_url, profile['id']))
        logger.debug("Response code: {0}".format(response.status_code))
        if response.content is None:
            logger.info(
                "Problem downloading tailoring file for {0} to {1}".format(
                    profile['attributes']['ref_id'], tailoring_file_path))
            return None

        with open(tailoring_file_path, mode="w+b") as f:
            f.write(response.content)
            logger.info("Saved tailoring file for {0} to {1}".format(
                profile['attributes']['ref_id'], tailoring_file_path))

        logger.debug("Policy {0} tailoring file download finished".format(
            profile['attributes']['ref_id']))

        return tailoring_file_path

    def get_profiles(self, search):
        response = self.conn.session.get(
            "https://{0}/compliance/profiles".format(self.config.base_url),
            params={
                'search': search,
                'relationships': 'false'
            })
        logger.debug("Content of the response: {0} - {1}".format(
            response, response.json()))
        if response.status_code == 200:
            return (response.json().get('data') or [])
        else:
            return []

    def get_initial_profiles(self):
        return self.get_profiles(
            'system_ids={0} canonical=false external=false'.format(
                self.inventory_id))

    def get_profiles_matching_os(self):
        return self.get_profiles(
            'system_ids={0} canonical=false os_minor_version={1}'.format(
                self.inventory_id, self.os_minor_version()))

    def profile_union_by_ref_id(self, prioritized_profiles, merged_profiles):
        profiles = dict(
            (p['attributes']['ref_id'], p) for p in merged_profiles)
        profiles.update(
            dict((p['attributes']['ref_id'], p) for p in prioritized_profiles))

        return list(profiles.values())

    def os_release(self):
        _, version = os_release_info()
        return version

    def os_major_version(self):
        return findall("^[6-8]", self.os_release())[0]

    def os_minor_version(self):
        return findall("\d+$", self.os_release())[0]

    def profile_files(self):
        return glob("{0}*rhel{1}*.xml".format(POLICY_FILE_LOCATION,
                                              self.os_major_version()))

    def find_scap_policy(self, profile_ref_id):
        grepcmd = 'grep ' + profile_ref_id + ' ' + ' '.join(
            self.profile_files())
        if not six.PY3:
            grepcmd = grepcmd.encode()
        rc, grep = call(grepcmd, keep_rc=True)
        if rc:
            logger.error(
                'XML profile file not found matching ref_id {0}\n{1}\n'.format(
                    profile_ref_id, grep))
            return None
        filenames = findall('/usr/share/xml/scap/.+xml', grep)
        if not filenames:
            logger.error(
                'No XML profile files found matching ref_id {0}\n{1}\n'.format(
                    profile_ref_id, ' '.join(filenames)))
            exit(constants.sig_kill_bad)
        return filenames[0]

    def build_oscap_command(self, profile_ref_id, policy_xml, output_path,
                            tailoring_file_path):
        command = 'oscap xccdf eval --profile ' + profile_ref_id
        if tailoring_file_path:
            command += ' --tailoring-file ' + tailoring_file_path
        command += ' --results ' + output_path + ' ' + policy_xml
        return command

    def run_scan(self,
                 profile_ref_id,
                 policy_xml,
                 output_path,
                 tailoring_file_path=None):
        if policy_xml is None:
            return
        logger.info('Running scan for {0}... this may take a while'.format(
            profile_ref_id))
        env = os.environ.copy()
        env.update({'TZ': 'UTC'})
        oscap_command = self.build_oscap_command(profile_ref_id, policy_xml,
                                                 output_path,
                                                 tailoring_file_path)
        if not six.PY3:
            oscap_command = oscap_command.encode()
        rc, oscap = call(oscap_command, keep_rc=True, env=env)
        if rc and rc != NONCOMPLIANT_STATUS:
            logger.error('Scan failed')
            logger.error(oscap)
            exit(constants.sig_kill_bad)

    @property
    def ssg_version(self):
        if not self._ssg_version:
            self._ssg_version = self.get_ssg_version()
        return self._ssg_version

    def get_ssg_version(self):
        rpmcmd = 'rpm -qa --qf "%{VERSION}" ' + SSG_PACKAGE
        if not six.PY3:
            rpmcmd = rpmcmd.encode()

        rc, ssg_version = call(rpmcmd, keep_rc=True)
        if rc:
            logger.warning(
                'Tried determinig SSG version but failed: {0}.\n'.format(
                    ssg_version))
            return

        logger.info('System uses SSG version %s', ssg_version)
        return ssg_version

    def results_need_repair(self):
        return self.ssg_version in VERSIONS_FOR_REPAIR

    def repair_results(self, results_file):
        if not os.path.isfile(results_file):
            return
        if not self.ssg_version:
            logger.warning("Couldn't repair SSG version in results file %s",
                           results_file)
            return

        results_file_in = '{0}.in'.format(results_file)
        os.rename(results_file, results_file_in)

        with open(results_file_in, 'r') as in_file:
            with open(results_file, 'w') as out_file:
                is_repaired = self._repair_ssg_version_in_results(
                    in_file, out_file, self.ssg_version)

        os.remove(results_file_in)
        if is_repaired:
            logger.debug('Repaired version in results file %s', results_file)
        return is_repaired

    def _repair_ssg_version_in_results(self, in_file, out_file, ssg_version):
        replacement = '<version>{0}</version>'.format(ssg_version)
        is_repaired = False
        for line in in_file:
            if is_repaired or SNIPPET_TO_FIX not in line:
                out_file.write(line)
            else:
                out_file.write(line.replace(SNIPPET_TO_FIX, replacement))
                is_repaired = True
                logger.debug('Substituted "%s" with "%s" in %s',
                             SNIPPET_TO_FIX, replacement, out_file.name)

        return is_repaired

    def _assert_oscap_rpms_exist(self):
        rpmcmd = 'rpm -qa ' + ' '.join(REQUIRED_PACKAGES)
        if not six.PY3:
            rpmcmd = rpmcmd.encode()
        rc, rpm = call(rpmcmd, keep_rc=True)
        if rc:
            logger.error(
                'Tried running rpm -qa but failed: {0}.\n'.format(rpm))
            exit(constants.sig_kill_bad)
        else:
            if len(rpm.strip().split('\n')) < len(REQUIRED_PACKAGES):
                logger.error(
                    'Missing required packages for compliance scanning. Please ensure the following packages are installed: {0}\n'
                    .format(', '.join(REQUIRED_PACKAGES)))
                exit(constants.sig_kill_bad)

    def _get_inventory_id(self):
        systems = self.conn._fetch_system_by_machine_id()
        if len(systems) == 1 and 'id' in systems[0]:
            return systems[0].get('id')
        else:
            logger.error('Failed to find system in Inventory')
            exit(constants.sig_kill_bad)

    def _results_file(self, archive_dir, profile):
        return os.path.join(
            archive_dir,
            'oscap_results-{0}.xml'.format(profile['attributes']['ref_id']))
Пример #12
0
 def __init__(self, config):
     self.config = config
     self.conn = InsightsConnection(config)
     self.hostname = determine_hostname()
     self.archive = InsightsArchive(config)
Пример #13
0
class ComplianceClient:
    def __init__(self, config):
        self.config = config
        self.conn = InsightsConnection(config)
        self.hostname = determine_hostname()
        self.archive = InsightsArchive(config)

    def oscap_scan(self):
        self._assert_oscap_rpms_exist()
        policies = self.get_policies()
        if not policies:
            logger.error("System is not associated with any profiles. Assign profiles by either uploading a SCAP scan or using the compliance web UI.\n")
            exit(constants.sig_kill_bad)
        for policy in policies:
            self.run_scan(
                policy['attributes']['ref_id'],
                self.find_scap_policy(policy['attributes']['ref_id']),
                '/var/tmp/oscap_results-{0}.xml'.format(policy['attributes']['ref_id']),
                tailoring_file_path=self.download_tailoring_file(policy)
            )

        return self.archive.create_tar_file(), COMPLIANCE_CONTENT_TYPE

    def download_tailoring_file(self, policy):
        if 'tailored' not in policy['attributes'] or policy['attributes']['tailored'] is False:
            return None

        # Download tailoring file to pass as argument to run_scan
        logger.debug(
            "Policy {0} is a tailored policy. Starting tailoring file download...".format(policy['attributes']['ref_id'])
        )
        tailoring_file_path = "/var/tmp/oscap_tailoring_file-{0}.xml".format(policy['attributes']['ref_id'])
        response = self.conn.session.get(
            "https://{0}/compliance/profiles/{1}/tailoring_file".format(self.config.base_url, policy['id'])
        )
        logger.debug("Response code: {0}".format(response.status_code))
        if response.content is None:
            logger.info("Problem downloading tailoring file for {0} to {1}".format(policy['attributes']['ref_id'], tailoring_file_path))
            return None

        with open(tailoring_file_path, mode="w+b") as f:
            f.write(response.content)
            logger.info("Saved tailoring file for {0} to {1}".format(policy['attributes']['ref_id'], tailoring_file_path))

        logger.debug("Policy {0} tailoring file download finished".format(policy['attributes']['ref_id']))

        return tailoring_file_path

    # TODO: Not a typo! This endpoint gives OSCAP policies, not profiles
    # We need to update compliance-backend to fix this
    def get_policies(self):
        response = self.conn.session.get("https://{0}/compliance/profiles".format(self.config.base_url),
                                         params={'search': 'system_names={0}'.format(self.hostname)})
        logger.debug("Content of the response: {0} - {1}".format(response,
                                                                 response.json()))
        if response.status_code == 200:
            return (response.json().get('data') or [])
        else:
            return []

    def os_release(self):
        _, version, _ = linux_distribution()
        return findall("^[6-8]", version)[0]

    def profile_files(self):
        return glob("{0}*rhel{1}*.xml".format(POLICY_FILE_LOCATION, self.os_release()))

    def find_scap_policy(self, profile_ref_id):
        rc, grep = call(('grep ' + profile_ref_id + ' ' + ' '.join(self.profile_files())).encode(), keep_rc=True)
        if rc:
            logger.error('XML profile file not found matching ref_id {0}\n{1}\n'.format(profile_ref_id, grep))
            exit(constants.sig_kill_bad)
        filenames = findall('/usr/share/xml/scap/.+xml', grep)
        if not filenames:
            logger.error('No XML profile files found matching ref_id {0}\n{1}\n'.format(profile_ref_id, ' '.join(filenames)))
            exit(constants.sig_kill_bad)
        return filenames[0]

    def build_oscap_command(self, profile_ref_id, policy_xml, output_path, tailoring_file_path):
        command = 'oscap xccdf eval --profile ' + profile_ref_id
        if tailoring_file_path:
            command += ' --tailoring-file ' + tailoring_file_path
        command += ' --results ' + output_path + ' ' + policy_xml
        return command

    def run_scan(self, profile_ref_id, policy_xml, output_path, tailoring_file_path=None):
        logger.info('Running scan for {0}... this may take a while'.format(profile_ref_id))
        env = os.environ.copy()
        env.update({'TZ': 'UTC'})
        oscap_command = self.build_oscap_command(profile_ref_id, policy_xml, output_path, tailoring_file_path)
        rc, oscap = call(oscap_command.encode(), keep_rc=True, env=env)
        if rc and rc != NONCOMPLIANT_STATUS:
            logger.error('Scan failed')
            logger.error(oscap)
            exit(constants.sig_kill_bad)
        else:
            self.archive.copy_file(output_path)

    def _assert_oscap_rpms_exist(self):
        rc, rpm = call('rpm -qa ' + ' '.join(REQUIRED_PACKAGES), keep_rc=True)
        if rc:
            logger.error('Tried running rpm -qa but failed: {0}.\n'.format(rpm))
            exit(constants.sig_kill_bad)
        else:
            if len(rpm.strip().split('\n')) < len(REQUIRED_PACKAGES):
                logger.error('Missing required packages for compliance scanning. Please ensure the following packages are installed: {0}\n'.format(', '.join(REQUIRED_PACKAGES)))
                exit(constants.sig_kill_bad)
Пример #14
0
 def __init__(self, config):
     self.config = config
     self.conn = InsightsConnection(config)
     self.hostname = get_canonical_facts().get('fqdn', '')
     self.archive = InsightsArchive(config)
Пример #15
0
class ComplianceClient:
    def __init__(self, config):
        self.config = config
        self.conn = InsightsConnection(config)
        self.hostname = get_canonical_facts().get('fqdn', '')
        self.archive = InsightsArchive(config)

    def oscap_scan(self):
        self._assert_oscap_rpms_exist()
        policies = self.get_policies()
        if not policies:
            logger.error(
                "System is not associated with any profiles. Assign profiles by either uploading a SCAP scan or using the compliance web UI.\n"
            )
            exit(constants.sig_kill_bad)
        profile_ref_ids = [policy['ref_id'] for policy in policies]
        for profile_ref_id in profile_ref_ids:
            self.run_scan(
                profile_ref_id, self.find_scap_policy(profile_ref_id),
                '/var/tmp/oscap_results-{0}.xml'.format(profile_ref_id))

        return self.archive.create_tar_file(), COMPLIANCE_CONTENT_TYPE

    # TODO: Not a typo! This endpoint gives OSCAP policies, not profiles
    # We need to update compliance-backend to fix this
    def get_policies(self):
        response = self.conn.session.get(
            "https://{0}/compliance/systems".format(self.config.base_url),
            params={'search': 'name={0}'.format(self.hostname)})
        if response.status_code == 200:
            return (response.json().get('data')
                    or [{}])[0].get('attributes', {}).get('profiles', [])
        else:
            return []

    def os_release(self):
        _, version, _ = linux_distribution()
        return findall("^[6-8]", version)[0]

    def profile_files(self):
        return glob("{0}*rhel{1}*.xml".format(POLICY_FILE_LOCATION,
                                              self.os_release()))

    def find_scap_policy(self, profile_ref_id):
        rc, grep = call('grep ' + profile_ref_id + ' ' +
                        ' '.join(self.profile_files()),
                        keep_rc=True)
        if rc:
            logger.error(
                'XML profile file not found matching ref_id {0}\n{1}\n'.format(
                    profile_ref_id, grep))
            exit(constants.sig_kill_bad)
        filenames = findall('/usr/share/xml/scap/.+xml', grep)
        if not filenames:
            logger.error(
                'No XML profile files found matching ref_id {0}\n{1}\n'.format(
                    profile_ref_id, ' '.join(filenames)))
            exit(constants.sig_kill_bad)
        return filenames[0]

    def run_scan(self, profile_ref_id, policy_xml, output_path):
        logger.info('Running scan for {0}... this may take a while'.format(
            profile_ref_id))
        env = os.environ.copy()
        env.update({'TZ': 'UTC'})
        rc, oscap = call('oscap xccdf eval --profile ' + profile_ref_id +
                         ' --results ' + output_path + ' ' + policy_xml,
                         keep_rc=True,
                         env=env)
        if rc and rc != NONCOMPLIANT_STATUS:
            logger.error('Scan failed')
            logger.error(oscap)
            exit(constants.sig_kill_bad)
        else:
            self.archive.copy_file(output_path)

    def _assert_oscap_rpms_exist(self):
        rc, rpm = call('rpm -qa ' + ' '.join(REQUIRED_PACKAGES), keep_rc=True)
        if rc:
            logger.error(
                'Tried running rpm -qa but failed: {0}.\n'.format(rpm))
            exit(constants.sig_kill_bad)
        else:
            if len(rpm.strip().split('\n')) < len(REQUIRED_PACKAGES):
                logger.error(
                    'Missing required packages for compliance scanning. Please ensure the following packages are installed: {0}\n'
                    .format(', '.join(REQUIRED_PACKAGES)))
                exit(constants.sig_kill_bad)
Пример #16
0
class ComplianceClient:
    def __init__(self, config):
        self.config = config
        self.conn = InsightsConnection(config)
        self.archive = InsightsArchive(config)

    def oscap_scan(self):
        self.inventory_id = self._get_inventory_id()
        self._assert_oscap_rpms_exist()
        initial_profiles = self.get_initial_profiles()
        matching_os_profiles = self.get_profiles_matching_os()
        profiles = self.profile_union_by_ref_id(matching_os_profiles,
                                                initial_profiles)
        if not profiles:
            logger.error(
                "System is not associated with any profiles. Assign profiles using the Compliance web UI.\n"
            )
            exit(constants.sig_kill_bad)
        for profile in profiles:
            self.run_scan(
                profile['attributes']['ref_id'],
                self.find_scap_policy(profile['attributes']['ref_id']),
                '/var/tmp/oscap_results-{0}.xml'.format(
                    profile['attributes']['ref_id']),
                tailoring_file_path=self.download_tailoring_file(profile))

        return self.archive.create_tar_file(), COMPLIANCE_CONTENT_TYPE

    def download_tailoring_file(self, profile):
        if ('tailored' not in profile['attributes']
                or profile['attributes']['tailored'] is False
                or ('os_minor_version' in profile['attributes']
                    and profile['attributes']['os_minor_version'] !=
                    self.os_minor_version())):
            return None

        # Download tailoring file to pass as argument to run_scan
        logger.debug(
            "Policy {0} is a tailored policy. Starting tailoring file download..."
            .format(profile['attributes']['ref_id']))
        tailoring_file_path = "/var/tmp/oscap_tailoring_file-{0}.xml".format(
            profile['attributes']['ref_id'])
        response = self.conn.session.get(
            "https://{0}/compliance/profiles/{1}/tailoring_file".format(
                self.config.base_url, profile['id']))
        logger.debug("Response code: {0}".format(response.status_code))
        if response.content is None:
            logger.info(
                "Problem downloading tailoring file for {0} to {1}".format(
                    profile['attributes']['ref_id'], tailoring_file_path))
            return None

        with open(tailoring_file_path, mode="w+b") as f:
            f.write(response.content)
            logger.info("Saved tailoring file for {0} to {1}".format(
                profile['attributes']['ref_id'], tailoring_file_path))

        logger.debug("Policy {0} tailoring file download finished".format(
            profile['attributes']['ref_id']))

        return tailoring_file_path

    def get_profiles(self, search):
        response = self.conn.session.get(
            "https://{0}/compliance/profiles".format(self.config.base_url),
            params={'search': search})
        logger.debug("Content of the response: {0} - {1}".format(
            response, response.json()))
        if response.status_code == 200:
            return (response.json().get('data') or [])
        else:
            return []

    def get_initial_profiles(self):
        return self.get_profiles(
            'system_ids={0} canonical=false external=false'.format(
                self.inventory_id))

    def get_profiles_matching_os(self):
        return self.get_profiles(
            'system_ids={0} canonical=false os_minor_version={1}'.format(
                self.inventory_id, self.os_minor_version()))

    def profile_union_by_ref_id(self, prioritized_profiles, merged_profiles):
        profiles = dict(
            (p['attributes']['ref_id'], p) for p in merged_profiles)
        profiles.update(
            dict((p['attributes']['ref_id'], p) for p in prioritized_profiles))

        return list(profiles.values())

    def os_release(self):
        _, version = os_release_info()
        return version

    def os_major_version(self):
        return findall("^[6-8]", self.os_release())[0]

    def os_minor_version(self):
        return findall("\d+$", self.os_release())[0]

    def profile_files(self):
        return glob("{0}*rhel{1}*.xml".format(POLICY_FILE_LOCATION,
                                              self.os_major_version()))

    def find_scap_policy(self, profile_ref_id):
        grepcmd = 'grep ' + profile_ref_id + ' ' + ' '.join(
            self.profile_files())
        if not six.PY3:
            grepcmd = grepcmd.encode()
        rc, grep = call(grepcmd, keep_rc=True)
        if rc:
            logger.error(
                'XML profile file not found matching ref_id {0}\n{1}\n'.format(
                    profile_ref_id, grep))
            return None
        filenames = findall('/usr/share/xml/scap/.+xml', grep)
        if not filenames:
            logger.error(
                'No XML profile files found matching ref_id {0}\n{1}\n'.format(
                    profile_ref_id, ' '.join(filenames)))
            exit(constants.sig_kill_bad)
        return filenames[0]

    def build_oscap_command(self, profile_ref_id, policy_xml, output_path,
                            tailoring_file_path):
        command = 'oscap xccdf eval --profile ' + profile_ref_id
        if tailoring_file_path:
            command += ' --tailoring-file ' + tailoring_file_path
        command += ' --results ' + output_path + ' ' + policy_xml
        return command

    def run_scan(self,
                 profile_ref_id,
                 policy_xml,
                 output_path,
                 tailoring_file_path=None):
        if policy_xml is None:
            return
        logger.info('Running scan for {0}... this may take a while'.format(
            profile_ref_id))
        env = os.environ.copy()
        env.update({'TZ': 'UTC'})
        oscap_command = self.build_oscap_command(profile_ref_id, policy_xml,
                                                 output_path,
                                                 tailoring_file_path)
        if not six.PY3:
            oscap_command = oscap_command.encode()
        rc, oscap = call(oscap_command, keep_rc=True, env=env)
        if rc and rc != NONCOMPLIANT_STATUS:
            logger.error('Scan failed')
            logger.error(oscap)
            exit(constants.sig_kill_bad)
        else:
            self.archive.copy_file(output_path)

    def _assert_oscap_rpms_exist(self):
        rpmcmd = 'rpm -qa ' + ' '.join(REQUIRED_PACKAGES)
        if not six.PY3:
            rpmcmd = rpmcmd.encode()
        rc, rpm = call(rpmcmd, keep_rc=True)
        if rc:
            logger.error(
                'Tried running rpm -qa but failed: {0}.\n'.format(rpm))
            exit(constants.sig_kill_bad)
        else:
            if len(rpm.strip().split('\n')) < len(REQUIRED_PACKAGES):
                logger.error(
                    'Missing required packages for compliance scanning. Please ensure the following packages are installed: {0}\n'
                    .format(', '.join(REQUIRED_PACKAGES)))
                exit(constants.sig_kill_bad)

    def _get_inventory_id(self):
        systems = self.conn._fetch_system_by_machine_id()
        if len(systems) == 1 and 'id' in systems[0]:
            return systems[0].get('id')
        else:
            logger.error('Failed to find system in Inventory')
            exit(constants.sig_kill_bad)
Пример #17
0
 def __init__(self, config):
     self.config = config
     self.conn = InsightsConnection(config)
     self.archive = InsightsArchive(config)