Skip to content

Commit dda9631

Browse files
committed
alma
rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED
1 parent 6fb4aed commit dda9631

19 files changed

+515
-21
lines changed

alma/distributionscanner.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package alma
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"io/fs"
8+
"regexp"
9+
"runtime/trace"
10+
"strconv"
11+
12+
"github.com/quay/zlog"
13+
14+
"github.com/quay/claircore"
15+
"github.com/quay/claircore/indexer"
16+
)
17+
18+
const (
19+
scannerName = "alma"
20+
scannerVersion = "1"
21+
scannerKind = "distribution"
22+
)
23+
24+
var cpeRegexp = regexp.MustCompile(`CPE_NAME="cpe:/o:almalinux:almalinux:(\d+)::baseos"`)
25+
26+
var (
27+
_ indexer.DistributionScanner = (*DistributionScanner)(nil)
28+
_ indexer.VersionedScanner = (*DistributionScanner)(nil)
29+
)
30+
31+
// DistributionScanner attempts to discover if a layer
32+
// displays characteristics of an alma distribution
33+
type DistributionScanner struct{}
34+
35+
// Name implements scanner.VersionedScanner.
36+
func (*DistributionScanner) Name() string { return scannerName }
37+
38+
// Version implements scanner.VersionedScanner.
39+
func (*DistributionScanner) Version() string { return scannerVersion }
40+
41+
// Kind implements scanner.VersionedScanner.
42+
func (*DistributionScanner) Kind() string { return scannerKind }
43+
44+
func (ds *DistributionScanner) Scan(ctx context.Context, l *claircore.Layer) ([]*claircore.Distribution, error) {
45+
defer trace.StartRegion(ctx, "Scanner.Scan").End()
46+
ctx = zlog.ContextWithValues(ctx,
47+
"component", "alma/DistributionScanner.Scan",
48+
"version", ds.Version(),
49+
"layer", l.Hash.String())
50+
zlog.Debug(ctx).Msg("start")
51+
defer zlog.Debug(ctx).Msg("done")
52+
sys, err := l.FS()
53+
if err != nil {
54+
return nil, fmt.Errorf("alma: unable to open layer: %w", err)
55+
}
56+
d, err := findDistribution(sys)
57+
if err != nil {
58+
return nil, fmt.Errorf("alma: unexpected error reading files: %w", err)
59+
}
60+
if d == nil {
61+
zlog.Debug(ctx).Msg("didn't find etc/os-release")
62+
return nil, nil
63+
}
64+
return []*claircore.Distribution{d}, nil
65+
}
66+
67+
func findDistribution(sys fs.FS) (*claircore.Distribution, error) {
68+
const osReleasePath = `etc/os-release`
69+
b, err := fs.ReadFile(sys, osReleasePath)
70+
switch {
71+
case errors.Is(err, nil):
72+
case errors.Is(err, fs.ErrNotExist):
73+
return nil, nil
74+
default:
75+
return nil, fmt.Errorf("alma: unexpected error reading os-release file: %w", err)
76+
}
77+
ms := cpeRegexp.FindSubmatch(b)
78+
if ms == nil {
79+
return nil, nil
80+
}
81+
if len(ms) != 2 {
82+
return nil, fmt.Errorf("alma: malformed os-release file: %q", b)
83+
}
84+
_, err = strconv.Atoi(string(ms[1]))
85+
if err != nil {
86+
return nil, fmt.Errorf("alma: unexpected error reading os-releasefile: %w", err)
87+
}
88+
return mkRelease(string(ms[1])), nil
89+
}

alma/matcher.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package alma
2+
3+
import (
4+
"context"
5+
6+
version "github.com/knqyf263/go-rpm-version"
7+
8+
"github.com/quay/claircore"
9+
"github.com/quay/claircore/libvuln/driver"
10+
)
11+
12+
type Matcher struct{}
13+
14+
var _ driver.Matcher = (*Matcher)(nil)
15+
16+
func (*Matcher) Name() string {
17+
return "alma-matcher"
18+
}
19+
20+
func (*Matcher) Filter(record *claircore.IndexRecord) bool {
21+
return record.Distribution != nil && record.Distribution.DID == "alma"
22+
}
23+
24+
// Query implements driver.Matcher
25+
func (*Matcher) Query() []driver.MatchConstraint {
26+
return []driver.MatchConstraint{
27+
driver.DistributionDID,
28+
driver.DistributionName,
29+
driver.DistributionVersion,
30+
}
31+
}
32+
33+
func (*Matcher) Vulnerable(_ context.Context, record *claircore.IndexRecord, vuln *claircore.Vulnerability) (bool, error) {
34+
pkgVer := version.NewVersion(record.Package.Version)
35+
var vulnVer version.Version
36+
// Assume the vulnerability record we have is for the last known vulnerable
37+
// version, so greater versions aren't vulnerable.
38+
cmp := func(i int) bool { return i != version.GREATER }
39+
// But if it's explicitly marked as a fixed-in version, it's only vulnerable
40+
// if less than that version.
41+
if vuln.FixedInVersion != "" {
42+
vulnVer = version.NewVersion(vuln.FixedInVersion)
43+
cmp = func(i int) bool { return i == version.LESS }
44+
} else {
45+
// If a vulnerability doesn't have FixedInVersion, assume it is unfixed.
46+
vulnVer = version.NewVersion("65535:0")
47+
}
48+
// compare version and architecture
49+
return cmp(pkgVer.Compare(vulnVer)) && vuln.ArchOperation.Cmp(record.Package.Arch, vuln.Package.Arch), nil
50+
}

alma/parser.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package alma
2+
3+
import (
4+
"context"
5+
"encoding/xml"
6+
"fmt"
7+
"io"
8+
"strings"
9+
10+
"github.com/quay/goval-parser/oval"
11+
"github.com/quay/zlog"
12+
13+
"github.com/quay/claircore"
14+
"github.com/quay/claircore/internal/xmlutil"
15+
"github.com/quay/claircore/pkg/ovalutil"
16+
)
17+
18+
// Parse implements [driver.Updater].
19+
//
20+
// Parse treats the data inside the provided io.ReadCloser as Red Hat
21+
// flavored OVAL XML. The distribution associated with vulnerabilities
22+
// is configured via the Updater. The repository associated with
23+
// vulnerabilies is based on the affected CPE list.
24+
func (u *Updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vulnerability, error) {
25+
ctx = zlog.ContextWithValues(ctx, "component", "alma/Updater.Parse")
26+
zlog.Info(ctx).Msg("starting parse")
27+
defer r.Close()
28+
root := oval.Root{}
29+
dec := xml.NewDecoder(r)
30+
dec.CharsetReader = xmlutil.CharsetReader
31+
if err := dec.Decode(&root); err != nil {
32+
return nil, fmt.Errorf("alma: unable to decode OVAL document: %w", err)
33+
}
34+
zlog.Debug(ctx).Msg("xml decoded")
35+
protoVulns := func(def oval.Definition) ([]*claircore.Vulnerability, error) {
36+
defType, err := ovalutil.GetAlmaDefinitionType(def)
37+
if err != nil {
38+
return nil, err
39+
}
40+
// Red Hat OVAL data include information about vulnerabilities,
41+
// that actually don't affect the package in any way. Storing them
42+
// would increase number of records in DB without adding any value.
43+
if isSkippableDefinitionType(defType) {
44+
return []*claircore.Vulnerability{}, nil
45+
}
46+
47+
// Go look for the vuln name in the references, fallback to
48+
// title if not found.
49+
name := def.Title
50+
if len(def.References) > 0 {
51+
name = def.References[0].RefID
52+
}
53+
54+
v := &claircore.Vulnerability{
55+
Updater: u.Name(),
56+
Name: name,
57+
Description: def.Description,
58+
Issued: def.Advisory.Issued.Date,
59+
Links: ovalutil.Links(def),
60+
Severity: def.Advisory.Severity,
61+
NormalizedSeverity: NormalizeSeverity(def.Advisory.Severity),
62+
Dist: u.dist,
63+
}
64+
return []*claircore.Vulnerability{v}, nil
65+
}
66+
vulns, err := ovalutil.RPMDefsToVulns(ctx, &root, protoVulns)
67+
if err != nil {
68+
return nil, err
69+
}
70+
return vulns, nil
71+
}
72+
73+
func isSkippableDefinitionType(defType ovalutil.DefinitionType) bool {
74+
return defType == ovalutil.UnaffectedDefinition || defType == ovalutil.NoneDefinition
75+
}
76+
77+
// NormalizeSeverity maps Red Hat severity strings to claircore's normalized
78+
// serverity levels.
79+
func NormalizeSeverity(severity string) claircore.Severity {
80+
switch strings.ToLower(severity) {
81+
case "none":
82+
return claircore.Unknown
83+
case "low":
84+
return claircore.Low
85+
case "moderate":
86+
return claircore.Medium
87+
case "important":
88+
return claircore.High
89+
case "critical":
90+
return claircore.Critical
91+
default:
92+
return claircore.Unknown
93+
}
94+
}

alma/release.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package alma
2+
3+
import (
4+
"sync"
5+
6+
"github.com/quay/claircore"
7+
"github.com/quay/claircore/toolkit/types/cpe"
8+
)
9+
10+
// RelMap memoizes the Distributions handed out by this package.
11+
//
12+
// Doing this is a cop-out to the previous approach of having a hard-coded set of structs.
13+
// In the case something is (mistakenly) doing pointer comparisons, this will make that work
14+
// but still allows us to have the list of distributions grow ad-hoc.
15+
var relMap sync.Map
16+
17+
func mkRelease(r string) *claircore.Distribution {
18+
v, ok := relMap.Load(r)
19+
if !ok {
20+
v, _ = relMap.LoadOrStore(r, &claircore.Distribution{
21+
Name: "AlmaLinux",
22+
Version: r,
23+
VersionID: r,
24+
DID: "alma",
25+
PrettyName: "AlmaLinux " + r,
26+
CPE: cpe.MustUnbind("cpe:/o:almalinux:almalinux:" + r),
27+
})
28+
}
29+
return v.(*claircore.Distribution)
30+
}

alma/updater.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package alma
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/quay/claircore"
7+
"github.com/quay/claircore/libvuln/driver"
8+
"github.com/quay/claircore/pkg/ovalutil"
9+
"github.com/quay/zlog"
10+
"net/http"
11+
"net/url"
12+
"strconv"
13+
)
14+
15+
var (
16+
_ driver.Updater = (*Updater)(nil)
17+
_ driver.Configurable = (*Updater)(nil)
18+
)
19+
20+
// Updater fetches and parses RHEL-flavored OVAL databases.
21+
type Updater struct {
22+
ovalutil.Fetcher // fetch method promoted via embed
23+
dist *claircore.Distribution
24+
name string
25+
}
26+
27+
// UpdaterConfig is the configuration expected for any given updater.
28+
//
29+
// See also [ovalutil.FetcherConfig].
30+
type UpdaterConfig struct {
31+
ovalutil.FetcherConfig
32+
Release int `json:"release" yaml:"release"`
33+
}
34+
35+
// NewUpdater returns an Updater.
36+
func NewUpdater(release int, uri string) (*Updater, error) {
37+
u := &Updater{
38+
name: fmt.Sprintf("alma-%d", release),
39+
dist: mkRelease(strconv.Itoa(release)),
40+
}
41+
var err error
42+
u.Fetcher.URL, err = url.Parse(uri)
43+
if err != nil {
44+
return nil, err
45+
}
46+
u.Fetcher.Compression = ovalutil.CompressionBzip2
47+
return u, nil
48+
}
49+
50+
// Configure implements [driver.Configurable].
51+
func (u *Updater) Configure(ctx context.Context, cf driver.ConfigUnmarshaler, c *http.Client) error {
52+
ctx = zlog.ContextWithValues(ctx, "component", "rhel/Updater.Configure")
53+
var cfg UpdaterConfig
54+
if err := cf(&cfg); err != nil {
55+
return err
56+
}
57+
if cfg.Release != 0 {
58+
u.dist = mkRelease(strconv.Itoa(cfg.Release))
59+
}
60+
61+
return u.Fetcher.Configure(ctx, cf, c)
62+
}
63+
64+
// Name implements [driver.Updater].
65+
func (u *Updater) Name() string { return u.name }

0 commit comments

Comments
 (0)