Skip to content

Commit

Permalink
Including the Scorecard API
Browse files Browse the repository at this point in the history
- Fixes #1892
- Updated tests
- Added a README for certifier/scorecard

Signed-off-by: neilnaveen <[email protected]>
  • Loading branch information
neilnaveen committed Jun 17, 2024
1 parent ff4c8af commit 2db7b2b
Show file tree
Hide file tree
Showing 16 changed files with 183 additions and 99 deletions.
12 changes: 10 additions & 2 deletions cmd/guaccollect/cmd/osv.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,14 @@ func validateOSVFlags(
interval string,
poll bool,
pubToQueue bool,
<<<<<<< HEAD

Check failure on line 132 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / Lint

syntax error: unexpected <<, expected )

Check failure on line 132 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / Lint

expected ')', found '<<' (typecheck)

Check failure on line 132 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / CI for integration tests

syntax error: unexpected <<, expected )
daysSince int,
certifierLatencyStr string,
batchSize int) (osvOptions, error) {

=======

Check failure on line 137 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / Lint

syntax error: unexpected ==, expected }

Check failure on line 137 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / CI for integration tests

syntax error: unexpected ==, expected }
) (osvOptions, error) {
>>>>>>> 080dbd51 (Including the Scorecard API)

Check failure on line 139 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / Lint

invalid digit '8' in octal literal

Check failure on line 139 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / CI for integration tests

invalid digit '8' in octal literal
var opts osvOptions

opts.graphqlEndpoint = graphqlEndpoint

Check failure on line 142 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / Lint

syntax error: non-declaration statement outside function body

Check failure on line 142 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / CI for integration tests

syntax error: non-declaration statement outside function body
Expand Down Expand Up @@ -178,8 +182,8 @@ func getPackageQuery(client graphql.Client, daysSinceLastScan int, batchSize int
}

func initializeNATsandCertifier(ctx context.Context, blobAddr, pubsubAddr string,
poll, publishToQueue bool, interval time.Duration, query certifier.QueryComponents) {

poll, publishToQueue bool, interval time.Duration, query certifier.QueryComponents,
) {
logger := logging.FromContext(ctx)

blobStore, err := blob.NewBlobStore(ctx, blobAddr)
Expand Down Expand Up @@ -236,7 +240,11 @@ func initializeNATsandCertifier(ctx context.Context, blobAddr, pubsubAddr string
wg.Add(1)
go func() {
defer wg.Done()
<<<<<<< HEAD

Check failure on line 243 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / Lint

syntax error: unexpected <<, expected }

Check failure on line 243 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / CI for integration tests

syntax error: unexpected <<, expected }
if err := certify.Certify(ctx, query, emit, errHandler, poll, interval); err != nil {
=======

Check failure on line 245 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / Lint

syntax error: unexpected ==, expected }

Check failure on line 245 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / CI for integration tests

syntax error: unexpected ==, expected }
if err := certify.Certify(ctx, query, emit, errHandler, poll, time.Minute*time.Duration(interval), false); err != nil {
>>>>>>> 080dbd51 (Including the Scorecard API)

Check failure on line 247 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / Lint

syntax error: unexpected >>, expected }

Check failure on line 247 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / Lint

invalid digit '8' in octal literal

Check failure on line 247 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / CI for integration tests

syntax error: unexpected >>, expected }

Check failure on line 247 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / CI for integration tests

invalid digit '8' in octal literal
logger.Fatal(err)
}
done <- true

Check failure on line 250 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / Lint

syntax error: non-declaration statement outside function body (typecheck)

Check failure on line 250 in cmd/guaccollect/cmd/osv.go

View workflow job for this annotation

GitHub Actions / CI for integration tests

syntax error: non-declaration statement outside function body
Expand Down
8 changes: 5 additions & 3 deletions cmd/guacone/cmd/osv.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ var osvCmd = &cobra.Command{
wg.Add(1)
go func() {
defer wg.Done()
if err := certify.Certify(ctx, packageQuery, emit, errHandler, opts.poll, opts.interval); err != nil {
if err := certify.Certify(ctx, packageQuery, emit, errHandler, opts.poll, opts.interval, false); err != nil {
logger.Errorf("Unhandled error in the certifier: %s", err)
}
done <- true
Expand Down Expand Up @@ -258,8 +258,10 @@ func validateOSVFlags(
}

func init() {
set, err := cli.BuildFlags([]string{"certifier-latency",
"certifier-batch-size"})
set, err := cli.BuildFlags([]string{
"certifier-latency",
"certifier-batch-size",
})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to setup flag: %v", err)
os.Exit(1)
Expand Down
29 changes: 16 additions & 13 deletions cmd/guacone/cmd/scorecard.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,14 @@ import (
"time"

"github.com/Khan/genqlient/graphql"
"github.com/guacsec/guac/pkg/certifier"
"github.com/guacsec/guac/pkg/certifier/certify"
sc "github.com/guacsec/guac/pkg/certifier/components/source"
"github.com/guacsec/guac/pkg/certifier/scorecard"
"github.com/guacsec/guac/pkg/cli"
csub_client "github.com/guacsec/guac/pkg/collectsub/client"
"github.com/guacsec/guac/pkg/ingestor"

"github.com/guacsec/guac/pkg/certifier"
"github.com/guacsec/guac/pkg/certifier/scorecard"

"github.com/guacsec/guac/pkg/certifier/certify"
"github.com/guacsec/guac/pkg/handler/processor"
"github.com/guacsec/guac/pkg/ingestor"
"github.com/guacsec/guac/pkg/logging"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand All @@ -51,7 +49,8 @@ type scorecardOptions struct {
// sets artificial latency on the certifier (default to nil)
addedLatency *time.Duration
// sets the batch size for pagination query for the certifier
batchSize int
batchSize int
useScorecardAPI bool
}

var scorecardCmd = &cobra.Command{
Expand All @@ -69,6 +68,7 @@ var scorecardCmd = &cobra.Command{
viper.GetBool("add-vuln-on-ingest"),
viper.GetString("certifier-latency"),
viper.GetInt("certifier-batch-size"),
viper.GetBool("use-scorecard-api"),
)
if err != nil {
fmt.Printf("unable to validate flags: %v\n", err)
Expand Down Expand Up @@ -102,7 +102,6 @@ var scorecardCmd = &cobra.Command{

// running and getting the scorecard checks
scorecardCertifier, err := scorecard.NewScorecardCertifier(scorecardRunner)

if err != nil {
fmt.Printf("unable to create scorecard certifier: %v\n", err)
_ = cmd.Help()
Expand All @@ -112,7 +111,6 @@ var scorecardCmd = &cobra.Command{
// scorecard certifier is the certifier that gets the scorecard data graphQL
// setting "daysSinceLastScan" to 0 does not check the timestamp on the scorecard that exist
query, err := sc.NewCertifier(gqlclient, 0, opts.batchSize, opts.addedLatency)

if err != nil {
fmt.Printf("unable to create scorecard certifier: %v\n", err)
_ = cmd.Help()
Expand All @@ -132,7 +130,6 @@ var scorecardCmd = &cobra.Command{
emit := func(d *processor.Document) error {
totalNum += 1
err := ingestor.Ingest(ctx, d, opts.graphqlEndpoint, transport, csubClient, opts.queryVulnOnIngestion)

if err != nil {
return fmt.Errorf("unable to ingest document: %v", err)
}
Expand All @@ -156,7 +153,7 @@ var scorecardCmd = &cobra.Command{
wg.Add(1)
go func() {
defer wg.Done()
if err := certify.Certify(ctx, query, emit, errHandler, opts.poll, opts.interval); err != nil {
if err := certify.Certify(ctx, query, emit, errHandler, opts.poll, opts.interval, opts.useScorecardAPI); err != nil {
logger.Errorf("Unhandled error in the certifier: %s", err)
}
done <- true
Expand Down Expand Up @@ -191,6 +188,7 @@ func validateScorecardFlags(
queryVulnIngestion bool,
certifierLatencyStr string,
batchSize int,
useScorecardAPI bool,
) (scorecardOptions, error) {
var opts scorecardOptions
opts.graphqlEndpoint = graphqlEndpoint
Expand All @@ -215,6 +213,7 @@ func validateScorecardFlags(
opts.csubClientOptions = csubOpts

opts.poll = poll
opts.useScorecardAPI = useScorecardAPI
i, err := time.ParseDuration(interval)
if err != nil {
return opts, err
Expand All @@ -225,8 +224,10 @@ func validateScorecardFlags(
}

func init() {
set, err := cli.BuildFlags([]string{"certifier-latency",
"certifier-batch-size"})
set, err := cli.BuildFlags([]string{
"certifier-latency",
"certifier-batch-size",
})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to setup flag: %v", err)
os.Exit(1)
Expand All @@ -237,4 +238,6 @@ func init() {
os.Exit(1)
}
certifierCmd.AddCommand(scorecardCmd)
scorecardCmd.Flags().Bool("use-scorecard-api", false, "use the scorecard API")
viper.BindPFlag("use-scorecard-api", scorecardCmd.Flags().Lookup("use-scorecard-api"))
}
3 changes: 1 addition & 2 deletions internal/testing/cmd/pubsub_test/cmd/osv.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ var osvCmd = &cobra.Command{
viper.GetBool("poll"),
viper.GetInt("interval"),
)

if err != nil {
fmt.Printf("unable to validate flags: %v\n", err)
_ = cmd.Help()
Expand Down Expand Up @@ -158,7 +157,7 @@ func initializeNATsandCertifier(ctx context.Context, opts options) {
wg.Add(1)
go func() {
defer wg.Done()
if err := certify.Certify(ctx, packageQueryFunc(), emit, errHandler, opts.poll, time.Minute*time.Duration(opts.interval)); err != nil {
if err := certify.Certify(ctx, packageQueryFunc(), emit, errHandler, opts.poll, time.Minute*time.Duration(opts.interval), false); err != nil {
logger.Fatal(err)
}
done <- true
Expand Down
12 changes: 6 additions & 6 deletions internal/testing/mocks/scorecard.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/certifier/certifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type Certifier interface {
// push to the docChannel to be ingested.
// Note: there is an implicit contract with "QueryComponents" where the compChan type must be the same as
// the one used by "components"
CertifyComponent(ctx context.Context, components interface{}, docChannel chan<- *processor.Document) error
CertifyComponent(ctx context.Context, components interface{}, docChannel chan<- *processor.Document, useScorecardAPI bool) error
}

type QueryComponents interface {
Expand Down
11 changes: 5 additions & 6 deletions pkg/certifier/certify/certify.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ func RegisterCertifier(c func() certifier.Certifier, certifierType certifier.Cer

// Certify queries the graph DB to get the components to scan. Utilizing the registered certifiers,
// it generates new nodes and attestations.
func Certify(ctx context.Context, query certifier.QueryComponents, emitter certifier.Emitter, handleErr certifier.ErrHandler, poll bool, interval time.Duration) error {

func Certify(ctx context.Context, query certifier.QueryComponents, emitter certifier.Emitter, handleErr certifier.ErrHandler, poll bool, interval time.Duration, useScorecardAPI bool) error {
runCertifier := func() error {
// compChan to collect query components
compChan := make(chan interface{}, BufferChannelSize)
Expand All @@ -70,7 +69,7 @@ func Certify(ctx context.Context, query certifier.QueryComponents, emitter certi
for !componentsCaptured {
select {
case d := <-compChan:
if err := generateDocuments(ctx, d, emitter, handleErr); err != nil {
if err := generateDocuments(ctx, d, emitter, handleErr, useScorecardAPI); err != nil {
return fmt.Errorf("generate certifier documents error: %w", err)
}
case err := <-errChan:
Expand All @@ -84,7 +83,7 @@ func Certify(ctx context.Context, query certifier.QueryComponents, emitter certi
}
for len(compChan) > 0 {
d := <-compChan
if err := generateDocuments(ctx, d, emitter, handleErr); err != nil {
if err := generateDocuments(ctx, d, emitter, handleErr, useScorecardAPI); err != nil {
logger.Errorf("generate certifier documents error: %v", err)
}
}
Expand Down Expand Up @@ -118,7 +117,7 @@ func Certify(ctx context.Context, query certifier.QueryComponents, emitter certi

// generateDocuments runs CertifyVulns as a goroutine to scan and generates attestations that
// are emitted as processor documents to be ingested
func generateDocuments(ctx context.Context, collectedComponent interface{}, emitter certifier.Emitter, handleErr certifier.ErrHandler) error {
func generateDocuments(ctx context.Context, collectedComponent interface{}, emitter certifier.Emitter, handleErr certifier.ErrHandler, useScorecardAPI bool) error {
// docChan to collect artifacts
docChan := make(chan *processor.Document, BufferChannelSize)
// errChan to receive error from collectors
Expand All @@ -129,7 +128,7 @@ func generateDocuments(ctx context.Context, collectedComponent interface{}, emit
for _, certifier := range documentCertifier {
c := certifier()
go func() {
errChan <- c.CertifyComponent(ctx, collectedComponent, docChan)
errChan <- c.CertifyComponent(ctx, collectedComponent, docChan, useScorecardAPI)
}()
}

Expand Down
9 changes: 3 additions & 6 deletions pkg/certifier/certify/certify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ import (
"github.com/guacsec/guac/pkg/logging"
)

type mockQuery struct {
}
type mockQuery struct{}

// NewMockQuery initializes the mockQuery to query for tests
func newMockQuery() certifier.QueryComponents {
Expand All @@ -44,8 +43,7 @@ func (q *mockQuery) GetComponents(ctx context.Context, compChan chan<- interface
return nil
}

type mockUnknownQuery struct {
}
type mockUnknownQuery struct{}

// NewMockQuery initializes the mockQuery to query for tests
func newMockUnknownQuery() certifier.QueryComponents {
Expand Down Expand Up @@ -170,7 +168,6 @@ func TestCertify(t *testing.T) {
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

ctx := logging.WithLogger(context.Background())
if tt.poll {
var cancel context.CancelFunc
Expand All @@ -184,7 +181,7 @@ func TestCertify(t *testing.T) {
return nil
}

err := Certify(ctx, tt.query, emit, errHandler, tt.poll, time.Second*1)
err := Certify(ctx, tt.query, emit, errHandler, tt.poll, time.Second*1, false)
if (err != nil) != tt.wantErr {
t.Errorf("Certify() error = %v, wantErr %v", err, tt.wantErr)
}
Expand Down
8 changes: 3 additions & 5 deletions pkg/certifier/osv/osv.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,15 @@ import (
"net/http"
"time"

jsoniter "github.com/json-iterator/go"

osv_scanner "github.com/google/osv-scanner/pkg/osv"
intoto "github.com/in-toto/in-toto-golang/in_toto"

"github.com/guacsec/guac/pkg/certifier"
attestation_vuln "github.com/guacsec/guac/pkg/certifier/attestation"
"github.com/guacsec/guac/pkg/certifier/components/root_package"
"github.com/guacsec/guac/pkg/events"
"github.com/guacsec/guac/pkg/handler/processor"
"github.com/guacsec/guac/pkg/version"
intoto "github.com/in-toto/in-toto-golang/in_toto"
jsoniter "github.com/json-iterator/go"
)

var json = jsoniter.ConfigCompatibleWithStandardLibrary
Expand Down Expand Up @@ -62,7 +60,7 @@ func NewOSVCertificationParser() certifier.Certifier {

// CertifyComponent takes in the root component from the gauc database and does a recursive scan
// to generate vulnerability attestations
func (o *osvCertifier) CertifyComponent(ctx context.Context, rootComponent interface{}, docChannel chan<- *processor.Document) error {
func (o *osvCertifier) CertifyComponent(ctx context.Context, rootComponent interface{}, docChannel chan<- *processor.Document, _ bool) error {
packageNodes, ok := rootComponent.([]*root_package.PackageNode)
if !ok {
return ErrOSVComponenetTypeMismatch
Expand Down
9 changes: 4 additions & 5 deletions pkg/certifier/osv/osv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@ import (
"time"

osv_scanner "github.com/google/osv-scanner/pkg/osv"
attestation_vuln "github.com/guacsec/guac/pkg/certifier/attestation"
"github.com/guacsec/guac/pkg/certifier/components/root_package"
intoto "github.com/in-toto/in-toto-golang/in_toto"

"github.com/guacsec/guac/internal/testing/dochelper"
"github.com/guacsec/guac/internal/testing/testdata"
attestation_vuln "github.com/guacsec/guac/pkg/certifier/attestation"
"github.com/guacsec/guac/pkg/certifier/components/root_package"
"github.com/guacsec/guac/pkg/handler/processor"
"github.com/guacsec/guac/pkg/logging"
intoto "github.com/in-toto/in-toto-golang/in_toto"
)

func TestOSVCertifier_CertifyVulns(t *testing.T) {
Expand Down Expand Up @@ -150,7 +149,7 @@ func TestOSVCertifier_CertifyVulns(t *testing.T) {
defer close(docChan)
defer close(errChan)
go func() {
errChan <- o.CertifyComponent(ctx, tt.rootComponent, docChan)
errChan <- o.CertifyComponent(ctx, tt.rootComponent, docChan, false)
}()
numCollectors := 1
certifiersDone := 0
Expand Down
29 changes: 29 additions & 0 deletions pkg/certifier/scorecard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Scorecard Certifier

The Scorecard Certifier is a component that generates scorecard attestations for repositories. It uses the [OpenSSF Scorecard](https://github.com/ossf/scorecard) to evaluate the security posture of a repository.

## How It Works

### Initialization

The `NewScorecardCertifier` function initializes the scorecard certifier. It checks if the `GITHUB_AUTH_TOKEN` is set in the environment. If not, it returns an error. The token is used to access the GitHub API.

### Certifying Components

The `CertifyComponent` function takes a `source.SourceNode` as input and generates a scorecard attestation. It uses the `GetScore` function to retrieve the scorecard data for the repository.

### Using the Scorecard Library and GitHub Auth Token

The `GetScore` function first checks if the `useScorecardAPI` flag is set to `true`. If it is, it calls the Scorecard API to retrieve the scorecard data. If the API call fails, it uses the Scorecard library and the GitHub auth token to retrieve the scorecard data.

### Using the Scorecard API

The Scorecard API is a public API that provides access to scorecard data. It can be used to retrieve scorecard data for any repository, regardless of whether the user has access to the GitHub repository. However, the API might fail if the repository does not exist in its database.

### Differences

The main difference between using the GitHub auth token/Scorecard library and the Scorecard API is that the GitHub auth token/Scorecard library requires access to the GitHub repository, while the Scorecard API does not.

The Scorecard API is also more efficient than using the GitHub auth token/Scorecard library, as it does not need to download the entire repository.

If the `useScorecardAPI` flag is not set, or the Scorecard API call fails, the certifier will default to using the GitHub auth token/Scorecard library.
Loading

0 comments on commit 2db7b2b

Please sign in to comment.