From 0799fd9d467b52052dd68d33d081e251a26ca057 Mon Sep 17 00:00:00 2001 From: Samuel Dacanay Date: Sun, 22 Aug 2021 12:40:08 +0200 Subject: [PATCH] Add support for older CentOS versions (6 & 5) by checking additional release files for information Signed-off-by: Samuel Dacanay --- syft/distro/identify.go | 55 +++++++ syft/distro/identify_test.go | 135 ++++++++++++++---- syft/distro/test-fixtures/bad-redhat-release | 1 + .../test-fixtures/bad-system-release-cpe | 1 + .../os/centos5/etc/redhat-release | 1 + .../os/centos6/etc/system-release-cpe | 1 + 6 files changed, 169 insertions(+), 25 deletions(-) create mode 100644 syft/distro/test-fixtures/bad-redhat-release create mode 100644 syft/distro/test-fixtures/bad-system-release-cpe create mode 100644 syft/distro/test-fixtures/os/centos5/etc/redhat-release create mode 100644 syft/distro/test-fixtures/os/centos6/etc/system-release-cpe diff --git a/syft/distro/identify.go b/syft/distro/identify.go index ea03e8f52..fd1691ba3 100644 --- a/syft/distro/identify.go +++ b/syft/distro/identify.go @@ -35,6 +35,16 @@ var identityFiles = []parseEntry{ path: "/bin/busybox", fn: parseBusyBox, }, + { + // check for centos:6 + path: "/etc/system-release-cpe", + fn: parseSystemReleaseCPE, + }, + { + // last ditch effort for determining older centos version distro information + path: "/etc/redhat-release", + fn: parseRedhatRelease, + }, } // Identify parses distro-specific files to determine distro metadata like version and release. @@ -145,3 +155,48 @@ func parseBusyBox(contents string) *Distro { } return nil } + +// TODO: we should update parseSystemReleaseCPE to use the CPE struct, pkg.CPE, which requires a refactor to avoid a circular import: +// TODO: pkg depends on distro to support pURLs. To avoid the circular import, either try to make pkg to not depend on distro (medium lift-ish) +// TODO: or migrate the cpe code out of the pkg package (small lift). +// example CPE: cpe:/o:centos:linux:6:GA +var systemReleaseCpeMatcher = regexp.MustCompile(`cpe:\/o:(.*?):.*?:(.*?):.*?$`) + +// parseSystemReleaseCPE parses the older centos (6) file to determine distro metadata +func parseSystemReleaseCPE(contents string) *Distro { + matches := systemReleaseCpeMatcher.FindAllStringSubmatch(contents, -1) + for _, match := range matches { + if len(match) < 3 { + log.Warnf("system release cpe does not match expected format") + return nil + } + // note: in SubMatches (capture groups), the 0th index is the full match string + // see https://pkg.go.dev/regexp#pkg-overview for more info + distro := assemble(match[1], match[2], "") + if distro != nil { + return distro + } + } + return nil +} + +// example: "CentOS release 6.10 (Final)" +var redhatReleaseMatcher = regexp.MustCompile(`(.*?)\srelease\s(\d\.\d+)`) + +// parseRedhatRelease is a fallback parsing method for determining distro information in older redhat versions +func parseRedhatRelease(contents string) *Distro { + matches := redhatReleaseMatcher.FindAllStringSubmatch(contents, -1) + for _, match := range matches { + if len(match) < 3 { + log.Warnf("failed to parse redhat-release file, unexpected format") + return nil + } + // note: in SubMatches (capture groups), the 0th index is the full match string + // see https://pkg.go.dev/regexp#pkg-overview for more info + distro := assemble(strings.ToLower(match[1]), match[2], "") + if distro != nil { + return distro + } + } + return nil +} diff --git a/syft/distro/identify_test.go b/syft/distro/identify_test.go index f4b06efe1..2e3fe307d 100644 --- a/syft/distro/identify_test.go +++ b/syft/distro/identify_test.go @@ -2,6 +2,7 @@ package distro import ( "fmt" + hashiVer "github.com/hashicorp/go-version" "io/ioutil" "os" "testing" @@ -103,6 +104,16 @@ func TestIdentifyDistro(t *testing.T) { fixture: "test-fixtures/partial-fields/missing-version", Type: UnknownDistroType, }, + { + fixture: "test-fixtures/os/centos6", + Type: CentOS, + Version: "6.0.0", + }, + { + fixture: "test-fixtures/os/centos5", + Type: CentOS, + Version: "5.7.0", + }, } observedDistros := internal.NewStringSet() @@ -205,18 +216,7 @@ func TestParseOsRelease(t *testing.T) { for _, test := range tests { name := fmt.Sprintf("%s:%s", test.name, test.RawVersion) - fixture, err := os.Open(test.fixture) - if err != nil { - t.Fatalf("could not open test fixture=%s: %+v", test.fixture, err) - } - defer fixture.Close() - - b, err := ioutil.ReadAll(fixture) - if err != nil { - t.Fatalf("unable to read fixture file: %+v", err) - } - - contents := string(b) + contents := retrieveFixtureContentsAsString(test.fixture, t) t.Run(name, func(t *testing.T) { distro := parseOsRelease(contents) @@ -245,18 +245,7 @@ func TestParseOsReleaseFailures(t *testing.T) { for _, test := range tests { name := fmt.Sprintf("%s:%s", test.name, test.fixture) - fixture, err := os.Open(test.fixture) - if err != nil { - t.Fatalf("could not open test fixture=%s: %+v", test.fixture, err) - } - defer fixture.Close() - - b, err := ioutil.ReadAll(fixture) - if err != nil { - t.Fatalf("unable to read fixture file: %+v", err) - } - - contents := string(b) + contents := retrieveFixtureContentsAsString(test.fixture, t) t.Run(name, func(t *testing.T) { distro := parseOsRelease(contents) @@ -265,5 +254,101 @@ func TestParseOsReleaseFailures(t *testing.T) { } }) } - +} + +func TestParseSystemReleaseCPE(t *testing.T) { + centos6Version, _ := hashiVer.NewVersion("6") + tests := []struct { + fixture string + name string + expected *Distro + }{ + { + fixture: "test-fixtures/os/centos6/etc/system-release-cpe", + name: "Centos 6", + expected: &Distro{ + Type: CentOS, + Version: centos6Version, + RawVersion: "6", + }, + }, + { + fixture: "test-fixtures/bad-system-release-cpe", + name: "Centos 6 Bad CPE", + expected: nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + contents := retrieveFixtureContentsAsString(test.fixture, t) + actual := parseSystemReleaseCPE(contents) + + if test.expected == nil { + assert.Nil(t, actual) + return + } + + // not comparing the full distro object because the hashiVer is a pointer + assert.Equal(t, test.expected.Type, actual.Type) + assert.Equal(t, &test.expected.Version, &actual.Version) + assert.Equal(t, test.expected.RawVersion, actual.RawVersion) + }) + } +} + +func TestParseRedhatRelease(t *testing.T) { + centos5Version, _ := hashiVer.NewVersion("5.7") + tests := []struct { + fixture string + name string + expected *Distro + }{ + { + fixture: "test-fixtures/os/centos5/etc/redhat-release", + name: "Centos 5", + expected: &Distro{ + Type: CentOS, + Version: centos5Version, + RawVersion: "5.7", + }, + }, + { + fixture: "test-fixtures/bad-redhat-release", + name: "Centos 5 Bad Redhat Release", + expected: nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + contents := retrieveFixtureContentsAsString(test.fixture, t) + actual := parseRedhatRelease(contents) + + if test.expected == nil { + assert.Nil(t, actual) + return + } + + // not comparing the full distro object because the hashiVer is a pointer + assert.Equal(t, test.expected.Type, actual.Type) + assert.Equal(t, &test.expected.Version, &actual.Version) + assert.Equal(t, test.expected.RawVersion, actual.RawVersion) + }) + } +} + +func retrieveFixtureContentsAsString(fixturePath string, t *testing.T) string { + fixture, err := os.Open(fixturePath) + if err != nil { + t.Fatalf("could not open test fixture=%s: %+v", fixturePath, err) + } + defer fixture.Close() + + b, err := ioutil.ReadAll(fixture) + if err != nil { + t.Fatalf("unable to read fixture file: %+v", err) + } + + return string(b) } diff --git a/syft/distro/test-fixtures/bad-redhat-release b/syft/distro/test-fixtures/bad-redhat-release new file mode 100644 index 000000000..3b3e09a80 --- /dev/null +++ b/syft/distro/test-fixtures/bad-redhat-release @@ -0,0 +1 @@ +CentOS release 5 (Final) \ No newline at end of file diff --git a/syft/distro/test-fixtures/bad-system-release-cpe b/syft/distro/test-fixtures/bad-system-release-cpe new file mode 100644 index 000000000..33f91e1e4 --- /dev/null +++ b/syft/distro/test-fixtures/bad-system-release-cpe @@ -0,0 +1 @@ +cpe:/o:centos:6:GA \ No newline at end of file diff --git a/syft/distro/test-fixtures/os/centos5/etc/redhat-release b/syft/distro/test-fixtures/os/centos5/etc/redhat-release new file mode 100644 index 000000000..5f60003a8 --- /dev/null +++ b/syft/distro/test-fixtures/os/centos5/etc/redhat-release @@ -0,0 +1 @@ +CentOS release 5.7 (Final) \ No newline at end of file diff --git a/syft/distro/test-fixtures/os/centos6/etc/system-release-cpe b/syft/distro/test-fixtures/os/centos6/etc/system-release-cpe new file mode 100644 index 000000000..f76713361 --- /dev/null +++ b/syft/distro/test-fixtures/os/centos6/etc/system-release-cpe @@ -0,0 +1 @@ +cpe:/o:centos:linux:6:GA \ No newline at end of file