Skip to content

Commit

Permalink
rhel(oval): support unfixed OpenShift 4 vulnerabilities
Browse files Browse the repository at this point in the history
Signed-off-by: RTann <[email protected]>
  • Loading branch information
RTann committed Nov 29, 2023
1 parent 25d5e29 commit d57da19
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 9 deletions.
10 changes: 4 additions & 6 deletions pkg/ovalutil/rpm.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@ const (
NoneDefinition DefinitionType = "none"
)

var moduleCommentRegex, definitionTypeRegex *regexp.Regexp

func init() {
moduleCommentRegex = regexp.MustCompile(`(Module )(.*)( is enabled)`)
definitionTypeRegex = regexp.MustCompile(`^oval\:com\.redhat\.([a-z]+)\:def\:\d+$`)
}
var (
moduleCommentRegex = regexp.MustCompile(`(Module )(.*)( is enabled)`)
definitionTypeRegex = regexp.MustCompile(`^oval:com\.redhat\.([a-z]+):def:\d+$`)
)

// ProtoVulnsFunc allows a caller to create prototype vulnerabilities that will be
// copied and further defined for every applicable oval.Criterion discovered.
Expand Down
87 changes: 84 additions & 3 deletions rhel/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import (
"encoding/xml"
"fmt"
"io"
"regexp"
"strconv"
"strings"

"github.com/quay/goval-parser/oval"
"github.com/quay/zlog"
Expand All @@ -16,6 +19,10 @@ import (
"github.com/quay/claircore/toolkit/types/cpe"
)

var (
openshift4CPEPattern = regexp.MustCompile(`^cpe:/a:redhat:openshift:(?P<openshiftVersion>4(\.(?P<minorVersion>\d+))?)(::el\d+)?$`)
)

// Parse implements [driver.Updater].
//
// Parse treats the data inside the provided io.ReadCloser as Red Hat
Expand Down Expand Up @@ -43,7 +50,7 @@ func (u *Updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln
// Red Hat OVAL data include information about vulnerabilities,
// that actually don't affect the package in any way. Storing them
// would increase number of records in DB without adding any value.
if isSkippableDefinitionType(defType, u.ignoreUnpatched) {
if u.shouldSkipDefType(defType) {
return vs, nil
}

Expand All @@ -59,6 +66,7 @@ func (u *Updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln
if err != nil {
return nil, err
}

v := &claircore.Vulnerability{
Updater: u.Name(),
Name: def.Title,
Expand All @@ -75,6 +83,41 @@ func (u *Updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln
Dist: u.dist,
}
vs = append(vs, v)

// If this is an unfixed OpenShift 4.x vulnerability, add a CPE for each minor version
// below the given minor version.
// There is only a single OVAL v2 file for all OpenShift 4 versions for each RHEL version,
// and it is assumed the CPE specified for the vulnerability indicates
// versions y such that 4.0 <= y <= 4.x are affected, where x is the next,
// unreleased minor version of OpenShift 4 specified in the CPE.
//
// It is expected the CPE is of the form cpe:/a:redhat:openshift:4.x or
// cpe:/a:redhat:openshift:4.x::el<RHEL version>.
// For example: cpe:/a:redhat:openshift:4.14 or cpe:/a:redhat:openshift:4.15::el9.
//
// Any other OpenShift 4-related CPEs are not supported at this time.
//
// Note: VEX files do not specify an OpenShift 4 minor version, so this will have to be revamped
// once VEX files are supported.
if defType == ovalutil.CVEDefinition && strings.HasPrefix(affected, "cpe:/a:redhat:openshift:4") {
if openshiftCPEs, err := allKnownOpenShift4CPEs(affected); err != nil {
zlog.Warn(ctx).Msgf("Skipping addition of extra OpenShift 4 CPEs for the unpatched vulnerability %q: %v", def.Title, err)
} else {
for _, openshiftCPE := range openshiftCPEs {
wfn, err := cpe.Unbind(openshiftCPE)
if err != nil {
return nil, err
}
v := *v
v.Repo = &claircore.Repository{
Name: openshiftCPE,
CPE: wfn,
Key: repositoryKey,
}
vs = append(vs, &v)

Check warning on line 117 in rhel/parser.go

View check run for this annotation

Codecov / codecov/patch

rhel/parser.go#L103-L117

Added lines #L103 - L117 were not covered by tests
}
}
}
}
return vs, nil
}
Expand All @@ -85,8 +128,46 @@ func (u *Updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln
return vulns, nil
}

func isSkippableDefinitionType(defType ovalutil.DefinitionType, ignoreUnpatched bool) bool {
// ShouldSkipDefType returns "true" if any of the following is "true":
//
// * defType == ovalutil.UnaffectedDefinition
// * defType == ovalutil.NoneDefinition
// * u.ignoreUnpatched && defType == ovalutil.CVEDefinition
func (u *Updater) shouldSkipDefType(defType ovalutil.DefinitionType) bool {
return defType == ovalutil.UnaffectedDefinition ||
defType == ovalutil.NoneDefinition ||
(ignoreUnpatched && defType == ovalutil.CVEDefinition)
(u.ignoreUnpatched && defType == ovalutil.CVEDefinition)
}

// AllKnownOpenShift4CPEs returns a slice of other CPEs related to the given Red Hat OpenShift 4 CPE.
// For example, given "cpe:/a:redhat:openshift:4.2", this returns
// ["cpe:/a:redhat:openshift:4.0", "cpe:/a:redhat:openshift:4.1"].
// Note: "cpe:/a:redhat:openshift:4.2" is skipped, as it does not exist.
func allKnownOpenShift4CPEs(cpe string) ([]string, error) {
// These must all stay in-sync at all times.
const (
openshiftVersionIdx = 1
minorVersionIdx = 3
submatchLength = 5
)

match := openshift4CPEPattern.FindStringSubmatch(cpe)
if len(match) != submatchLength || match[minorVersionIdx] == "" {
return nil, fmt.Errorf("CPE %q does not match an expected OpenShift 4 CPE format", cpe)
}

Check warning on line 157 in rhel/parser.go

View check run for this annotation

Codecov / codecov/patch

rhel/parser.go#L156-L157

Added lines #L156 - L157 were not covered by tests

maxMinorVersion, err := strconv.Atoi(match[minorVersionIdx])
if err != nil {
return nil, fmt.Errorf("CPE %q does not match an expected OpenShift 4 CPE format: %w", cpe, err)
}

Check warning on line 162 in rhel/parser.go

View check run for this annotation

Codecov / codecov/patch

rhel/parser.go#L161-L162

Added lines #L161 - L162 were not covered by tests

openshiftVersion := match[openshiftVersionIdx]
cpes := make([]string, 0, maxMinorVersion)
// Skip maxMinorVersion, as this version of OpenShift 4 does not exist yet.
for i := 0; i < maxMinorVersion; i++ {
version := strconv.Itoa(i)
cpes = append(cpes, strings.Replace(cpe, openshiftVersion, "4."+version, 1))
}

return cpes, nil
}
81 changes: 81 additions & 0 deletions rhel/parse_test.go → rhel/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/quay/goval-parser/oval"
"github.com/quay/zlog"

Expand Down Expand Up @@ -100,6 +101,86 @@ func TestParse(t *testing.T) {
}
}

func TestAllKnownOpenShift4CPEs(t *testing.T) {
table := []struct {
cpe string
expected []string
}{
{
cpe: "cpe:/a:redhat:openshift:4.14",
expected: []string{
"cpe:/a:redhat:openshift:4.0",
"cpe:/a:redhat:openshift:4.1",
"cpe:/a:redhat:openshift:4.2",
"cpe:/a:redhat:openshift:4.3",
"cpe:/a:redhat:openshift:4.4",
"cpe:/a:redhat:openshift:4.5",
"cpe:/a:redhat:openshift:4.6",
"cpe:/a:redhat:openshift:4.7",
"cpe:/a:redhat:openshift:4.8",
"cpe:/a:redhat:openshift:4.9",
"cpe:/a:redhat:openshift:4.10",
"cpe:/a:redhat:openshift:4.11",
"cpe:/a:redhat:openshift:4.12",
"cpe:/a:redhat:openshift:4.13",
},
},
{
cpe: "cpe:/a:redhat:openshift:4.15::el8",
expected: []string{
"cpe:/a:redhat:openshift:4.0::el8",
"cpe:/a:redhat:openshift:4.1::el8",
"cpe:/a:redhat:openshift:4.2::el8",
"cpe:/a:redhat:openshift:4.3::el8",
"cpe:/a:redhat:openshift:4.4::el8",
"cpe:/a:redhat:openshift:4.5::el8",
"cpe:/a:redhat:openshift:4.6::el8",
"cpe:/a:redhat:openshift:4.7::el8",
"cpe:/a:redhat:openshift:4.8::el8",
"cpe:/a:redhat:openshift:4.9::el8",
"cpe:/a:redhat:openshift:4.10::el8",
"cpe:/a:redhat:openshift:4.11::el8",
"cpe:/a:redhat:openshift:4.12::el8",
"cpe:/a:redhat:openshift:4.13::el8",
"cpe:/a:redhat:openshift:4.14::el8",
},
},
{
cpe: "cpe:/a:redhat:openshift:4.15::el9",
expected: []string{
"cpe:/a:redhat:openshift:4.0::el9",
"cpe:/a:redhat:openshift:4.1::el9",
"cpe:/a:redhat:openshift:4.2::el9",
"cpe:/a:redhat:openshift:4.3::el9",
"cpe:/a:redhat:openshift:4.4::el9",
"cpe:/a:redhat:openshift:4.5::el9",
"cpe:/a:redhat:openshift:4.6::el9",
"cpe:/a:redhat:openshift:4.7::el9",
"cpe:/a:redhat:openshift:4.8::el9",
"cpe:/a:redhat:openshift:4.9::el9",
"cpe:/a:redhat:openshift:4.10::el9",
"cpe:/a:redhat:openshift:4.11::el9",
"cpe:/a:redhat:openshift:4.12::el9",
"cpe:/a:redhat:openshift:4.13::el9",
"cpe:/a:redhat:openshift:4.14::el9",
},
},
}

for _, test := range table {
t.Run(test.cpe, func(t *testing.T) {
cpes, err := allKnownOpenShift4CPEs(test.cpe)
if err != nil {
t.Fatal(err)
}

if !cmp.Equal(cpes, test.expected) {
t.Fatal(cmp.Diff(cpes, test.expected))
}
})
}
}

// Here's a giant restructured struct for reference and tests.
var ovalDef = oval.Definition{
XMLName: xml.Name{Space: "http://oval.mitre.org/XMLSchema/oval-definitions-5", Local: "definition"},
Expand Down

0 comments on commit d57da19

Please sign in to comment.