示例#1
0
class EffectiveUserTrigger(BaseTrigger):
    __trigger_name__ = 'effectiveuser'
    __description__ = 'Triggers if the effective user for the container is either root when not allowed or is not in a whitelist'

    allowed_users = CommaDelimitedStringListParameter(name='allowed', description='List of user names allowed to be the effective user (last user entry) in the images history', is_required=False)
    denied_users = CommaDelimitedStringListParameter(name='denied', description='List of user names forbidden from being the effective user for the image in the image history', is_required=False)

    _sanitize_regex = '\s*USER\s+\[?([^\]]+)\]?\s*'

    def evaluate(self, image_obj, context):
        if not context.data.get('prepared_dockerfile'):
            return # Prep step blocked this eval due to condition on the dockerfile, so skip

        allowed_users = self.allowed_users.value(default_if_none=[])
        denied_users = self.denied_users.value(default_if_none=[])

        user_lines = context.data.get('prepared_dockerfile').get('USER', [])

        # If not overt, make it so
        if not user_lines:
            user_lines = ['USER root']

        user = user_lines[-1].strip()  # The last USER line is the determining entry
        match = re.search(self._sanitize_regex, user)
        if match and match.groups():
            user = match.groups()[0]
        else:
            log.warn('Found USER line in dockerfile that does not match expected regex: {}, Line: {}'.format(self._sanitize_regex, user))
            return

        if allowed_users and user not in allowed_users:
            self._fire(msg='User {} found as effective user, which is not on the allowed list'.format(user))
        if denied_users and user in denied_users:
            self._fire(msg='User {} found as effective user, which is on the denied list'.format(user))
class UsernameMatchTrigger(BaseTrigger, PentryBlacklistMixin):
    __trigger_name__ = 'USERNAMEMATCH'
    __description__ = 'triggers if specified username is found in the /etc/passwd file'
    #__params__ = {
    #    'USERNAMEBLACKLIST': CommaDelimitedStringListValidator()
    #}
    user_blacklist = CommaDelimitedStringListParameter(
        name='usernameblacklist',
        description=
        'Comma-delimited list of usernames that will cause the trigger to fire if found in /etc/passwd'
    )

    def evaluate(self, image_obj, context):
        if not context.data.get('passwd_entries'):
            return

        user_entries = context.data.get('passwd_entries')
        #find_users = set([x.strip() for x in self.eval_params['USERNAMEBLACKLIST'].split(',')])
        find_users = set([x.strip() for x in self.user_blacklist.value(
        )] if self.user_blacklist.value() else [])

        for username, pentry in self.exec_blacklist(find_users, -1,
                                                    user_entries):
            self._fire(
                msg=
                "Blacklisted user '{}' found in image's /etc/passwd: pentry={}"
                .format(username, pentry))
示例#3
0
class FullMatchTrigger(BaseTrigger):
    __trigger_name__ = "blacklist_exact_match"
    __description__ = "Triggers if the evaluated image has a package installed with software distributed under the specified (exact match) license(s)."

    license_blacklist = CommaDelimitedStringListParameter(
        name="licenses",
        example_str="GPLv2+,GPL-3+,BSD-2-clause",
        description="List of license names to blacklist exactly.",
        is_required=True,
    )

    def evaluate(self, image_obj, context):
        fullmatchpkgs = []
        blacklist = (
            [x.strip() for x in self.license_blacklist.value()]
            if self.license_blacklist.value()
            else []
        )

        for pkg, license in context.data.get("licenses", []):
            if license in blacklist:
                fullmatchpkgs.append(pkg + "(" + license + ")")

        if fullmatchpkgs:
            self._fire(
                msg="LICFULLMATCH Packages are installed that have blacklisted licenses: "
                + ", ".join(fullmatchpkgs)
            )
class ShellMatchTrigger(BaseTrigger, PentryBlacklistMixin):
    __trigger_name__ = 'SHELLMATCH'
    __description__ = 'triggers if specified login shell for any user is found in the /etc/passwd file'
    #__params__ = {
    #    'SHELLBLACKLIST': CommaDelimitedStringListValidator()
    #}
    shell_blacklist = CommaDelimitedStringListParameter(
        name='shellblacklist', description='Comma-delimiter list of group')

    def evaluate(self, image_obj, context):
        if not context.data.get('passwd_entries'):
            return

        user_entries = context.data.get('passwd_entries')
        find_shell = set(self.shell_blacklist.value()
                         ) if self.shell_blacklist.value() else set()
        #find_shell = set([x.strip() for x in self.eval_params['SHELLBLACKLIST'].split(',')])

        for shell, pentry in self.exec_blacklist(find_shell, 5, user_entries):
            self._fire(
                msg=
                "Blacklisted shell '{}' found in image's /etc/passwd: pentry={}"
                .format(shell, str(pentry)))

        return
class ShellMatchTrigger(BaseTrigger, PentryBlacklistMixin):
    __lifecycle_state__ = LifecycleStates.deprecated
    __trigger_name__ = 'shellmatch'
    __description__ = 'triggers if specified login shell for any user is found in the /etc/passwd file'

    shell_blacklist = CommaDelimitedStringListParameter(
        name='shellblacklist',
        example_str='/bin/bash,/bin/sh',
        description='Comma-delimiter list of group')

    def evaluate(self, image_obj, context):
        if not context.data.get('passwd_entries'):
            return

        user_entries = context.data.get('passwd_entries')
        find_shell = set(self.shell_blacklist.value()
                         ) if self.shell_blacklist.value() else set()

        for shell, pentry in self.exec_blacklist(find_shell, 5, user_entries):
            self._fire(
                msg=
                "Blacklisted shell '{}' found in image's /etc/passwd: pentry={}"
                .format(shell, str(pentry)))

        return
示例#6
0
class SubstringMatchTrigger(BaseTrigger):
    __trigger_name__ = 'LICSUBMATCH'
    __description__ = 'triggers if the evaluated image has a package installed with software distributed under the specified (substring match) license(s)'
    #__params__ = {
    #    'LICBLACKLIST_SUBMATCH': CommaDelimitedStringListValidator()
    #}
    licenseblacklise_submatches = CommaDelimitedStringListParameter(
        name='licblacklist_submatch',
        description='List of strings to do substring match for blacklist')

    def evaluate(self, image_obj, context):
        match_vals = []
        matchpkgs = []

        match_vals = [
            x.strip() for x in self.licenseblacklise_submatches.value()
        ] if self.licenseblacklise_submatches.value() else []
        #for match_val in delim_parser(self.eval_params.get('LICBLACKLIST_SUBMATCH', '')):
        #    match_vals.append(match_val)

        for pkg, license in context.data.get('licenses', []):
            for l in match_vals:
                if re.match(".*" + re.escape(l) + ".*", license):
                    matchpkgs.append(pkg + "(" + license + ")")

        if matchpkgs:
            self._fire(
                msg=
                'LICSUBMATCH Packages are installed that have blacklisted licenses: '
                + ', '.join(matchpkgs))
示例#7
0
class PkgNameMatchTrigger(BaseTrigger):
    __lifecycle_state__ = LifecycleStates.deprecated
    __trigger_name__ = 'gempkgnamematch'
    __description__ = 'triggers if the evaluated image has an GEM package installed that matches one in the list given as a param (package_name)'
    namematch_blacklist = CommaDelimitedStringListParameter(
        name='blacklist_gemnamematch',
        example_str='json,time_diff',
        description=
        'List of gem package names that are blacklisted and will cause trigger to fire if detected in image'
    )

    def evaluate(self, image_obj, context):
        gems = image_obj.gems
        if not gems:
            return

        pkgs = context.data.get(GEM_LIST_KEY)
        if not pkgs:
            return
        blacklist = self.namematch_blacklist.value()
        if blacklist is None:
            blacklist = []

        for match_val in blacklist:
            if match_val and match_val in pkgs:
                self._fire(msg='GEMPKGNAMEMATCH Package is blacklisted: ' +
                           match_val)
示例#8
0
class EffectiveUserTrigger(DockerfileModeCheckedBaseTrigger):
    __trigger_name__ = 'effective_user'
    __description__ = 'Checks if the effective user matches the provided user names, either as a whitelist or blacklist depending on the type parameter setting.'

    user = CommaDelimitedStringListParameter(name='users', example_str='root,docker', description='User names to check against as the effective user (last user entry) in the images history.', is_required=True, validator=TypeValidator('string'), sort_order=1)
    allowed_type = EnumStringParameter(name='type', enum_values=['whitelist', 'blacklist'], example_str='blacklist', description='How to treat the provided user names.', is_required=True, sort_order=2)

    _sanitize_regex = '\s*USER\s+\[?([^\]]+)\]?\s*'

    def _evaluate(self, image_obj, context):
        rule_users = self.user.value()
        is_allowed = self.allowed_type.value().lower() == 'whitelist'

        user_lines = context.data.get('prepared_dockerfile').get('USER', [])

        # If not overt, make it so
        if not user_lines:
            user_lines = ['USER root']

        user = user_lines[-1].strip()  # The last USER line is the determining entry
        match = re.search(self._sanitize_regex, user)
        if match and match.groups():
            user = match.groups()[0]
        else:
            logger.warn('Found USER line in dockerfile that does not match expected regex: {}, Line: {}'.format(self._sanitize_regex, user))
            return

        if is_allowed and user not in rule_users:
            self._fire(msg='User {} found as effective user, which is not on the allowed list'.format(user))
        elif not is_allowed and user in rule_users:
            self._fire(msg='User {} found as effective user, which is explicity not allowed'.format(user))
示例#9
0
class PkgNameMatchTrigger(BaseTrigger):
    __lifecycle_state__ = LifecycleStates.deprecated
    __trigger_name__ = 'npmpkgnamematch'
    __description__ = 'triggers if the evaluated image has an NPM package installed that matches one in the list given as a param (package_name)'

    npmname_blacklist = CommaDelimitedStringListParameter(
        name='blacklist_npmnamematch',
        example_str='json,moment',
        description=
        'List of name strings to blacklist npm package names against')

    def evaluate(self, image_obj, context):
        npms = image_obj.npms
        if not npms:
            return

        pkgs = context.data.get(NPM_LISTING_KEY)
        if not pkgs:
            return

        match_names = self.npmname_blacklist.value(
        ) if self.npmname_blacklist.value() else []

        for match_val in match_names:
            if match_val and match_val in pkgs:
                self._fire(msg='NPMPKGNAMEMATCH Package is blacklisted: ' +
                           match_val)
示例#10
0
class ShellMatchTrigger(BaseTrigger, PentryBlacklistMixin):
    __trigger_name__ = 'blacklist_shells'
    __aliases__ = ['shellmatch']
    __description__ = 'Triggers if specified login shell for any user is found in the /etc/passwd file'

    shell_blacklist = CommaDelimitedStringListParameter(
        name='shells',
        example_str='/bin/bash,/bin/zsh',
        description='List of shell commands to blacklist.',
        is_required=True)

    def evaluate(self, image_obj, context):
        if not context.data.get('passwd_entries'):
            return

        user_entries = context.data.get('passwd_entries')
        find_shell = set(self.shell_blacklist.value()
                         ) if self.shell_blacklist.value() else set()

        for shell, pentry in self.exec_blacklist(find_shell, 5, user_entries):
            self._fire(
                msg=
                "Blacklisted shell '{}' found in image's /etc/passwd: pentry={}"
                .format(shell, str(pentry)))

        return
示例#11
0
class SubstringMatchTrigger(BaseTrigger):
    __trigger_name__ = 'blacklist_partial_match'
    __description__ = 'triggers if the evaluated image has a package installed with software distributed under the specified (substring match) license(s)'

    licenseblacklist_submatches = CommaDelimitedStringListParameter(
        name='licenses',
        example_str='LGPL,BSD',
        description='List of strings to do substring match for blacklist.',
        is_required=True)

    def evaluate(self, image_obj, context):
        matchpkgs = []

        match_vals = [
            x.strip() for x in self.licenseblacklist_submatches.value()
        ] if self.licenseblacklist_submatches.value() else []

        for pkg, license in context.data.get('licenses', []):
            for l in match_vals:
                if re.match(".*" + re.escape(l) + ".*", license):
                    matchpkgs.append(pkg + "(" + license + ")")

        if matchpkgs:
            self._fire(
                msg=
                'LICSUBMATCH Packages are installed that have blacklisted licenses: '
                + ', '.join(matchpkgs))
示例#12
0
class UsernameMatchTrigger(BaseTrigger, PentryBlacklistMixin):
    __trigger_name__ = 'blacklist_usernames'
    __description__ = 'Triggers if specified username is found in the /etc/passwd file'

    user_blacklist = CommaDelimitedStringListParameter(
        name='user_names',
        example_str='daemon,ftp',
        aliases=['usernameblacklist'],
        description=
        'List of usernames that will cause the trigger to fire if found in /etc/passwd.',
        is_required=True)

    def evaluate(self, image_obj, context):
        if not context.data.get('passwd_entries'):
            return

        user_entries = context.data.get('passwd_entries')
        find_users = set([x.strip() for x in self.user_blacklist.value(
        )] if self.user_blacklist.value() else [])

        for username, pentry in self.exec_blacklist(find_users, -1,
                                                    user_entries):
            self._fire(
                msg=
                "Blacklisted user '{}' found in image's /etc/passwd: pentry={}"
                .format(username, pentry))
示例#13
0
class UsernameMatchTrigger(BaseTrigger, PentryBlacklistMixin):
    __lifecycle_state__ = LifecycleStates.deprecated
    __trigger_name__ = 'usernamematch'
    __description__ = 'triggers if specified username is found in the /etc/passwd file'

    user_blacklist = CommaDelimitedStringListParameter(
        name='usernameblacklist',
        example_str='root,docker',
        description=
        'Comma-delimited list of usernames that will cause the trigger to fire if found in /etc/passwd'
    )

    def evaluate(self, image_obj, context):
        if not context.data.get('passwd_entries'):
            return

        user_entries = context.data.get('passwd_entries')
        find_users = set([x.strip() for x in self.user_blacklist.value(
        )] if self.user_blacklist.value() else [])

        for username, pentry in self.exec_blacklist(find_users, -1,
                                                    user_entries):
            self._fire(
                msg=
                "Blacklisted user '{}' found in image's /etc/passwd: pentry={}"
                .format(username, pentry))
示例#14
0
class FullMatchTrigger(BaseTrigger):
    __trigger_name__ = 'LICFULLMATCH'
    __description__ = 'triggers if the evaluated image has a package installed with software distributed under the specified (exact match) license(s)'
    #__params__ = {
    #    'LICBLACKLIST_FULLMATCH': CommaDelimitedStringListValidator()
    #}
    license_blacklist = CommaDelimitedStringListParameter(
        name='licblacklist_fullmatch',
        description='List of license names to blacklist exactly')

    def evaluate(self, image_obj, context):
        match_vals = []
        fullmatchpkgs = []
        blacklist = [x.strip() for x in self.license_blacklist.value()
                     ] if self.license_blacklist.value() else []

        #for match_val in blacklist:
        #    match_vals.append(match_val)

        for pkg, license in context.data.get('licenses', []):
            if license in blacklist:
                fullmatchpkgs.append(pkg + "(" + license + ")")

        if fullmatchpkgs:
            self._fire(
                msg=
                'LICFULLMATCH Packages are installed that have blacklisted licenses: '
                + ', '.join(fullmatchpkgs))
class VulnerabilityBlacklistTrigger(BaseTrigger):
    __trigger_name__ = "blacklist"
    __description__ = "Triggers if any of a list of specified vulnerabilities has been detected in the image."

    vulnerability_ids = CommaDelimitedStringListParameter(
        name="vulnerability_ids",
        example_str="CVE-2019-1234",
        description=
        "List of vulnerability IDs, will cause the trigger to fire if any are detected.",
        is_required=True,
        sort_order=1,
    )
    vendor_only = BooleanStringParameter(
        name="vendor_only",
        example_str="true",
        description=
        "If set to True, discard matches against this vulnerability if vendor has marked as will not fix in the vulnerability record.",
        is_required=False,
        sort_order=2,
    )

    def evaluate(self, image_obj, context):
        vids = self.vulnerability_ids.value()
        is_vendor_only = self.vendor_only.value(default_if_none=True)

        found_vids = []

        for vid in vids:
            found = False

            # search for vid in OS vulns
            vulns = context.data.get("loaded_vulnerabilities")
            for vuln in vulns:
                if is_vendor_only:
                    if vuln.fix_has_no_advisory():
                        continue
                if vid == vuln.vulnerability_id:
                    found = True
                    break

            if not found:
                # search for vid in non-os vulns
                cpevulns = context.data.get("loaded_cpe_vulnerabilities")
                for sev in list(cpevulns.keys()):
                    for image_cpe, vulnerability_cpe in cpevulns[sev]:
                        if vid == vulnerability_cpe.vulnerability_id:
                            found = True
                            break
                    if found:
                        break
            if found:
                found_vids.append(vid)

        if found_vids:
            self._fire(msg="Blacklisted vulnerabilities detected: {}".format(
                found_vids))
示例#16
0
class EffectiveUserTrigger(DockerfileModeCheckedBaseTrigger):
    __trigger_name__ = "effective_user"
    __description__ = "Checks if the effective user matches the provided user names, either as a whitelist or blacklist depending on the type parameter setting."

    user = CommaDelimitedStringListParameter(
        name="users",
        example_str="root,docker",
        description=
        "User names to check against as the effective user (last user entry) in the images history.",
        is_required=True,
        validator=TypeValidator("string"),
        sort_order=1,
    )
    allowed_type = EnumStringParameter(
        name="type",
        enum_values=["whitelist", "blacklist"],
        example_str="blacklist",
        description="How to treat the provided user names.",
        is_required=True,
        sort_order=2,
    )

    _sanitize_regex = "\s*USER\s+\[?([^\]]+)\]?\s*"

    def _evaluate(self, image_obj, context):
        rule_users = self.user.value()
        is_allowed = self.allowed_type.value().lower() == "whitelist"

        user_lines = context.data.get("prepared_dockerfile").get("USER", [])

        # If not overt, make it so
        if not user_lines:
            user_lines = ["USER root"]

        user = user_lines[-1].strip(
        )  # The last USER line is the determining entry
        match = re.search(self._sanitize_regex, user)
        if match and match.groups():
            user = match.groups()[0]
        else:
            logger.warn(
                "Found USER line in dockerfile that does not match expected regex: {}, Line: {}"
                .format(self._sanitize_regex, user))
            return

        if is_allowed and user not in rule_users:
            self._fire(
                msg=
                "User {} found as effective user, which is not on the allowed list"
                .format(user))
        elif not is_allowed and user in rule_users:
            self._fire(
                msg=
                "User {} found as effective user, which is explicity not allowed"
                .format(user))
示例#17
0
class VulnerabilityBlacklistTrigger(BaseTrigger):
    __trigger_name__ = "blacklist"
    __description__ = "Triggers if any of a list of specified vulnerabilities has been detected in the image."

    vulnerability_ids = CommaDelimitedStringListParameter(
        name="vulnerability_ids",
        example_str="CVE-2019-1234",
        description=
        "List of vulnerability IDs, will cause the trigger to fire if any are detected.",
        is_required=True,
        sort_order=1,
    )
    vendor_only = BooleanStringParameter(
        name="vendor_only",
        example_str="true",
        description=
        "If set to True, discard matches against this vulnerability if vendor has marked as will not fix in the vulnerability record.",
        is_required=False,
        sort_order=2,
    )

    def evaluate(self, image_obj, context):
        vids = self.vulnerability_ids.value()
        is_vendor_only = self.vendor_only.value(default_if_none=True)

        found_vids = []

        for vid in vids:
            found = False

            matches = context.data.get("loaded_vulnerabilities_new")
            for match in matches:
                if is_vendor_only:
                    if match.fixes and any(item.wont_fix
                                           for item in match.fixes):
                        continue
                # search for vid in all vulns
                if vid == match.vulnerability.vulnerability_id:
                    found = True
                    break

            if found:
                found_vids.append(vid)

        if found_vids:
            self._fire(msg="Blacklisted vulnerabilities detected: {}".format(
                found_vids))
示例#18
0
class FullMatchTrigger(BaseTrigger):
    __lifecycle_state__ = LifecycleStates.deprecated
    __trigger_name__ = 'licfullmatch'
    __description__ = 'triggers if the evaluated image has a package installed with software distributed under the specified (exact match) license(s)'

    license_blacklist = CommaDelimitedStringListParameter(name='licblacklist_fullmatch', example_str='BSD 2-Clause,GPLv3', description='List of license names to blacklist exactly')

    def evaluate(self, image_obj, context):
        fullmatchpkgs = []
        blacklist = [ x.strip() for x in self.license_blacklist.value()] if self.license_blacklist.value() else []

        for pkg, license in context.data.get('licenses', []):
            if license in blacklist:
                fullmatchpkgs.append(pkg + "(" + license + ")")

        if fullmatchpkgs:
            self._fire(msg='LICFULLMATCH Packages are installed that have blacklisted licenses: ' + ', '.join(fullmatchpkgs))
class NameMatchTrigger(BaseTrigger):
    __trigger_name__ = 'pkgnamematch'
    __description__ = 'triggers if the evaluated image has a package installed that matches one in the list given as a param (package_name)'
    namematch_blacklist = CommaDelimitedStringListParameter(
        name='blacklist_namematch',
        description='List of package names to be blacklisted')

    def evaluate(self, image_obj, context):
        pkg_names = self.namematch_blacklist.value(
        ) if self.namematch_blacklist.value() else []

        for pval in pkg_names:
            try:
                for pkg in image_obj.packages.filter(
                        ImagePackage.name == pval):
                    self._fire(msg='PKGNAMEMATCH Package is blacklisted: ' +
                               pkg.name)
            except Exception as e:
                log.exception('Error searching packages for blacklisted names')
                pass
示例#20
0
class PkgNameMatchTrigger(BaseTrigger):
    __trigger_name__ = 'GEMPKGNAMEMATCH'
    __description__ = 'triggers if the evaluated image has an GEM package installed that matches one in the list given as a param (package_name)'
    #__params__ = {
    #    'BLACKLIST_GEMNAMEMATCH': CommaDelimitedStringListValidator()
    #}
    namematch_blacklist = CommaDelimitedStringListParameter(name='blacklist_gemnamematch', description='List of gem package names that are blacklisted and will cause trigger to fire if detected in image')

    def evaluate(self, image_obj, context):
        gems = image_obj.gems
        if not gems:
            return

        pkgs = context.data.get(GEM_LIST_KEY)
        if not pkgs:
            return
        blacklist = self.namematch_blacklist.value()
        if blacklist is None:
            blacklist = []

        for match_val in blacklist:
            if match_val and match_val in pkgs:
                self._fire(msg='GEMPKGNAMEMATCH Package is blacklisted: ' + match_val)
示例#21
0
class PkgNotPresentTrigger(BaseTrigger):
    __lifecycle_state__ = LifecycleStates.deprecated
    __trigger_name__ = 'pkgnotpresent'
    __description__ = 'triggers if the package(s) specified in the params are not installed in the container image. The parameters specify different types of matches.',

    pkg_full_match = NameVersionStringListParameter(
        name='pkgfullmatch',
        description=
        'Match these values on both name and exact version. Entries are comma-delimited with a pipe between pkg name and version',
        example_str='pkg1|version1,pkg2|version2',
        is_required=False)
    pkg_name_match = CommaDelimitedStringListParameter(
        name='pkgnamematch',
        description='List of names to match',
        example_str='wget,curl,libssl',
        is_required=False)
    pkg_version_match = NameVersionStringListParameter(
        name='pkgversmatch',
        description=
        'Names and versions to do a minimum-version check on. Any package in the list with a version less than the specified version will cause the trigger to fire',
        example_str='wget|1.19.3,curl|7.55.1',
        is_required=False)

    def evaluate(self, image_obj, context):
        fullmatch = self.pkg_full_match.value(default_if_none={})
        namematch = self.pkg_name_match.value(default_if_none=[])
        vermatch = self.pkg_version_match.value(default_if_none={})

        names = set(fullmatch.keys()).union(set(namematch)).union(
            set(vermatch.keys()))
        if not names:
            return

        # Filter is possible since the lazy='dynamic' is set on the packages relationship in Image.
        for img_pkg in image_obj.packages.filter(
                ImagePackage.name.in_(names)).all():
            if img_pkg.name in fullmatch:
                if img_pkg.fullversion != fullmatch.get(img_pkg.name):
                    # Found but not right version
                    self._fire(
                        msg="PKGNOTPRESENT input package (" +
                        str(img_pkg.name) + ") is present (" +
                        str(img_pkg.fullversion) +
                        "), but not at the version specified in policy (" +
                        str(fullmatch[img_pkg.name]) + ")")
                    fullmatch.pop(
                        img_pkg.name
                    )  # Assume only one version of a given package name is installed
                else:
                    # Remove it from the list
                    fullmatch.pop(img_pkg.name)

            # Name match is sufficient
            if img_pkg.name in namematch:
                namematch.remove(img_pkg.name)

            if img_pkg.name in vermatch:
                if img_pkg.fullversion != vermatch[img_pkg.name]:
                    # Check if version is less than param value
                    if compare_package_versions(
                            img_pkg.distro_namespace_meta.flavor, img_pkg.name,
                            img_pkg.version, img_pkg.name,
                            vermatch[img_pkg.name]) < 0:
                        self._fire(
                            msg="PKGNOTPRESENT input package (" +
                            str(img_pkg.name) + ") is present (" +
                            str(img_pkg.fullversion) +
                            "), but is lower version than what is specified in policy ("
                            + str(vermatch[img_pkg.name]) + ")")

                vermatch.pop(img_pkg.name)

        # Any remaining
        for pkg, version in fullmatch.items():
            self._fire(msg="PKGNOTPRESENT input package (" + str(pkg) + "-" +
                       str(version) + ") is not present in container image")

        for pkg, version in vermatch.items():
            self._fire(msg="PKGNOTPRESENT input package (" + str(pkg) + "-" +
                       str(version) + ") is not present in container image")

        for pkg in namematch:
            self._fire(msg="PKGNOTPRESENT input package (" + str(pkg) +
                       ") is not present in container image")
示例#22
0
class VerifyTrigger(BaseTrigger):
    __lifecycle_state__ = LifecycleStates.deprecated
    __trigger_name__ = 'verify'
    __description__ = 'Check package integrity against package db in in the image. Triggers for changes or removal or content in all or the selected DIRS param if provided, and can filter type of check with the CHECK_ONLY param'

    pkgs = CommaDelimitedStringListParameter(
        name='pkgs',
        description='List of package names to verify',
        example_str='libssl,openssl,curl',
        is_required=False)
    directories = CommaDelimitedStringListParameter(
        name='dirs',
        description=
        'List of directories to limit checks to so as to avoid checks on all dir',
        example_str='/usr/share,/usr,/var',
        is_required=False)
    check_only = CommaDelimitedStringListParameter(
        name='check_only',
        description='List of types of checks to perform instead of all',
        example_str='changed',
        is_required=False)

    analyzer_type = 'base'
    analyzer_id = 'file_package_verify'
    analyzer_artifact = 'distro.pkgfilemeta'

    class VerificationStates(enum.Enum):
        changed = 'changed'
        missing = 'missing'

    def evaluate(self, image_obj, context):
        pkg_names = self.pkgs.value(default_if_none=[])
        pkg_dirs = self.directories.value(default_if_none=[])
        checks = self.check_only.value(default_if_none=[])

        if image_obj.fs:
            extracted_files_json = image_obj.fs.files
        else:
            extracted_files_json = []

        if pkg_names:
            pkgs = image_obj.packages.filter(
                ImagePackage.name.in_(pkg_names)).all()
        else:
            pkgs = image_obj.packages.all()

        for pkg in pkgs:
            pkg_name = pkg.name
            records = []
            if pkg_dirs:
                # Filter the specified dirs
                for d in pkg_dirs:
                    records += pkg.pkg_db_entries.filter(
                        ImagePackageManifestEntry.file_path.startswith(d))
            else:
                records = [x for x in pkg.pkg_db_entries.all()]

            for pkg_db_record in records:
                status = self._diff_pkg_meta_and_file(
                    pkg_db_record,
                    extracted_files_json.get(pkg_db_record.file_path))

                if status and (not checks or status.value in checks):
                    self._fire(
                        msg=
                        "VERIFY check against package db for package '{}' failed on entry '{}' with status: '{}'"
                        .format(pkg_name, pkg_db_record.file_path,
                                status.value))

    @classmethod
    def _diff_pkg_meta_and_file(cls, meta_db_entry, fs_entry):
        """
        Given the db record and the fs record, return one of [False, 'changed', 'removed'] for the diff depending on the diff detected.

        If entries are identical, return False since there is no diff.
        If there isa difference return a VerificationState.

        fs_entry is a dict expected to have the following keys:
        sha256_checksum
        md5_checksum
        sha1_checksum (expected but not required)
        mode - integer converted from the octal mode string
        size - integer size of the file

        :param meta_db_entry: An ImagePackageManifestEntry object built from the pkg db in the image indicating the expected state of the file
        :param fs_entry: A dict with metadata detected from image analysis
        :return: one of [False, <VerificationStates>]
        """

        # The fs record is None or empty
        if meta_db_entry and not fs_entry:
            return VerifyTrigger.VerificationStates.missing

        # This is unexpected
        if (fs_entry and not meta_db_entry
            ) or fs_entry.get('name') != meta_db_entry.file_path:
            return False

        if meta_db_entry.is_config_file:
            return False  # skip checks on config files if the flag is set

        # Store type of file
        fs_type = fs_entry.get('entry_type')

        # Check checksums
        if fs_type in ['file']:
            fs_digest = None
            if meta_db_entry.digest_algorithm == 'sha256':
                fs_digest = fs_entry.get('sha256_checksum')
            elif meta_db_entry.digest_algorithm == 'md5':
                fs_digest = fs_entry.get('md5_checksum')
            elif meta_db_entry.digest_algorithm == 'sha1':
                fs_digest = fs_entry.get('sha1_checksum')

            if meta_db_entry.digest and fs_digest and fs_digest != meta_db_entry.digest:
                return VerifyTrigger.VerificationStates.changed

        # Check mode
        if fs_type in ['file', 'dir']:
            fs_mode = fs_entry.get('mode')
            if meta_db_entry.mode and fs_mode:
                # Convert to octal for consistent checks
                oct_fs_mode = oct(fs_mode)
                oct_db_mode = oct(meta_db_entry.mode)

                # Trim mismatched lengths in octal mode
                if len(oct_db_mode) < len(oct_fs_mode):
                    oct_fs_mode = oct_fs_mode[-len(oct_db_mode):]
                elif len(oct_db_mode) > len(oct_fs_mode):
                    oct_db_mode = oct_db_mode[-len(oct_fs_mode):]

                if oct_db_mode != oct_fs_mode:
                    return VerifyTrigger.VerificationStates.changed

        if fs_type in ['file']:
            # Check size (Checksum should handle this)
            db_size = meta_db_entry.size
            fs_size = int(fs_entry.get('size'))
            if fs_size and db_size and fs_size != db_size:
                return VerifyTrigger.VerificationStates.changed

        # No changes or not enough data to compare
        return False
示例#23
0
class VerifyTrigger(BaseTrigger):
    __trigger_name__ = "verify"
    __description__ = 'Check package integrity against package db in the image. Triggers for changes or removal or content in all or the selected "dirs" parameter if provided, and can filter type of check with the "check_only" parameter.'

    pkgs = CommaDelimitedStringListParameter(
        name="only_packages",
        example_str="libssl,openssl",
        description="List of package names to limit verification.",
        is_required=False,
        sort_order=1,
    )
    directories = CommaDelimitedStringListParameter(
        name="only_directories",
        example_str="/usr,/var/lib",
        description=
        "List of directories to limit checks so as to avoid checks on all dir.",
        is_required=False,
        sort_order=2,
    )
    check_only = EnumStringParameter(
        name="check",
        enum_values=["changed", "missing"],
        example_str="changed",
        description="Check to perform instead of all.",
        is_required=False,
        sort_order=3,
    )

    analyzer_type = "base"
    analyzer_id = "file_package_verify"
    analyzer_artifact = "distro.pkgfilemeta"

    class VerificationStates(enum.Enum):
        changed = "changed"
        missing = "missing"

    def evaluate(self, image_obj, context):
        pkg_names = self.pkgs.value(default_if_none=[])
        pkg_dirs = self.directories.value(default_if_none=[])
        check = self.check_only.value()

        if check:
            check = getattr(self.VerificationStates, check)

        if image_obj.fs:
            extracted_files_json = image_obj.fs.files
        else:
            extracted_files_json = []

        if pkg_names:
            pkgs = image_obj.packages.filter(
                ImagePackage.name.in_(pkg_names)).all()
        else:
            pkgs = image_obj.packages.all()

        for pkg in pkgs:
            pkg_name = pkg.name
            records = []
            if pkg_dirs:
                # Filter the specified dirs
                for d in pkg_dirs:
                    records += pkg.pkg_db_entries.filter(
                        ImagePackageManifestEntry.file_path.startswith(d))
            else:
                records = [x for x in pkg.pkg_db_entries.all()]

            for pkg_db_record in records:
                status = self._diff_pkg_meta_and_file(
                    pkg_db_record,
                    extracted_files_json.get(pkg_db_record.file_path))

                if status and (check is None or status == check):
                    self._fire(
                        msg=
                        "VERIFY check against package db for package '{}' failed on entry '{}' with status: '{}'"
                        .format(pkg_name, pkg_db_record.file_path,
                                status.value))

    @classmethod
    def _diff_pkg_meta_and_file(cls, meta_db_entry, fs_entry):
        """
        Given the db record and the fs record, return one of [False, 'changed', 'removed'] for the diff depending on the diff detected.

        If entries are identical, return False since there is no diff.
        If there isa difference return a VerificationState.

        fs_entry is a dict expected to have the following keys:
        sha256_checksum
        md5_checksum
        sha1_checksum (expected but not required)
        mode - integer converted from the octal mode string
        size - integer size of the file

        :param meta_db_entry: An ImagePackageManifestEntry object built from the pkg db in the image indicating the expected state of the file
        :param fs_entry: A dict with metadata detected from image analysis
        :return: one of [False, <VerificationStates>]
        """

        # The fs record is None or empty
        if meta_db_entry and not fs_entry:
            return VerifyTrigger.VerificationStates.missing

        # This is unexpected
        if (fs_entry and not meta_db_entry
            ) or fs_entry.get("name") != meta_db_entry.file_path:
            return False

        if meta_db_entry.is_config_file:
            return False  # skip checks on config files if the flag is set

        # Store type of file
        fs_type = fs_entry.get("entry_type")

        # Check checksums
        if fs_type in ["file"]:
            fs_digest = None
            if meta_db_entry.digest_algorithm == "sha256":
                fs_digest = fs_entry.get("sha256_checksum")
            elif meta_db_entry.digest_algorithm == "md5":
                fs_digest = fs_entry.get("md5_checksum")
            elif meta_db_entry.digest_algorithm == "sha1":
                fs_digest = fs_entry.get("sha1_checksum")

            if meta_db_entry.digest and fs_digest and fs_digest != meta_db_entry.digest:
                return VerifyTrigger.VerificationStates.changed

        # Check mode
        if fs_type in ["file", "dir"]:
            fs_mode = fs_entry.get("mode")
            if meta_db_entry.mode and fs_mode:
                # Convert to octal for consistent checks
                oct_fs_mode = oct(fs_mode)[2:]
                oct_db_mode = oct(meta_db_entry.mode)[2:]

                # Trim mismatched lengths in octal mode
                # Add 2 to handle the '0o' prefix that oct() outputs in py3
                if len(oct_db_mode) < len(oct_fs_mode):
                    oct_fs_mode = oct_fs_mode[-len(oct_db_mode):]
                elif len(oct_db_mode) > len(oct_fs_mode):
                    oct_db_mode = oct_db_mode[-len(oct_fs_mode):]

                if oct_db_mode != oct_fs_mode:
                    return VerifyTrigger.VerificationStates.changed

        if fs_type in ["file"]:
            # Check size (Checksum should handle this)
            db_size = meta_db_entry.size
            fs_size = int(fs_entry.get("size"))
            if fs_size and db_size and fs_size != db_size:
                return VerifyTrigger.VerificationStates.changed

        # No changes or not enough data to compare
        return False