Skip to content

Commit

Permalink
policy: handle local eval packages
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Norton committed Jan 11, 2024
1 parent cf01e6f commit b4a8e11
Show file tree
Hide file tree
Showing 7 changed files with 435 additions and 50 deletions.
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/hasura/go-graphql-client v0.9.3
github.com/mitchellh/hashstructure/v2 v2.0.1
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.1
golang.org/x/oauth2 v0.13.0
google.golang.org/api v0.147.0
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3
Expand All @@ -20,13 +21,15 @@ require (
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.2 // indirect
cloud.google.com/go/longrunning v0.5.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/klauspost/compress v1.10.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
Expand All @@ -40,5 +43,6 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect
google.golang.org/grpc v1.58.2 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
nhooyr.io/websocket v1.8.7 // indirect
)
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
Expand Down
156 changes: 156 additions & 0 deletions policy/policy_handler/legacy/image_packages_by_digest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package legacy

import (
"context"
"github.com/atomist-skills/go-skill"
"github.com/atomist-skills/go-skill/policy/data"
)

// Versions of scout-cli-plugin created before the introduction of fixedQueryResults
// directly passed a []Package object in the metadata for local evaluation.
// This was then supplemented by a synchronous GraphQL call to load vulnerability data,
// so we mock the entire process to support these older versions.
// TODO remove this whole system when no longer used

const (
ImagePackagesByDigestQueryName = "image-packages-by-digest"
vulnerabilitiesByPackageQueryName = "vulnerabilities-by-package"

// language=graphql
vulnerabilitiesByPackageQuery = `
query ($context: Context!, $packageUrls: [String!]!) {
vulnerabilitiesByPackage(context: $context, packageUrls: $packageUrls) {
purl
vulnerabilities {
cvss {
severity
score
}
fixedBy
publishedAt
source
sourceId
updatedAt
url
vulnerableRange
}
}
}`
)

type (
Package struct {
Licenses []string `edn:"licenses,omitempty"` // only needed for the license policy evaluation
Name string `edn:"name"`
Namespace string `edn:"namespace"`
Version string `edn:"version"`
Purl string `edn:"purl"`
Type string `edn:"type"`
}

ImagePackagesByDigestResponse struct {
ImagePackagesByDigest *ImagePackagesByDigest `json:"imagePackagesByDigest" edn:"imagePackagesByDigest"`
}

ImagePackagesByDigest struct {
ImagePackages ImagePackages `json:"imagePackages" edn:"imagePackages"`
}

ImagePackages struct {
Packages []Packages `json:"packages" edn:"packages"`
}

Packages struct {
Package PackageWithLicenses `json:"package" edn:"package"`
}

PackageWithLicenses struct {
Licenses []string `json:"licenses" edn:"licenses"`
Name string `json:"name" edn:"name"`
Namespace *string `json:"namespace" edn:"namespace"`
Version string `json:"version" edn:"version"`
Purl string `json:"purl" edn:"purl"`
Type string `json:"type" edn:"type"`
Vulnerabilities []Vulnerability `json:"vulnerabilities" edn:"vulnerabilities"`
}

Vulnerability struct {
Cvss Cvss `json:"cvss"`
FixedBy *string `json:"fixedBy"`
PublishedAt string `json:"publishedAt"`
Source string `json:"source"`
SourceID string `json:"sourceId"`
UpdatedAt string `json:"updatedAt"`
URL *string `json:"url"`
VulnerableRange string `json:"vulnerableRange"`
}

Cvss struct {
Severity *string `json:"severity"`
Score *float32 `json:"score"`
}

VulnerabilitiesByPackageResponse struct {
VulnerabilitiesByPackage []VulnerabilitiesByPackage `json:"vulnerabilitiesByPackage"`
}

VulnerabilitiesByPackage struct {
Purl string `json:"purl"`
Vulnerabilities []Vulnerability `json:"vulnerabilities"`
}
)

func MockImagePackagesByDigest(ctx context.Context, req skill.RequestContext, sbomPkgs []Package) (ImagePackagesByDigestResponse, error) {
// separated for testing
ds, err := data.NewSyncGraphqlDataSource(ctx, req)
if err != nil {
return ImagePackagesByDigestResponse{}, err
}

return mockImagePackagesByDigest(ctx, req, sbomPkgs, ds)
}

func mockImagePackagesByDigest(ctx context.Context, req skill.RequestContext, sbomPkgs []Package, ds data.DataSource) (ImagePackagesByDigestResponse, error) {
purls := []string{}
for _, p := range sbomPkgs {
purls = append(purls, p.Purl)
}

var vulnsResponse VulnerabilitiesByPackageResponse
_, err := ds.Query(ctx, vulnerabilitiesByPackageQueryName, vulnerabilitiesByPackageQuery, map[string]interface{}{
"context": data.GqlContext(req),
"packageUrls": purls,
}, &vulnsResponse)
if err != nil {
return ImagePackagesByDigestResponse{}, err
}

vulns := map[string][]Vulnerability{}
for _, v := range vulnsResponse.VulnerabilitiesByPackage {
vulns[v.Purl] = v.Vulnerabilities
}

pkgs := []Packages{}
for _, a := range sbomPkgs {
ns := a.Namespace
pkgs = append(pkgs, Packages{
Package: PackageWithLicenses{
Licenses: a.Licenses,
Name: a.Name,
Namespace: &ns,
Version: a.Version,
Purl: a.Purl,
Type: a.Type,
Vulnerabilities: vulns[a.Purl],
},
})
}

return ImagePackagesByDigestResponse{
ImagePackagesByDigest: &ImagePackagesByDigest{
ImagePackages: ImagePackages{
Packages: pkgs,
},
},
}, nil
}
110 changes: 110 additions & 0 deletions policy/policy_handler/legacy/image_packages_by_digest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package legacy

import (
"context"
"github.com/atomist-skills/go-skill"
"github.com/atomist-skills/go-skill/internal/test_util"
"github.com/atomist-skills/go-skill/policy/data"
"github.com/stretchr/testify/assert"
"testing"
)

type MockDs struct {
t *testing.T
}

func (ds MockDs) Query(ctx context.Context, queryName string, query string, variables map[string]interface{}, output interface{}) (*data.QueryResponse, error) {
assert.Equal(ds.t, queryName, vulnerabilitiesByPackageQueryName)
assert.Equal(ds.t, query, vulnerabilitiesByPackageQuery)

r := output.(*VulnerabilitiesByPackageResponse)
r.VulnerabilitiesByPackage = []VulnerabilitiesByPackage{
{
Purl: "pkg:deb/ubuntu/libpcre3@2:8.39-12ubuntu0.1?arch=amd64&upstream=pcre3&distro=ubuntu-20.04",
Vulnerabilities: []Vulnerability{{
Cvss: Cvss{
Severity: test_util.Pointer("HIGH"),
Score: test_util.Pointer(float32(7.5)),
},
FixedBy: nil,
PublishedAt: "2017-07-10T11:29:00Z",
Source: "nist",
SourceID: "CVE-2017-11164",
UpdatedAt: "2023-04-12T11:15:00Z", // 2006-01-02T15:04:05Z07:00
URL: test_util.Pointer("https://scout.docker.com/v/CVE-2017-11164"),
VulnerableRange: ">=0",
}},
},
}

return &data.QueryResponse{}, nil
}

func Test_mockImagePackagesByDigest(t *testing.T) {
lPkgs := []Package{
{
Licenses: []string{"GPL-3.0"},
Name: "libpcre3",
Namespace: "pkgNamespace",
Version: "2:8.39-12ubuntu0.1",
Purl: "pkg:deb/ubuntu/libpcre3@2:8.39-12ubuntu0.1?arch=amd64&upstream=pcre3&distro=ubuntu-20.04",
Type: "pkgType",
},
{
Licenses: []string{"AGPL"},
Name: "coreutils",
Namespace: "coreutilsNamespace",
Version: "8.30-3ubuntu2",
Purl: "pkg:deb/ubuntu/[email protected]?arch=amd64&distro=ubuntu-20.04",
Type: "coreutilsType",
},
}

actual, err := mockImagePackagesByDigest(context.TODO(), skill.RequestContext{}, lPkgs, MockDs{t})
assert.NoError(t, err)

expected := ImagePackagesByDigestResponse{
ImagePackagesByDigest: &ImagePackagesByDigest{
ImagePackages: ImagePackages{
Packages: []Packages{
{
Package: PackageWithLicenses{
Licenses: []string{"GPL-3.0"},
Name: "libpcre3",
Namespace: test_util.Pointer("pkgNamespace"),
Version: "2:8.39-12ubuntu0.1",
Purl: "pkg:deb/ubuntu/libpcre3@2:8.39-12ubuntu0.1?arch=amd64&upstream=pcre3&distro=ubuntu-20.04",
Type: "pkgType",
Vulnerabilities: []Vulnerability{{
Cvss: Cvss{
Severity: test_util.Pointer("HIGH"),
Score: test_util.Pointer(float32(7.5)),
},
FixedBy: nil,
PublishedAt: "2017-07-10T11:29:00Z",
Source: "nist",
SourceID: "CVE-2017-11164",
UpdatedAt: "2023-04-12T11:15:00Z", // 2006-01-02T15:04:05Z07:00
URL: test_util.Pointer("https://scout.docker.com/v/CVE-2017-11164"),
VulnerableRange: ">=0",
}},
},
},
{
Package: PackageWithLicenses{
Licenses: []string{"AGPL"},
Name: "coreutils",
Namespace: test_util.Pointer("coreutilsNamespace"),
Version: "8.30-3ubuntu2",
Purl: "pkg:deb/ubuntu/[email protected]?arch=amd64&distro=ubuntu-20.04",
Type: "coreutilsType",
Vulnerabilities: nil,
},
},
},
},
},
}

assert.Equal(t, expected, actual)
}
38 changes: 32 additions & 6 deletions policy/policy_handler/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,22 @@ package policy_handler

import (
"context"
"encoding/json"
"fmt"
"github.com/atomist-skills/go-skill"
"github.com/atomist-skills/go-skill/policy/data"
"github.com/atomist-skills/go-skill/policy/goals"
"github.com/atomist-skills/go-skill/policy/policy_handler/legacy"
"olympos.io/encoding/edn"
)

const eventNameLocalEval = "evaluate_goals_locally"

type SyncRequestMetadata struct {
QueryResults map[edn.Keyword]edn.RawMessage `edn:"fixedQueryResults"`
Packages []legacy.Package `edn:"packages"` // todo remove when no longer used
User string `edn:"imgConfigUser"` // The user from the image config blob // todo remove when no longer used
}

func WithLocal() Opt {
return func(h *EventHandler) {
h.subscriptionNames = append(h.subscriptionNames, eventNameLocalEval)
Expand Down Expand Up @@ -43,14 +50,33 @@ func buildLocalDataSources(ctx context.Context, req skill.RequestContext, evalMe
return []data.DataSource{}, nil
}

metaFixedData := map[string][]byte{}
for k, v := range req.Event.Context.SyncRequest.Metadata {
metaFixedData[string(k)] = v
var srMeta SyncRequestMetadata
err := edn.Unmarshal(req.Event.Context.SyncRequest.Metadata, &srMeta)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal SyncRequest metadata: %w", err)
}

fixedQueryResults := map[string][]byte{}
for k, v := range srMeta.QueryResults {
fixedQueryResults[string(k)] = v
}

if _, ok := fixedQueryResults[legacy.ImagePackagesByDigestQueryName]; !ok && len(srMeta.Packages) != 0 {
mockedQueryResult, err := legacy.MockImagePackagesByDigest(ctx, req, srMeta.Packages)
if err != nil {
return nil, err
}

pkgsEdn, err := edn.Marshal(mockedQueryResult)
if err != nil {
return nil, fmt.Errorf("failed to marshal mocked query %s: %w", legacy.ImagePackagesByDigestQueryName, err)
}

fixedQueryResults[legacy.ImagePackagesByDigestQueryName] = pkgsEdn
}
fixedDs := data.NewFixedDataSource(json.Unmarshal, metaFixedData)

return []data.DataSource{
fixedDs,
data.NewFixedDataSource(edn.Unmarshal, fixedQueryResults),
}, nil
}

Expand Down
Loading

0 comments on commit b4a8e11

Please sign in to comment.