Skip to content

Commit

Permalink
enricher: add RHCC enricher
Browse files Browse the repository at this point in the history
This change introduces a new enricher that reports where rhcc packages
exist (if at all), it allows callers to discount vulnerabilities /
packages that come from the same layers. This approach
helps to keep the index report unchanged and therefore state is less
of an issue, it also builds on existing machinary.

Signed-off-by: crozzy <joseph.crosland@gmail.com>
  • Loading branch information
crozzy committed Jan 2, 2025
1 parent 6bbe356 commit 523a947
Show file tree
Hide file tree
Showing 2 changed files with 275 additions and 0 deletions.
49 changes: 49 additions & 0 deletions enricher/rhcc/rhcc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package rhcc

import (
"context"
"encoding/json"

"github.com/quay/claircore"
"github.com/quay/claircore/libvuln/driver"
"github.com/quay/claircore/rhel/rhcc"
)

type Enricher struct{}

var (
_ driver.Enricher = (*Enricher)(nil)
)

const (
// Type is the type of data returned from the Enricher's Enrich method.
Type = `message/vnd.clair.map.layer; enricher=clair.rhcc`
)

func (e *Enricher) Name() string { return "rhcc" }

func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *claircore.VulnerabilityReport) (string, []json.RawMessage, error) {
rhccPkgs := make(map[string]string)
for id, p := range r.Packages {
if envs, ok := r.Environments[id]; ok && p.Kind == claircore.BINARY {
for _, e := range envs {
for _, repoID := range e.RepositoryIDs {
repo := r.Repositories[repoID]
if repo.Name == rhcc.GoldRepo.Name {
rhccPkgs[id] = e.IntroducedIn.String()
break
}
}
}
}
}

if len(rhccPkgs) == 0 {
return Type, nil, nil
}
b, err := json.Marshal(rhccPkgs)
if err != nil {
return Type, nil, err
}
return Type, []json.RawMessage{b}, nil
}
226 changes: 226 additions & 0 deletions enricher/rhcc/rhcc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package rhcc

import (
"context"
"crypto/sha256"
"encoding/json"
"io"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/quay/zlog"

"github.com/quay/claircore"
"github.com/quay/claircore/libvuln/driver"
)

func Digest(name string) claircore.Digest {
h := sha256.New()
if _, err := io.WriteString(h, name); err != nil {
panic(err)
}
d, err := claircore.NewDigest("sha256", h.Sum(nil))
if err != nil {
panic(err)
}
return d
}

func TestEnrich(t *testing.T) {
t.Parallel()
ctx := zlog.Test(context.Background(), t)
firstLayerHash := Digest("first layer")
secondLayerHash := Digest("second layer")
tests := []struct {
name string
vr *claircore.VulnerabilityReport
layers []*claircore.Layer
want map[string]string
}{
{
name: "one package that is a layer one that isn't",
vr: &claircore.VulnerabilityReport{
Packages: map[string]*claircore.Package{
"1": {
Name: "some-rh-package-slash-image",
Version: "v1.0.0",
Kind: claircore.BINARY,
},
"2": {
Name: "grafana",
Version: "v4.7.0",
Kind: claircore.BINARY,
},
},
Environments: map[string][]*claircore.Environment{
"1": {{IntroducedIn: firstLayerHash, RepositoryIDs: []string{"1"}}},
"2": {{IntroducedIn: secondLayerHash}},
},
Repositories: map[string]*claircore.Repository{
"1": {
ID: "1",
Name: "Red Hat Container Catalog",
URI: "https://catalog.redhat.com/software/containers/explore",
},
},
},
layers: []*claircore.Layer{
{Hash: firstLayerHash},
{Hash: secondLayerHash},
},
want: map[string]string{"1": firstLayerHash.String()},
},
{
name: "two packages, neither are layers",
vr: &claircore.VulnerabilityReport{
Packages: map[string]*claircore.Package{
"1": {
Name: "cool app",
Version: "v1.0.0",
Kind: claircore.BINARY,
},
"2": {
Name: "grafana",
Version: "v4.7.0",
Kind: claircore.BINARY,
},
},
Environments: map[string][]*claircore.Environment{
"1": {{IntroducedIn: firstLayerHash}},
"2": {{IntroducedIn: firstLayerHash}},
},
},
layers: []*claircore.Layer{
{Hash: firstLayerHash},
{Hash: secondLayerHash},
},
want: nil,
},
{
name: "multiple rhcc packages in different layers",
vr: &claircore.VulnerabilityReport{
Packages: map[string]*claircore.Package{
"1": {
Name: "some-rh-package-slash-image",
RepositoryHint: "rhcc",
Version: "v1.0.0",
Kind: claircore.BINARY,
},
"2": {
Name: "some-other-rh-package-slash-image",
RepositoryHint: "rhcc",
Version: "v1.0.0",
Kind: claircore.BINARY,
},
"3": {
Name: "grafana",
Version: "v4.7.0",
Kind: claircore.BINARY,
},
},
Environments: map[string][]*claircore.Environment{
"1": {{IntroducedIn: firstLayerHash, RepositoryIDs: []string{"1"}}},
"2": {{IntroducedIn: secondLayerHash, RepositoryIDs: []string{"1"}}},
"3": {{IntroducedIn: firstLayerHash}},
},
Repositories: map[string]*claircore.Repository{
"1": {
ID: "1",
Name: "Red Hat Container Catalog",
URI: "https://catalog.redhat.com/software/containers/explore",
},
},
},
layers: []*claircore.Layer{
{Hash: firstLayerHash},
{Hash: secondLayerHash},
},
want: map[string]string{"1": firstLayerHash.String(), "2": secondLayerHash.String()},
},
{
name: "multiple rhcc packages in same layers (source and binary)",
vr: &claircore.VulnerabilityReport{
Packages: map[string]*claircore.Package{
"1": {
Name: "some-rh-package-slash-image-binary",
Version: "v1.0.0",
Kind: claircore.BINARY,
Source: &claircore.Package{
Name: "some-rh-package-slash-image-source",
Version: "v1.0.0",
Kind: claircore.SOURCE,
},
},
"2": {
Name: "some-rh-package-slash-image-source",
Version: "v1.0.0",
Kind: claircore.SOURCE,
},
"3": {
Name: "grafana",
Version: "v4.7.0",
Kind: claircore.BINARY,
},
},
Environments: map[string][]*claircore.Environment{
"1": {{IntroducedIn: firstLayerHash, RepositoryIDs: []string{"1"}}},
"2": {{IntroducedIn: firstLayerHash, RepositoryIDs: []string{"1"}}},
"3": {{IntroducedIn: secondLayerHash}},
},
Repositories: map[string]*claircore.Repository{
"1": {
ID: "1",
Name: "Red Hat Container Catalog",
URI: "https://catalog.redhat.com/software/containers/explore",
},
},
},
layers: []*claircore.Layer{
{Hash: firstLayerHash},
{Hash: secondLayerHash},
},
want: map[string]string{"1": firstLayerHash.String()},
},
}

e := &Enricher{}
nog := &noopGetter{}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tp, data, err := e.Enrich(ctx, nog, tc.vr)
if err != nil {
t.Fatal(err)
}
if tc.want == nil {
if data != nil {
t.Fatal("unexpected data")
}
return
}
if tp != "message/vnd.clair.map.layer; enricher=clair.rhcc" {
t.Fatal("wrong type")
}
got := make(map[string]string)
if err := json.Unmarshal(data[0], &got); err != nil {
t.Error(err)
}
if !cmp.Equal(got, tc.want) {
t.Error(cmp.Diff(got, tc.want))
}
})

}
}

func TestName(t *testing.T) {
e := &Enricher{}
if e.Name() != "rhcc" {
t.Fatal("name should be rhcc")
}
}

type noopGetter struct{}

func (f *noopGetter) GetEnrichment(ctx context.Context, tags []string) ([]driver.EnrichmentRecord, error) {
return nil, nil
}

0 comments on commit 523a947

Please sign in to comment.