Skip to content

Commit d3f004b

Browse files
committed
rhcc: account for labels.json file
TODO(crozzy): Proper commit message. Signed-off-by: crozzy <[email protected]>
1 parent 5a13ffb commit d3f004b

11 files changed

+304
-87
lines changed

pkg/rhctag/version_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,16 @@ func TestSimple(t *testing.T) {
118118
Original: "4-22",
119119
},
120120
},
121+
{
122+
Name: "tester",
123+
In: "1742843776",
124+
Err: false,
125+
Want: Version{
126+
Major: 1742843776,
127+
Minor: 0,
128+
Original: "1742843776",
129+
},
130+
},
121131
}
122132

123133
for _, tc := range tt {

rhel/matcher.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (m *Matcher) Query() []driver.MatchConstraint {
4848
// added "ANY" attributes from the pattern.
4949
//
5050
// TODO(crozzy) Remove once RH VEX data updates CPEs with standard matching expressions.
51-
func isCPESubstringMatch(recordCPE cpe.WFN, vulnCPE cpe.WFN) bool {
51+
func IsCPESubstringMatch(recordCPE cpe.WFN, vulnCPE cpe.WFN) bool {
5252
return strings.HasPrefix(recordCPE.String(), strings.TrimRight(vulnCPE.String(), ":*"))
5353
}
5454

@@ -75,7 +75,7 @@ func (m *Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord,
7575
Msg("unable to unbind repo CPE")
7676
return false, nil
7777
}
78-
if !cpe.Compare(vuln.Repo.CPE, record.Repository.CPE).IsSuperset() && !isCPESubstringMatch(record.Repository.CPE, vuln.Repo.CPE) {
78+
if !cpe.Compare(vuln.Repo.CPE, record.Repository.CPE).IsSuperset() && !IsCPESubstringMatch(record.Repository.CPE, vuln.Repo.CPE) {
7979
return false, nil
8080
}
8181

rhel/matcher_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ func TestIsCPEStringSubsetMatch(t *testing.T) {
288288
for _, tc := range testcases {
289289
t.Run(tc.name, func(t *testing.T) {
290290
tt := tc
291-
matched := isCPESubstringMatch(tt.recordCPE, tt.vulnCPE)
291+
matched := IsCPESubstringMatch(tt.recordCPE, tt.vulnCPE)
292292
if matched != tt.match {
293293
t.Errorf("unexpected matching %s and %s", tt.recordCPE, tt.vulnCPE)
294294
}

rhel/rhcc/coalescer.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@ func (c *coalescer) Coalesce(ctx context.Context, ls []*indexer.LayerArtifacts)
1515
if ctx.Err() != nil {
1616
return nil, ctx.Err()
1717
}
18+
1819
ir := &claircore.IndexReport{
1920
Environments: map[string][]*claircore.Environment{},
2021
Packages: map[string]*claircore.Package{},
2122
Repositories: map[string]*claircore.Repository{},
2223
}
2324

24-
for _, l := range ls {
25+
// We need to find the last layer that has rhcc content.
26+
for i := len(ls) - 1; i >= 0; i-- {
27+
l := ls[i]
2528
if len(l.Repos) == 0 {
2629
continue
2730
}
@@ -43,6 +46,7 @@ func (c *coalescer) Coalesce(ctx context.Context, ls []*indexer.LayerArtifacts)
4346
},
4447
}
4548
}
49+
break
4650
}
4751
return ir, nil
4852
}

rhel/rhcc/coalescer_test.go

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package rhcc
22

33
import (
44
"context"
5-
"strconv"
65
"testing"
76

87
"github.com/quay/zlog"
@@ -16,60 +15,84 @@ func TestCoalescer(t *testing.T) {
1615
t.Parallel()
1716
ctx := zlog.Test(context.Background(), t)
1817
coalescer := &coalescer{}
19-
pkgs := test.GenUniquePackages(6)
20-
for _, p := range pkgs {
21-
// Mark them as if they came from this package's package scanner
22-
p.RepositoryHint = `rhcc`
23-
}
2418
repo := []*claircore.Repository{&GoldRepo}
19+
repo[0].ID = "1" // Assign it an ID and check it later.
2520
layerArtifacts := []*indexer.LayerArtifacts{
2621
{
2722
Hash: test.RandomSHA256Digest(t),
28-
Pkgs: pkgs[:1],
2923
},
3024
{
3125
Hash: test.RandomSHA256Digest(t),
32-
Pkgs: pkgs[:2],
3326
},
3427
{
35-
Hash: test.RandomSHA256Digest(t),
36-
Pkgs: pkgs[:3],
28+
Hash: test.RandomSHA256Digest(t),
29+
Pkgs: []*claircore.Package{
30+
{
31+
ID: "1",
32+
Name: "ubi8",
33+
Version: "8.4",
34+
RepositoryHint: "rhcc",
35+
Kind: claircore.BINARY,
36+
Arch: "x86_64",
37+
PackageDB: "Dockerfile-rhacm",
38+
Source: &claircore.Package{
39+
ID: "3",
40+
Name: "ubi8-container",
41+
Version: "8.10-1088",
42+
Kind: claircore.SOURCE,
43+
Arch: "x86_64",
44+
PackageDB: "Dockerfile-rhacm",
45+
},
46+
},
47+
},
3748
Repos: repo,
3849
},
3950
{
4051
Hash: test.RandomSHA256Digest(t),
41-
Pkgs: pkgs[:4],
4252
},
4353
{
44-
Hash: test.RandomSHA256Digest(t),
45-
Pkgs: pkgs[:5],
54+
Hash: test.RandomSHA256Digest(t),
55+
Pkgs: []*claircore.Package{
56+
{
57+
ID: "2",
58+
Name: "rhacm2/acm-grafana-rhel8",
59+
Version: "v2.9.5-8",
60+
RepositoryHint: "rhcc",
61+
Kind: claircore.BINARY,
62+
Arch: "x86_64",
63+
PackageDB: "Dockerfile-rhacm",
64+
Source: &claircore.Package{
65+
ID: "4",
66+
Name: "acm-grafana-container",
67+
Version: "v2.9.5-8",
68+
Kind: claircore.SOURCE,
69+
Arch: "x86_64",
70+
PackageDB: "Dockerfile-rhacm",
71+
},
72+
},
73+
},
4674
Repos: repo,
4775
},
4876
{
4977
Hash: test.RandomSHA256Digest(t),
50-
Pkgs: pkgs,
5178
},
5279
}
5380
ir, err := coalescer.Coalesce(ctx, layerArtifacts)
5481
if err != nil {
5582
t.Fatalf("received error from coalesce method: %v", err)
5683
}
57-
// Expect 0-5 to have gotten associated with the repository.
58-
for i := range pkgs {
59-
es, ok := ir.Environments[strconv.Itoa(i)]
60-
if !ok && i == 5 {
61-
// Left out the last package.
62-
continue
63-
}
64-
e := es[0]
65-
if len(e.RepositoryIDs) == 0 {
66-
t.Error("expected some repositories")
67-
}
68-
for _, id := range e.RepositoryIDs {
69-
r := ir.Repositories[id]
70-
if got, want := r.Name, GoldRepo.Name; got != want {
71-
t.Errorf("got: %q, want: %q", got, want)
72-
}
73-
}
84+
// Check that index report only has the package found in the last layer
85+
// that has rhcc content.
86+
if len(ir.Packages) != 1 {
87+
t.Errorf("expected 1 package, got %d", len(ir.Packages))
88+
}
89+
if len(ir.Environments["2"]) != 1 {
90+
t.Errorf("expected 1 environment, got %d", len(ir.Environments["2"]))
91+
}
92+
if len(ir.Environments["2"][0].RepositoryIDs) != 1 {
93+
t.Errorf("expected 1 repository, got %d", len(ir.Environments["2"][0].RepositoryIDs))
94+
}
95+
if ir.Environments["2"][0].RepositoryIDs[0] != "1" {
96+
t.Errorf("expected repository ID 1, got %s", ir.Environments["2"][0].RepositoryIDs[0])
7497
}
7598
}

rhel/rhcc/matcher.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88

99
"github.com/quay/claircore"
1010
"github.com/quay/claircore/libvuln/driver"
11+
"github.com/quay/claircore/rhel"
12+
"github.com/quay/claircore/toolkit/types/cpe"
1113
)
1214

1315
// Matcher is an instance of the rhcc matcher. It's exported so it can be used
@@ -26,16 +28,31 @@ func (*matcher) Name() string { return "rhel-container-matcher" }
2628
// Filter implements [driver.Matcher].
2729
func (*matcher) Filter(r *claircore.IndexRecord) bool {
2830
return r.Repository != nil &&
29-
r.Repository.Name == GoldRepo.Name
31+
r.Repository.Key == RepositoryKey
3032
}
3133

3234
// Query implements [driver.Matcher].
3335
func (*matcher) Query() []driver.MatchConstraint {
34-
return []driver.MatchConstraint{driver.RepositoryName}
36+
return []driver.MatchConstraint{driver.RepositoryKey}
3537
}
3638

3739
// Vulnerable implements [driver.Matcher].
3840
func (*matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, vuln *claircore.Vulnerability) (bool, error) {
41+
var err error
42+
if record.Repository.Name != GoldRepo.Name {
43+
// This is not a gold repo record, so we need to check if the CPE matches.
44+
vuln.Repo.CPE, err = cpe.Unbind(vuln.Repo.Name)
45+
if err != nil {
46+
zlog.Warn(ctx).
47+
Str("vulnerability name", vuln.Name).
48+
Err(err).
49+
Msg("unable to unbind repo CPE")
50+
return false, nil
51+
}
52+
if !cpe.Compare(vuln.Repo.CPE, record.Repository.CPE).IsSuperset() && !rhel.IsCPESubstringMatch(record.Repository.CPE, vuln.Repo.CPE) {
53+
return false, nil
54+
}
55+
}
3956
pkgVer, fixedInVer := rpmVersion.NewVersion(record.Package.Version), rpmVersion.NewVersion(vuln.FixedInVersion)
4057
zlog.Debug(ctx).
4158
Str("record", record.Package.Version).

rhel/rhcc/rhcc.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ import (
88
"github.com/quay/claircore"
99
)
1010

11+
const RepositoryKey = "rhcc-container-repository"
12+
1113
// GoldRepo is the claircore.Repository that every RHCC index record is associated with.
1214
// It is also the claircore.Repository that is associated with OCI VEX vulnerabilities.
1315
var GoldRepo = claircore.Repository{
1416
Name: "Red Hat Container Catalog",
1517
URI: `https://catalog.redhat.com/software/containers/explore`,
18+
Key: RepositoryKey,
1619
}

rhel/rhcc/scanner.go

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/quay/claircore/pkg/rhctag"
2020
"github.com/quay/claircore/rhel/dockerfile"
2121
"github.com/quay/claircore/rhel/internal/common"
22+
"github.com/quay/claircore/toolkit/types/cpe"
2223
)
2324

2425
var (
@@ -105,7 +106,7 @@ func (s *scanner) Configure(ctx context.Context, f indexer.ConfigDeserializer, c
105106
func (s *scanner) Name() string { return "rhel_containerscanner" }
106107

107108
// Version implements [indexer.VersionedScanner].
108-
func (s *scanner) Version() string { return "1" }
109+
func (s *scanner) Version() string { return "2" }
109110

110111
// Kind implements [indexer.VersionedScanner].
111112
func (s *scanner) Kind() string { return "package" }
@@ -130,11 +131,19 @@ func (s *scanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Pa
130131
}
131132

132133
// add source package from component label
133-
labels, p, err := findLabels(ctx, sys)
134+
labels, p, err := findDockerfileLabels(ctx, sys)
134135
switch {
135136
case errors.Is(err, nil):
136137
case errors.Is(err, errNotFound):
137-
return nil, nil
138+
// try and find the labels from labels.json
139+
labels, p, err = findJSONLabels(ctx, sys)
140+
switch {
141+
case errors.Is(err, nil):
142+
case errors.Is(err, errNotFound):
143+
return nil, nil
144+
default:
145+
return nil, err
146+
}
138147
default:
139148
return nil, err
140149
}
@@ -219,7 +228,26 @@ type mappingFile struct {
219228
Data map[string][]string `json:"data"`
220229
}
221230

222-
func findLabels(ctx context.Context, sys fs.FS) (map[string]string, string, error) {
231+
func findJSONLabels(ctx context.Context, sys fs.FS) (map[string]string, string, error) {
232+
labels := make(map[string]string)
233+
l, err := fs.ReadFile(sys, "root/buildinfo/labels.json")
234+
switch {
235+
case errors.Is(err, nil):
236+
case errors.Is(err, fs.ErrNotExist):
237+
zlog.Debug(ctx).
238+
Msg("labels.json file doesn't exist")
239+
return nil, "", errNotFound
240+
default:
241+
return nil, "", err
242+
}
243+
err = json.Unmarshal(l, &labels)
244+
if err != nil {
245+
return nil, "", err
246+
}
247+
return labels, "", nil
248+
}
249+
250+
func findDockerfileLabels(ctx context.Context, sys fs.FS) (map[string]string, string, error) {
223251
ms, err := fs.Glob(sys, "root/buildinfo/Dockerfile-*")
224252
if err != nil { // Can only return ErrBadPattern.
225253
panic("progammer error: " + err.Error())
@@ -278,7 +306,7 @@ var _ indexer.RepositoryScanner = (*reposcanner)(nil)
278306
func (s *reposcanner) Name() string { return "rhel_containerscanner" }
279307

280308
// Version implements [indexer.VersionedScanner].
281-
func (s *reposcanner) Version() string { return "1" }
309+
func (s *reposcanner) Version() string { return "2" }
282310

283311
// Kind implements [indexer.VersionedScanner].
284312
func (s *reposcanner) Kind() string { return "repository" }
@@ -294,10 +322,33 @@ func (s *reposcanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircor
294322
if err != nil { // Can only return ErrBadPattern.
295323
panic("progammer error")
296324
}
297-
if len(ms) == 0 {
325+
if len(ms) > 0 {
326+
zlog.Debug(ctx).
327+
Msg("found buildinfo Dockerfile")
328+
return []*claircore.Repository{&GoldRepo}, nil
329+
}
330+
331+
// We didn't get a Dockerfile, so check for labels.json.
332+
labels, _, err := findJSONLabels(ctx, sys)
333+
switch {
334+
case errors.Is(err, nil):
335+
case errors.Is(err, errNotFound):
298336
return nil, nil
337+
default:
338+
return nil, err
299339
}
300-
zlog.Debug(ctx).
301-
Msg("found buildinfo Dockerfile")
302-
return []*claircore.Repository{&GoldRepo}, nil
340+
if c, ok := labels["cpe"]; ok {
341+
wfn, err := cpe.Unbind(c)
342+
if err != nil {
343+
return nil, err
344+
}
345+
return []*claircore.Repository{
346+
{
347+
CPE: wfn,
348+
Name: wfn.String(),
349+
Key: RepositoryKey,
350+
},
351+
}, nil
352+
}
353+
return []*claircore.Repository{}, nil
303354
}

0 commit comments

Comments
 (0)