Skip to content

Commit

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

Signed-off-by: neilnaveen <[email protected]>
  • Loading branch information
neilnaveen committed May 30, 2024
1 parent 7ee25d1 commit af3fa37
Show file tree
Hide file tree
Showing 16 changed files with 169 additions and 98 deletions.
10 changes: 5 additions & 5 deletions cmd/guaccollect/cmd/osv.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ func validateOSVFlags(
blobAddr,
interval string,
poll bool,
pubToQueue bool) (osvOptions, error) {

pubToQueue bool,
) (osvOptions, error) {
var opts osvOptions

opts.graphqlEndpoint = graphqlEndpoint
Expand Down Expand Up @@ -152,8 +152,8 @@ func getPackageQuery(client graphql.Client) (func() certifier.QueryComponents, e
}

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 @@ -210,7 +210,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, time.Minute*time.Duration(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
2 changes: 1 addition & 1 deletion cmd/guacone/cmd/osv.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,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
23 changes: 12 additions & 11 deletions cmd/guacone/cmd/scorecard.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,15 @@ 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"
"github.com/guacsec/guac/pkg/collectsub/client"
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 @@ -48,6 +46,7 @@ type scorecardOptions struct {
poll bool
interval time.Duration
csubClientOptions client.CsubClientOptions
useScorecardAPI bool
}

var scorecardCmd = &cobra.Command{
Expand All @@ -62,6 +61,7 @@ var scorecardCmd = &cobra.Command{
viper.GetBool("csub-tls"),
viper.GetBool("csub-tls-skip-verify"),
viper.GetBool("poll"),
viper.GetBool("use-scorecard-api"),
)
if err != nil {
fmt.Printf("unable to validate flags: %v\n", err)
Expand Down Expand Up @@ -95,7 +95,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 @@ -105,7 +104,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)

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

if err != nil {
return fmt.Errorf("unable to ingest document: %v", err)
}
Expand All @@ -149,7 +146,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 @@ -180,7 +177,8 @@ func validateScorecardFlags(
interval string,
csubTls,
csubTlsSkipVerify,
poll bool,
poll,
useScorecardAPI bool,
) (scorecardOptions, error) {
var opts scorecardOptions
opts.graphqlEndpoint = graphqlEndpoint
Expand All @@ -193,6 +191,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 @@ -204,4 +203,6 @@ func validateScorecardFlags(

func init() {
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
9 changes: 3 additions & 6 deletions pkg/certifier/osv/osv.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,14 @@ 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/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 @@ -60,7 +58,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 Expand Up @@ -112,7 +110,6 @@ func generateDocument(packNodes []*root_package.PackageNode, vulns []osv_scanner
}

func createAttestation(packageNode *root_package.PackageNode, vulns []osv_scanner.MinimalVulnerability, currentTime time.Time) *attestation_vuln.VulnerabilityStatement {

attestation := &attestation_vuln.VulnerabilityStatement{
StatementHeader: intoto.StatementHeader{
Type: intoto.StatementInTotoV01,
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 af3fa37

Please sign in to comment.