Skip to content

Commit

Permalink
Add scorecard guesser/processor (guacsec#127)
Browse files Browse the repository at this point in the history
* add scorecard guesser/processor

Signed-off-by: Brandon Lum <[email protected]>

* register scorecard processor

Signed-off-by: Brandon Lum <[email protected]>

Signed-off-by: Brandon Lum <[email protected]>
  • Loading branch information
lumjjb authored Oct 11, 2022
1 parent 9054128 commit 599889c
Show file tree
Hide file tree
Showing 13 changed files with 1,398 additions and 10 deletions.
42 changes: 40 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,59 @@ require (
)

require (
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 // indirect
github.com/bombsimon/logrusr/v2 v2.0.1 // indirect
github.com/bradleyfalzon/ghinstallation/v2 v2.1.0 // indirect
github.com/caarlos0/env/v6 v6.10.0 // indirect
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/containerd/stargz-snapshotter/estargz v0.12.0 // indirect
github.com/containerd/typeurl v1.0.2 // indirect
github.com/docker/cli v20.10.17+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v20.10.17+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/google/go-containerregistry v0.11.0 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/google/go-github/v38 v38.1.0 // indirect
github.com/google/go-github/v45 v45.2.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/wire v0.5.0 // indirect
github.com/h2non/filetype v1.1.3 // indirect
github.com/klauspost/compress v1.15.8 // indirect
github.com/letsencrypt/boulder v0.0.0-20220929215747-76583552c2be // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/buildkit v0.10.3 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rhysd/actionlint v1.6.15 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/robfig/cron v1.2.0 // indirect
github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa // indirect
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a // indirect
github.com/theupdateframework/go-tuf v0.5.2-0.20220930112810-3890c1e7ace4 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/vbatts/tar-split v0.11.2 // indirect
go.uber.org/goleak v1.1.12 // indirect
gocloud.dev v0.26.0 // indirect
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
mvdan.cc/sh/v3 v3.5.1 // indirect
sigs.k8s.io/release-utils v0.6.0 // indirect
)

require (
github.com/ossf/scorecard/v4 v4.7.0
github.com/sigstore/sigstore v1.4.3
github.com/spdx/tools-golang v0.3.1-0.20221003161519-fb7fe8874d01
)
966 changes: 958 additions & 8 deletions go.sum

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions internal/testing/processor/testdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,12 @@ var (
// Invalid types for field spdxVersion
//go:embed testdata/invalid-spdx.json
SpdxInvalidExample []byte

// Example scorecard
//go:embed testdata/kubernetes-scorecard.json
ScorecardExample []byte

// Invalid scorecard
//go:embed testdata/invalid-scorecard.json
ScorecardInvalid []byte
)
13 changes: 13 additions & 0 deletions internal/testing/processor/testdata/invalid-scorecard.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"date": "2022-10-06",
"repo": {
"commit": "5835544ca568b757a8ecae5c153f317e5736700e"
},
"scorecard": {
"version": "v4.7.0",
"commit": "7cd6406aef0b80a819402e631919293d5eb6adcf"
},
"score": 8.9,
"checks": [],
"metadata": null
}
105 changes: 105 additions & 0 deletions internal/testing/processor/testdata/kubernetes-scorecard.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{
"date": "2022-10-06",
"repo": {
"name": "github.com/kubernetes/kubernetes",
"commit": "5835544ca568b757a8ecae5c153f317e5736700e"
},
"scorecard": {
"version": "v4.7.0",
"commit": "7cd6406aef0b80a819402e631919293d5eb6adcf"
},
"score": 8.9,
"checks": [
{
"details": null,
"score": 10,
"reason": "no binaries found in the repo",
"name": "Binary-Artifacts",
"documentation": {
"url": "https://github.com/ossf/scorecard/blob/7cd6406aef0b80a819402e631919293d5eb6adcf/docs/checks.md#binary-artifacts",
"short": "Determines if the project has generated executable (binary) artifacts in the source repository."
}
},
{
"details": null,
"score": 10,
"reason": "26 out of 26 merged PRs checked by a CI test -- score normalized to 10",
"name": "CI-Tests",
"documentation": {
"url": "https://github.com/ossf/scorecard/blob/7cd6406aef0b80a819402e631919293d5eb6adcf/docs/checks.md#ci-tests",
"short": "Determines if the project runs tests before pull requests are merged."
}
},
{
"details": null,
"score": 7,
"reason": "16 out of last 16 changesets reviewed before merge -- score normalized to 7",
"name": "Code-Review",
"documentation": {
"url": "https://github.com/ossf/scorecard/blob/7cd6406aef0b80a819402e631919293d5eb6adcf/docs/checks.md#code-review",
"short": "Determines if the project requires code review before pull requests (aka merge requests) are merged."
}
},
{
"details": null,
"score": 10,
"reason": "no dangerous workflow patterns detected",
"name": "Dangerous-Workflow",
"documentation": {
"url": "https://github.com/ossf/scorecard/blob/7cd6406aef0b80a819402e631919293d5eb6adcf/docs/checks.md#dangerous-workflow",
"short": "Determines if the project's GitHub Action workflows avoid dangerous patterns."
}
},
{
"details": null,
"score": 10,
"reason": "license file detected",
"name": "License",
"documentation": {
"url": "https://github.com/ossf/scorecard/blob/7cd6406aef0b80a819402e631919293d5eb6adcf/docs/checks.md#license",
"short": "Determines if the project has defined a license."
}
},
{
"details": null,
"score": 2,
"reason": "dependency not pinned by hash detected -- score normalized to 2",
"name": "Pinned-Dependencies",
"documentation": {
"url": "https://github.com/ossf/scorecard/blob/7cd6406aef0b80a819402e631919293d5eb6adcf/docs/checks.md#pinned-dependencies",
"short": "Determines if the project has declared and pinned its dependencies."
}
},
{
"details": null,
"score": 10,
"reason": "security policy file detected",
"name": "Security-Policy",
"documentation": {
"url": "https://github.com/ossf/scorecard/blob/7cd6406aef0b80a819402e631919293d5eb6adcf/docs/checks.md#security-policy",
"short": "Determines if the project has published a security policy."
}
},
{
"details": null,
"score": 10,
"reason": "tokens are read-only in GitHub workflows",
"name": "Token-Permissions",
"documentation": {
"url": "https://github.com/ossf/scorecard/blob/7cd6406aef0b80a819402e631919293d5eb6adcf/docs/checks.md#token-permissions",
"short": "Determines if the project's workflows follow the principle of least privilege."
}
},
{
"details": null,
"score": 10,
"reason": "no vulnerabilities detected",
"name": "Vulnerabilities",
"documentation": {
"url": "https://github.com/ossf/scorecard/blob/7cd6406aef0b80a819402e631919293d5eb6adcf/docs/checks.md#vulnerabilities",
"short": "Determines if the project has open, known unfixed vulnerabilities."
}
}
],
"metadata": null
}
10 changes: 10 additions & 0 deletions pkg/handler/processor/guesser/guesser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ func Test_GuessDocument(t *testing.T) {
},
expectedType: processor.DocumentITE6SLSA,
expectedFormat: processor.FormatJSON,
}, {
name: "valid scorecard Document",
document: &processor.Document{
Blob: testdata.ScorecardExample,
Type: processor.DocumentUnknown,
Format: processor.FormatUnknown,
SourceInformation: processor.SourceInformation{},
},
expectedType: processor.DocumentScorecard,
expectedFormat: processor.FormatJSON,
}}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions pkg/handler/processor/guesser/type_guesser.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func init() {
_ = RegisterDocumentTypeGuesser(&ite6TypeGuesser{}, "ite6")
_ = RegisterDocumentTypeGuesser(&dsseTypeGuesser{}, "dsse")
_ = RegisterDocumentTypeGuesser(&spdxTypeGuesser{}, "spdx")
_ = RegisterDocumentTypeGuesser(&scorecardTypeGuesser{}, "scorecard")
}

// DocumentTypeGuesser guesses the document type based on the blob and format given
Expand Down
35 changes: 35 additions & 0 deletions pkg/handler/processor/guesser/type_scorecard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// Copyright 2022 The GUAC Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package guesser

import (
"encoding/json"

"github.com/guacsec/guac/pkg/handler/processor"
sc "github.com/ossf/scorecard/v4/pkg"
)

type scorecardTypeGuesser struct{}

func (_ *scorecardTypeGuesser) GuessDocumentType(blob []byte, format processor.FormatType) processor.DocumentType {
var scorecard sc.JSONScorecardResultV2
if json.Unmarshal(blob, &scorecard) == nil && format == processor.FormatJSON {
if scorecard.Scorecard.Version != "" || scorecard.Scorecard.Commit != "" {
return processor.DocumentScorecard
}
}
return processor.DocumentUnknown
}
50 changes: 50 additions & 0 deletions pkg/handler/processor/guesser/type_scorecard_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// Copyright 2022 The GUAC Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package guesser

import (
"testing"

testdata "github.com/guacsec/guac/internal/testing/processor"
"github.com/guacsec/guac/pkg/handler/processor"
)

func Test_scorecardTypeGuesser_GuessDocumentType(t *testing.T) {
testCases := []struct {
name string
blob []byte
expected processor.DocumentType
}{{
name: "invalid scorecard Document",
blob: []byte(`{
"abc": "def"
}`),
expected: processor.DocumentUnknown,
}, {
name: "valid scorecard Document",
blob: testdata.ScorecardExample,
expected: processor.DocumentScorecard,
}}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
guesser := &scorecardTypeGuesser{}
f := guesser.GuessDocumentType(tt.blob, processor.FormatJSON)
if f != tt.expected {
t.Errorf("got the wrong format, got %v, expected %v", f, tt.expected)
}
})
}
}
2 changes: 2 additions & 0 deletions pkg/handler/processor/process/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/guacsec/guac/pkg/handler/processor/dsse"
"github.com/guacsec/guac/pkg/handler/processor/guesser"
"github.com/guacsec/guac/pkg/handler/processor/ite6"
"github.com/guacsec/guac/pkg/handler/processor/scorecard"
"github.com/guacsec/guac/pkg/handler/processor/spdx"
)

Expand All @@ -36,6 +37,7 @@ func init() {
_ = RegisterDocumentProcessor(&ite6.ITE6Processor{}, processor.DocumentITE6SLSA)
_ = RegisterDocumentProcessor(&dsse.DSSEProcessor{}, processor.DocumentDSSE)
_ = RegisterDocumentProcessor(&spdx.SPDXProcessor{}, processor.DocumentSPDX)
_ = RegisterDocumentProcessor(&scorecard.ScorecardProcessor{}, processor.DocumentScorecard)
}

func RegisterDocumentProcessor(p processor.DocumentProcessor, d processor.DocumentType) error {
Expand Down
1 change: 1 addition & 0 deletions pkg/handler/processor/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const (
DocumentITE6Unknown DocumentType = "ITE6"
DocumentDSSE DocumentType = "DSSE"
DocumentSPDX DocumentType = "SPDX"
DocumentScorecard DocumentType = "SCORECARD"
DocumentUnknown DocumentType = "UNKNOWN"
)

Expand Down
66 changes: 66 additions & 0 deletions pkg/handler/processor/scorecard/scorecard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// Copyright 2022 The GUAC Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package scorecard

import (
"encoding/json"
"fmt"

"github.com/guacsec/guac/pkg/handler/processor"
sc "github.com/ossf/scorecard/v4/pkg"
)

// ScorecardProcessor processes Scorecard documents.
// Currently only supports JSON Scorecard documents
type ScorecardProcessor struct {
}

func (p *ScorecardProcessor) ValidateSchema(d *processor.Document) error {
if d.Type != processor.DocumentScorecard {
return fmt.Errorf("expected document type: %v, actual document type: %v", processor.DocumentScorecard, d.Type)
}

switch d.Format {
case processor.FormatJSON:
var scorecard sc.JSONScorecardResultV2
if err := json.Unmarshal(d.Blob, &scorecard); err != nil {
return err
}
if scorecard.Repo.Name == "" ||
scorecard.Repo.Commit == "" ||
len(scorecard.Checks) == 0 {
return fmt.Errorf("missing required scorecard fields")
}

return nil
}

return fmt.Errorf("unable to support parsing of Scorecard document format: %v", d.Format)
}

// Unpack takes in the document and tries to unpack it
// if there is a valid decomposition of sub-documents.
//
// Returns empty list and nil error if nothing to unpack
// Returns unpacked list and nil error if successfully unpacked
func (p *ScorecardProcessor) Unpack(d *processor.Document) ([]*processor.Document, error) {
if d.Type != processor.DocumentScorecard {
return nil, fmt.Errorf("expected document type: %v, actual document type: %v", processor.DocumentScorecard, d.Type)
}

// Scorecard doesn't unpack into additional documents at the moment.
return []*processor.Document{}, nil
}
Loading

0 comments on commit 599889c

Please sign in to comment.