Skip to content

Commit 9538277

Browse files
committed
more stuff
Signed-off-by: RTann <[email protected]>
1 parent a94123e commit 9538277

12 files changed

+1081
-46
lines changed

chainguard/distributionscanner.go

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ const (
1919
scannerVersion = "1"
2020
scannerKind = "distribution"
2121

22-
chainguard = `chainguard`
23-
wolfi = `wolfi`
22+
chainguardID = `chainguard`
23+
wolfiID = `wolfi`
2424
)
2525

2626
var (
@@ -63,31 +63,25 @@ func (s *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([]*
6363
b, err := fs.ReadFile(sys, osrelease.Path)
6464
switch {
6565
case errors.Is(err, nil):
66-
m, err := osrelease.Parse(ctx, bytes.NewReader(b))
67-
if err != nil {
68-
return nil, err
69-
}
70-
71-
switch id := m[`ID`]; id {
72-
case chainguard, wolfi:
73-
return []*claircore.Distribution{
74-
{
75-
Name: m[`NAME`],
76-
DID: id,
77-
// Neither chainguard nor wolfi images are considered to be "versioned".
78-
// Explicitly set the version to the empty string for clarity.
79-
Version: "",
80-
PrettyName: m[`PRETTY_NAME`],
81-
},
82-
}, nil
83-
default:
84-
// This is neither chainguard nor wolfi.
85-
return nil, nil
86-
}
8766
case errors.Is(err, fs.ErrNotExist):
8867
// os-release file must exist in chainguard and wolfi images.
8968
return nil, nil
9069
default:
9170
return nil, err
9271
}
72+
73+
m, err := osrelease.Parse(ctx, bytes.NewReader(b))
74+
if err != nil {
75+
return nil, err
76+
}
77+
78+
switch id := m[`ID`]; id {
79+
case chainguardID:
80+
return []*claircore.Distribution{chainguardDist}, nil
81+
case wolfiID:
82+
return []*claircore.Distribution{wolfiDist}, nil
83+
default:
84+
// This is neither chainguard nor wolfi.
85+
return nil, nil
86+
}
9387
}

chainguard/doc.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Package chainguard implements everything required to index chainguard-based and wolfi-based container images
2+
// and match their respective packages to related vulnerabilities.
3+
//
4+
// The implementation is based on the [documentation] provided upstream.
5+
//
6+
// [documentation] https://github.com/chainguard-dev/vulnerability-scanner-support/blob/main/docs/scanning_implementation.md
7+
package chainguard

chainguard/ecosystem.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"github.com/quay/claircore/linux"
99
)
1010

11-
// NewEcosystem provides the set of scanners and coalescers for the chainguard ecosystem
11+
// NewEcosystem provides the set of scanners and coalescers for the chainguard/wolfi ecosystem.
1212
func NewEcosystem(_ context.Context) *indexer.Ecosystem {
1313
return &indexer.Ecosystem{
1414
PackageScanners: func(ctx context.Context) ([]indexer.PackageScanner, error) {

chainguard/fetcher.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package chainguard
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
9+
"github.com/quay/zlog"
10+
11+
"github.com/quay/claircore/libvuln/driver"
12+
"github.com/quay/claircore/pkg/tmp"
13+
)
14+
15+
func (u *updater) Fetch(ctx context.Context, hint driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) {
16+
ctx = zlog.ContextWithValues(ctx, "component", "chainguard/Updater.Fetch")
17+
18+
zlog.Info(ctx).Str("database", u.url).Msg("starting fetch")
19+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.url, nil)
20+
if err != nil {
21+
return nil, hint, fmt.Errorf("chainguard: unable to construct request: %w", err)
22+
}
23+
24+
if hint != "" {
25+
zlog.Debug(ctx).
26+
Str("hint", string(hint)).
27+
Msg("using hint")
28+
req.Header.Set("if-none-match", string(hint))
29+
}
30+
31+
res, err := u.client.Do(req)
32+
if err != nil {
33+
return nil, hint, fmt.Errorf("chainguard: error making request: %w", err)
34+
}
35+
defer res.Body.Close()
36+
37+
switch res.StatusCode {
38+
case http.StatusOK:
39+
if t := string(hint); t == "" || t != res.Header.Get("etag") {
40+
break
41+
}
42+
fallthrough
43+
case http.StatusNotModified:
44+
zlog.Info(ctx).Msg("database unchanged since last fetch")
45+
return nil, hint, driver.Unchanged
46+
default:
47+
return nil, hint, fmt.Errorf("chainguard: http response error: %s %d", res.Status, res.StatusCode)
48+
}
49+
zlog.Debug(ctx).Msg("successfully requested database")
50+
51+
tf, err := tmp.NewFile("", u.Name()+".")
52+
if err != nil {
53+
return nil, hint, fmt.Errorf("chainguard: unable to open tempfile: %w", err)
54+
}
55+
zlog.Debug(ctx).
56+
Str("name", tf.Name()).
57+
Msg("created tempfile")
58+
var success bool
59+
defer func() {
60+
if !success {
61+
if err := tf.Close(); err != nil {
62+
zlog.Warn(ctx).Err(err).Msg("unable to close spool")
63+
}
64+
}
65+
}()
66+
67+
var r io.Reader = res.Body
68+
if _, err := io.Copy(tf, r); err != nil {
69+
return nil, hint, fmt.Errorf("chainguard: unable to copy resp body to tempfile: %w", err)
70+
}
71+
if n, err := tf.Seek(0, io.SeekStart); err != nil || n != 0 {
72+
return nil, hint, fmt.Errorf("chainguard: unable to seek database to start: at %d, %v", n, err)
73+
}
74+
zlog.Debug(ctx).Msg("decompressed and buffered database")
75+
76+
success = true
77+
hint = driver.Fingerprint(res.Header.Get("etag"))
78+
zlog.Debug(ctx).
79+
Str("hint", string(hint)).
80+
Msg("using new hint")
81+
82+
return tf, hint, nil
83+
}

chainguard/matcher.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package chainguard
2+
3+
import (
4+
"context"
5+
6+
version "github.com/knqyf263/go-apk-version"
7+
"github.com/quay/claircore"
8+
"github.com/quay/claircore/libvuln/driver"
9+
)
10+
11+
var (
12+
ChainguardMatcher = &matcher{"chainguard"}
13+
WolfiMatcher = &matcher{"wolfi"}
14+
)
15+
16+
var _ driver.Matcher = (*matcher)(nil)
17+
18+
// Matcher implements driver.Matcher for Chainguard and Wolfi containers.
19+
type matcher struct {
20+
name string
21+
}
22+
23+
// Name implements driver.Matcher.
24+
func (m *matcher) Name() string {
25+
return m.name + "-matcher"
26+
}
27+
28+
// Filter implements driver.Matcher.
29+
func (m *matcher) Filter(record *claircore.IndexRecord) bool {
30+
if record.Distribution == nil {
31+
return false
32+
}
33+
34+
switch {
35+
case record.Distribution.DID == m.name:
36+
return true
37+
case record.Distribution.Name == m.name:
38+
return true
39+
default:
40+
return false
41+
}
42+
}
43+
44+
// Query implements driver.Matcher.
45+
func (*matcher) Query() []driver.MatchConstraint {
46+
return []driver.MatchConstraint{
47+
driver.DistributionDID,
48+
driver.DistributionName,
49+
driver.DistributionPrettyName,
50+
}
51+
}
52+
53+
// Vulnerable implements driver.Matcher.
54+
func (*matcher) Vulnerable(_ context.Context, record *claircore.IndexRecord, vuln *claircore.Vulnerability) (bool, error) {
55+
if vuln.FixedInVersion == "" {
56+
return true, nil
57+
}
58+
59+
// Version "0" tracks false-positives, and it indicates the package is not affected by the vulnerability.
60+
// See the following for more information:
61+
// https://github.com/chainguard-dev/vulnerability-scanner-support/blob/main/docs/scanning_implementation.md#the-meaning-of-version-0
62+
if vuln.FixedInVersion == "0" {
63+
return false, nil
64+
}
65+
66+
pkgVersion, err := version.NewVersion(record.Package.Version)
67+
if err != nil {
68+
return false, nil
69+
}
70+
71+
fixedInVersion, err := version.NewVersion(vuln.FixedInVersion)
72+
if err != nil {
73+
return false, nil
74+
}
75+
76+
if pkgVersion.LessThan(fixedInVersion) {
77+
return true, nil
78+
}
79+
80+
return false, nil
81+
}

chainguard/parser.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package chainguard
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"github.com/quay/zlog"
8+
"io"
9+
10+
"github.com/quay/claircore"
11+
"github.com/quay/claircore/updater/secdb"
12+
)
13+
14+
const urlPrefix = "https://images.chainguard.dev/security/"
15+
16+
func (u *updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vulnerability, error) {
17+
ctx = zlog.ContextWithValues(ctx, "component", "chainguard/Updater.Parse")
18+
zlog.Info(ctx).Msg("starting parse")
19+
defer r.Close()
20+
21+
var db secdb.SecurityDB
22+
if err := json.NewDecoder(r).Decode(&db); err != nil {
23+
return nil, err
24+
}
25+
return u.parse(ctx, &db)
26+
}
27+
28+
// parse parses the alpine SecurityDB
29+
func (u *updater) parse(ctx context.Context, sdb *secdb.SecurityDB) ([]*claircore.Vulnerability, error) {
30+
var dist *claircore.Distribution
31+
switch u.Name() {
32+
case "chainguard-updater":
33+
dist = chainguardDist
34+
case "wolfi-updater":
35+
dist = wolfiDist
36+
}
37+
if dist == nil {
38+
return nil, fmt.Errorf("chainguard: no distribution found for %s", u.Name())
39+
}
40+
out := []*claircore.Vulnerability{}
41+
for _, pkg := range sdb.Packages {
42+
if err := ctx.Err(); err != nil {
43+
return nil, ctx.Err()
44+
}
45+
partial := claircore.Vulnerability{
46+
Updater: u.Name(),
47+
NormalizedSeverity: claircore.Unknown,
48+
Package: &claircore.Package{
49+
Name: pkg.Pkg.Name,
50+
Kind: claircore.SOURCE,
51+
},
52+
Dist: dist,
53+
}
54+
out = append(out, unpackSecFixes(partial, pkg.Pkg.Secfixes)...)
55+
}
56+
return out, nil
57+
}
58+
59+
// unpackSecFixes takes a map of secFixes and creates a claircore.Vulnerability for each all CVEs present.
60+
func unpackSecFixes(partial claircore.Vulnerability, secFixes map[string][]string) []*claircore.Vulnerability {
61+
out := []*claircore.Vulnerability{}
62+
for fixedIn, ids := range secFixes {
63+
for _, id := range ids {
64+
v := partial
65+
v.Name = id
66+
v.FixedInVersion = fixedIn
67+
v.Links = urlPrefix + id
68+
out = append(out, &v)
69+
}
70+
}
71+
return out
72+
}

chainguard/release.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package chainguard
2+
3+
import (
4+
"github.com/quay/claircore"
5+
)
6+
7+
var chainguardDist = &claircore.Distribution{
8+
Name: "chainguard",
9+
DID: "chainguard",
10+
// Chainguard images are not versioned.
11+
// Explicitly set the version to the empty string for clarity.
12+
// See https://github.com/chainguard-dev/vulnerability-scanner-support/blob/main/docs/scanning_implementation.md#chainguards-distros-are-not-versioned
13+
// for more information.
14+
Version: "",
15+
PrettyName: "Chainguard",
16+
}
17+
18+
var wolfiDist = &claircore.Distribution{
19+
Name: "wolfi",
20+
DID: "wolfi",
21+
// Wolfi images are not versioned.
22+
// Explicitly set the version to the empty string for clarity.
23+
// See https://github.com/chainguard-dev/vulnerability-scanner-support/blob/main/docs/scanning_implementation.md#chainguards-distros-are-not-versioned
24+
// for more information.
25+
Version: "",
26+
PrettyName: "Wolfi",
27+
}

0 commit comments

Comments
 (0)