mirror of
https://github.com/ossf/scorecard.git
synced 2024-09-11 00:45:36 +03:00
✨ Miscellaneous refactors to ease downstream consumption (#1645)
* checker: Add `NewLogger` constructor for `DetailLogger` impl * checker: Add `NewRunner` constructor for `Runner` * cmd: Update to use refactored packages * cmd: Move command flags and validation into an `options` package * cmd: Move client accessors to `githubrepo` package * cmd: Move policy and enabled checks to `policy` package * cmd: Move results formatting to `format` package * checker: Prefer `Set` prefixes for setters * checker: Use `DetailLogger` return value for `NewLogger()` * checker: Add `GetClients` accessor * Move `FormatResults` to `pkg/` * checks: Add getter for all checks Signed-off-by: Stephen Augustus <foo@auggie.dev>
This commit is contained in:
parent
76105194da
commit
7956ff4fe7
@ -36,6 +36,30 @@ type Runner struct {
|
|||||||
CheckRequest CheckRequest
|
CheckRequest CheckRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewRunner creates a new instance of `Runner`.
|
||||||
|
func NewRunner(checkName, repo string, checkReq *CheckRequest) *Runner {
|
||||||
|
return &Runner{
|
||||||
|
CheckName: checkName,
|
||||||
|
Repo: repo,
|
||||||
|
CheckRequest: *checkReq,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCheckName sets the check name.
|
||||||
|
func (r *Runner) SetCheckName(check string) {
|
||||||
|
r.CheckName = check
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRepo sets the repository.
|
||||||
|
func (r *Runner) SetRepo(repo string) {
|
||||||
|
r.Repo = repo
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCheckRequest sets the check request.
|
||||||
|
func (r *Runner) SetCheckRequest(checkReq *CheckRequest) {
|
||||||
|
r.CheckRequest = *checkReq
|
||||||
|
}
|
||||||
|
|
||||||
// CheckFn defined for convenience.
|
// CheckFn defined for convenience.
|
||||||
type CheckFn func(*CheckRequest) CheckResult
|
type CheckFn func(*CheckRequest) CheckResult
|
||||||
|
|
||||||
@ -79,12 +103,11 @@ func (r *Runner) Run(ctx context.Context, c Check) CheckResult {
|
|||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
var res CheckResult
|
var res CheckResult
|
||||||
var l logger
|
l := NewLogger()
|
||||||
for retriesRemaining := checkRetries; retriesRemaining > 0; retriesRemaining-- {
|
for retriesRemaining := checkRetries; retriesRemaining > 0; retriesRemaining-- {
|
||||||
checkRequest := r.CheckRequest
|
checkRequest := r.CheckRequest
|
||||||
checkRequest.Ctx = ctx
|
checkRequest.Ctx = ctx
|
||||||
l = logger{}
|
checkRequest.Dlogger = l
|
||||||
checkRequest.Dlogger = &l
|
|
||||||
res = c.Fn(&checkRequest)
|
res = c.Fn(&checkRequest)
|
||||||
if res.Error2 != nil && errors.Is(res.Error2, sce.ErrRepoUnreachable) {
|
if res.Error2 != nil && errors.Is(res.Error2, sce.ErrRepoUnreachable) {
|
||||||
checkRequest.Dlogger.Warn(&LogMessage{
|
checkRequest.Dlogger.Warn(&LogMessage{
|
||||||
|
66
checker/client.go
Normal file
66
checker/client.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2022 Security Scorecard 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 checker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ossf/scorecard/v4/clients"
|
||||||
|
ghrepo "github.com/ossf/scorecard/v4/clients/githubrepo"
|
||||||
|
"github.com/ossf/scorecard/v4/clients/localdir"
|
||||||
|
"github.com/ossf/scorecard/v4/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetClients returns a list of clients for running scorecard checks.
|
||||||
|
// TODO(repo): Pass a `http.RoundTripper` here.
|
||||||
|
func GetClients(ctx context.Context, repoURI, localURI string, logger *log.Logger) (
|
||||||
|
clients.Repo, // repo
|
||||||
|
clients.RepoClient, // repoClient
|
||||||
|
clients.RepoClient, // ossFuzzClient
|
||||||
|
clients.CIIBestPracticesClient, // ciiClient
|
||||||
|
clients.VulnerabilitiesClient, // vulnClient
|
||||||
|
error) {
|
||||||
|
var githubRepo clients.Repo
|
||||||
|
if localURI != "" {
|
||||||
|
localRepo, errLocal := localdir.MakeLocalDirRepo(localURI)
|
||||||
|
return localRepo, /*repo*/
|
||||||
|
localdir.CreateLocalDirClient(ctx, logger), /*repoClient*/
|
||||||
|
nil, /*ossFuzzClient*/
|
||||||
|
nil, /*ciiClient*/
|
||||||
|
nil, /*vulnClient*/
|
||||||
|
fmt.Errorf("getting local directory client: %w", errLocal)
|
||||||
|
}
|
||||||
|
|
||||||
|
githubRepo, errGitHub := ghrepo.MakeGithubRepo(repoURI)
|
||||||
|
if errGitHub != nil {
|
||||||
|
return githubRepo,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
fmt.Errorf("getting local directory client: %w", errGitHub)
|
||||||
|
}
|
||||||
|
|
||||||
|
ossFuzzRepoClient, errOssFuzz := ghrepo.CreateOssFuzzRepoClient(ctx, logger)
|
||||||
|
|
||||||
|
// TODO(repo): Should we be handling the OSS-Fuzz client error like this?
|
||||||
|
return githubRepo, /*repo*/
|
||||||
|
ghrepo.CreateGithubRepoClient(ctx, logger), /*repoClient*/
|
||||||
|
ossFuzzRepoClient, /*ossFuzzClient*/
|
||||||
|
clients.DefaultCIIBestPracticesClient(), /*ciiClient*/
|
||||||
|
clients.DefaultVulnerabilitiesClient(), /*vulnClient*/
|
||||||
|
fmt.Errorf("getting OSS-Fuzz repo client: %w", errOssFuzz)
|
||||||
|
}
|
@ -14,10 +14,17 @@
|
|||||||
|
|
||||||
package checker
|
package checker
|
||||||
|
|
||||||
|
// Logger is an implementation of the `DetailLogger` interface.
|
||||||
type logger struct {
|
type logger struct {
|
||||||
logs []CheckDetail
|
logs []CheckDetail
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewLogger creates a new instance of `DetailLogger`.
|
||||||
|
func NewLogger() DetailLogger {
|
||||||
|
return &logger{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info emits info level logs.
|
||||||
func (l *logger) Info(msg *LogMessage) {
|
func (l *logger) Info(msg *LogMessage) {
|
||||||
cd := CheckDetail{
|
cd := CheckDetail{
|
||||||
Type: DetailInfo,
|
Type: DetailInfo,
|
||||||
@ -26,6 +33,7 @@ func (l *logger) Info(msg *LogMessage) {
|
|||||||
l.logs = append(l.logs, cd)
|
l.logs = append(l.logs, cd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Warn emits warn level logs.
|
||||||
func (l *logger) Warn(msg *LogMessage) {
|
func (l *logger) Warn(msg *LogMessage) {
|
||||||
cd := CheckDetail{
|
cd := CheckDetail{
|
||||||
Type: DetailWarn,
|
Type: DetailWarn,
|
||||||
@ -34,6 +42,7 @@ func (l *logger) Warn(msg *LogMessage) {
|
|||||||
l.logs = append(l.logs, cd)
|
l.logs = append(l.logs, cd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug emits debug level logs.
|
||||||
func (l *logger) Debug(msg *LogMessage) {
|
func (l *logger) Debug(msg *LogMessage) {
|
||||||
cd := CheckDetail{
|
cd := CheckDetail{
|
||||||
Type: DetailDebug,
|
Type: DetailDebug,
|
||||||
@ -42,8 +51,14 @@ func (l *logger) Debug(msg *LogMessage) {
|
|||||||
l.logs = append(l.logs, cd)
|
l.logs = append(l.logs, cd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flush returns existing logs and resets the logger instance.
|
||||||
func (l *logger) Flush() []CheckDetail {
|
func (l *logger) Flush() []CheckDetail {
|
||||||
ret := l.logs
|
ret := l.Logs()
|
||||||
l.logs = nil
|
l.logs = nil
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logs returns existing logs.
|
||||||
|
func (l *logger) Logs() []CheckDetail {
|
||||||
|
return l.logs
|
||||||
|
}
|
||||||
|
@ -22,6 +22,14 @@ import (
|
|||||||
// AllChecks is the list of all security checks that will be run.
|
// AllChecks is the list of all security checks that will be run.
|
||||||
var AllChecks = checker.CheckNameToFnMap{}
|
var AllChecks = checker.CheckNameToFnMap{}
|
||||||
|
|
||||||
|
// GetAll returns the full list of checks, given any environment variable
|
||||||
|
// constraints.
|
||||||
|
// TODO(checks): Is this actually necessary given `AllChecks` exists?
|
||||||
|
func GetAll() checker.CheckNameToFnMap {
|
||||||
|
possibleChecks := AllChecks
|
||||||
|
return possibleChecks
|
||||||
|
}
|
||||||
|
|
||||||
func registerCheck(name string, fn checker.CheckFn, supportedRequestTypes []checker.RequestType) error {
|
func registerCheck(name string, fn checker.CheckFn, supportedRequestTypes []checker.RequestType) error {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return errInternalNameCannotBeEmpty
|
return errInternalNameCannotBeEmpty
|
||||||
|
@ -228,7 +228,7 @@ func CreateGithubRepoClient(ctx context.Context, logger *log.Logger) clients.Rep
|
|||||||
func CreateOssFuzzRepoClient(ctx context.Context, logger *log.Logger) (clients.RepoClient, error) {
|
func CreateOssFuzzRepoClient(ctx context.Context, logger *log.Logger) (clients.RepoClient, error) {
|
||||||
ossFuzzRepo, err := MakeGithubRepo("google/oss-fuzz")
|
ossFuzzRepo, err := MakeGithubRepo("google/oss-fuzz")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error during githubrepo.MakeGithubRepo: %w", err)
|
return nil, fmt.Errorf("error during MakeGithubRepo: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ossFuzzRepoClient := CreateGithubRepoClient(ctx, logger)
|
ossFuzzRepoClient := CreateGithubRepoClient(ctx, logger)
|
||||||
|
31
cmd/flags.go
31
cmd/flags.go
@ -1,31 +0,0 @@
|
|||||||
// Copyright 2020 Security Scorecard 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 cmd implements Scorecard commandline.
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
var (
|
|
||||||
flagRepo string
|
|
||||||
flagLocal string
|
|
||||||
flagCommit string
|
|
||||||
flagChecksToRun []string
|
|
||||||
flagMetadata []string
|
|
||||||
flagLogLevel string
|
|
||||||
flagFormat string
|
|
||||||
flagNPM string
|
|
||||||
flagPyPI string
|
|
||||||
flagRubyGems string
|
|
||||||
flagShowDetails bool
|
|
||||||
flagPolicyFile string
|
|
||||||
)
|
|
346
cmd/root.go
346
cmd/root.go
@ -28,23 +28,14 @@ import (
|
|||||||
"github.com/ossf/scorecard/v4/checker"
|
"github.com/ossf/scorecard/v4/checker"
|
||||||
"github.com/ossf/scorecard/v4/checks"
|
"github.com/ossf/scorecard/v4/checks"
|
||||||
"github.com/ossf/scorecard/v4/clients"
|
"github.com/ossf/scorecard/v4/clients"
|
||||||
"github.com/ossf/scorecard/v4/clients/githubrepo"
|
|
||||||
"github.com/ossf/scorecard/v4/clients/localdir"
|
|
||||||
docs "github.com/ossf/scorecard/v4/docs/checks"
|
docs "github.com/ossf/scorecard/v4/docs/checks"
|
||||||
sce "github.com/ossf/scorecard/v4/errors"
|
|
||||||
sclog "github.com/ossf/scorecard/v4/log"
|
sclog "github.com/ossf/scorecard/v4/log"
|
||||||
|
"github.com/ossf/scorecard/v4/options"
|
||||||
"github.com/ossf/scorecard/v4/pkg"
|
"github.com/ossf/scorecard/v4/pkg"
|
||||||
spol "github.com/ossf/scorecard/v4/policy"
|
"github.com/ossf/scorecard/v4/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
formatJSON = "json"
|
|
||||||
formatSarif = "sarif"
|
|
||||||
formatDefault = "default"
|
|
||||||
formatRaw = "raw"
|
|
||||||
|
|
||||||
cliEnableSarif = "ENABLE_SARIF"
|
|
||||||
|
|
||||||
scorecardLong = "A program that shows security scorecard for an open source software."
|
scorecardLong = "A program that shows security scorecard for an open source software."
|
||||||
scorecardUse = `./scorecard [--repo=<repo_url>] [--local=folder] [--checks=check1,...]
|
scorecardUse = `./scorecard [--repo=<repo_url>] [--local=folder] [--checks=check1,...]
|
||||||
[--show-details] or ./scorecard --{npm,pypi,rubygems}=<package_name>
|
[--show-details] or ./scorecard --{npm,pypi,rubygems}=<package_name>
|
||||||
@ -59,42 +50,45 @@ var rootCmd = &cobra.Command{
|
|||||||
Run: scorecardCmd,
|
Run: scorecardCmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var opts = options.New()
|
||||||
|
|
||||||
//nolint:gochecknoinits
|
//nolint:gochecknoinits
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.Flags().StringVar(&flagRepo, "repo", "", "repository to check")
|
rootCmd.Flags().StringVar(&opts.Repo, "repo", "", "repository to check")
|
||||||
rootCmd.Flags().StringVar(&flagLocal, "local", "", "local folder to check")
|
rootCmd.Flags().StringVar(&opts.Local, "local", "", "local folder to check")
|
||||||
rootCmd.Flags().StringVar(&flagCommit, "commit", clients.HeadSHA, "commit to analyze")
|
rootCmd.Flags().StringVar(&opts.Commit, "commit", options.DefaultCommit, "commit to analyze")
|
||||||
rootCmd.Flags().StringVar(
|
rootCmd.Flags().StringVar(
|
||||||
&flagLogLevel,
|
&opts.LogLevel,
|
||||||
"verbosity",
|
"verbosity",
|
||||||
sclog.DefaultLevel.String(),
|
options.DefaultLogLevel,
|
||||||
"set the log level",
|
"set the log level",
|
||||||
)
|
)
|
||||||
rootCmd.Flags().StringVar(
|
rootCmd.Flags().StringVar(
|
||||||
&flagNPM, "npm", "",
|
&opts.NPM, "npm", "",
|
||||||
"npm package to check, given that the npm package has a GitHub repository")
|
"npm package to check, given that the npm package has a GitHub repository")
|
||||||
rootCmd.Flags().StringVar(
|
rootCmd.Flags().StringVar(
|
||||||
&flagPyPI, "pypi", "",
|
&opts.PyPI, "pypi", "",
|
||||||
"pypi package to check, given that the pypi package has a GitHub repository")
|
"pypi package to check, given that the pypi package has a GitHub repository")
|
||||||
rootCmd.Flags().StringVar(
|
rootCmd.Flags().StringVar(
|
||||||
&flagRubyGems, "rubygems", "",
|
&opts.RubyGems, "rubygems", "",
|
||||||
"rubygems package to check, given that the rubygems package has a GitHub repository")
|
"rubygems package to check, given that the rubygems package has a GitHub repository")
|
||||||
rootCmd.Flags().StringSliceVar(
|
rootCmd.Flags().StringSliceVar(
|
||||||
&flagMetadata, "metadata", []string{}, "metadata for the project. It can be multiple separated by commas")
|
&opts.Metadata, "metadata", []string{}, "metadata for the project. It can be multiple separated by commas")
|
||||||
rootCmd.Flags().BoolVar(&flagShowDetails, "show-details", false, "show extra details about each check")
|
rootCmd.Flags().BoolVar(&opts.ShowDetails, "show-details", false, "show extra details about each check")
|
||||||
checkNames := []string{}
|
checkNames := []string{}
|
||||||
for checkName := range getAllChecks() {
|
for checkName := range checks.GetAll() {
|
||||||
checkNames = append(checkNames, checkName)
|
checkNames = append(checkNames, checkName)
|
||||||
}
|
}
|
||||||
rootCmd.Flags().StringSliceVar(&flagChecksToRun, "checks", []string{},
|
rootCmd.Flags().StringSliceVar(&opts.ChecksToRun, "checks", []string{},
|
||||||
fmt.Sprintf("Checks to run. Possible values are: %s", strings.Join(checkNames, ",")))
|
fmt.Sprintf("Checks to run. Possible values are: %s", strings.Join(checkNames, ",")))
|
||||||
|
|
||||||
if isSarifEnabled() {
|
// TODO(cmd): Extract logic
|
||||||
rootCmd.Flags().StringVar(&flagPolicyFile, "policy", "", "policy to enforce")
|
if options.IsSarifEnabled() {
|
||||||
rootCmd.Flags().StringVar(&flagFormat, "format", formatDefault,
|
rootCmd.Flags().StringVar(&opts.PolicyFile, "policy", "", "policy to enforce")
|
||||||
|
rootCmd.Flags().StringVar(&opts.Format, "format", options.FormatDefault,
|
||||||
"output format allowed values are [default, sarif, json]")
|
"output format allowed values are [default, sarif, json]")
|
||||||
} else {
|
} else {
|
||||||
rootCmd.Flags().StringVar(&flagFormat, "format", formatDefault,
|
rootCmd.Flags().StringVar(&opts.Format, "format", options.FormatDefault,
|
||||||
"output format allowed values are [default, json]")
|
"output format allowed values are [default, json]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,28 +102,39 @@ func Execute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func scorecardCmd(cmd *cobra.Command, args []string) {
|
func scorecardCmd(cmd *cobra.Command, args []string) {
|
||||||
validateCmdFlags()
|
RunScorecard(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunScorecard runs scorecard checks given a set of arguments.
|
||||||
|
// TODO(cmd): Is `args` required?
|
||||||
|
func RunScorecard(args []string) {
|
||||||
|
// TODO(cmd): Catch validation errors
|
||||||
|
valErrs := opts.Validate()
|
||||||
|
if len(valErrs) != 0 {
|
||||||
|
log.Panicf(
|
||||||
|
"the following validation errors occurred: %+v",
|
||||||
|
valErrs,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Set `repo` from package managers.
|
// Set `repo` from package managers.
|
||||||
pkgResp, err := fetchGitRepositoryFromPackageManagers(flagNPM, flagPyPI, flagRubyGems)
|
pkgResp, err := fetchGitRepositoryFromPackageManagers(opts.NPM, opts.PyPI, opts.RubyGems)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
if pkgResp.exists {
|
if pkgResp.exists {
|
||||||
if err := cmd.Flags().Set("repo", pkgResp.associatedRepo); err != nil {
|
opts.Repo = pkgResp.associatedRepo
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
policy, err := readPolicy()
|
pol, err := policy.ParseFromFile(opts.PolicyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("readPolicy: %v", err)
|
log.Panicf("readPolicy: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
logger := sclog.NewLogger(sclog.ParseLevel(flagLogLevel))
|
logger := sclog.NewLogger(sclog.ParseLevel(opts.LogLevel))
|
||||||
repoURI, repoClient, ossFuzzRepoClient, ciiClient, vulnsClient, err := getRepoAccessors(
|
repoURI, repoClient, ossFuzzRepoClient, ciiClient, vulnsClient, err := checker.GetClients(
|
||||||
ctx, flagRepo, flagLocal, logger)
|
ctx, opts.Repo, opts.Local, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
@ -145,279 +150,58 @@ func scorecardCmd(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var requiredRequestTypes []checker.RequestType
|
var requiredRequestTypes []checker.RequestType
|
||||||
if flagLocal != "" {
|
if opts.Local != "" {
|
||||||
requiredRequestTypes = append(requiredRequestTypes, checker.FileBased)
|
requiredRequestTypes = append(requiredRequestTypes, checker.FileBased)
|
||||||
}
|
}
|
||||||
if !strings.EqualFold(flagCommit, clients.HeadSHA) {
|
if !strings.EqualFold(opts.Commit, clients.HeadSHA) {
|
||||||
requiredRequestTypes = append(requiredRequestTypes, checker.CommitBased)
|
requiredRequestTypes = append(requiredRequestTypes, checker.CommitBased)
|
||||||
}
|
}
|
||||||
enabledChecks, err := getEnabledChecks(policy, flagChecksToRun, requiredRequestTypes)
|
enabledChecks, err := policy.GetEnabled(pol, opts.ChecksToRun, requiredRequestTypes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if flagFormat == formatDefault {
|
if opts.Format == options.FormatDefault {
|
||||||
for checkName := range enabledChecks {
|
for checkName := range enabledChecks {
|
||||||
fmt.Fprintf(os.Stderr, "Starting [%s]\n", checkName)
|
fmt.Fprintf(os.Stderr, "Starting [%s]\n", checkName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repoResult, err := pkg.RunScorecards(ctx, repoURI, flagCommit, flagFormat == formatRaw, enabledChecks, repoClient,
|
repoResult, err := pkg.RunScorecards(
|
||||||
ossFuzzRepoClient, ciiClient, vulnsClient)
|
ctx,
|
||||||
|
repoURI,
|
||||||
|
opts.Commit,
|
||||||
|
opts.Format == options.FormatRaw,
|
||||||
|
enabledChecks,
|
||||||
|
repoClient,
|
||||||
|
ossFuzzRepoClient,
|
||||||
|
ciiClient,
|
||||||
|
vulnsClient,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
repoResult.Metadata = append(repoResult.Metadata, flagMetadata...)
|
repoResult.Metadata = append(repoResult.Metadata, opts.Metadata...)
|
||||||
|
|
||||||
// Sort them by name
|
// Sort them by name
|
||||||
sort.Slice(repoResult.Checks, func(i, j int) bool {
|
sort.Slice(repoResult.Checks, func(i, j int) bool {
|
||||||
return repoResult.Checks[i].Name < repoResult.Checks[j].Name
|
return repoResult.Checks[i].Name < repoResult.Checks[j].Name
|
||||||
})
|
})
|
||||||
|
|
||||||
if flagFormat == formatDefault {
|
if opts.Format == options.FormatDefault {
|
||||||
for checkName := range enabledChecks {
|
for checkName := range enabledChecks {
|
||||||
fmt.Fprintf(os.Stderr, "Finished [%s]\n", checkName)
|
fmt.Fprintf(os.Stderr, "Finished [%s]\n", checkName)
|
||||||
}
|
}
|
||||||
fmt.Println("\nRESULTS\n-------")
|
fmt.Println("\nRESULTS\n-------")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch flagFormat {
|
resultsErr := pkg.FormatResults(
|
||||||
case formatDefault:
|
opts,
|
||||||
err = repoResult.AsString(flagShowDetails, sclog.ParseLevel(flagLogLevel), checkDocs, os.Stdout)
|
&repoResult,
|
||||||
case formatSarif:
|
checkDocs,
|
||||||
// TODO: support config files and update checker.MaxResultScore.
|
pol,
|
||||||
err = repoResult.AsSARIF(flagShowDetails, sclog.ParseLevel(flagLogLevel), os.Stdout, checkDocs, policy)
|
)
|
||||||
case formatJSON:
|
if resultsErr != nil {
|
||||||
err = repoResult.AsJSON2(flagShowDetails, sclog.ParseLevel(flagLogLevel), checkDocs, os.Stdout)
|
|
||||||
case formatRaw:
|
|
||||||
err = repoResult.AsRawJSON(os.Stdout)
|
|
||||||
default:
|
|
||||||
err = sce.WithMessage(sce.ErrScorecardInternal,
|
|
||||||
fmt.Sprintf("invalid format flag: %v. Expected [default, json]", flagFormat))
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Failed to output results: %v", err)
|
log.Panicf("Failed to output results: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateCmdFlags() {
|
|
||||||
// Validate exactly one of `--repo`, `--npm`, `--pypi`, `--rubygems`, `--local` is enabled.
|
|
||||||
if boolSum(flagRepo != "",
|
|
||||||
flagNPM != "",
|
|
||||||
flagPyPI != "",
|
|
||||||
flagRubyGems != "",
|
|
||||||
flagLocal != "") != 1 {
|
|
||||||
log.Panic("Exactly one of `--repo`, `--npm`, `--pypi`, `--rubygems` or `--local` must be set")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate SARIF features are flag-guarded.
|
|
||||||
if !isSarifEnabled() {
|
|
||||||
if flagFormat == formatSarif {
|
|
||||||
log.Panic("sarif format not supported yet")
|
|
||||||
}
|
|
||||||
if flagPolicyFile != "" {
|
|
||||||
log.Panic("policy file not supported yet")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate V6 features are flag-guarded.
|
|
||||||
if !isV6Enabled() {
|
|
||||||
if flagFormat == formatRaw {
|
|
||||||
log.Panic("raw option not supported yet")
|
|
||||||
}
|
|
||||||
if flagCommit != clients.HeadSHA {
|
|
||||||
log.Panic("--commit option not supported yet")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate format.
|
|
||||||
if !validateFormat(flagFormat) {
|
|
||||||
log.Panicf("unsupported format '%s'", flagFormat)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate `commit` is non-empty.
|
|
||||||
if flagCommit == "" {
|
|
||||||
log.Panic("commit should be non-empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func boolSum(bools ...bool) int {
|
|
||||||
sum := 0
|
|
||||||
for _, b := range bools {
|
|
||||||
if b {
|
|
||||||
sum++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSarifEnabled() bool {
|
|
||||||
// UPGRADEv4: remove.
|
|
||||||
var sarifEnabled bool
|
|
||||||
_, sarifEnabled = os.LookupEnv(cliEnableSarif)
|
|
||||||
return sarifEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
func isV6Enabled() bool {
|
|
||||||
var v6 bool
|
|
||||||
_, v6 = os.LookupEnv("SCORECARD_V6")
|
|
||||||
return v6
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateFormat(format string) bool {
|
|
||||||
switch format {
|
|
||||||
case formatJSON, formatSarif, formatDefault, formatRaw:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func readPolicy() (*spol.ScorecardPolicy, error) {
|
|
||||||
if flagPolicyFile != "" {
|
|
||||||
data, err := os.ReadFile(flagPolicyFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, sce.WithMessage(sce.ErrScorecardInternal,
|
|
||||||
fmt.Sprintf("os.ReadFile: %v", err))
|
|
||||||
}
|
|
||||||
sp, err := spol.ParseFromYAML(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil,
|
|
||||||
sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("spol.ParseFromYAML: %v", err))
|
|
||||||
}
|
|
||||||
return sp, nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checksHavePolicies(sp *spol.ScorecardPolicy, enabledChecks checker.CheckNameToFnMap) bool {
|
|
||||||
for checkName := range enabledChecks {
|
|
||||||
_, exists := sp.Policies[checkName]
|
|
||||||
if !exists {
|
|
||||||
log.Printf("check %s has no policy declared", checkName)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSupportedCheck(checkName string, requiredRequestTypes []checker.RequestType) bool {
|
|
||||||
unsupported := checker.ListUnsupported(
|
|
||||||
requiredRequestTypes,
|
|
||||||
checks.AllChecks[checkName].SupportedRequestTypes)
|
|
||||||
return len(unsupported) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAllChecks() checker.CheckNameToFnMap {
|
|
||||||
// Returns the full list of checks, given any environment variable constraints.
|
|
||||||
possibleChecks := checks.AllChecks
|
|
||||||
return possibleChecks
|
|
||||||
}
|
|
||||||
|
|
||||||
func getEnabledChecks(sp *spol.ScorecardPolicy, argsChecks []string,
|
|
||||||
requiredRequestTypes []checker.RequestType) (checker.CheckNameToFnMap, error) {
|
|
||||||
enabledChecks := checker.CheckNameToFnMap{}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case len(argsChecks) != 0:
|
|
||||||
// Populate checks to run with the `--repo` CLI argument.
|
|
||||||
for _, checkName := range argsChecks {
|
|
||||||
if !isSupportedCheck(checkName, requiredRequestTypes) {
|
|
||||||
return enabledChecks,
|
|
||||||
sce.WithMessage(sce.ErrScorecardInternal,
|
|
||||||
fmt.Sprintf("Unsupported RequestType %s by check: %s",
|
|
||||||
fmt.Sprint(requiredRequestTypes), checkName))
|
|
||||||
}
|
|
||||||
if !enableCheck(checkName, &enabledChecks) {
|
|
||||||
return enabledChecks,
|
|
||||||
sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("invalid check: %s", checkName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case sp != nil:
|
|
||||||
// Populate checks to run with policy file.
|
|
||||||
for checkName := range sp.GetPolicies() {
|
|
||||||
if !isSupportedCheck(checkName, requiredRequestTypes) {
|
|
||||||
// We silently ignore the check, like we do
|
|
||||||
// for the default case when no argsChecks
|
|
||||||
// or policy are present.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !enableCheck(checkName, &enabledChecks) {
|
|
||||||
return enabledChecks,
|
|
||||||
sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("invalid check: %s", checkName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// Enable all checks that are supported.
|
|
||||||
for checkName := range getAllChecks() {
|
|
||||||
if !isSupportedCheck(checkName, requiredRequestTypes) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !enableCheck(checkName, &enabledChecks) {
|
|
||||||
return enabledChecks,
|
|
||||||
sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("invalid check: %s", checkName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a policy was passed as argument, ensure all checks
|
|
||||||
// to run have a corresponding policy.
|
|
||||||
if sp != nil && !checksHavePolicies(sp, enabledChecks) {
|
|
||||||
return enabledChecks, sce.WithMessage(sce.ErrScorecardInternal, "checks don't have policies")
|
|
||||||
}
|
|
||||||
|
|
||||||
return enabledChecks, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRepoAccessors(ctx context.Context, repoURI, localURI string, logger *sclog.Logger) (
|
|
||||||
clients.Repo, // repo
|
|
||||||
clients.RepoClient, // repoClient
|
|
||||||
clients.RepoClient, // ossFuzzClient
|
|
||||||
clients.CIIBestPracticesClient, // ciiClient
|
|
||||||
clients.VulnerabilitiesClient, // vulnClient
|
|
||||||
error) {
|
|
||||||
var githubRepo clients.Repo
|
|
||||||
var errGitHub error
|
|
||||||
if localURI != "" {
|
|
||||||
localRepo, errLocal := localdir.MakeLocalDirRepo(localURI)
|
|
||||||
return localRepo, /*repo*/
|
|
||||||
localdir.CreateLocalDirClient(ctx, logger), /*repoClient*/
|
|
||||||
nil, /*ossFuzzClient*/
|
|
||||||
nil, /*ciiClient*/
|
|
||||||
nil, /*vulnClient*/
|
|
||||||
errLocal
|
|
||||||
}
|
|
||||||
|
|
||||||
githubRepo, errGitHub = githubrepo.MakeGithubRepo(repoURI)
|
|
||||||
if errGitHub != nil {
|
|
||||||
// nolint: wrapcheck
|
|
||||||
return githubRepo,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
errGitHub
|
|
||||||
}
|
|
||||||
|
|
||||||
ossFuzzRepoClient, errOssFuzz := githubrepo.CreateOssFuzzRepoClient(ctx, logger)
|
|
||||||
return githubRepo, /*repo*/
|
|
||||||
githubrepo.CreateGithubRepoClient(ctx, logger), /*repoClient*/
|
|
||||||
ossFuzzRepoClient, /*ossFuzzClient*/
|
|
||||||
clients.DefaultCIIBestPracticesClient(), /*ciiClient*/
|
|
||||||
clients.DefaultVulnerabilitiesClient(), /*vulnClient*/
|
|
||||||
errOssFuzz
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enables checks by name.
|
|
||||||
func enableCheck(checkName string, enabledChecks *checker.CheckNameToFnMap) bool {
|
|
||||||
if enabledChecks != nil {
|
|
||||||
for key, checkFn := range getAllChecks() {
|
|
||||||
if strings.EqualFold(key, checkName) {
|
|
||||||
(*enabledChecks)[key] = checkFn
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
@ -40,7 +40,7 @@ var serveCmd = &cobra.Command{
|
|||||||
Short: "Serve the scorecard program over http",
|
Short: "Serve the scorecard program over http",
|
||||||
Long: ``,
|
Long: ``,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
logger := log.NewLogger(log.ParseLevel(flagLogLevel))
|
logger := log.NewLogger(log.ParseLevel(opts.LogLevel))
|
||||||
|
|
||||||
t, err := template.New("webpage").Parse(tpl)
|
t, err := template.New("webpage").Parse(tpl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -79,7 +79,7 @@ var serveCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r.Header.Get("Content-Type") == "application/json" {
|
if r.Header.Get("Content-Type") == "application/json" {
|
||||||
if err := repoResult.AsJSON(flagShowDetails, log.ParseLevel(flagLogLevel), rw); err != nil {
|
if err := repoResult.AsJSON(opts.ShowDetails, log.ParseLevel(opts.LogLevel), rw); err != nil {
|
||||||
// TODO(log): Improve error message
|
// TODO(log): Improve error message
|
||||||
logger.Error(err, "")
|
logger.Error(err, "")
|
||||||
rw.WriteHeader(http.StatusInternalServerError)
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
|
187
options/options.go
Normal file
187
options/options.go
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
// Copyright 2020 Security Scorecard 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 options implements Scorecard options.
|
||||||
|
package options
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/ossf/scorecard/v4/clients"
|
||||||
|
"github.com/ossf/scorecard/v4/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options define common options for configuring scorecard.
|
||||||
|
type Options struct {
|
||||||
|
Repo string
|
||||||
|
Local string
|
||||||
|
Commit string
|
||||||
|
LogLevel string
|
||||||
|
Format string
|
||||||
|
NPM string
|
||||||
|
PyPI string
|
||||||
|
RubyGems string
|
||||||
|
PolicyFile string
|
||||||
|
ChecksToRun []string
|
||||||
|
Metadata []string
|
||||||
|
ShowDetails bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new instance of `Options`.
|
||||||
|
func New() *Options {
|
||||||
|
return &Options{}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultCommit specifies the default commit reference to use.
|
||||||
|
DefaultCommit = clients.HeadSHA
|
||||||
|
|
||||||
|
// Formats.
|
||||||
|
|
||||||
|
// FormatJSON specifies that results should be output in JSON format.
|
||||||
|
FormatJSON = "json"
|
||||||
|
// FormatSarif specifies that results should be output in SARIF format.
|
||||||
|
FormatSarif = "sarif"
|
||||||
|
// FormatDefault specifies that results should be output in default format.
|
||||||
|
FormatDefault = "default"
|
||||||
|
// FormatRaw specifies that results should be output in raw format.
|
||||||
|
FormatRaw = "raw"
|
||||||
|
|
||||||
|
// Environment variables.
|
||||||
|
|
||||||
|
// EnvVarEnableSarif is the environment variable which controls enabling
|
||||||
|
// SARIF logging.
|
||||||
|
EnvVarEnableSarif = "ENABLE_SARIF"
|
||||||
|
// EnvVarScorecardV6 is the environment variable which enables scorecard v6
|
||||||
|
// options.
|
||||||
|
EnvVarScorecardV6 = "SCORECARD_V6"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultLogLevel retrieves the default log level.
|
||||||
|
DefaultLogLevel = log.DefaultLevel.String()
|
||||||
|
|
||||||
|
errCommitIsEmpty = errors.New("commit should be non-empty")
|
||||||
|
errCommitOptionNotSupported = errors.New("commit option is not supported yet")
|
||||||
|
errFormatNotSupported = errors.New("unsupported format")
|
||||||
|
errPolicyFileNotSupported = errors.New("policy file is not supported yet")
|
||||||
|
errRawOptionNotSupported = errors.New("raw option is not supported yet")
|
||||||
|
errRepoOptionMustBeSet = errors.New(
|
||||||
|
"exactly one of `repo`, `npm`, `pypi`, `rubygems` or `local` must be set",
|
||||||
|
)
|
||||||
|
errSARIFNotSupported = errors.New("SARIF format is not supported yet")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validate validates scorecard configuration options.
|
||||||
|
// TODO(options): Cleanup error messages.
|
||||||
|
func (o *Options) Validate() []error {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
// Validate exactly one of `--repo`, `--npm`, `--pypi`, `--rubygems`, `--local` is enabled.
|
||||||
|
if boolSum(o.Repo != "",
|
||||||
|
o.NPM != "",
|
||||||
|
o.PyPI != "",
|
||||||
|
o.RubyGems != "",
|
||||||
|
o.Local != "") != 1 {
|
||||||
|
errs = append(
|
||||||
|
errs,
|
||||||
|
errRepoOptionMustBeSet,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate SARIF features are flag-guarded.
|
||||||
|
if !IsSarifEnabled() {
|
||||||
|
if o.Format == FormatSarif {
|
||||||
|
errs = append(
|
||||||
|
errs,
|
||||||
|
errSARIFNotSupported,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if o.PolicyFile != "" {
|
||||||
|
errs = append(
|
||||||
|
errs,
|
||||||
|
errPolicyFileNotSupported,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate V6 features are flag-guarded.
|
||||||
|
if !isV6Enabled() {
|
||||||
|
if o.Format == FormatRaw {
|
||||||
|
errs = append(
|
||||||
|
errs,
|
||||||
|
errRawOptionNotSupported,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if o.Commit != clients.HeadSHA {
|
||||||
|
errs = append(
|
||||||
|
errs,
|
||||||
|
errCommitOptionNotSupported,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate format.
|
||||||
|
if !validateFormat(o.Format) {
|
||||||
|
errs = append(
|
||||||
|
errs,
|
||||||
|
errFormatNotSupported,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate `commit` is non-empty.
|
||||||
|
if o.Commit == "" {
|
||||||
|
errs = append(
|
||||||
|
errs,
|
||||||
|
errCommitIsEmpty,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolSum(bools ...bool) int {
|
||||||
|
sum := 0
|
||||||
|
for _, b := range bools {
|
||||||
|
if b {
|
||||||
|
sum++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSarifEnabled returns true if `EnvVarEnableSarif` is specified.
|
||||||
|
// TODO(options): This probably doesn't need to be exported.
|
||||||
|
func IsSarifEnabled() bool {
|
||||||
|
// UPGRADEv4: remove.
|
||||||
|
var sarifEnabled bool
|
||||||
|
_, sarifEnabled = os.LookupEnv(EnvVarEnableSarif)
|
||||||
|
return sarifEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func isV6Enabled() bool {
|
||||||
|
var v6 bool
|
||||||
|
_, v6 = os.LookupEnv(EnvVarScorecardV6)
|
||||||
|
return v6
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateFormat(format string) bool {
|
||||||
|
switch format {
|
||||||
|
case FormatJSON, FormatSarif, FormatDefault, FormatRaw:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
@ -48,11 +48,12 @@ func runEnabledChecks(ctx context.Context,
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
runner := checker.Runner{
|
runner := checker.NewRunner(
|
||||||
Repo: repo.URI(),
|
checkName,
|
||||||
CheckName: checkName,
|
repo.URI(),
|
||||||
CheckRequest: request,
|
&request,
|
||||||
}
|
)
|
||||||
|
|
||||||
resultsCh <- runner.Run(ctx, checkFn)
|
resultsCh <- runner.Run(ctx, checkFn)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,11 @@ import (
|
|||||||
"github.com/olekukonko/tablewriter"
|
"github.com/olekukonko/tablewriter"
|
||||||
|
|
||||||
"github.com/ossf/scorecard/v4/checker"
|
"github.com/ossf/scorecard/v4/checker"
|
||||||
docs "github.com/ossf/scorecard/v4/docs/checks"
|
"github.com/ossf/scorecard/v4/docs/checks"
|
||||||
sce "github.com/ossf/scorecard/v4/errors"
|
sce "github.com/ossf/scorecard/v4/errors"
|
||||||
"github.com/ossf/scorecard/v4/log"
|
"github.com/ossf/scorecard/v4/log"
|
||||||
|
"github.com/ossf/scorecard/v4/options"
|
||||||
|
spol "github.com/ossf/scorecard/v4/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ScorecardInfo contains information about the scorecard code that was run.
|
// ScorecardInfo contains information about the scorecard code that was run.
|
||||||
@ -58,7 +60,7 @@ func scoreToString(s float64) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAggregateScore returns the aggregate score.
|
// GetAggregateScore returns the aggregate score.
|
||||||
func (r *ScorecardResult) GetAggregateScore(checkDocs docs.Doc) (float64, error) {
|
func (r *ScorecardResult) GetAggregateScore(checkDocs checks.Doc) (float64, error) {
|
||||||
// TODO: calculate the score and make it a field
|
// TODO: calculate the score and make it a field
|
||||||
// of ScorecardResult
|
// of ScorecardResult
|
||||||
weights := map[string]float64{"Critical": 10, "High": 7.5, "Medium": 5, "Low": 2.5}
|
weights := map[string]float64{"Critical": 10, "High": 7.5, "Medium": 5, "Low": 2.5}
|
||||||
@ -97,9 +99,45 @@ func (r *ScorecardResult) GetAggregateScore(checkDocs docs.Doc) (float64, error)
|
|||||||
return score / total, nil
|
return score / total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FormatResults formats scorecard results.
|
||||||
|
func FormatResults(
|
||||||
|
opts *options.Options,
|
||||||
|
results *ScorecardResult,
|
||||||
|
doc checks.Doc,
|
||||||
|
policy *spol.ScorecardPolicy,
|
||||||
|
) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch opts.Format {
|
||||||
|
case options.FormatDefault:
|
||||||
|
err = results.AsString(opts.ShowDetails, log.ParseLevel(opts.LogLevel), doc, os.Stdout)
|
||||||
|
case options.FormatSarif:
|
||||||
|
// TODO: support config files and update checker.MaxResultScore.
|
||||||
|
err = results.AsSARIF(opts.ShowDetails, log.ParseLevel(opts.LogLevel), os.Stdout, doc, policy)
|
||||||
|
case options.FormatJSON:
|
||||||
|
err = results.AsJSON2(opts.ShowDetails, log.ParseLevel(opts.LogLevel), doc, os.Stdout)
|
||||||
|
case options.FormatRaw:
|
||||||
|
err = results.AsRawJSON(os.Stdout)
|
||||||
|
default:
|
||||||
|
err = sce.WithMessage(
|
||||||
|
sce.ErrScorecardInternal,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"invalid format flag: %v. Expected [default, json]",
|
||||||
|
opts.Format,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to output results: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// AsString returns ScorecardResult in string format.
|
// AsString returns ScorecardResult in string format.
|
||||||
func (r *ScorecardResult) AsString(showDetails bool, logLevel log.Level,
|
func (r *ScorecardResult) AsString(showDetails bool, logLevel log.Level,
|
||||||
checkDocs docs.Doc, writer io.Writer) error {
|
checkDocs checks.Doc, writer io.Writer) error {
|
||||||
data := make([][]string, len(r.Checks))
|
data := make([][]string, len(r.Checks))
|
||||||
//nolint
|
//nolint
|
||||||
for i, row := range r.Checks {
|
for i, row := range r.Checks {
|
||||||
|
121
policy/policy.go
121
policy/policy.go
@ -17,9 +17,13 @@ package policy
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/ossf/scorecard/v4/checker"
|
||||||
"github.com/ossf/scorecard/v4/checks"
|
"github.com/ossf/scorecard/v4/checks"
|
||||||
sce "github.com/ossf/scorecard/v4/errors"
|
sce "github.com/ossf/scorecard/v4/errors"
|
||||||
)
|
)
|
||||||
@ -62,9 +66,29 @@ func modeToProto(m string) CheckPolicy_Mode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseFromYAML parses a policy file and returns
|
// ParseFromFile takes a policy file and returns a `ScorecardPolicy`.
|
||||||
// a scorecardPolicy.
|
func ParseFromFile(policyFile string) (*ScorecardPolicy, error) {
|
||||||
func ParseFromYAML(b []byte) (*ScorecardPolicy, error) {
|
if policyFile != "" {
|
||||||
|
data, err := os.ReadFile(policyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, sce.WithMessage(sce.ErrScorecardInternal,
|
||||||
|
fmt.Sprintf("os.ReadFile: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
sp, err := parseFromYAML(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil,
|
||||||
|
sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("spol.ParseFromYAML: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseFromYAML parses a policy file and returns a `ScorecardPolicy`.
|
||||||
|
func parseFromYAML(b []byte) (*ScorecardPolicy, error) {
|
||||||
// Internal golang for unmarshalling the policy file.
|
// Internal golang for unmarshalling the policy file.
|
||||||
sp := scorecardPolicy{}
|
sp := scorecardPolicy{}
|
||||||
// Protobuf-defined policy (policy.proto and policy.pb.go).
|
// Protobuf-defined policy (policy.proto and policy.pb.go).
|
||||||
@ -112,3 +136,94 @@ func ParseFromYAML(b []byte) (*ScorecardPolicy, error) {
|
|||||||
|
|
||||||
return &retPolicy, nil
|
return &retPolicy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetEnabled returns the list of enabled checks.
|
||||||
|
func GetEnabled(
|
||||||
|
sp *ScorecardPolicy,
|
||||||
|
argsChecks []string,
|
||||||
|
requiredRequestTypes []checker.RequestType,
|
||||||
|
) (checker.CheckNameToFnMap, error) {
|
||||||
|
enabledChecks := checker.CheckNameToFnMap{}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case len(argsChecks) != 0:
|
||||||
|
// Populate checks to run with the `--repo` CLI argument.
|
||||||
|
for _, checkName := range argsChecks {
|
||||||
|
if !isSupportedCheck(checkName, requiredRequestTypes) {
|
||||||
|
return enabledChecks,
|
||||||
|
sce.WithMessage(sce.ErrScorecardInternal,
|
||||||
|
fmt.Sprintf("Unsupported RequestType %s by check: %s",
|
||||||
|
fmt.Sprint(requiredRequestTypes), checkName))
|
||||||
|
}
|
||||||
|
if !enableCheck(checkName, &enabledChecks) {
|
||||||
|
return enabledChecks,
|
||||||
|
sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("invalid check: %s", checkName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case sp != nil:
|
||||||
|
// Populate checks to run with policy file.
|
||||||
|
for checkName := range sp.GetPolicies() {
|
||||||
|
if !isSupportedCheck(checkName, requiredRequestTypes) {
|
||||||
|
// We silently ignore the check, like we do
|
||||||
|
// for the default case when no argsChecks
|
||||||
|
// or policy are present.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !enableCheck(checkName, &enabledChecks) {
|
||||||
|
return enabledChecks,
|
||||||
|
sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("invalid check: %s", checkName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Enable all checks that are supported.
|
||||||
|
for checkName := range checks.GetAll() {
|
||||||
|
if !isSupportedCheck(checkName, requiredRequestTypes) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !enableCheck(checkName, &enabledChecks) {
|
||||||
|
return enabledChecks,
|
||||||
|
sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("invalid check: %s", checkName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a policy was passed as argument, ensure all checks
|
||||||
|
// to run have a corresponding policy.
|
||||||
|
if sp != nil && !checksHavePolicies(sp, enabledChecks) {
|
||||||
|
return enabledChecks, sce.WithMessage(sce.ErrScorecardInternal, "checks don't have policies")
|
||||||
|
}
|
||||||
|
|
||||||
|
return enabledChecks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checksHavePolicies(sp *ScorecardPolicy, enabledChecks checker.CheckNameToFnMap) bool {
|
||||||
|
for checkName := range enabledChecks {
|
||||||
|
_, exists := sp.Policies[checkName]
|
||||||
|
if !exists {
|
||||||
|
log.Printf("check %s has no policy declared", checkName)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSupportedCheck(checkName string, requiredRequestTypes []checker.RequestType) bool {
|
||||||
|
unsupported := checker.ListUnsupported(
|
||||||
|
requiredRequestTypes,
|
||||||
|
checks.AllChecks[checkName].SupportedRequestTypes)
|
||||||
|
return len(unsupported) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enables checks by name.
|
||||||
|
func enableCheck(checkName string, enabledChecks *checker.CheckNameToFnMap) bool {
|
||||||
|
if enabledChecks != nil {
|
||||||
|
for key, checkFn := range checks.GetAll() {
|
||||||
|
if strings.EqualFold(key, checkName) {
|
||||||
|
(*enabledChecks)[key] = checkFn
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -114,7 +114,7 @@ func TestPolicyRead(t *testing.T) {
|
|||||||
t.Fatalf("cannot read file: %v", err)
|
t.Fatalf("cannot read file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
p, err := ParseFromYAML(content)
|
p, err := parseFromYAML(content)
|
||||||
|
|
||||||
if !errors.Is(err, tt.err) {
|
if !errors.Is(err, tt.err) {
|
||||||
t.Fatalf("%s: expected %v, got %v", tt.name, tt.err, err)
|
t.Fatalf("%s: expected %v, got %v", tt.name, tt.err, err)
|
||||||
|
Loading…
Reference in New Issue
Block a user