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

Including the Scorecard API #1938

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 3 additions & 3 deletions cmd/guaccollect/cmd/osv.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,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 +236,7 @@ func initializeNATsandCertifier(ctx context.Context, blobAddr, pubsubAddr string
wg.Add(1)
go func() {
defer wg.Done()
if err := certify.Certify(ctx, query, emit, errHandler, poll, interval); err != nil {
if err := certify.Certify(ctx, query, emit, errHandler, poll, time.Minute*time.Duration(interval), false); err != nil {
logger.Fatal(err)
}
done <- true
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
24 changes: 11 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,7 @@ func validateScorecardFlags(
}

func init() {
set, err := cli.BuildFlags([]string{"certifier-latency",
"certifier-batch-size"})
set, err := cli.BuildFlags([]string{"certifier-latency", "certifier-batch-size", "use-scorecard-api"})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to setup flag: %v", err)
os.Exit(1)
Expand Down
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there shouldn't be a scorecard specific flag for the ceritfier interface, things that are specific to the certifier should be part of the initialization of the certifier. Maybe this is something that should be part of RegisterCertifier.

}

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice addition!


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
Loading