-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: RTann <rtannenb@redhat.com>
- Loading branch information
Showing
5 changed files
with
427 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package ruby | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/url" | ||
|
||
"github.com/Masterminds/semver" | ||
"github.com/quay/zlog" | ||
|
||
"github.com/quay/claircore" | ||
"github.com/quay/claircore/libvuln/driver" | ||
) | ||
|
||
var _ driver.Matcher = (*Matcher)(nil) | ||
|
||
// Matcher attempts to correlate discovered ruby packages with reported | ||
// vulnerabilities. | ||
type Matcher struct{} | ||
|
||
// Name implements driver.Matcher. | ||
func (*Matcher) Name() string { return "ruby" } | ||
|
||
// Filter implements driver.Matcher. | ||
func (*Matcher) Filter(record *claircore.IndexRecord) bool { | ||
return record.Repository != nil && record.Repository.Name == repository | ||
} | ||
|
||
// Query implements driver.Matcher. | ||
func (*Matcher) Query() []driver.MatchConstraint { | ||
return []driver.MatchConstraint{driver.RepositoryName} | ||
} | ||
|
||
// Vulnerable implements driver.Matcher. | ||
func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, vuln *claircore.Vulnerability) (bool, error) { | ||
if vuln.FixedInVersion == "" { | ||
return true, nil | ||
} | ||
|
||
decodedVersions, err := url.ParseQuery(vuln.FixedInVersion) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
// Check for missing upper version | ||
if !decodedVersions.Has("fixed") && !decodedVersions.Has("lastAffected") { | ||
return false, fmt.Errorf("ruby: missing upper version") | ||
} | ||
|
||
upperVersion := decodedVersions.Get("fixed") | ||
if upperVersion == "" { | ||
upperVersion = decodedVersions.Get("lastAffected") | ||
} | ||
|
||
rv, err := semver.NewVersion(record.Package.Version) | ||
if err != nil { | ||
zlog.Warn(ctx). | ||
Str("package", record.Package.Name). | ||
Str("version", record.Package.Version). | ||
Msg("unable to parse ruby package version") | ||
return false, err | ||
} | ||
|
||
uv, err := semver.NewVersion(upperVersion) | ||
if err != nil { | ||
zlog.Warn(ctx). | ||
Str("vulnerability", vuln.Name). | ||
Str("package", vuln.Package.Name). | ||
Str("version", upperVersion). | ||
Msg("unable to parse ruby vulnerability 'fixed version' or 'last affected'") | ||
return false, err | ||
} | ||
|
||
switch { | ||
case decodedVersions.Has("fixed") && rv.Compare(uv) >= 0: | ||
return false, nil | ||
case decodedVersions.Has("lastAffected") && rv.Compare(uv) > 0: | ||
return false, nil | ||
case decodedVersions.Has("introduced"): | ||
introduced := decodedVersions.Get("introduced") | ||
iv, err := semver.NewVersion(introduced) | ||
if err != nil { | ||
zlog.Warn(ctx). | ||
Str("vulnerability", vuln.Name). | ||
Str("package", vuln.Package.Name). | ||
Str("version", introduced). | ||
Msg("unable to parse ruby vulnerability 'introduced version'") | ||
return false, err | ||
} | ||
|
||
if rv.Compare(iv) < 0 { | ||
return false, nil | ||
} | ||
} | ||
|
||
return true, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package ruby | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"net/http" | ||
"net/http/httptest" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/quay/zlog" | ||
|
||
"github.com/quay/claircore" | ||
"github.com/quay/claircore/datastore/postgres" | ||
internalMatcher "github.com/quay/claircore/internal/matcher" | ||
"github.com/quay/claircore/libvuln/driver" | ||
"github.com/quay/claircore/libvuln/updates" | ||
"github.com/quay/claircore/pkg/ctxlock" | ||
"github.com/quay/claircore/test/integration" | ||
pgtest "github.com/quay/claircore/test/postgres" | ||
"github.com/quay/claircore/updater/osv" | ||
) | ||
|
||
func TestMain(m *testing.M) { | ||
var c int | ||
defer func() { os.Exit(c) }() | ||
defer integration.DBSetup()() | ||
c = m.Run() | ||
} | ||
|
||
func TestMatcherIntegration(t *testing.T) { | ||
integration.NeedDB(t) | ||
ctx := zlog.Test(context.Background(), t) | ||
pool := pgtest.TestMatcherDB(ctx, t) | ||
store := postgres.NewMatcherStore(pool) | ||
|
||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusTeapot) | ||
})) | ||
defer srv.Close() | ||
|
||
m := &Matcher{} | ||
locks, err := ctxlock.New(ctx, pool) | ||
if err != nil { | ||
t.Fatalf("%v", err) | ||
} | ||
defer locks.Close(ctx) | ||
|
||
cfg := map[string]driver.ConfigUnmarshaler{ | ||
"osv": func(v interface{}) error { | ||
cfg := v.(*osv.Config) | ||
cfg.URL = osv.DefaultURL | ||
return nil | ||
}, | ||
} | ||
|
||
facs := map[string]driver.UpdaterSetFactory{ | ||
"osv": osv.Factory, | ||
} | ||
mgr, err := updates.NewManager(ctx, store, locks, srv.Client(), | ||
updates.WithFactories(facs), updates.WithConfigs(cfg)) | ||
if err != nil { | ||
t.Fatalf("%v", err) | ||
} | ||
|
||
// force update | ||
if err := mgr.Run(ctx); err != nil { | ||
t.Fatalf("%v", err) | ||
} | ||
|
||
path := filepath.Join("testdata", "indexreport-bullseye-ruby.json") | ||
f, err := os.Open(path) | ||
if err != nil { | ||
t.Fatalf("%v", err) | ||
} | ||
defer f.Close() | ||
var ir claircore.IndexReport | ||
err = json.NewDecoder(f).Decode(&ir) | ||
if err != nil { | ||
t.Fatalf("failed to decode IndexReport: %v", err) | ||
} | ||
vr, err := internalMatcher.Match(ctx, &ir, []driver.Matcher{m}, store) | ||
if err != nil { | ||
t.Fatalf("expected error to be nil but got %v", err) | ||
} | ||
|
||
vulns := vr.Vulnerabilities | ||
t.Logf("Number of Vulnerabilities found: %d", len(vulns)) | ||
|
||
if len(vulns) < 1 { | ||
t.Fatalf("failed to match vulns: %v", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
package ruby | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/quay/claircore" | ||
) | ||
|
||
func TestVulnerable(t *testing.T) { | ||
matcher := &Matcher{} | ||
|
||
testcases := []struct { | ||
name string | ||
record *claircore.IndexRecord | ||
vuln *claircore.Vulnerability | ||
want bool | ||
}{ | ||
{ | ||
name: "bootstrap affected", | ||
record: &claircore.IndexRecord{ | ||
Package: &claircore.Package{ | ||
Name: "bootstrap", | ||
Version: "3.2.9", | ||
Kind: "binary", | ||
}, | ||
}, | ||
vuln: &claircore.Vulnerability{ | ||
Updater: "osv", | ||
Name: "GHSA-7mvr-5x2g-wfc8", | ||
Description: "Bootstrap Cross-site Scripting vulnerability", | ||
Package: &claircore.Package{ | ||
Name: "bootstrap", | ||
RepositoryHint: "RubyGems", | ||
}, | ||
FixedInVersion: "fixed=4.1.2", | ||
}, | ||
want: true, | ||
}, | ||
{ | ||
name: "bootstrap unaffected", | ||
record: &claircore.IndexRecord{ | ||
Package: &claircore.Package{ | ||
Name: "bootstrap", | ||
Version: "4.1.2", | ||
Kind: "binary", | ||
}, | ||
}, | ||
vuln: &claircore.Vulnerability{ | ||
Updater: "osv", | ||
Name: "GHSA-7mvr-5x2g-wfc8", | ||
Description: "Bootstrap Cross-site Scripting vulnerability", | ||
Package: &claircore.Package{ | ||
Name: "bootstrap", | ||
RepositoryHint: "rubygems", | ||
}, | ||
FixedInVersion: "fixed=4.1.2-alpha", | ||
}, | ||
want: false, | ||
}, | ||
{ | ||
name: "openshift-origin-node unfixed", | ||
record: &claircore.IndexRecord{ | ||
Package: &claircore.Package{ | ||
Name: "openshift-origin-node", | ||
Version: "1.3.2", | ||
Kind: "binary", | ||
}, | ||
}, | ||
vuln: &claircore.Vulnerability{ | ||
Updater: "osv", | ||
Name: "GHSA-2c25-xfpq-8n9r", | ||
Description: "Ruby gem openshift-origin-node before 2014-02-14 does not contain a cronjob timeout which could result in a denial of service in cron.daily and cron.weekly.", | ||
Package: &claircore.Package{ | ||
Name: "openshift-origin-node", | ||
RepositoryHint: "rubygems", | ||
}, | ||
FixedInVersion: "lastAffected=1.3.3", | ||
}, | ||
want: true, | ||
}, | ||
{ | ||
name: "openshift-origin-node unfixed again", | ||
record: &claircore.IndexRecord{ | ||
Package: &claircore.Package{ | ||
Name: "openshift-origin-node", | ||
Version: "1.3.3", | ||
Kind: "binary", | ||
}, | ||
}, | ||
vuln: &claircore.Vulnerability{ | ||
Updater: "osv", | ||
Name: "GHSA-2c25-xfpq-8n9r", | ||
Description: "Ruby gem openshift-origin-node before 2014-02-14 does not contain a cronjob timeout which could result in a denial of service in cron.daily and cron.weekly.", | ||
Package: &claircore.Package{ | ||
Name: "openshift-origin-node", | ||
RepositoryHint: "rubygems", | ||
}, | ||
FixedInVersion: "lastAffected=1.3.3", | ||
}, | ||
want: true, | ||
}, | ||
{ | ||
name: "dependabot-omnibus affected", | ||
record: &claircore.IndexRecord{ | ||
Package: &claircore.Package{ | ||
Name: "dependabot-omnibus", | ||
Version: "0.120.0-beta2", | ||
Kind: "binary", | ||
}, | ||
}, | ||
vuln: &claircore.Vulnerability{ | ||
Updater: "osv", | ||
Name: "GHSA-23f7-99jx-m54r", | ||
Description: "Remote code execution in dependabot-core branch names when cloning", | ||
Package: &claircore.Package{ | ||
Name: "dependabot-omnibus", | ||
RepositoryHint: "rubygems", | ||
}, | ||
FixedInVersion: "fixed=0.125.1&introduced=0.119.0-beta1", | ||
}, | ||
want: true, | ||
}, | ||
{ | ||
name: "dependabot-omnibus unaffected", | ||
record: &claircore.IndexRecord{ | ||
Package: &claircore.Package{ | ||
Name: "dependabot-omnibus", | ||
Version: "0.119.0-alpha3", | ||
Kind: "binary", | ||
}, | ||
}, | ||
vuln: &claircore.Vulnerability{ | ||
Updater: "osv", | ||
Name: "GHSA-23f7-99jx-m54r", | ||
Description: "Remote code execution in dependabot-core branch names when cloning", | ||
Package: &claircore.Package{ | ||
Name: "dependabot-omnibus", | ||
RepositoryHint: "rubygems", | ||
}, | ||
FixedInVersion: "fixed=0.125.1&introduced=0.119.0-beta1", | ||
}, | ||
want: false, | ||
}, | ||
} | ||
|
||
for _, testcase := range testcases { | ||
t.Run(testcase.name, func(t *testing.T) { | ||
got, err := matcher.Vulnerable(context.Background(), testcase.record, testcase.vuln) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !cmp.Equal(got, testcase.want) { | ||
t.Error(cmp.Diff(got, testcase.want)) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.