From 1bf8ff99817e53cdd2cfc6a6219b426252047e12 Mon Sep 17 00:00:00 2001 From: Benji Visser Date: Tue, 28 Feb 2023 10:38:05 -0700 Subject: [PATCH 1/5] adding speculative cpenames for distros Signed-off-by: Benji Visser --- internal/cpe/cpe.go | 25 ------- internal/cpe/cpe_test.go | 58 --------------- xeol/db/eol_provider.go | 13 +++- xeol/distro/cpe.go | 33 +++++++++ xeol/distro/distro.go | 9 ++- xeol/distro/distro_test.go | 147 +++++++++++++++++++++++++++++++++++++ xeol/distro/type.go | 54 ++++++++++++++ 7 files changed, 250 insertions(+), 89 deletions(-) delete mode 100644 internal/cpe/cpe.go delete mode 100644 internal/cpe/cpe_test.go create mode 100644 xeol/distro/cpe.go diff --git a/internal/cpe/cpe.go b/internal/cpe/cpe.go deleted file mode 100644 index df9e560b..00000000 --- a/internal/cpe/cpe.go +++ /dev/null @@ -1,25 +0,0 @@ -package cpe - -import ( - "strings" - - "github.com/noqcks/xeol/internal/log" -) - -func Destructure(cpe string) (shortCPE, version string) { - parts := strings.Split(cpe, ":") - - if len(parts) < 5 { - log.Debugf("CPE string '%s' is too short", cpe) - return "", "" - } - - var splitIndex int - if parts[1] == "2.3" { - splitIndex = 5 - } else { - splitIndex = 4 - } - - return strings.Join(parts[:splitIndex], ":"), parts[splitIndex] -} diff --git a/internal/cpe/cpe_test.go b/internal/cpe/cpe_test.go deleted file mode 100644 index d94dbf0d..00000000 --- a/internal/cpe/cpe_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package cpe - -import ( - "testing" -) - -func TestCpeDestructure(t *testing.T) { - testCases := []struct { - name string - input string - expectedShortCpe string - expectedVersion string - }{ - { - name: "Exact CPE 2.2", - input: "cpe:/a:apache:struts:2.5.10", - expectedShortCpe: "cpe:/a:apache:struts", - expectedVersion: "2.5.10", - }, - { - name: "Exact CPE 2.3", - input: "cpe:2.3:a:apache:struts:2.5.10", - expectedShortCpe: "cpe:2.3:a:apache:struts", - expectedVersion: "2.5.10", - }, - { - name: "CPE 2.2", - input: "cpe:/a:apache:struts:2.5:*:*:*:*:*:*:*", - expectedShortCpe: "cpe:/a:apache:struts", - expectedVersion: "2.5", - }, - { - name: "CPE 2.3", - input: "cpe:2.3:a:apache:struts:2.5:*:*:*:*:*:*:*", - expectedShortCpe: "cpe:2.3:a:apache:struts", - expectedVersion: "2.5", - }, - { - name: "Empty CPE", - input: "", - expectedShortCpe: "", - expectedVersion: "", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - gotCpe, gotVersion := Destructure(tc.input) - - if gotVersion != tc.expectedVersion { - t.Errorf("Expected version '%v', got '%v'", tc.expectedVersion, gotVersion) - } - if gotCpe != tc.expectedShortCpe { - t.Errorf("Expected short CPE '%v', got '%v'", tc.expectedShortCpe, gotCpe) - } - }) - } -} diff --git a/xeol/db/eol_provider.go b/xeol/db/eol_provider.go index c57f8af8..41a68dfd 100644 --- a/xeol/db/eol_provider.go +++ b/xeol/db/eol_provider.go @@ -5,9 +5,9 @@ import ( "github.com/anchore/syft/syft/linux" - "github.com/noqcks/xeol/internal/cpe" "github.com/noqcks/xeol/internal/purl" xeolDB "github.com/noqcks/xeol/xeol/db/v1" + "github.com/noqcks/xeol/xeol/distro" "github.com/noqcks/xeol/xeol/eol" "github.com/noqcks/xeol/xeol/pkg" ) @@ -24,14 +24,19 @@ func NewEolProvider(reader xeolDB.EolStoreReader) (*EolProvider, error) { }, nil } -func (pr *EolProvider) GetByDistroCpe(d *linux.Release) (string, []eol.Cycle, error) { +func (pr *EolProvider) GetByDistroCpe(r *linux.Release) (string, []eol.Cycle, error) { cycles := make([]eol.Cycle, 0) - if d == nil || d.CPEName == "" { + d, err := distro.NewFromRelease(*r) + if err != nil { + return "", []eol.Cycle{}, err + } + + if d == nil || d.CPEName.String() == "" { return "", []eol.Cycle{}, errors.New("empty distro CPEName") } - shortCPE, version := cpe.Destructure(d.CPEName) + shortCPE, version := d.CPEName.Destructured() if version == "" || shortCPE == "" { return "", []eol.Cycle{}, errors.New("invalid distro CPEName") } diff --git a/xeol/distro/cpe.go b/xeol/distro/cpe.go new file mode 100644 index 00000000..b7be050e --- /dev/null +++ b/xeol/distro/cpe.go @@ -0,0 +1,33 @@ +package distro + +import ( + "strings" + + "github.com/noqcks/xeol/internal/log" +) + +// CPEName returns the CPE name for the distro. +type CPEName string + +func (c CPEName) String() string { + return string(c) +} + +// Destructured splits a CPE name into its (cpe:2.3:o:vendor:package) and version components. +func (c CPEName) Destructured() (shortCPE, version string) { + parts := strings.Split(c.String(), ":") + + if len(parts) < 5 { + log.Debugf("CPE string '%s' is too short", c.String()) + return "", "" + } + + var splitIndex int + if parts[1] == "2.3" { + splitIndex = 5 + } else { + splitIndex = 4 + } + + return strings.Join(parts[:splitIndex], ":"), parts[splitIndex] +} diff --git a/xeol/distro/distro.go b/xeol/distro/distro.go index f852f18a..de635263 100644 --- a/xeol/distro/distro.go +++ b/xeol/distro/distro.go @@ -14,10 +14,11 @@ type Distro struct { Version *hashiVer.Version RawVersion string IDLike []string + CPEName CPEName } // New creates a new Distro object populated with the given values. -func New(t Type, version string, idLikes ...string) (*Distro, error) { +func New(t Type, cpeName, version string, idLikes ...string) (*Distro, error) { var verObj *hashiVer.Version var err error @@ -26,6 +27,9 @@ func New(t Type, version string, idLikes ...string) (*Distro, error) { if err != nil { return nil, fmt.Errorf("unable to parse version: %w", err) } + if cpeName == "" { + cpeName = fmt.Sprintf("cpe:2.3:o:%s:%s:%s", t.CpeVendor(), t.CpeProduct(), version) + } } return &Distro{ @@ -33,6 +37,7 @@ func New(t Type, version string, idLikes ...string) (*Distro, error) { Version: verObj, RawVersion: version, IDLike: idLikes, + CPEName: CPEName(cpeName), }, nil } @@ -56,7 +61,7 @@ func NewFromRelease(release linux.Release) (*Distro, error) { } } - return New(t, selectedVersion, release.IDLike...) + return New(t, release.CPEName, selectedVersion, release.IDLike...) } func (d Distro) Name() string { diff --git a/xeol/distro/distro_test.go b/xeol/distro/distro_test.go index f687c800..1380fd98 100644 --- a/xeol/distro/distro_test.go +++ b/xeol/distro/distro_test.go @@ -17,6 +17,7 @@ func Test_NewDistroFromRelease(t *testing.T) { release linux.Release expectedVersion string expectedRawVersion string + expectedCpe string expectedType Type expectErr bool }{ @@ -333,3 +334,149 @@ func TestDistro_MajorVersion(t *testing.T) { } } + +func TestDistro_CpeName(t *testing.T) { + tests := []struct { + fixture string + expectedCpe string + }{ + { + fixture: "test-fixtures/os/ubuntu", + expectedCpe: "cpe:2.3:o:canonical:ubuntu_linux:20.04", + }, + { + fixture: "test-fixtures/os/redhat", + expectedCpe: "cpe:/o:redhat:enterprise_linux:7.3:GA:server", + }, + { + fixture: "test-fixtures/os/debian", + expectedCpe: "cpe:2.3:o:debian:debian_linux:8", + }, + { + fixture: "test-fixtures/os/fedora", + expectedCpe: "cpe:/o:fedoraproject:fedora:31", + }, + { + fixture: "test-fixtures/os/photon", + expectedCpe: "cpe:2.3:o:vmware:photon_os:2.0", + }, + { + fixture: "test-fixtures/os/almalinux", + expectedCpe: "cpe:/o:almalinux:almalinux:8.4:GA", + }, + { + fixture: "test-fixtures/os/alpine", + expectedCpe: "cpe:2.3:o:alpinelinux:alpine_linux:3.11.6", + }, + { + fixture: "test-fixtures/os/amazon", + expectedCpe: "cpe:2.3:o:amazon:amazon_linux:2", + }, + { + fixture: "test-fixtures/os/arch", + expectedCpe: "", + }, + { + fixture: "test-fixtures/os/busybox", + expectedCpe: "cpe:2.3:o:busybox:busybox:1.31.1", + }, + { + fixture: "test-fixtures/os/centos", + expectedCpe: "cpe:/o:centos:centos:8", + }, + { + fixture: "test-fixtures/os/gentoo", + expectedCpe: "", + }, + { + fixture: "test-fixtures/os/oraclelinux", + expectedCpe: "cpe:/o:oracle:linux:8:3:server", + }, + { + fixture: "test-fixtures/os/opensuse-leap", + expectedCpe: "cpe:/o:opensuse:leap:15.2", + }, + { + fixture: "test-fixtures/os/sles", + expectedCpe: "cpe:/o:suse:sles:15:sp2", + }, + { + fixture: "test-fixtures/os/mariner", + expectedCpe: "cpe:2.3:o:microsoft:mariner:1.0", + }, + } + + for _, test := range tests { + t.Run(test.fixture, func(t *testing.T) { + s, err := source.NewFromDirectory(test.fixture) + require.NoError(t, err) + + resolver, err := s.FileResolver(source.SquashedScope) + require.NoError(t, err) + + // make certain syft and pick up on the raw information we need + release := linux.IdentifyRelease(resolver) + require.NotNil(t, release, "empty linux release info") + + // craft a new distro from the syft raw info + d, err := NewFromRelease(*release) + require.NoError(t, err) + + assert.Equal(t, d.CPEName.String(), test.expectedCpe) + }) + } + +} + +func TestDistro_CpeNameDestructured(t *testing.T) { + testCases := []struct { + name string + input string + expectedShortCpe string + expectedVersion string + }{ + { + name: "Exact CPE 2.2", + input: "cpe:/a:apache:struts:2.5.10", + expectedShortCpe: "cpe:/a:apache:struts", + expectedVersion: "2.5.10", + }, + { + name: "Exact CPE 2.3", + input: "cpe:2.3:a:apache:struts:2.5.10", + expectedShortCpe: "cpe:2.3:a:apache:struts", + expectedVersion: "2.5.10", + }, + { + name: "CPE 2.2", + input: "cpe:/a:apache:struts:2.5:*:*:*:*:*:*:*", + expectedShortCpe: "cpe:/a:apache:struts", + expectedVersion: "2.5", + }, + { + name: "CPE 2.3", + input: "cpe:2.3:a:apache:struts:2.5:*:*:*:*:*:*:*", + expectedShortCpe: "cpe:2.3:a:apache:struts", + expectedVersion: "2.5", + }, + { + name: "Empty CPE", + input: "", + expectedShortCpe: "", + expectedVersion: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + gotCpe, gotVersion := CPEName(tc.input).Destructured() + + if gotVersion != tc.expectedVersion { + t.Errorf("Expected version '%v', got '%v'", tc.expectedVersion, gotVersion) + } + if gotCpe != tc.expectedShortCpe { + t.Errorf("Expected short CPE '%v', got '%v'", tc.expectedShortCpe, gotCpe) + } + }) + } +} diff --git a/xeol/distro/type.go b/xeol/distro/type.go index f2519460..690dffaf 100644 --- a/xeol/distro/type.go +++ b/xeol/distro/type.go @@ -77,6 +77,60 @@ var IDMapping = map[string]Type{ "wolfi": Wolfi, } +// CpeOsVendorMapping connects a distro type to a CPE OS vendor string +var CpeOsVendorMapping = map[Type]string{ + Debian: "debian", + Ubuntu: "canonical", + RedHat: "redhat", + CentOS: "centos", + Fedora: "fedoraproject", + Alpine: "alpinelinux", + Busybox: "busybox", + AmazonLinux: "amazon", + OracleLinux: "oracle", + ArchLinux: "archlinux", + OpenSuseLeap: "opensuse", + SLES: "suse", + Photon: "vmware", + Windows: "microsoft", + Mariner: "microsoft", + RockyLinux: "rocky", + AlmaLinux: "almalinux", + Gentoo: "gentoo", + Wolfi: "wolfi", +} + +// CpeOsProductMapping connects a distro type to a CPE OS product string +var CpeOsProductMapping = map[Type]string{ + Debian: "debian_linux", + Ubuntu: "ubuntu_linux", + RedHat: "enterprise_linux", + CentOS: "centos", + Fedora: "fedora", + Alpine: "alpine_linux", + Busybox: "busybox", + AmazonLinux: "amazon_linux", + OracleLinux: "linux", + ArchLinux: "arch_linux", + OpenSuseLeap: "leap", + SLES: "linux_enterprise_server", + Photon: "photon_os", + Windows: "windows", + Mariner: "mariner", + RockyLinux: "rocky", + AlmaLinux: "almalinux", + Gentoo: "gentoo", + Wolfi: "wolfi", +} + +func (t Type) CpeVendor() string { + return CpeOsVendorMapping[t] +} + +func (t Type) CpeProduct() string { + return CpeOsProductMapping[t] +} + func TypeFromRelease(release linux.Release) Type { // first try the release ID t, ok := IDMapping[release.ID] From efe6b3eddc45a9672bd19ebea779ac1aa9160d92 Mon Sep 17 00:00:00 2001 From: Benji Visser Date: Tue, 28 Feb 2023 10:38:56 -0700 Subject: [PATCH 2/5] updating comments in type Signed-off-by: Benji Visser --- xeol/distro/type.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xeol/distro/type.go b/xeol/distro/type.go index 690dffaf..fdd2771f 100644 --- a/xeol/distro/type.go +++ b/xeol/distro/type.go @@ -78,6 +78,8 @@ var IDMapping = map[string]Type{ } // CpeOsVendorMapping connects a distro type to a CPE OS vendor string +// The reason this exists is because there is low coverage of CPE_NAME in /etc/os-release +// file across distros. This is a best effort to map the distro type to a CPE vendor string. var CpeOsVendorMapping = map[Type]string{ Debian: "debian", Ubuntu: "canonical", @@ -101,6 +103,8 @@ var CpeOsVendorMapping = map[Type]string{ } // CpeOsProductMapping connects a distro type to a CPE OS product string +// The reason this exists is because there is low coverage of CPE_NAME in /etc/os-release +// file across distros. This is a best effort to map the distro type to a CPE vendor string. var CpeOsProductMapping = map[Type]string{ Debian: "debian_linux", Ubuntu: "ubuntu_linux", From 5b48ec10403e11e768db5d3692ada0f123d3dddd Mon Sep 17 00:00:00 2001 From: Benji Visser Date: Tue, 28 Feb 2023 10:57:26 -0700 Subject: [PATCH 3/5] updating distro matcher test Signed-off-by: Benji Visser --- xeol/matcher/distro/matcher_test.go | 4 ++++ .../test-fixtures/snapshot/TestEmptyTablePresenter.golden | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/xeol/matcher/distro/matcher_test.go b/xeol/matcher/distro/matcher_test.go index e3d406a9..5f101796 100644 --- a/xeol/matcher/distro/matcher_test.go +++ b/xeol/matcher/distro/matcher_test.go @@ -61,6 +61,7 @@ func TestMatch(t *testing.T) { cycleFound, err := eol.NewCycle(cycle) d := &linux.Release{ + ID: "fedora", Name: "Fedora", Version: "29", CPEName: "cpe:/o:fedoraproject:fedora:29", @@ -94,6 +95,7 @@ func TestMatchCpeMismatch(t *testing.T) { require.NoError(t, err) d := &linux.Release{ + ID: "fedora", Name: "Fedora", Version: "29", CPEName: "cpe:/o:fedoraproject:fedora:29", @@ -125,6 +127,7 @@ func TestMatchNoMatchingVersion(t *testing.T) { // Set up a matcher and a package with the same PURL but a different version m := Matcher{} d := &linux.Release{ + ID: "fedora", Name: "Fedora", Version: "29", CPEName: "cpe:/o:fedoraproject:fedora:29", @@ -155,6 +158,7 @@ func TestMatchTimeChange(t *testing.T) { m := Matcher{} d := &linux.Release{ + ID: "fedora", Name: "Fedora", Version: "29", CPEName: "cpe:/o:fedoraproject:fedora:29", diff --git a/xeol/presenter/table/test-fixtures/snapshot/TestEmptyTablePresenter.golden b/xeol/presenter/table/test-fixtures/snapshot/TestEmptyTablePresenter.golden index f5f7fad3..e0ef9a85 100644 --- a/xeol/presenter/table/test-fixtures/snapshot/TestEmptyTablePresenter.golden +++ b/xeol/presenter/table/test-fixtures/snapshot/TestEmptyTablePresenter.golden @@ -1 +1 @@ -No eol software found +✅ no EOL software has been found From 024a5b9361e4a938d273cd236ccb3f05c1b7012c Mon Sep 17 00:00:00 2001 From: "xeol-actions[bot]" <122061159+xeol-actions[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 12:39:04 -0500 Subject: [PATCH 4/5] Update Syft to v0.73.0 (#39) Signed-off-by: GitHub Signed-off-by: Benji Visser Co-authored-by: noqcks Co-authored-by: Benji Visser --- go.mod | 4 ++-- go.sum | 8 ++++---- xeol/pkg/package_test.go | 6 +++++- xeol/presenter/table/presenter_test.go | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index c76ac9b6..34490e0b 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,8 @@ require ( github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 github.com/anchore/sqlite v1.4.6-0.20220607210448-bcc6ee5c4963 - github.com/anchore/stereoscope v0.0.0-20230216143338-4b5ebf8c7f4b - github.com/anchore/syft v0.72.0 + github.com/anchore/stereoscope v0.0.0-20230222185948-fab1c9638abc + github.com/anchore/syft v0.73.0 github.com/bmatcuk/doublestar/v2 v2.0.4 github.com/docker/docker v23.0.1+incompatible github.com/dustin/go-humanize v1.0.1 diff --git a/go.sum b/go.sum index b5fa54f7..c00f84d9 100644 --- a/go.sum +++ b/go.sum @@ -185,10 +185,10 @@ github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwM github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4= github.com/anchore/sqlite v1.4.6-0.20220607210448-bcc6ee5c4963 h1:vrf2PYH77vqVJoNR15ZuFJ63qwBMqrmGIt/7VsBhLF8= github.com/anchore/sqlite v1.4.6-0.20220607210448-bcc6ee5c4963/go.mod h1:AVRyXOUP0hTz9Cb8OlD1XnwA8t4lBPfTuwPHmEUuiLc= -github.com/anchore/stereoscope v0.0.0-20230216143338-4b5ebf8c7f4b h1:vMEAfz91QLjJq2W8JPxpIC4dG4OeynTY4MisHnZ19F0= -github.com/anchore/stereoscope v0.0.0-20230216143338-4b5ebf8c7f4b/go.mod h1:6oSG43mzahqiktzXZDctqi1o66fwU2wDk3xki0KlnbA= -github.com/anchore/syft v0.72.0 h1:EpZMDitSElK/Qm1zgrL/3HM2Cw0hHo7hy9uwrhIXDGA= -github.com/anchore/syft v0.72.0/go.mod h1:T3ZSrApwb+jwI+vyTfE5R54Xej4NBoQ8c2t1LyJWGao= +github.com/anchore/stereoscope v0.0.0-20230222185948-fab1c9638abc h1:b+2KauWByrCPLNnzRHjLoUxo85tpszFtU7S1I5pAKK0= +github.com/anchore/stereoscope v0.0.0-20230222185948-fab1c9638abc/go.mod h1:Y+jiUa5PmQh9jUvzmvus4EvLnEG87cDTOYgV3nw3wDg= +github.com/anchore/syft v0.73.0 h1:htS03PVnCwvTuVEna2hpXcYrgOU4j+kB/l0rUAe0PW0= +github.com/anchore/syft v0.73.0/go.mod h1:oKRfCKZVYtzdWwaPzSvmTgTB7p71IwOUkK6B7/LsUZw= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= diff --git a/xeol/pkg/package_test.go b/xeol/pkg/package_test.go index 8f5d4752..b61664b7 100644 --- a/xeol/pkg/package_test.go +++ b/xeol/pkg/package_test.go @@ -422,7 +422,11 @@ func TestNew(t *testing.T) { syftPkg: syftPkg.Package{ MetadataType: syftPkg.BinaryMetadataType, Metadata: syftPkg.BinaryMetadata{ - Classifier: "classifier", + Matches: []syftPkg.ClassifierMatch{ + { + Classifier: "node", + }, + }, }, }, }, diff --git a/xeol/presenter/table/presenter_test.go b/xeol/presenter/table/presenter_test.go index f0cd9285..e51efba9 100644 --- a/xeol/presenter/table/presenter_test.go +++ b/xeol/presenter/table/presenter_test.go @@ -19,7 +19,7 @@ import ( "github.com/noqcks/xeol/xeol/presenter/models" ) -var update = flag.Bool("update", true, "update the *.golden files for table presenters") +var update = flag.Bool("update", false, "update the *.golden files for table presenters") func TestCreateRow(t *testing.T) { pkg := pkg.Package{ From 64e6d383d9af27e3cbf11a681d2df17db3fbafdb Mon Sep 17 00:00:00 2001 From: Benji Visser Date: Tue, 28 Feb 2023 11:57:41 -0700 Subject: [PATCH 5/5] checking for nil release Signed-off-by: Benji Visser --- xeol/db/eol_provider.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xeol/db/eol_provider.go b/xeol/db/eol_provider.go index 41a68dfd..9a3872e8 100644 --- a/xeol/db/eol_provider.go +++ b/xeol/db/eol_provider.go @@ -26,6 +26,9 @@ func NewEolProvider(reader xeolDB.EolStoreReader) (*EolProvider, error) { func (pr *EolProvider) GetByDistroCpe(r *linux.Release) (string, []eol.Cycle, error) { cycles := make([]eol.Cycle, 0) + if r == nil { + return "", []eol.Cycle{}, errors.New("empty distro release") + } d, err := distro.NewFromRelease(*r) if err != nil {