Skip to content

Commit

Permalink
Finalize v4 Grype schema (#803)
Browse files Browse the repository at this point in the history
* initial v4 schema setup

Signed-off-by: Weston Steimel <[email protected]>

* update v3 => v4 for unit tests

-- did NOT update
    - grype/db/v3/*

Signed-off-by: Christopher Phillips <[email protected]>

* use nullable string in sqlite so null values get represented correctly

Signed-off-by: Weston Steimel <[email protected]>

* add missing unit test case for dotnet

Signed-off-by: Weston Steimel <[email protected]>

* Add db writer function for calling sqlite vacuum

Signed-off-by: Weston Steimel <[email protected]>

* adding normalization of package names at database adapter layer

Signed-off-by: Weston Steimel <[email protected]>

* refactor namespaces for v4

Signed-off-by: Weston Steimel <[email protected]>

* update v4 stuff to use sqlite fork

Signed-off-by: Weston Steimel <[email protected]>

* Namespace should satisfy Stringer interface

Signed-off-by: Weston Steimel <[email protected]>

* normalize CPEs before comparison

Signed-off-by: Weston Steimel <[email protected]>

* vulnerability exclusion => vulnerability match exclusion

Signed-off-by: Weston Steimel <[email protected]>

* updates to vulnerability match exclusion models

Signed-off-by: Weston Steimel <[email protected]>

* add initial vulnerability match exclusion store unit tests

Signed-off-by: Weston Steimel <[email protected]>

* make vuln match exclusion constraints nullable

Signed-off-by: Weston Steimel <[email protected]>

* move vuln match namespace into constraints object and refactor

Signed-off-by: Weston Steimel <[email protected]>

* check db match constraints to ensure there aren't any unknown fields and add json hints

Signed-off-by: Weston Steimel <[email protected]>

* ensure we only keep compatible match exclusion constraints

Signed-off-by: Weston Steimel <[email protected]>

* use omitempty on all match exclusion structs

Signed-off-by: Weston Steimel <[email protected]>

* remove db v4 schema resolver and namespace types

Signed-off-by: Alex Goodman <[email protected]>

* rename Vacuum to Close

Signed-off-by: Alex Goodman <[email protected]>

* lint fixes + remove panic on vuln provider creation

Signed-off-by: Alex Goodman <[email protected]>

* WIP match exclusions

Signed-off-by: Weston Steimel <[email protected]>

* build list of ignore rules from v4 db records

Signed-off-by: Weston Steimel <[email protected]>

* quick attempt at a new uber object

Signed-off-by: Weston Steimel <[email protected]>

* just pass around the full object for now to quickly get to a usable state

Signed-off-by: Weston Steimel <[email protected]>

* fix panic when no vuln db loaded

Signed-off-by: Weston Steimel <[email protected]>

* use interfaces for db.store function signatures

Signed-off-by: Alex Goodman <[email protected]>

* Flatten the match exclusion constraint model to simplify logic

Signed-off-by: Weston Steimel <[email protected]>

* updating some tests

Signed-off-by: Weston Steimel <[email protected]>

* fix panic when no db update possible

Signed-off-by: Weston Steimel <[email protected]>

* more tests

Signed-off-by: Weston Steimel <[email protected]>

* WIP fixing match exclusion constraint usability and json mapping logic

Signed-off-by: Weston Steimel <[email protected]>

* add v4 db diff logic (excluding vulnerability_match_exclusion data for now)

Signed-off-by: Weston Steimel <[email protected]>

* lint fix

Signed-off-by: Weston Steimel <[email protected]>

* update integration tests

Signed-off-by: Weston Steimel <[email protected]>

* nvd -> nvd:cpe namespace updates

Signed-off-by: Weston Steimel <[email protected]>

* ensure test store uses v4 normalized names

Signed-off-by: Weston Steimel <[email protected]>

* set the grype db update url to staging for v4

Signed-off-by: Weston Steimel <[email protected]>

* prevent more segfaults on database open

Signed-off-by: Weston Steimel <[email protected]>

* add continue when unable to load ignore rules

Signed-off-by: Weston Steimel <[email protected]>

* remove db.Status from the Store object

Signed-off-by: Weston Steimel <[email protected]>

* fix compare_sbom_input_vs_lib_test.go

Signed-off-by: Weston Steimel <[email protected]>

* remove staging endpoint now that v4 is published

Signed-off-by: Weston Steimel <[email protected]>

Co-authored-by: Christopher Phillips <[email protected]>
Co-authored-by: Alex Goodman <[email protected]>
  • Loading branch information
3 people authored Jul 5, 2022
1 parent 75a7e54 commit 44032c5
Show file tree
Hide file tree
Showing 83 changed files with 5,012 additions and 218 deletions.
20 changes: 10 additions & 10 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ import (

"github.com/anchore/grype/grype"
"github.com/anchore/grype/grype/db"
grypeDb "github.com/anchore/grype/grype/db/v3"
grypeDb "github.com/anchore/grype/grype/db/v4"
"github.com/anchore/grype/grype/event"
"github.com/anchore/grype/grype/grypeerr"
"github.com/anchore/grype/grype/match"
"github.com/anchore/grype/grype/matcher"
"github.com/anchore/grype/grype/pkg"
"github.com/anchore/grype/grype/presenter"
"github.com/anchore/grype/grype/store"
"github.com/anchore/grype/grype/vulnerability"
"github.com/anchore/grype/internal"
"github.com/anchore/grype/internal/bus"
Expand Down Expand Up @@ -281,9 +282,8 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha
}
}

var provider vulnerability.Provider
var metadataProvider vulnerability.MetadataProvider
var dbStatus *db.Status
var store *store.Store
var status *db.Status
var packages []pkg.Package
var context pkg.Context
var wg = &sync.WaitGroup{}
Expand All @@ -294,8 +294,8 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha
go func() {
defer wg.Done()
log.Debug("loading DB")
provider, metadataProvider, dbStatus, err = grype.LoadVulnerabilityDB(appConfig.DB.ToCuratorConfig(), appConfig.DB.AutoUpdate)
if err = validateDBLoad(err, dbStatus); err != nil {
store, status, err = grype.LoadVulnerabilityDB(appConfig.DB.ToCuratorConfig(), appConfig.DB.AutoUpdate)
if err = validateDBLoad(err, status); err != nil {
errs <- err
return
}
Expand Down Expand Up @@ -328,7 +328,7 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha
Java: appConfig.ExternalSources.ToJavaMatcherConfig(),
})

allMatches := grype.FindVulnerabilitiesForPackage(provider, context.Distro, matchers, packages)
allMatches := grype.FindVulnerabilitiesForPackage(*store, context.Distro, matchers, packages)
remainingMatches, ignoredMatches := match.ApplyIgnoreRules(allMatches, appConfig.Ignore)

if count := len(ignoredMatches); count > 0 {
Expand All @@ -338,13 +338,13 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha
// determine if there are any severities >= to the max allowable severity (which is optional).
// note: until the shared file lock in sqlittle is fixed the sqlite DB cannot be access concurrently,
// implying that the fail-on-severity check must be done before sending the presenter object.
if hitSeverityThreshold(failOnSeverity, remainingMatches, metadataProvider) {
if hitSeverityThreshold(failOnSeverity, remainingMatches, store) {
errs <- grypeerr.ErrAboveSeverityThreshold
}

bus.Publish(partybus.Event{
Type: event.VulnerabilityScanningFinished,
Value: presenter.GetPresenter(presenterConfig, remainingMatches, ignoredMatches, packages, context, metadataProvider, appConfig, dbStatus),
Value: presenter.GetPresenter(presenterConfig, remainingMatches, ignoredMatches, packages, context, store, appConfig, status),
})
}()
return errs
Expand Down Expand Up @@ -395,7 +395,7 @@ func validateDBLoad(loadErr error, status *db.Status) error {
return fmt.Errorf("failed to load vulnerability db: %w", loadErr)
}
if status == nil {
return fmt.Errorf("unable to determine DB status")
return fmt.Errorf("unable to determine the status of the vulnerability db")
}
if status.Err != nil {
return fmt.Errorf("db could not be loaded: %w", status.Err)
Expand Down
2 changes: 1 addition & 1 deletion cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert"

"github.com/anchore/grype/grype/db"
grypeDB "github.com/anchore/grype/grype/db/v3"
grypeDB "github.com/anchore/grype/grype/db/v4"
"github.com/anchore/grype/grype/match"
"github.com/anchore/grype/grype/pkg"
"github.com/anchore/grype/grype/vulnerability"
Expand Down
12 changes: 6 additions & 6 deletions grype/db/curator.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import (
"time"

"github.com/hako/durafmt"
"github.com/hashicorp/go-cleanhttp"
"github.com/mholt/archiver/v3"
cleanhttp "github.com/hashicorp/go-cleanhttp"
archiver "github.com/mholt/archiver/v3"
"github.com/spf13/afero"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
partybus "github.com/wagoodman/go-partybus"
progress "github.com/wagoodman/go-progress"

grypeDB "github.com/anchore/grype/grype/db/v3"
"github.com/anchore/grype/grype/db/v3/store"
grypeDB "github.com/anchore/grype/grype/db/v4"
"github.com/anchore/grype/grype/db/v4/store"
"github.com/anchore/grype/grype/event"
"github.com/anchore/grype/grype/vulnerability"
"github.com/anchore/grype/internal/bus"
Expand Down
73 changes: 73 additions & 0 deletions grype/db/internal/sqlite/nullable_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package sqlite

import (
"database/sql"
"encoding/json"
)

type NullString struct {
sql.NullString
}

func NewNullString(s string, valid bool) NullString {
return NullString{
sql.NullString{
String: s,
Valid: valid,
},
}
}

func ToNullString(v any) NullString {
nullString := NullString{}
nullString.Valid = false

if v != nil {
var stringValue string

if s, ok := v.(string); ok {
stringValue = s
} else {
vBytes, err := json.Marshal(v)
if err != nil {
// TODO: just no
panic(err)
}

stringValue = string(vBytes)
}

if stringValue != "null" {
nullString.String = stringValue
nullString.Valid = true
}
}

return nullString
}

func (v NullString) ToByteSlice() []byte {
if v.Valid {
return []byte(v.String)
}

return []byte("null")
}

func (v NullString) MarshalJSON() ([]byte, error) {
if v.Valid {
return json.Marshal(v.String)
}

return json.Marshal(nil)
}

func (v *NullString) UnmarshalJSON(data []byte) error {
if data != nil && string(data) != "null" {
v.Valid = true
v.String = string(data)
} else {
v.Valid = false
}
return nil
}
109 changes: 109 additions & 0 deletions grype/db/internal/sqlite/nullable_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package sqlite

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestToNullString(t *testing.T) {
tests := []struct {
name string
input any
expected NullString
}{
{
name: "Nil input",
input: nil,
expected: NullString{},
},
{
name: "String null",
input: "null",
expected: NullString{},
},
{
name: "Other string",
input: "Hello there {}",
expected: NewNullString("Hello there {}", true),
},
{
name: "Single struct with all fields populated",
input: struct {
Boolean bool `json:"boolean"`
String string `json:"string"`
Integer int `json:"integer"`
InnerStruct struct {
StringList []string `json:"string_list"`
} `json:"inner_struct"`
}{
Boolean: true,
String: "{}",
Integer: 1034,
InnerStruct: struct {
StringList []string `json:"string_list"`
}{
StringList: []string{"a", "b", "c"},
},
},
expected: NewNullString(`{"boolean":true,"string":"{}","integer":1034,"inner_struct":{"string_list":["a","b","c"]}}`, true),
},
{
name: "Single struct with one field populated",
input: struct {
Boolean bool `json:"boolean"`
String string `json:"string"`
Integer int `json:"integer"`
InnerStruct struct {
StringList []string `json:"string_list"`
} `json:"inner_struct"`
}{
Boolean: true,
},
expected: NewNullString(`{"boolean":true,"string":"","integer":0,"inner_struct":{"string_list":null}}`, true),
},
{
name: "Single struct with one field populated omit empty",
input: struct {
Boolean bool `json:"boolean,omitempty"`
String string `json:"string,omitempty"`
Integer int `json:"integer,omitempty"`
InnerStruct struct {
StringList []string `json:"string_list,omitempty"`
} `json:"inner_struct,omitempty"`
}{
Boolean: true,
},
expected: NewNullString(`{"boolean":true,"inner_struct":{}}`, true),
},
{
name: "Array of structs",
input: []struct {
Boolean bool `json:"boolean,omitempty"`
String string `json:"string,omitempty"`
Integer int `json:"integer,omitempty"`
}{
{
Boolean: true,
String: "{}",
Integer: 1034,
},
{
String: "[{}]",
},
{
Integer: -5000,
Boolean: false,
},
},
expected: NewNullString(`[{"boolean":true,"string":"{}","integer":1034},{"string":"[{}]"},{"integer":-5000}]`, true),
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := ToNullString(test.input)
assert.Equal(t, test.expected, result)
})
}
}
62 changes: 62 additions & 0 deletions grype/db/match_exclusion_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package db

import (
"fmt"

grypeDB "github.com/anchore/grype/grype/db/v4"
"github.com/anchore/grype/grype/match"
)

var _ match.ExclusionProvider = (*MatchExclusionProvider)(nil)

type MatchExclusionProvider struct {
reader grypeDB.VulnerabilityMatchExclusionStoreReader
}

func NewMatchExclusionProvider(reader grypeDB.VulnerabilityMatchExclusionStoreReader) *MatchExclusionProvider {
return &MatchExclusionProvider{
reader: reader,
}
}

func buildIgnoreRulesFromMatchExclusion(e grypeDB.VulnerabilityMatchExclusion) []match.IgnoreRule {
var ignoreRules []match.IgnoreRule

if len(e.Constraints) == 0 {
ignoreRules = append(ignoreRules, match.IgnoreRule{Vulnerability: e.ID})
return ignoreRules
}

for _, c := range e.Constraints {
ignoreRules = append(ignoreRules, match.IgnoreRule{
Vulnerability: e.ID,
Namespace: c.Vulnerability.Namespace,
FixState: string(c.Vulnerability.FixState),
Package: match.IgnoreRulePackage{
Name: c.Package.Name,
Language: c.Package.Language,
Type: c.Package.Type,
Location: c.Package.Location,
Version: c.Package.Version,
},
})
}

return ignoreRules
}

func (pr *MatchExclusionProvider) GetRules(vulnerabilityID string) ([]match.IgnoreRule, error) {
matchExclusions, err := pr.reader.GetVulnerabilityMatchExclusion(vulnerabilityID)
if err != nil {
return nil, fmt.Errorf("match exclusion provider failed to fetch records for vulnerability id='%s': %w", vulnerabilityID, err)
}

var ignoreRules []match.IgnoreRule

for _, e := range matchExclusions {
rules := buildIgnoreRulesFromMatchExclusion(e)
ignoreRules = append(ignoreRules, rules...)
}

return ignoreRules, nil
}
1 change: 1 addition & 0 deletions grype/db/v1/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ type StoreWriter interface {
IDWriter
VulnerabilityStoreWriter
VulnerabilityMetadataStoreWriter
Close()
}
4 changes: 4 additions & 0 deletions grype/db/v1/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,7 @@ func (s *store) AddVulnerabilityMetadata(metadata ...v1.VulnerabilityMetadata) e
}
return nil
}

func (s *store) Close() {
s.db.Exec("VACUUM;")
}
1 change: 1 addition & 0 deletions grype/db/v2/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ type StoreWriter interface {
IDWriter
VulnerabilityStoreWriter
VulnerabilityMetadataStoreWriter
Close()
}
4 changes: 4 additions & 0 deletions grype/db/v2/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,7 @@ func (s *store) AddVulnerabilityMetadata(metadata ...v2.VulnerabilityMetadata) e
}
return nil
}

func (s *store) Close() {
s.db.Exec("VACUUM;")
}
2 changes: 1 addition & 1 deletion grype/db/v3/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/anchore/grype/grype/pkg"
"github.com/anchore/grype/internal"
"github.com/anchore/grype/internal/log"
"github.com/anchore/packageurl-go"
packageurl "github.com/anchore/packageurl-go"
syftPkg "github.com/anchore/syft/syft/pkg"
)

Expand Down
1 change: 1 addition & 0 deletions grype/db/v3/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type StoreWriter interface {
IDWriter
VulnerabilityStoreWriter
VulnerabilityMetadataStoreWriter
Close()
}

type DiffReader interface {
Expand Down
Loading

0 comments on commit 44032c5

Please sign in to comment.