Skip to content

Commit

Permalink
Add speculative OS CPEName (#40)
Browse files Browse the repository at this point in the history
Signed-off-by: Benji Visser <benji@093b.org>
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: xeol-actions[bot] <122061159+xeol-actions[bot]@users.noreply.github.com>
Co-authored-by: noqcks <noqcks@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 28, 2023
1 parent 2af3bcf commit bd083e6
Show file tree
Hide file tree
Showing 8 changed files with 261 additions and 89 deletions.
25 changes: 0 additions & 25 deletions internal/cpe/cpe.go

This file was deleted.

58 changes: 0 additions & 58 deletions internal/cpe/cpe_test.go

This file was deleted.

16 changes: 12 additions & 4 deletions xeol/db/eol_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -24,14 +24,22 @@ 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 r == nil {
return "", []eol.Cycle{}, errors.New("empty distro release")
}

d, err := distro.NewFromRelease(*r)
if err != nil {
return "", []eol.Cycle{}, err
}

if d == nil || d.CPEName == "" {
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")
}
Expand Down
33 changes: 33 additions & 0 deletions xeol/distro/cpe.go
Original file line number Diff line number Diff line change
@@ -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]
}
9 changes: 7 additions & 2 deletions xeol/distro/distro.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -26,13 +27,17 @@ 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{
Type: t,
Version: verObj,
RawVersion: version,
IDLike: idLikes,
CPEName: CPEName(cpeName),
}, nil
}

Expand All @@ -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 {
Expand Down
147 changes: 147 additions & 0 deletions xeol/distro/distro_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func Test_NewDistroFromRelease(t *testing.T) {
release linux.Release
expectedVersion string
expectedRawVersion string
expectedCpe string
expectedType Type
expectErr bool
}{
Expand Down Expand Up @@ -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)
}
})
}
}
Loading

0 comments on commit bd083e6

Please sign in to comment.