Skip to content

Commit

Permalink
add target
Browse files Browse the repository at this point in the history
  • Loading branch information
mchmarny committed Apr 4, 2023
1 parent 802b57e commit 1121721
Show file tree
Hide file tree
Showing 1,607 changed files with 778,781 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.3.10
v0.3.11
8 changes: 0 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
[![test](https://github.com/mchmarny/vimp/actions/workflows/on-push.yaml/badge.svg?branch=main)](https://github.com/mchmarny/vimp/actions/workflows/on-push.yaml)
[![release-cli](https://github.com/mchmarny/vimp/actions/workflows/on-tag-cli.yaml/badge.svg?branch=main)](https://github.com/mchmarny/vimp/actions/workflows/on-tag-cli.yaml)
[![release-img](https://github.com/mchmarny/vimp/actions/workflows/on-tag-img.yaml/badge.svg?branch=main)](https://github.com/mchmarny/vimp/actions/workflows/on-tag-img.yaml)
[![](https://codecov.io/gh/mchmarny/vimp/branch/main/graph/badge.svg?token=9HLYDZZADN)](https://codecov.io/gh/mchmarny/vimp)
[![version](https://img.shields.io/github/release/mchmarny/vimp.svg?label=version)](https://github.com/mchmarny/vimp/releases/latest)
[![](https://img.shields.io/github/go-mod/go-version/mchmarny/vimp.svg?label=go)](https://github.com/mchmarny/vimp)
[![](https://goreportcard.com/badge/github.com/mchmarny/vimp)](https://goreportcard.com/report/github.com/mchmarny/vimp)

# vimp

Import CLI for OSS vulnerability scanner output. Generalizes vulnerability reports from common OSS scanners into a generic format and imports them into a target database. Useful for comparing data across multiple scanners.
Expand Down
41 changes: 39 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,56 @@ module github.com/mchmarny/vimp
go 1.20

require (
cloud.google.com/go/bigquery v1.50.0
github.com/Jeffail/gabs/v2 v2.7.0
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.29.0
github.com/stretchr/testify v1.8.2
google.golang.org/api v0.114.0
)

require (
cloud.google.com/go v0.110.0 // indirect
cloud.google.com/go/compute v1.19.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v0.13.0 // indirect
cloud.google.com/go/storage v1.30.1 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/apache/arrow/go/v11 v11.0.0 // indirect
github.com/apache/thrift v0.18.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/flatbuffers v23.3.3+incompatible // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.8.0 // indirect
github.com/klauspost/asmfmt v1.3.2 // indirect
github.com/klauspost/compress v1.16.3 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/oauth2 v0.6.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.6.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/tools v0.7.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230330200707-38013875ee22 // indirect
google.golang.org/grpc v1.54.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
183 changes: 176 additions & 7 deletions go.sum

Large diffs are not rendered by default.

127 changes: 127 additions & 0 deletions internal/target/bq/setup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package bq

import (
"context"
"strings"

"cloud.google.com/go/bigquery"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"google.golang.org/api/iterator"
)

func configureTarget(ctx context.Context, t *targetConfig) error {
if t == nil {
return errors.New("nil target config")
}

log.Debug().
Str("project", t.ProjectID).
Str("dataset", t.DatasetID).
Str("table", t.TableID).
Msg("configuring target")

exists, err := datasetExists(ctx, t)
if err != nil {
return errors.Wrap(err, "failed to check if dataset exists")
}

if !exists {
if err := createDataset(ctx, t); err != nil {
return errors.Wrap(err, "failed to create dataset")
}
}

exists, err = tableExists(ctx, t)
if err != nil {
return errors.Wrap(err, "failed to check if table exists")
}

if !exists {
if err := createTable(ctx, t, vulnerabilitySchema); err != nil {
return errors.Wrapf(err, "failed to create table with ID: %s", t.TableID)
}
}

return nil
}

func createTable(ctx context.Context, t *targetConfig, schema bigquery.Schema) error {
client, err := bigquery.NewClient(ctx, t.ProjectID)
if err != nil {
return errors.Wrapf(err, "failed to create bigquery client for project %s", t.ProjectID)
}
defer client.Close()

metaData := &bigquery.TableMetadata{
Schema: schema,
}

tableRef := client.Dataset(t.DatasetID).Table(t.TableID)
if err := tableRef.Create(ctx, metaData); err != nil {
return errors.Wrapf(err, "failed to create table %s", t.TableID)
}
return nil
}

func createDataset(ctx context.Context, t *targetConfig) error {
client, err := bigquery.NewClient(ctx, t.ProjectID)
if err != nil {
return errors.Wrapf(err, "failed to create bigquery client for project %s", t.ProjectID)
}
defer client.Close()

meta := &bigquery.DatasetMetadata{Location: t.Location}
if err := client.Dataset(t.DatasetID).Create(ctx, meta); err != nil {
return errors.Wrapf(err, "failed to create dataset %s", t.DatasetID)
}
return nil
}

func datasetExists(ctx context.Context, t *targetConfig) (bool, error) {
client, err := bigquery.NewClient(ctx, t.ProjectID)
if err != nil {
return false, errors.Wrapf(err, "failed to create bigquery client for project %s", t.ProjectID)
}
defer client.Close()

it := client.Datasets(ctx)
for {
dataset, err := it.Next()
if errors.Is(err, iterator.Done) {
break
}
if err != nil {
return false, errors.Wrapf(err, "failed to list datasets for project %s", t.ProjectID)
}

if strings.EqualFold(dataset.DatasetID, t.DatasetID) {
return true, nil
}
}
return false, nil
}

func tableExists(ctx context.Context, t *targetConfig) (bool, error) {
client, err := bigquery.NewClient(ctx, t.ProjectID)
if err != nil {
return false, errors.Wrapf(err, "failed to create bigquery client for project %s", t.ProjectID)
}
defer client.Close()

it := client.Dataset(t.DatasetID).Tables(ctx)
for {
table, err := it.Next()
if errors.Is(err, iterator.Done) {
break
}
if err != nil {
return false, errors.Wrapf(err, "failed to list datasets for project %s", t.ProjectID)
}

if strings.EqualFold(table.TableID, t.TableID) {
return true, nil
}
}
return false, nil
}
76 changes: 76 additions & 0 deletions internal/target/bq/target.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package bq

import (
"context"
"strings"

"cloud.google.com/go/bigquery"
"github.com/pkg/errors"
)

const (
importDefaultLocation = "US"
importTargetParts = 3
importTargetPartProject = 0
importTargetPartDataset = 1
importTargetPartTable = 2

expectedFormat = "bq://project.dataset.table"

targetSchema = "bq://"
)

type targetConfig struct {
ProjectID string
Location string
DatasetID string
TableID string
}

// ParseImportRequest parses import request.
// e.g. bq://cloudy-demos.disco.packages
func parseTarget(uri string) (*targetConfig, error) {
if !strings.HasPrefix(uri, "bq://") {
return nil, errors.Errorf("invalid import target schema, expected %s, got: %s", targetSchema, uri)
}

v := strings.Replace(uri, targetSchema, "", 1)

parts := strings.Split(v, ".")
if len(parts) != importTargetParts {
return nil, errors.Errorf("invalid import target: %s, expected %d part format: %s", uri, importTargetParts, expectedFormat)
}

t := &targetConfig{
Location: importDefaultLocation,
ProjectID: parts[importTargetPartProject],
DatasetID: parts[importTargetPartDataset],
TableID: parts[importTargetPartTable],
}

if t.ProjectID == "" || t.DatasetID == "" || t.TableID == "" {
return nil, errors.Errorf("invalid import target: %+v", t)
}

return t, nil
}

func insert(ctx context.Context, t *targetConfig, items interface{}) error {
if t == nil || t.ProjectID == "" || t.DatasetID == "" || t.TableID == "" {
return errors.New("project, dataset and table must be specified")
}

client, err := bigquery.NewClient(ctx, t.ProjectID)
if err != nil {
return errors.Wrap(err, "failed to create bigquery client")
}
defer client.Close()

inserter := client.Dataset(t.DatasetID).Table(t.TableID).Inserter()
inserter.SkipInvalidRows = true
if err := inserter.Put(ctx, items); err != nil {
return errors.Wrap(err, "failed to insert rows")
}

return nil
}
29 changes: 29 additions & 0 deletions internal/target/bq/target_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package bq

import (
"testing"

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

func TestTargetParsing(t *testing.T) {
_, err := parseTarget("bq://")
assert.Error(t, err)

_, err = parseTarget("bq://project")
assert.Error(t, err)

_, err = parseTarget("bq://project.dataset")
assert.Error(t, err)

_, err = parseTarget("bq://project.dataset.table.port")
assert.Error(t, err)

tr, err := parseTarget("bq://test.vimp.vuls")
assert.NoError(t, err)
assert.NotNil(t, t)
assert.Equal(t, "test", tr.ProjectID)
assert.Equal(t, "vimp", tr.DatasetID)
assert.Equal(t, "vuls", tr.TableID)
assert.Equal(t, importDefaultLocation, tr.Location)
}
77 changes: 77 additions & 0 deletions internal/target/bq/vulnerability.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package bq

import (
"context"

"cloud.google.com/go/bigquery"
"github.com/mchmarny/vimp/pkg/data"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)

var (
vulnerabilitySchema = bigquery.Schema{
{Name: "image", Type: bigquery.StringFieldType, Required: true},
{Name: "digest", Type: bigquery.StringFieldType, Required: true},
{Name: "source", Type: bigquery.StringFieldType, Required: true},
{Name: "processed", Type: bigquery.TimestampFieldType, Required: true},
{Name: "cve", Type: bigquery.StringFieldType, Required: true},
{Name: "package", Type: bigquery.StringFieldType, Required: true},
{Name: "version", Type: bigquery.StringFieldType, Required: true},
{Name: "severity", Type: bigquery.StringFieldType, Required: true},
{Name: "score", Type: bigquery.FloatFieldType, Required: true},
{Name: "fixed", Type: bigquery.BooleanFieldType, Required: true},
}
)

func Import(uri string, vuls []*data.ImageVulnerability) error {
t, err := parseTarget(uri)
if err != nil {
return errors.Wrap(err, "failed to parse target")
}

ctx := context.Background()
if err := configureTarget(ctx, t); err != nil {
return errors.Wrap(err, "errors checking target configuration")
}

rows := makeVulnerabilityRows(vuls)
if err := insert(ctx, t, rows); err != nil {
return errors.Wrap(err, "failed to insert rows")
}

log.Info().Msgf("inserted %d records into %s.%s.%s", len(rows), t.ProjectID, t.DatasetID, t.TableID)

return nil
}

func makeVulnerabilityRows(in []*data.ImageVulnerability) []*VulnerabilityRow {
list := make([]*VulnerabilityRow, 0)

for _, r := range in {
list = append(list, &VulnerabilityRow{
vul: r,
})
}

return list
}

type VulnerabilityRow struct {
vul *data.ImageVulnerability
}

func (v *VulnerabilityRow) Save() (map[string]bigquery.Value, string, error) {
return map[string]bigquery.Value{
"image": v.vul.Image,
"digest": v.vul.Digest,
"source": v.vul.Source,
"processed": v.vul.ProcessedAt,
"cve": v.vul.ID,
"package": v.vul.Package,
"version": v.vul.Version,
"severity": v.vul.Severity,
"score": v.vul.Score,
"fixed": v.vul.IsFixed,
}, "", nil
}
Loading

0 comments on commit 1121721

Please sign in to comment.