Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion cmd/grype/cli/commands/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ type DBOptions struct {
}

func dbOptionsDefault(id clio.Identification) *DBOptions {
dbDefaults := options.DefaultDatabase(id)
// by default, require update check success for db operations which check for updates
dbDefaults.RequireUpdateCheck = true
return &DBOptions{
DB: options.DefaultDatabase(id),
DB: dbDefaults,
}
}

Expand Down
4 changes: 4 additions & 0 deletions cmd/grype/cli/options/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Database struct {
ValidateByHashOnStart bool `yaml:"validate-by-hash-on-start" json:"validate-by-hash-on-start" mapstructure:"validate-by-hash-on-start"`
ValidateAge bool `yaml:"validate-age" json:"validate-age" mapstructure:"validate-age"`
MaxAllowedBuiltAge time.Duration `yaml:"max-allowed-built-age" json:"max-allowed-built-age" mapstructure:"max-allowed-built-age"`
RequireUpdateCheck bool `yaml:"require-update-check" json:"require-update-check" mapstructure:"require-update-check"`
UpdateAvailableTimeout time.Duration `yaml:"update-available-timeout" json:"update-available-timeout" mapstructure:"update-available-timeout"`
UpdateDownloadTimeout time.Duration `yaml:"update-download-timeout" json:"update-download-timeout" mapstructure:"update-download-timeout"`
}
Expand All @@ -41,6 +42,7 @@ func DefaultDatabase(id clio.Identification) Database {
ValidateAge: true,
// After this period (5 days) the db data is considered stale
MaxAllowedBuiltAge: defaultMaxDBAge,
RequireUpdateCheck: false,
UpdateAvailableTimeout: defaultUpdateAvailableTimeout,
UpdateDownloadTimeout: defaultUpdateDownloadTimeout,
}
Expand All @@ -54,6 +56,7 @@ func (cfg Database) ToCuratorConfig() db.Config {
ValidateByHashOnGet: cfg.ValidateByHashOnStart,
ValidateAge: cfg.ValidateAge,
MaxAllowedBuiltAge: cfg.MaxAllowedBuiltAge,
RequireUpdateCheck: cfg.RequireUpdateCheck,
ListingFileTimeout: cfg.UpdateAvailableTimeout,
UpdateTimeout: cfg.UpdateDownloadTimeout,
}
Expand All @@ -69,6 +72,7 @@ func (cfg *Database) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&cfg.MaxAllowedBuiltAge, `Max allowed age for vulnerability database,
age being the time since it was built
Default max age is 120h (or five days)`)
descriptions.Add(&cfg.RequireUpdateCheck, `fail the scan if unable to check for database updates`)
descriptions.Add(&cfg.UpdateAvailableTimeout, `Timeout for downloading GRYPE_DB_UPDATE_URL to see if the database needs to be downloaded
This file is ~156KiB as of 2024-04-17 so the download should be quick; adjust as needed`)
descriptions.Add(&cfg.UpdateDownloadTimeout, `Timeout for downloading actual vulnerability DB
Expand Down
7 changes: 6 additions & 1 deletion grype/db/curator.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type Config struct {
ValidateByHashOnGet bool
ValidateAge bool
MaxAllowedBuiltAge time.Duration
RequireUpdateCheck bool
ListingFileTimeout time.Duration
UpdateTimeout time.Duration
}
Expand All @@ -52,6 +53,7 @@ type Curator struct {
validateByHashOnGet bool
validateAge bool
maxAllowedBuiltAge time.Duration
requireUpdateCheck bool
}

func NewCurator(cfg Config) (Curator, error) {
Expand Down Expand Up @@ -81,6 +83,7 @@ func NewCurator(cfg Config) (Curator, error) {
validateByHashOnGet: cfg.ValidateByHashOnGet,
validateAge: cfg.ValidateAge,
maxAllowedBuiltAge: cfg.MaxAllowedBuiltAge,
requireUpdateCheck: cfg.RequireUpdateCheck,
}, nil
}

Expand Down Expand Up @@ -150,7 +153,9 @@ func (c *Curator) Update() (bool, error) {

updateAvailable, metadata, updateEntry, err := c.IsUpdateAvailable()
if err != nil {
// we want to continue if possible even if we can't check for an update
if c.requireUpdateCheck {
return false, fmt.Errorf("check for vulnerability database update failed: %+v", err)
}
log.Warnf("unable to check for vulnerability database update")
log.Debugf("check for vulnerability update failed: %+v", err)
}
Expand Down
171 changes: 171 additions & 0 deletions grype/db/curator_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package db

import (
"archive/tar"
"bufio"
"bytes"
"compress/gzip"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -374,6 +380,171 @@ func TestCurator_validateStaleness(t *testing.T) {
}
}

func Test_requireUpdateCheck(t *testing.T) {
toJson := func(listing any) []byte {
listingContents := bytes.Buffer{}
enc := json.NewEncoder(&listingContents)
_ = enc.Encode(listing)
return listingContents.Bytes()
}
checksum := func(b []byte) string {
h := sha256.New()
h.Write(b)
return hex.EncodeToString(h.Sum(nil))
}
makeTarGz := func(mod time.Time, contents []byte) []byte {
metadata := toJson(MetadataJSON{
Built: mod.Format(time.RFC3339),
Version: 5,
Checksum: "sha256:" + checksum(contents),
})
tgz := bytes.Buffer{}
gz := gzip.NewWriter(&tgz)
w := tar.NewWriter(gz)
_ = w.WriteHeader(&tar.Header{
Name: "metadata.json",
Size: int64(len(metadata)),
Mode: 0600,
})
_, _ = w.Write(metadata)
_ = w.WriteHeader(&tar.Header{
Name: "vulnerability.db",
Size: int64(len(contents)),
Mode: 0600,
})
_, _ = w.Write(contents)
_ = w.Close()
_ = gz.Close()
return tgz.Bytes()
}

newTime := time.Date(2024, 06, 13, 17, 13, 13, 0, time.UTC)
midTime := time.Date(2022, 06, 13, 17, 13, 13, 0, time.UTC)
oldTime := time.Date(2020, 06, 13, 17, 13, 13, 0, time.UTC)

newDB := makeTarGz(newTime, []byte("some-good-contents"))

midMetadata := toJson(MetadataJSON{
Built: midTime.Format(time.RFC3339),
Version: 5,
Checksum: "sha256:deadbeefcafe",
})

var handlerFunc http.HandlerFunc

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handlerFunc(w, r)
}))
defer srv.Close()

newDbURI := "/db.tar.gz"

newListing := toJson(Listing{Available: map[int][]ListingEntry{5: {ListingEntry{
Built: newTime,
URL: mustUrl(url.Parse(srv.URL + newDbURI)),
Checksum: "sha256:" + checksum(newDB),
}}}})

oldListing := toJson(Listing{Available: map[int][]ListingEntry{5: {ListingEntry{
Built: oldTime,
URL: mustUrl(url.Parse(srv.URL + newDbURI)),
Checksum: "sha256:" + checksum(newDB),
}}}})

newListingURI := "/listing.json"
oldListingURI := "/oldlisting.json"
badListingURI := "/badlisting.json"

handlerFunc = func(response http.ResponseWriter, request *http.Request) {
switch request.RequestURI {
case newListingURI:
response.WriteHeader(http.StatusOK)
_, _ = response.Write(newListing)
case oldListingURI:
response.WriteHeader(http.StatusOK)
_, _ = response.Write(oldListing)
case newDbURI:
response.WriteHeader(http.StatusOK)
_, _ = response.Write(newDB)
default:
http.Error(response, "not found", http.StatusNotFound)
}
}

tests := []struct {
name string
config Config
dbDir map[string][]byte
wantResult bool
wantErr require.ErrorAssertionFunc
}{
{
name: "listing with update",
config: Config{
ListingURL: srv.URL + newListingURI,
RequireUpdateCheck: true,
},
dbDir: map[string][]byte{
"5/metadata.json": midMetadata,
},
wantResult: true,
wantErr: require.NoError,
},
{
name: "no update",
config: Config{
ListingURL: srv.URL + oldListingURI,
RequireUpdateCheck: false,
},
dbDir: map[string][]byte{
"5/metadata.json": midMetadata,
},
wantResult: false,
wantErr: require.NoError,
},
{
name: "update error fail",
config: Config{
ListingURL: srv.URL + badListingURI,
RequireUpdateCheck: true,
},
wantResult: false,
wantErr: require.Error,
},
{
name: "update error continue",
config: Config{
ListingURL: srv.URL + badListingURI,
RequireUpdateCheck: false,
},
wantResult: false,
wantErr: require.NoError,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dbTmpDir := t.TempDir()
tt.config.DBRootDir = dbTmpDir
tt.config.ListingFileTimeout = 1 * time.Minute
tt.config.UpdateTimeout = 1 * time.Minute
for filePath, contents := range tt.dbDir {
fullPath := filepath.Join(dbTmpDir, filepath.FromSlash(filePath))
err := os.MkdirAll(filepath.Dir(fullPath), 0700|os.ModeDir)
require.NoError(t, err)
err = os.WriteFile(fullPath, contents, 0700)
require.NoError(t, err)
}
c, err := NewCurator(tt.config)
require.NoError(t, err)

result, err := c.Update()
require.Equal(t, tt.wantResult, result)
tt.wantErr(t, err)
})
}
}

func TestCuratorTimeoutBehavior(t *testing.T) {
failAfter := 10 * time.Second
success := make(chan struct{})
Expand Down
2 changes: 1 addition & 1 deletion grype/match/matches_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package match
import (
"testing"

"github.com/anchore/syft/syft/file"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/anchore/grype/grype/pkg"
"github.com/anchore/grype/grype/vulnerability"
"github.com/anchore/syft/syft/file"
syftPkg "github.com/anchore/syft/syft/pkg"
)

Expand Down