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

Update vexctl to spec v0.2.0 #92

Merged
merged 6 commits into from
Aug 25, 2023
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
1 change: 0 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ linters:
enable:
- asciicheck
- bodyclose
- depguard
- dogsled
- dupl
- durationcheck
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.20
require (
github.com/google/go-containerregistry v0.16.1
github.com/in-toto/in-toto-golang v0.9.0
github.com/openvex/go-vex v0.2.1
github.com/openvex/go-vex v0.2.5
github.com/owenrumney/go-sarif v1.1.1
github.com/secure-systems-lab/go-securesystemslib v0.7.0
github.com/sigstore/cosign/v2 v2.1.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -670,8 +670,8 @@ github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0
github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/openvex/go-vex v0.2.1 h1:OiZlTwYnT7jQjv68JRk+CmEcz+YAQhNQkJxg6kSkvuc=
github.com/openvex/go-vex v0.2.1/go.mod h1:BkkoLLIZxS5D8yDKM9pe6eRDHF00H2PuqNBnOxaExz0=
github.com/openvex/go-vex v0.2.5 h1:41utdp2rHgAGCsG+UbjmfMG5CWQxs15nGqir1eRgSrQ=
github.com/openvex/go-vex v0.2.5/go.mod h1:j+oadBxSUELkrKh4NfNb+BPo77U3q7gdKME88IO/0Wo=
github.com/owenrumney/go-sarif v1.1.1 h1:QNObu6YX1igyFKhdzd7vgzmw7XsWN3/6NMGuDzBgXmE=
github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U=
github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU=
Expand Down
53 changes: 35 additions & 18 deletions internal/cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ func (o *createOptions) Validate(args []string) error {
if o.Status != string(vex.StatusAffected) && o.ActionStatement == vex.NoActionStatementMsg {
o.ActionStatement = ""
}
if len(args) == 0 && len(o.Products) == 0 {
return errors.New("a required product id is required to generate a valid VEX statement")
if o.Product == "" {
return errors.New("a required product id is needed to generate a valid VEX statement")
}

if len(args) < 2 && o.Vulnerability == "" {
Expand Down Expand Up @@ -98,14 +98,14 @@ Examples:
Use: "create [flags] [product_id [vuln_id [status]]]",
Example: fmt.Sprintf("%s create \"pkg:apk/wolfi/[email protected]?arch=x86_64\" CVE-2022-39260 fixed ", appname),
SilenceUsage: false,
SilenceErrors: false,
SilenceErrors: true,
PersistentPreRunE: initLogging,
RunE: func(cmd *cobra.Command, args []string) error {
// If we have arguments, add them
for i := range args {
switch i {
case 0:
opts.Products = append(opts.Products, args[i])
opts.Product = args[i]
case 1:
opts.Vulnerability = args[i]
case 2:
Expand All @@ -127,16 +127,33 @@ Examples:
}

statement := vex.Statement{
Vulnerability: opts.Vulnerability,
Products: opts.Products,
Subcomponents: opts.Subcomponents,
Vulnerability: vex.Vulnerability{
Name: vex.VulnerabilityID(opts.Vulnerability),
},
Products: []vex.Product{
{
Component: vex.Component{
ID: opts.Product,
Hashes: map[vex.Algorithm]vex.Hash{},
Identifiers: map[vex.IdentifierType]string{},
},
Subcomponents: []vex.Subcomponent{},
},
},
Status: vex.Status(opts.Status),
StatusNotes: opts.StatusNotes,
Justification: vex.Justification(opts.Justification),
ImpactStatement: opts.ImpactStatement,
ActionStatement: opts.ActionStatement,
}

for _, sc := range opts.Subcomponents {
statement.Products[0].Subcomponents = append(
statement.Products[0].Subcomponents,
vex.Subcomponent{Component: vex.Component{ID: sc}},
)
}

if err := statement.Validate(); err != nil {
return fmt.Errorf("invalid statement: %w", err)
}
Expand Down Expand Up @@ -172,7 +189,7 @@ Examples:
&opts.DocumentID,
"id",
"",
"ID for the new VEX document (default will be computed)",
"ID string for the new VEX document (autogenerated by default)",
)

createCmd.PersistentFlags().StringVar(
Expand All @@ -186,7 +203,7 @@ Examples:
&opts.AuthorRole,
"author-role",
vex.DefaultRole,
"author role to record in the new document",
"optional author role to record in the new document",
)

createCmd.PersistentFlags().StringVarP(
Expand All @@ -197,20 +214,20 @@ Examples:
"vulnerability to add to the statement (eg CVE-2023-12345)",
)

createCmd.PersistentFlags().StringSliceVarP(
&opts.Products,
createCmd.PersistentFlags().StringVarP(
&opts.Product,
"product",
"p",
[]string{},
"list of products to list in the statement, at least one is required",
"",
"main identifier of the product, a package URL or another IRI",
)

createCmd.PersistentFlags().StringVarP(
&opts.Status,
"status",
"s",
"",
fmt.Sprintf("status of the product vs the vulnerability, see '%s show statuses' for list", appname),
"impact status of the product vs the vulnerability",
)

createCmd.PersistentFlags().StringVar(
Expand All @@ -224,30 +241,30 @@ Examples:
&opts.Subcomponents,
"subcomponents",
[]string{},
"list of subcomponents to add to the statement",
"list of subcomponents to add to the statement, package URLs or other IRIs",
)

createCmd.PersistentFlags().StringVarP(
&opts.Justification,
"justification",
"j",
"",
fmt.Sprintf("justification for not_affected status, see '%s show justifications' for list", appname),
"justification for not_affected status",
)

createCmd.PersistentFlags().StringVarP(
&opts.ActionStatement,
"action-statement",
"a",
vex.NoActionStatementMsg,
"action statement for affected status",
"action statement for affected status (only when status=affected)",
)

createCmd.PersistentFlags().StringVar(
&opts.outFilePath,
"file",
"",
"file to write the document (default is STDOUT)",
"file to write the document to (default is STDOUT)",
)

createCmd.PersistentFlags().StringVar(
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ type vexStatementOptions struct {
ImpactStatement string
Vulnerability string
ActionStatement string
Products []string
Product string
Subcomponents []string
}

Expand Down
101 changes: 35 additions & 66 deletions pkg/ctl/ctl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ SPDX-License-Identifier: Apache-2.0
package ctl

import (
"context"
"testing"

"github.com/stretchr/testify/require"
Expand All @@ -16,74 +15,44 @@ import (
)

func TestVexReport(t *testing.T) {
vexDoc, err := vex.OpenJSON("testdata/test.vex.json")
require.NoError(t, err)
require.NotNil(t, vexDoc)
require.Len(t, vexDoc.Statements, 2)

report, err := sarif.Open("testdata/nginx.sarif.json")
require.NoError(t, err)
require.NotNil(t, report)
require.Len(t, report.Runs, 1)
require.Len(t, report.Runs[0].Results, 123)

impl := defaultVexCtlImplementation{}
newReport, err := impl.ApplySingleVEX(report, vexDoc)
require.NoError(t, err)
require.Len(t, newReport.Runs, 1)
require.Len(t, newReport.Runs[0].Results, 122)
}

func TestMerge(t *testing.T) {
ctx := context.Background()
doc1, err := vex.Load("testdata/document1.vex.json")
require.NoError(t, err)
doc2, err := vex.Load("testdata/document1.vex.json")
require.NoError(t, err)

impl := defaultVexCtlImplementation{}
for _, tc := range []struct {
opts MergeOptions
docs []*vex.VEX
expectedDoc *vex.VEX
shouldErr bool
vexDoc string
lenStatements int
sarifDoc string
lenRuns int
lenResults int
lenAfterFilter int
}{
// Zero docs should fail
{
opts: MergeOptions{},
docs: []*vex.VEX{},
expectedDoc: &vex.VEX{},
shouldErr: true,
},
// One doc results in the same doc
{
opts: MergeOptions{},
docs: []*vex.VEX{doc1},
expectedDoc: doc1,
shouldErr: false,
},
// Two docs, as they are
{
opts: MergeOptions{},
docs: []*vex.VEX{doc1, doc2},
expectedDoc: &vex.VEX{
Metadata: vex.Metadata{},
Statements: []vex.Statement{
doc1.Statements[0],
doc2.Statements[0],
},
},
shouldErr: false,
},
// One OpenVEX statement, filters one vuln
{"testdata/sarif/sample.openvex.json", 1, "testdata/sarif/nginx-grype.sarif.json", 1, 99, 98},
{"testdata/sarif/sample.openvex.json", 1, "testdata/sarif/nginx-trivy.sarif.json", 1, 99, 98},
{"testdata/sarif/sample.openvex.json", 1, "testdata/sarif/nginx-snyk.sarif.json", 2, 65, 64},

// Two OpenVEX statements, filters one vuln
{"testdata/sarif/sample-history.json", 2, "testdata/sarif/nginx-grype.sarif.json", 1, 99, 98},
{"testdata/sarif/sample-history.json", 2, "testdata/sarif/nginx-trivy.sarif.json", 1, 99, 98},
{"testdata/sarif/sample-history.json", 2, "testdata/sarif/nginx-snyk.sarif.json", 2, 65, 64},

// Two OpenVEX statements, filters two vuln
{"testdata/sarif/sample-2vulns.json", 2, "testdata/sarif/nginx-grype.sarif.json", 1, 99, 96},
{"testdata/sarif/sample-2vulns.json", 2, "testdata/sarif/nginx-trivy.sarif.json", 1, 99, 96},
{"testdata/sarif/sample-2vulns.json", 2, "testdata/sarif/nginx-snyk.sarif.json", 2, 65, 63},
} {
doc, err := impl.Merge(ctx, &tc.opts, tc.docs)
if tc.shouldErr {
require.Error(t, err)
continue
}

// Check doc
require.Len(t, doc.Statements, len(tc.expectedDoc.Statements))
require.Equal(t, doc.Statements, tc.expectedDoc.Statements)
vexDoc, err := vex.Open(tc.vexDoc)
require.NoError(t, err)
require.NotNil(t, vexDoc)
require.Len(t, vexDoc.Statements, tc.lenStatements)

report, err := sarif.Open(tc.sarifDoc)
require.NoError(t, err)
require.NotNil(t, report)
require.Len(t, report.Runs, tc.lenRuns)
require.Len(t, report.Runs[0].Results, tc.lenResults)

newReport, err := impl.ApplySingleVEX(report, vexDoc)
require.NoError(t, err)
require.Len(t, newReport.Runs, tc.lenRuns)
require.Len(t, newReport.Runs[0].Results, tc.lenAfterFilter)
}
}
Loading