diff --git a/enricher/rhcc/rhcc.go b/enricher/rhcc/rhcc.go new file mode 100644 index 000000000..e4a784553 --- /dev/null +++ b/enricher/rhcc/rhcc.go @@ -0,0 +1,48 @@ +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) { + problematicPkgs := 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 { + problematicPkgs[id] = e.IntroducedIn.String() + } + } + } + } + } + + if len(problematicPkgs) == 0 { + return Type, nil, nil + } + b, err := json.Marshal(problematicPkgs) + if err != nil { + return Type, nil, err + } + return Type, []json.RawMessage{b}, nil +} diff --git a/enricher/rhcc/rhcc_test.go b/enricher/rhcc/rhcc_test.go new file mode 100644 index 000000000..8b05289d8 --- /dev/null +++ b/enricher/rhcc/rhcc_test.go @@ -0,0 +1,224 @@ +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() + io.WriteString(h, name) + 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 +} diff --git a/rhel/rhcc/coalescer_test.go b/rhel/rhcc/coalescer_test.go index bc5ce91c9..21c2cbe7a 100644 --- a/rhel/rhcc/coalescer_test.go +++ b/rhel/rhcc/coalescer_test.go @@ -21,7 +21,7 @@ func TestCoalescer(t *testing.T) { // Mark them as if they came from this package's package scanner p.RepositoryHint = `rhcc` } - repo := []*claircore.Repository{&goldRepo} + repo := []*claircore.Repository{&GoldRepo} layerArtifacts := []*indexer.LayerArtifacts{ { Hash: test.RandomSHA256Digest(t), @@ -67,7 +67,7 @@ func TestCoalescer(t *testing.T) { } for _, id := range e.RepositoryIDs { r := ir.Repositories[id] - if got, want := r.Name, goldRepo.Name; got != want { + if got, want := r.Name, GoldRepo.Name; got != want { t.Errorf("got: %q, want: %q", got, want) } } diff --git a/rhel/rhcc/matcher.go b/rhel/rhcc/matcher.go index dd761ec0c..054859c2c 100644 --- a/rhel/rhcc/matcher.go +++ b/rhel/rhcc/matcher.go @@ -26,7 +26,7 @@ func (*matcher) Name() string { return "rhel-container-matcher" } // Filter implements [driver.Matcher]. func (*matcher) Filter(r *claircore.IndexRecord) bool { return r.Repository != nil && - r.Repository.Name == goldRepo.Name + r.Repository.Name == GoldRepo.Name } // Query implements [driver.Matcher]. diff --git a/rhel/rhcc/parser_test.go b/rhel/rhcc/parser_test.go index eab81c2b4..1633ad7b5 100644 --- a/rhel/rhcc/parser_test.go +++ b/rhel/rhcc/parser_test.go @@ -39,7 +39,7 @@ func TestDB(t *testing.T) { Links: "https://access.redhat.com/errata/RHSA-2021:3665 https://access.redhat.com/security/cve/CVE-2021-3762", NormalizedSeverity: claircore.High, FixedInVersion: "v3.5.7-8", - Repo: &goldRepo, + Repo: &GoldRepo, Range: &claircore.Range{ Lower: claircore.Version{ Kind: "rhctag", @@ -81,7 +81,7 @@ func TestDB(t *testing.T) { }, }, FixedInVersion: "v4.6.0-202112140546.p0.g8b9da97.assembly.stream", - Repo: &goldRepo, + Repo: &GoldRepo, }, { Name: "RHSA-2021:5107", @@ -103,7 +103,7 @@ func TestDB(t *testing.T) { }, }, FixedInVersion: "v4.7.0-202112140553.p0.g091bb99.assembly.stream", - Repo: &goldRepo, + Repo: &GoldRepo, }, { Name: "RHSA-2021:5108", @@ -125,7 +125,7 @@ func TestDB(t *testing.T) { }, }, FixedInVersion: "v4.8.0-202112132154.p0.g57dd03a.assembly.stream", - Repo: &goldRepo, + Repo: &GoldRepo, }, }, }, @@ -153,7 +153,7 @@ func TestDB(t *testing.T) { }, }, FixedInVersion: "v6.8.1-65", - Repo: &goldRepo, + Repo: &GoldRepo, }, { Name: "RHSA-2021:5137", @@ -175,7 +175,7 @@ func TestDB(t *testing.T) { }, }, FixedInVersion: "v5.0.10-1", - Repo: &goldRepo, + Repo: &GoldRepo, }, }, }, @@ -203,7 +203,7 @@ func TestDB(t *testing.T) { }, }, FixedInVersion: "4.8-167.9a9db5f.release_4.8", - Repo: &goldRepo, + Repo: &GoldRepo, }, { Name: "RHSA-2021:2041", @@ -225,7 +225,7 @@ func TestDB(t *testing.T) { }, }, FixedInVersion: "4.7-140.49a6fcf.release_4.7", - Repo: &goldRepo, + Repo: &GoldRepo, }, }, }, diff --git a/rhel/rhcc/rhcc.go b/rhel/rhcc/rhcc.go index 6390536da..38e876181 100644 --- a/rhel/rhcc/rhcc.go +++ b/rhel/rhcc/rhcc.go @@ -14,7 +14,7 @@ import ( "github.com/quay/claircore/toolkit/types/cpe" ) -var goldRepo = claircore.Repository{ +var GoldRepo = claircore.Repository{ Name: "Red Hat Container Catalog", URI: `https://catalog.redhat.com/software/containers/explore`, } diff --git a/rhel/rhcc/scanner.go b/rhel/rhcc/scanner.go index 727b07cce..247fc1c95 100644 --- a/rhel/rhcc/scanner.go +++ b/rhel/rhcc/scanner.go @@ -299,5 +299,5 @@ func (s *reposcanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircor } zlog.Debug(ctx). Msg("found buildinfo Dockerfile") - return []*claircore.Repository{&goldRepo}, nil + return []*claircore.Repository{&GoldRepo}, nil } diff --git a/rhel/rhcc/updater.go b/rhel/rhcc/updater.go index 111e84d6d..f68518b19 100644 --- a/rhel/rhcc/updater.go +++ b/rhel/rhcc/updater.go @@ -288,7 +288,7 @@ func (u *updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln Severity: releasesByMinor[minor].Severity, NormalizedSeverity: common.NormalizeSeverity(releasesByMinor[minor].Severity), Package: p, - Repo: &goldRepo, + Repo: &GoldRepo, Links: links, FixedInVersion: firstPatch.Original, Range: r,