Skip to content

Commit 0a9465c

Browse files
committed
enricher: add RHCC enricher
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 <[email protected]>
1 parent 7088f7b commit 0a9465c

File tree

8 files changed

+286
-14
lines changed

8 files changed

+286
-14
lines changed

enricher/rhcc/rhcc.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package rhcc
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
7+
"github.com/quay/claircore"
8+
"github.com/quay/claircore/libvuln/driver"
9+
"github.com/quay/claircore/rhel/rhcc"
10+
)
11+
12+
type Enricher struct{}
13+
14+
var (
15+
_ driver.Enricher = (*Enricher)(nil)
16+
)
17+
18+
const (
19+
// Type is the type of data returned from the Enricher's Enrich method.
20+
Type = `message/vnd.clair.map.layer; enricher=clair.rhcc`
21+
)
22+
23+
func (e *Enricher) Name() string { return "rhcc" }
24+
25+
func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *claircore.VulnerabilityReport) (string, []json.RawMessage, error) {
26+
problematicPkgs := make(map[string]string)
27+
for id, p := range r.Packages {
28+
if envs, ok := r.Environments[id]; ok && p.Kind == claircore.BINARY {
29+
for _, e := range envs {
30+
for _, repoID := range e.RepositoryIDs {
31+
repo := r.Repositories[repoID]
32+
if repo.Name == rhcc.GoldRepo.Name {
33+
problematicPkgs[id] = e.IntroducedIn.String()
34+
}
35+
}
36+
}
37+
}
38+
}
39+
40+
if len(problematicPkgs) == 0 {
41+
return Type, nil, nil
42+
}
43+
b, err := json.Marshal(problematicPkgs)
44+
if err != nil {
45+
return Type, nil, err
46+
}
47+
return Type, []json.RawMessage{b}, nil
48+
}

enricher/rhcc/rhcc_test.go

+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
package rhcc
2+
3+
import (
4+
"context"
5+
"crypto/sha256"
6+
"encoding/json"
7+
"io"
8+
"testing"
9+
10+
"github.com/google/go-cmp/cmp"
11+
"github.com/quay/zlog"
12+
13+
"github.com/quay/claircore"
14+
"github.com/quay/claircore/libvuln/driver"
15+
)
16+
17+
func Digest(name string) claircore.Digest {
18+
h := sha256.New()
19+
io.WriteString(h, name)
20+
d, err := claircore.NewDigest("sha256", h.Sum(nil))
21+
if err != nil {
22+
panic(err)
23+
}
24+
return d
25+
}
26+
27+
func TestEnrich(t *testing.T) {
28+
t.Parallel()
29+
ctx := zlog.Test(context.Background(), t)
30+
firstLayerHash := Digest("first layer")
31+
secondLayerHash := Digest("second layer")
32+
tests := []struct {
33+
name string
34+
vr *claircore.VulnerabilityReport
35+
layers []*claircore.Layer
36+
want map[string]string
37+
}{
38+
{
39+
name: "one package that is a layer one that isn't",
40+
vr: &claircore.VulnerabilityReport{
41+
Packages: map[string]*claircore.Package{
42+
"1": {
43+
Name: "some-rh-package-slash-image",
44+
Version: "v1.0.0",
45+
Kind: claircore.BINARY,
46+
},
47+
"2": {
48+
Name: "grafana",
49+
Version: "v4.7.0",
50+
Kind: claircore.BINARY,
51+
},
52+
},
53+
Environments: map[string][]*claircore.Environment{
54+
"1": {{IntroducedIn: firstLayerHash, RepositoryIDs: []string{"1"}}},
55+
"2": {{IntroducedIn: secondLayerHash}},
56+
},
57+
Repositories: map[string]*claircore.Repository{
58+
"1": {
59+
ID: "1",
60+
Name: "Red Hat Container Catalog",
61+
URI: "https://catalog.redhat.com/software/containers/explore",
62+
},
63+
},
64+
},
65+
layers: []*claircore.Layer{
66+
{Hash: firstLayerHash},
67+
{Hash: secondLayerHash},
68+
},
69+
want: map[string]string{"1": firstLayerHash.String()},
70+
},
71+
{
72+
name: "two packages, neither are layers",
73+
vr: &claircore.VulnerabilityReport{
74+
Packages: map[string]*claircore.Package{
75+
"1": {
76+
Name: "cool app",
77+
Version: "v1.0.0",
78+
Kind: claircore.BINARY,
79+
},
80+
"2": {
81+
Name: "grafana",
82+
Version: "v4.7.0",
83+
Kind: claircore.BINARY,
84+
},
85+
},
86+
Environments: map[string][]*claircore.Environment{
87+
"1": {{IntroducedIn: firstLayerHash}},
88+
"2": {{IntroducedIn: firstLayerHash}},
89+
},
90+
},
91+
layers: []*claircore.Layer{
92+
{Hash: firstLayerHash},
93+
{Hash: secondLayerHash},
94+
},
95+
want: nil,
96+
},
97+
{
98+
name: "multiple rhcc packages in different layers",
99+
vr: &claircore.VulnerabilityReport{
100+
Packages: map[string]*claircore.Package{
101+
"1": {
102+
Name: "some-rh-package-slash-image",
103+
RepositoryHint: "rhcc",
104+
Version: "v1.0.0",
105+
Kind: claircore.BINARY,
106+
},
107+
"2": {
108+
Name: "some-other-rh-package-slash-image",
109+
RepositoryHint: "rhcc",
110+
Version: "v1.0.0",
111+
Kind: claircore.BINARY,
112+
},
113+
"3": {
114+
Name: "grafana",
115+
Version: "v4.7.0",
116+
Kind: claircore.BINARY,
117+
},
118+
},
119+
Environments: map[string][]*claircore.Environment{
120+
"1": {{IntroducedIn: firstLayerHash, RepositoryIDs: []string{"1"}}},
121+
"2": {{IntroducedIn: secondLayerHash, RepositoryIDs: []string{"1"}}},
122+
"3": {{IntroducedIn: firstLayerHash}},
123+
},
124+
Repositories: map[string]*claircore.Repository{
125+
"1": {
126+
ID: "1",
127+
Name: "Red Hat Container Catalog",
128+
URI: "https://catalog.redhat.com/software/containers/explore",
129+
},
130+
},
131+
},
132+
layers: []*claircore.Layer{
133+
{Hash: firstLayerHash},
134+
{Hash: secondLayerHash},
135+
},
136+
want: map[string]string{"1": firstLayerHash.String(), "2": secondLayerHash.String()},
137+
},
138+
{
139+
name: "multiple rhcc packages in same layers (source and binary)",
140+
vr: &claircore.VulnerabilityReport{
141+
Packages: map[string]*claircore.Package{
142+
"1": {
143+
Name: "some-rh-package-slash-image-binary",
144+
Version: "v1.0.0",
145+
Kind: claircore.BINARY,
146+
Source: &claircore.Package{
147+
Name: "some-rh-package-slash-image-source",
148+
Version: "v1.0.0",
149+
Kind: claircore.SOURCE,
150+
},
151+
},
152+
"2": {
153+
Name: "some-rh-package-slash-image-source",
154+
Version: "v1.0.0",
155+
Kind: claircore.SOURCE,
156+
},
157+
"3": {
158+
Name: "grafana",
159+
Version: "v4.7.0",
160+
Kind: claircore.BINARY,
161+
},
162+
},
163+
Environments: map[string][]*claircore.Environment{
164+
"1": {{IntroducedIn: firstLayerHash, RepositoryIDs: []string{"1"}}},
165+
"2": {{IntroducedIn: firstLayerHash, RepositoryIDs: []string{"1"}}},
166+
"3": {{IntroducedIn: secondLayerHash}},
167+
},
168+
Repositories: map[string]*claircore.Repository{
169+
"1": {
170+
ID: "1",
171+
Name: "Red Hat Container Catalog",
172+
URI: "https://catalog.redhat.com/software/containers/explore",
173+
},
174+
},
175+
},
176+
layers: []*claircore.Layer{
177+
{Hash: firstLayerHash},
178+
{Hash: secondLayerHash},
179+
},
180+
want: map[string]string{"1": firstLayerHash.String()},
181+
},
182+
}
183+
184+
e := &Enricher{}
185+
nog := &noopGetter{}
186+
for _, tc := range tests {
187+
t.Run(tc.name, func(t *testing.T) {
188+
tp, data, err := e.Enrich(ctx, nog, tc.vr)
189+
if err != nil {
190+
t.Fatal(err)
191+
}
192+
if tc.want == nil {
193+
if data != nil {
194+
t.Fatal("unexpected data")
195+
}
196+
return
197+
}
198+
if tp != "message/vnd.clair.map.layer; enricher=clair.rhcc" {
199+
t.Fatal("wrong type")
200+
}
201+
got := make(map[string]string)
202+
if err := json.Unmarshal(data[0], &got); err != nil {
203+
t.Error(err)
204+
}
205+
if !cmp.Equal(got, tc.want) {
206+
t.Error(cmp.Diff(got, tc.want))
207+
}
208+
})
209+
210+
}
211+
}
212+
213+
func TestName(t *testing.T) {
214+
e := &Enricher{}
215+
if e.Name() != "rhcc" {
216+
t.Fatal("name should be rhcc")
217+
}
218+
}
219+
220+
type noopGetter struct{}
221+
222+
func (f *noopGetter) GetEnrichment(ctx context.Context, tags []string) ([]driver.EnrichmentRecord, error) {
223+
return nil, nil
224+
}

rhel/rhcc/coalescer_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func TestCoalescer(t *testing.T) {
2121
// Mark them as if they came from this package's package scanner
2222
p.RepositoryHint = `rhcc`
2323
}
24-
repo := []*claircore.Repository{&goldRepo}
24+
repo := []*claircore.Repository{&GoldRepo}
2525
layerArtifacts := []*indexer.LayerArtifacts{
2626
{
2727
Hash: test.RandomSHA256Digest(t),
@@ -67,7 +67,7 @@ func TestCoalescer(t *testing.T) {
6767
}
6868
for _, id := range e.RepositoryIDs {
6969
r := ir.Repositories[id]
70-
if got, want := r.Name, goldRepo.Name; got != want {
70+
if got, want := r.Name, GoldRepo.Name; got != want {
7171
t.Errorf("got: %q, want: %q", got, want)
7272
}
7373
}

rhel/rhcc/matcher.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func (*matcher) Name() string { return "rhel-container-matcher" }
2626
// Filter implements [driver.Matcher].
2727
func (*matcher) Filter(r *claircore.IndexRecord) bool {
2828
return r.Repository != nil &&
29-
r.Repository.Name == goldRepo.Name
29+
r.Repository.Name == GoldRepo.Name
3030
}
3131

3232
// Query implements [driver.Matcher].

rhel/rhcc/parser_test.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func TestDB(t *testing.T) {
3939
Links: "https://access.redhat.com/errata/RHSA-2021:3665 https://access.redhat.com/security/cve/CVE-2021-3762",
4040
NormalizedSeverity: claircore.High,
4141
FixedInVersion: "v3.5.7-8",
42-
Repo: &goldRepo,
42+
Repo: &GoldRepo,
4343
Range: &claircore.Range{
4444
Lower: claircore.Version{
4545
Kind: "rhctag",
@@ -81,7 +81,7 @@ func TestDB(t *testing.T) {
8181
},
8282
},
8383
FixedInVersion: "v4.6.0-202112140546.p0.g8b9da97.assembly.stream",
84-
Repo: &goldRepo,
84+
Repo: &GoldRepo,
8585
},
8686
{
8787
Name: "RHSA-2021:5107",
@@ -103,7 +103,7 @@ func TestDB(t *testing.T) {
103103
},
104104
},
105105
FixedInVersion: "v4.7.0-202112140553.p0.g091bb99.assembly.stream",
106-
Repo: &goldRepo,
106+
Repo: &GoldRepo,
107107
},
108108
{
109109
Name: "RHSA-2021:5108",
@@ -125,7 +125,7 @@ func TestDB(t *testing.T) {
125125
},
126126
},
127127
FixedInVersion: "v4.8.0-202112132154.p0.g57dd03a.assembly.stream",
128-
Repo: &goldRepo,
128+
Repo: &GoldRepo,
129129
},
130130
},
131131
},
@@ -153,7 +153,7 @@ func TestDB(t *testing.T) {
153153
},
154154
},
155155
FixedInVersion: "v6.8.1-65",
156-
Repo: &goldRepo,
156+
Repo: &GoldRepo,
157157
},
158158
{
159159
Name: "RHSA-2021:5137",
@@ -175,7 +175,7 @@ func TestDB(t *testing.T) {
175175
},
176176
},
177177
FixedInVersion: "v5.0.10-1",
178-
Repo: &goldRepo,
178+
Repo: &GoldRepo,
179179
},
180180
},
181181
},
@@ -203,7 +203,7 @@ func TestDB(t *testing.T) {
203203
},
204204
},
205205
FixedInVersion: "4.8-167.9a9db5f.release_4.8",
206-
Repo: &goldRepo,
206+
Repo: &GoldRepo,
207207
},
208208
{
209209
Name: "RHSA-2021:2041",
@@ -225,7 +225,7 @@ func TestDB(t *testing.T) {
225225
},
226226
},
227227
FixedInVersion: "4.7-140.49a6fcf.release_4.7",
228-
Repo: &goldRepo,
228+
Repo: &GoldRepo,
229229
},
230230
},
231231
},

rhel/rhcc/rhcc.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"github.com/quay/claircore/toolkit/types/cpe"
1515
)
1616

17-
var goldRepo = claircore.Repository{
17+
var GoldRepo = claircore.Repository{
1818
Name: "Red Hat Container Catalog",
1919
URI: `https://catalog.redhat.com/software/containers/explore`,
2020
}

rhel/rhcc/scanner.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -299,5 +299,5 @@ func (s *reposcanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircor
299299
}
300300
zlog.Debug(ctx).
301301
Msg("found buildinfo Dockerfile")
302-
return []*claircore.Repository{&goldRepo}, nil
302+
return []*claircore.Repository{&GoldRepo}, nil
303303
}

rhel/rhcc/updater.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ func (u *updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln
288288
Severity: releasesByMinor[minor].Severity,
289289
NormalizedSeverity: common.NormalizeSeverity(releasesByMinor[minor].Severity),
290290
Package: p,
291-
Repo: &goldRepo,
291+
Repo: &GoldRepo,
292292
Links: links,
293293
FixedInVersion: firstPatch.Original,
294294
Range: r,

0 commit comments

Comments
 (0)