Skip to content

Commit 17ccff3

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 <joseph.crosland@gmail.com>
1 parent b037b25 commit 17ccff3

8 files changed

Lines changed: 289 additions & 14 deletions

File tree

enricher/rhcc/rhcc.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
rhccPkgs := 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+
rhccPkgs[id] = e.IntroducedIn.String()
34+
break
35+
}
36+
}
37+
}
38+
}
39+
}
40+
41+
if len(rhccPkgs) == 0 {
42+
return Type, nil, nil
43+
}
44+
b, err := json.Marshal(rhccPkgs)
45+
if err != nil {
46+
return Type, nil, err
47+
}
48+
return Type, []json.RawMessage{b}, nil
49+
}

enricher/rhcc/rhcc_test.go

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

rhel/rhcc/coalescer_test.go

Lines changed: 2 additions & 2 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 8 additions & 8 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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
}

0 commit comments

Comments
 (0)