Skip to content

Commit

Permalink
Add v6 DB curator (#2151)
Browse files Browse the repository at this point in the history
* add db curator

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

* pr review comments

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

* fix unit tests

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

---------

Signed-off-by: Alex Goodman <[email protected]>
  • Loading branch information
wagoodman authored Nov 14, 2024
1 parent 4e6f371 commit 6a74fff
Show file tree
Hide file tree
Showing 16 changed files with 1,367 additions and 57 deletions.
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 @@ -43,11 +43,19 @@ type Writer interface {
io.Closer
}

type Curator interface {
Reader() (Reader, error)
Status() Status
Delete() error
Update() (bool, error)
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

0 comments on commit 6a74fff

Please sign in to comment.