Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add v6 DB curator #2151

Merged
merged 3 commits into from
Nov 14, 2024
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
4 changes: 2 additions & 2 deletions grype/db/v6/affected_package_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

func TestAffectedPackageStore_AddAffectedPackages(t *testing.T) {
db := setupTestDB(t)
db := setupTestStore(t).db
bs := newBlobStore(db)
s := newAffectedPackageStore(db, bs)

Expand Down Expand Up @@ -42,7 +42,7 @@ func TestAffectedPackageStore_AddAffectedPackages(t *testing.T) {
}

func TestAffectedPackageStore_GetAffectedPackagesByName(t *testing.T) {
db := setupTestDB(t)
db := setupTestStore(t).db
bs := newBlobStore(db)
s := newAffectedPackageStore(db, bs)

Expand Down
4 changes: 2 additions & 2 deletions grype/db/v6/blob_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

func TestBlobWriter_AddBlobs(t *testing.T) {
db := setupTestDB(t)
db := setupTestStore(t).db
writer := newBlobStore(db)

obj1 := map[string]string{"key": "value1"}
Expand All @@ -34,7 +34,7 @@ func TestBlobWriter_AddBlobs(t *testing.T) {
}

func TestBlobWriter_Close(t *testing.T) {
db := setupTestDB(t)
db := setupTestStore(t).db
writer := newBlobStore(db)

obj := map[string]string{"key": "value"}
Expand Down
10 changes: 9 additions & 1 deletion grype/db/v6/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,19 @@ type Writer interface {
io.Closer
}

type Curator interface {
Reader() (Reader, error)
Status() Status
Delete() error
Update() (bool, error)
willmurphyscode marked this conversation as resolved.
Show resolved Hide resolved
Import(dbArchivePath string) error
}

type Config struct {
DBDirPath string
}

func (c *Config) DBFilePath() string {
func (c Config) DBFilePath() string {
return filepath.Join(c.DBDirPath, VulnerabilityDBFileName)
}

Expand Down
24 changes: 18 additions & 6 deletions grype/db/v6/db_metadata_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

func TestDbMetadataStore_empty(t *testing.T) {
s := newDBMetadataStore(setupTestDB(t))
s := newDBMetadataStore(setupTestStore(t).db)

// attempt to fetch a non-existent record
actualMetadata, err := s.GetDBMetadata()
Expand All @@ -18,7 +18,7 @@ func TestDbMetadataStore_empty(t *testing.T) {
}

func TestDbMetadataStore(t *testing.T) {
s := newDBMetadataStore(setupTestDB(t))
s := newDBMetadataStore(setupTestStore(t).db)

require.NoError(t, s.SetDBMetadata())

Expand All @@ -42,10 +42,22 @@ func TestDbMetadataStore(t *testing.T) {
}, *actualMetadata)
}

func setupTestDB(t *testing.T) *gorm.DB {
// note: empty path means in-memory db
s, err := newStore(Config{}, true)
func setupTestStore(t testing.TB, d ...string) *store {
var dir string
switch len(d) {
case 0:
dir = t.TempDir()
case 1:
dir = d[0]
default:
t.Fatal("too many arguments")

}

s, err := newStore(Config{
DBDirPath: dir,
}, true)
require.NoError(t, err)

return s.db
return s
}
102 changes: 90 additions & 12 deletions grype/db/v6/description.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package v6

import (
"bytes"
"errors"
"fmt"
"path"
"io"
"os"
"path/filepath"
"strings"
"time"

"github.com/OneOfOne/xxhash"
Expand All @@ -12,7 +17,7 @@ import (
"github.com/anchore/grype/internal/file"
)

const DescriptionFileName = "description.json"
const ChecksumFileName = VulnerabilityDBFileName + ".checksum"

type Description struct {
// SchemaVersion is the version of the DB schema
Expand Down Expand Up @@ -53,35 +58,108 @@ func (t Time) String() string {
return t.Time.UTC().Round(time.Second).Format(time.RFC3339)
}

func NewDescriptionFromDir(fs afero.Fs, dir string) (*Description, error) {
// checksum the DB file
dbFilePath := path.Join(dir, VulnerabilityDBFileName)
digest, err := file.HashFile(fs, dbFilePath, xxhash.New64())
func ReadDescription(dir string) (*Description, error) {
dbFilePath := filepath.Join(dir, VulnerabilityDBFileName)

// check if exists
if _, err := os.Stat(dbFilePath); err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("database does not exist")
}
return nil, fmt.Errorf("failed to access database file: %w", err)
}

desc, err := newPartialDescriptionFromDB(dbFilePath)
if err != nil {
return nil, err
}

// read checksums file value
checksum, err := ReadDBChecksum(dir)
if err != nil {
return nil, err
}

desc.Checksum = checksum

return desc, nil
}

func ReadDBChecksum(dir string) (string, error) {
checksumsFilePath := filepath.Join(dir, ChecksumFileName)
checksums, err := os.ReadFile(checksumsFilePath)
if err != nil {
return nil, fmt.Errorf("failed to calculate checksum for DB file (%s): %w", dbFilePath, err)
return "", fmt.Errorf("failed to read checksums file: %w", err)
}
namedDigest := fmt.Sprintf("xxh64:%s", digest)

if len(checksums) == 0 {
return "", fmt.Errorf("checksums file is empty")
}

if !bytes.HasPrefix(checksums, []byte("xxh64:")) {
return "", fmt.Errorf("checksums file is not in the expected format")
}

return string(checksums), nil
}

func CalculateDescription(dbFilePath string) (*Description, error) {
desc, err := newPartialDescriptionFromDB(dbFilePath)
if err != nil {
return nil, err
}

namedDigest, err := CalculateDigest(dbFilePath)
if err != nil {
return nil, err
}

desc.Checksum = namedDigest

return desc, nil
}

func CalculateDigest(dbFilePath string) (string, error) {
digest, err := file.HashFile(afero.NewOsFs(), dbFilePath, xxhash.New64())
if err != nil {
return "", fmt.Errorf("failed to calculate checksum for DB file: %w", err)
}
return fmt.Sprintf("xxh64:%s", digest), nil
}

func newPartialDescriptionFromDB(dbFilePath string) (*Description, error) {
// access the DB to get the built time and schema version
r, err := NewReader(Config{
DBDirPath: dir,
DBDirPath: filepath.Dir(dbFilePath),
})
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to read DB description: %w", err)
}

meta, err := r.GetDBMetadata()
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to read DB metadata: %w", err)
}

return &Description{
SchemaVersion: schemaver.New(meta.Model, meta.Revision, meta.Addition),
Built: Time{Time: *meta.BuildTimestamp},
Checksum: namedDigest,
}, nil
}

func (m Description) String() string {
return fmt.Sprintf("DB(version=%s built=%s checksum=%s)", m.SchemaVersion, m.Built, m.Checksum)
}

func WriteChecksums(writer io.Writer, m Description) error {
if m.Checksum == "" {
return fmt.Errorf("checksum is required")
}

if !strings.HasPrefix(m.Checksum, "xxh64:") {
return fmt.Errorf("checksum missing algorithm prefix")
}

_, err := writer.Write([]byte(m.Checksum))
return err
}
Loading
Loading