mirror of
https://github.com/ossf/scorecard.git
synced 2024-09-19 04:57:14 +03:00
Refactor to improve readability (#1394)
Co-authored-by: Azeem Shaikh <azeems@google.com>
This commit is contained in:
parent
bbbca2bd87
commit
ecc96576f4
@ -20,32 +20,33 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UPGRADEv2: to remove.
|
|
||||||
const (
|
const (
|
||||||
MaxResultConfidence = 10
|
// MaxResultConfidence implies full certainty about a check result.
|
||||||
|
// TODO(#1393): remove after deprecation.
|
||||||
|
MaxResultConfidence = 10
|
||||||
|
// HalfResultConfidence signifies uncertainty about a check's score.
|
||||||
|
// TODO(#1393): remove after deprecation.
|
||||||
HalfResultConfidence = 5
|
HalfResultConfidence = 5
|
||||||
MinResultConfidence = 0
|
// MinResultConfidence signifies no confidence in the check result.
|
||||||
)
|
// TODO(#1393): remove after deprecation.
|
||||||
|
MinResultConfidence = 0
|
||||||
|
// TODO(#1393): remove after deprecation.
|
||||||
|
migrationThresholdPassValue = 8
|
||||||
|
|
||||||
// UPGRADEv2: to remove.
|
// MaxResultScore is the best score that can be given by a check.
|
||||||
const migrationThresholdPassValue = 8
|
MaxResultScore = 10
|
||||||
|
// MinResultScore is the worst score that can be given by a check.
|
||||||
|
MinResultScore = 0
|
||||||
|
// InconclusiveResultScore is returned when no reliable information can be retrieved by a check.
|
||||||
|
InconclusiveResultScore = -1
|
||||||
|
|
||||||
// DetailType is the type of details.
|
|
||||||
type DetailType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DetailInfo is info-level log.
|
// DetailInfo is info-level log.
|
||||||
DetailInfo DetailType = iota
|
DetailInfo DetailType = iota
|
||||||
// DetailWarn is warn log.
|
// DetailWarn is warn log.
|
||||||
DetailWarn
|
DetailWarn
|
||||||
// DetailDebug is debug log.
|
// DetailDebug is debug log.
|
||||||
DetailDebug
|
DetailDebug
|
||||||
)
|
|
||||||
|
|
||||||
// FileType is the type of a file.
|
|
||||||
type FileType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// FileTypeNone is a default, not defined.
|
// FileTypeNone is a default, not defined.
|
||||||
FileTypeNone FileType = iota
|
FileTypeNone FileType = iota
|
||||||
// FileTypeSource is for source code files.
|
// FileTypeSource is for source code files.
|
||||||
@ -56,55 +57,16 @@ const (
|
|||||||
FileTypeText
|
FileTypeText
|
||||||
// FileTypeURL for URLs.
|
// FileTypeURL for URLs.
|
||||||
FileTypeURL
|
FileTypeURL
|
||||||
)
|
|
||||||
|
|
||||||
// OffsetDefault is used if we can't determine the offset, for example when referencing a file but not a
|
// OffsetDefault is used if we can't determine the offset, for example when referencing a file but not a
|
||||||
// specific location in the file.
|
// specific location in the file.
|
||||||
const OffsetDefault = 1
|
OffsetDefault = 1
|
||||||
|
|
||||||
// LogMessage is a structure that encapsulates detail's information.
|
|
||||||
// This allows updating the definition easily.
|
|
||||||
//nolint
|
|
||||||
type LogMessage struct {
|
|
||||||
Text string // A short string explaining why the detail was recorded/logged.
|
|
||||||
Path string // Fullpath to the file.
|
|
||||||
Type FileType // Type of file.
|
|
||||||
Offset int // Offset in the file of Path (line for source/text files).
|
|
||||||
Snippet string // Snippet of code
|
|
||||||
// UPGRADEv3: to remove.
|
|
||||||
Version int // `3` to indicate the detail was logged using new structure.
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckDetail contains information for each detail.
|
|
||||||
type CheckDetail struct {
|
|
||||||
Msg LogMessage
|
|
||||||
Type DetailType // Any of DetailWarn, DetailInfo, DetailDebug.
|
|
||||||
}
|
|
||||||
|
|
||||||
// DetailLogger logs a CheckDetail struct.
|
|
||||||
type DetailLogger interface {
|
|
||||||
Info(desc string, args ...interface{})
|
|
||||||
Warn(desc string, args ...interface{})
|
|
||||||
Debug(desc string, args ...interface{})
|
|
||||||
|
|
||||||
// Functions to use for moving to SARIF format.
|
|
||||||
// UPGRADEv3: to rename.
|
|
||||||
Info3(msg *LogMessage)
|
|
||||||
Warn3(msg *LogMessage)
|
|
||||||
Debug3(msg *LogMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint
|
|
||||||
const (
|
|
||||||
MaxResultScore = 10
|
|
||||||
MinResultScore = 0
|
|
||||||
InconclusiveResultScore = -1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CheckResult captures result from a check run.
|
// CheckResult captures result from a check run.
|
||||||
// nolint
|
// nolint:govet
|
||||||
type CheckResult struct {
|
type CheckResult struct {
|
||||||
// Old structure
|
// TODO(#1393): Remove old structure after deprecation.
|
||||||
Error error `json:"-"`
|
Error error `json:"-"`
|
||||||
Name string
|
Name string
|
||||||
Details []string
|
Details []string
|
||||||
@ -118,81 +80,32 @@ type CheckResult struct {
|
|||||||
Details2 []CheckDetail `json:"-"` // Details of tests and sub-checks
|
Details2 []CheckDetail `json:"-"` // Details of tests and sub-checks
|
||||||
Score int `json:"-"` // {[-1,0...10], -1 = Inconclusive}
|
Score int `json:"-"` // {[-1,0...10], -1 = Inconclusive}
|
||||||
Reason string `json:"-"` // A sentence describing the check result (score, etc)
|
Reason string `json:"-"` // A sentence describing the check result (score, etc)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====== Raw results for checks =========.
|
// CheckDetail contains information for each detail.
|
||||||
|
type CheckDetail struct {
|
||||||
|
Msg LogMessage
|
||||||
|
Type DetailType // Any of DetailWarn, DetailInfo, DetailDebug.
|
||||||
|
}
|
||||||
|
|
||||||
// File represents a file.
|
// LogMessage is a structure that encapsulates detail's information.
|
||||||
type File struct {
|
// This allows updating the definition easily.
|
||||||
Path string
|
// nolint:govet
|
||||||
Snippet string // Snippet of code
|
type LogMessage struct {
|
||||||
Offset int // Offset in the file of Path (line for source/text files).
|
Text string // A short string explaining why the detail was recorded/logged.
|
||||||
|
Path string // Fullpath to the file.
|
||||||
Type FileType // Type of file.
|
Type FileType // Type of file.
|
||||||
// TODO: add hash.
|
Offset int // Offset in the file of Path (line for source/text files).
|
||||||
|
Snippet string // Snippet of code
|
||||||
|
// UPGRADEv3: to remove.
|
||||||
|
Version int // `3` to indicate the detail was logged using new structure.
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecurityPolicyData contains the raw results
|
// DetailType is the type of details.
|
||||||
// for the Security-Policy check.
|
type DetailType int
|
||||||
type SecurityPolicyData struct {
|
|
||||||
// Files contains a list of files.
|
|
||||||
Files []File
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run represents a run.
|
// FileType is the type of a file.
|
||||||
type Run struct {
|
type FileType int
|
||||||
URL string
|
|
||||||
// TODO: add fields, e.g., Result=["success", "failure"]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Issue represents an issue.
|
|
||||||
type Issue struct {
|
|
||||||
URL string
|
|
||||||
// TODO: add fields, e.g., state=[opened|closed]
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergeRequest represents a merge request.
|
|
||||||
type MergeRequest struct {
|
|
||||||
URL string
|
|
||||||
// TODO: add fields, e.g., State=["merged"|"closed"]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tool represents a tool.
|
|
||||||
type Tool struct {
|
|
||||||
// Runs of the tool.
|
|
||||||
Runs []Run
|
|
||||||
// Issues created by the tool.
|
|
||||||
Issues []Issue
|
|
||||||
// Merges requests created by the tool.
|
|
||||||
MergeRequests []MergeRequest
|
|
||||||
Name string
|
|
||||||
URL string
|
|
||||||
Desc string
|
|
||||||
ConfigFiles []File
|
|
||||||
}
|
|
||||||
|
|
||||||
// BinaryArtifactData contains the raw results
|
|
||||||
// for the Binary-Artifact check.
|
|
||||||
type BinaryArtifactData struct {
|
|
||||||
// Files contains a list of files.
|
|
||||||
Files []File
|
|
||||||
}
|
|
||||||
|
|
||||||
// DependencyUpdateToolData contains the raw results
|
|
||||||
// for the Dependency-Update-Tool check.
|
|
||||||
type DependencyUpdateToolData struct {
|
|
||||||
// Tools contains a list of tools.
|
|
||||||
// Note: we only populate one entry at most.
|
|
||||||
Tools []Tool
|
|
||||||
}
|
|
||||||
|
|
||||||
// RawResults contains results before a policy
|
|
||||||
// is applied.
|
|
||||||
type RawResults struct {
|
|
||||||
BinaryArtifactResults BinaryArtifactData
|
|
||||||
SecurityPolicyResults SecurityPolicyData
|
|
||||||
DependencyUpdateToolResults DependencyUpdateToolData
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateProportionalScore creates a proportional score.
|
// CreateProportionalScore creates a proportional score.
|
||||||
func CreateProportionalScore(success, total int) int {
|
func CreateProportionalScore(success, total int) int {
|
||||||
|
@ -42,55 +42,6 @@ type CheckFn func(*CheckRequest) CheckResult
|
|||||||
// CheckNameToFnMap defined here for convenience.
|
// CheckNameToFnMap defined here for convenience.
|
||||||
type CheckNameToFnMap map[string]CheckFn
|
type CheckNameToFnMap map[string]CheckFn
|
||||||
|
|
||||||
// UPGRADEv2: messages2 will ultimately
|
|
||||||
// be renamed to messages.
|
|
||||||
type logger struct {
|
|
||||||
messages2 []CheckDetail
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) Info(desc string, args ...interface{}) {
|
|
||||||
cd := CheckDetail{Type: DetailInfo, Msg: LogMessage{Text: fmt.Sprintf(desc, args...)}}
|
|
||||||
l.messages2 = append(l.messages2, cd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) Warn(desc string, args ...interface{}) {
|
|
||||||
cd := CheckDetail{Type: DetailWarn, Msg: LogMessage{Text: fmt.Sprintf(desc, args...)}}
|
|
||||||
l.messages2 = append(l.messages2, cd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) Debug(desc string, args ...interface{}) {
|
|
||||||
cd := CheckDetail{Type: DetailDebug, Msg: LogMessage{Text: fmt.Sprintf(desc, args...)}}
|
|
||||||
l.messages2 = append(l.messages2, cd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UPGRADEv3: to rename.
|
|
||||||
func (l *logger) Info3(msg *LogMessage) {
|
|
||||||
cd := CheckDetail{
|
|
||||||
Type: DetailInfo,
|
|
||||||
Msg: *msg,
|
|
||||||
}
|
|
||||||
cd.Msg.Version = 3
|
|
||||||
l.messages2 = append(l.messages2, cd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) Warn3(msg *LogMessage) {
|
|
||||||
cd := CheckDetail{
|
|
||||||
Type: DetailWarn,
|
|
||||||
Msg: *msg,
|
|
||||||
}
|
|
||||||
cd.Msg.Version = 3
|
|
||||||
l.messages2 = append(l.messages2, cd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *logger) Debug3(msg *LogMessage) {
|
|
||||||
cd := CheckDetail{
|
|
||||||
Type: DetailDebug,
|
|
||||||
Msg: *msg,
|
|
||||||
}
|
|
||||||
cd.Msg.Version = 3
|
|
||||||
l.messages2 = append(l.messages2, cd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func logStats(ctx context.Context, startTime time.Time, result *CheckResult) error {
|
func logStats(ctx context.Context, startTime time.Time, result *CheckResult) error {
|
||||||
runTimeInSecs := time.Now().Unix() - startTime.Unix()
|
runTimeInSecs := time.Now().Unix() - startTime.Unix()
|
||||||
opencensusstats.Record(ctx, stats.CheckRuntimeInSec.M(runTimeInSecs))
|
opencensusstats.Record(ctx, stats.CheckRuntimeInSec.M(runTimeInSecs))
|
||||||
|
28
checker/detail_logger.go
Normal file
28
checker/detail_logger.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// 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 checker
|
||||||
|
|
||||||
|
// DetailLogger logs a CheckDetail struct.
|
||||||
|
type DetailLogger interface {
|
||||||
|
Info(desc string, args ...interface{})
|
||||||
|
Warn(desc string, args ...interface{})
|
||||||
|
Debug(desc string, args ...interface{})
|
||||||
|
|
||||||
|
// Functions to use for moving to SARIF format.
|
||||||
|
// UPGRADEv3: to rename.
|
||||||
|
Info3(msg *LogMessage)
|
||||||
|
Warn3(msg *LogMessage)
|
||||||
|
Debug3(msg *LogMessage)
|
||||||
|
}
|
68
checker/detail_logger_impl.go
Normal file
68
checker/detail_logger_impl.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// 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 checker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UPGRADEv2: messages2 will ultimately
|
||||||
|
// be renamed to messages.
|
||||||
|
type logger struct {
|
||||||
|
messages2 []CheckDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) Info(desc string, args ...interface{}) {
|
||||||
|
cd := CheckDetail{Type: DetailInfo, Msg: LogMessage{Text: fmt.Sprintf(desc, args...)}}
|
||||||
|
l.messages2 = append(l.messages2, cd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) Warn(desc string, args ...interface{}) {
|
||||||
|
cd := CheckDetail{Type: DetailWarn, Msg: LogMessage{Text: fmt.Sprintf(desc, args...)}}
|
||||||
|
l.messages2 = append(l.messages2, cd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) Debug(desc string, args ...interface{}) {
|
||||||
|
cd := CheckDetail{Type: DetailDebug, Msg: LogMessage{Text: fmt.Sprintf(desc, args...)}}
|
||||||
|
l.messages2 = append(l.messages2, cd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UPGRADEv3: to rename.
|
||||||
|
func (l *logger) Info3(msg *LogMessage) {
|
||||||
|
cd := CheckDetail{
|
||||||
|
Type: DetailInfo,
|
||||||
|
Msg: *msg,
|
||||||
|
}
|
||||||
|
cd.Msg.Version = 3
|
||||||
|
l.messages2 = append(l.messages2, cd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) Warn3(msg *LogMessage) {
|
||||||
|
cd := CheckDetail{
|
||||||
|
Type: DetailWarn,
|
||||||
|
Msg: *msg,
|
||||||
|
}
|
||||||
|
cd.Msg.Version = 3
|
||||||
|
l.messages2 = append(l.messages2, cd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) Debug3(msg *LogMessage) {
|
||||||
|
cd := CheckDetail{
|
||||||
|
Type: DetailDebug,
|
||||||
|
Msg: *msg,
|
||||||
|
}
|
||||||
|
cd.Msg.Version = 3
|
||||||
|
l.messages2 = append(l.messages2, cd)
|
||||||
|
}
|
86
checker/raw_result.go
Normal file
86
checker/raw_result.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// 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 checker
|
||||||
|
|
||||||
|
// RawResults contains results before a policy
|
||||||
|
// is applied.
|
||||||
|
type RawResults struct {
|
||||||
|
BinaryArtifactResults BinaryArtifactData
|
||||||
|
SecurityPolicyResults SecurityPolicyData
|
||||||
|
DependencyUpdateToolResults DependencyUpdateToolData
|
||||||
|
}
|
||||||
|
|
||||||
|
// BinaryArtifactData contains the raw results
|
||||||
|
// for the Binary-Artifact check.
|
||||||
|
type BinaryArtifactData struct {
|
||||||
|
// Files contains a list of files.
|
||||||
|
Files []File
|
||||||
|
}
|
||||||
|
|
||||||
|
// DependencyUpdateToolData contains the raw results
|
||||||
|
// for the Dependency-Update-Tool check.
|
||||||
|
type DependencyUpdateToolData struct {
|
||||||
|
// Tools contains a list of tools.
|
||||||
|
// Note: we only populate one entry at most.
|
||||||
|
Tools []Tool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecurityPolicyData contains the raw results
|
||||||
|
// for the Security-Policy check.
|
||||||
|
type SecurityPolicyData struct {
|
||||||
|
// Files contains a list of files.
|
||||||
|
Files []File
|
||||||
|
}
|
||||||
|
|
||||||
|
// File represents a file.
|
||||||
|
type File struct {
|
||||||
|
Path string
|
||||||
|
Snippet string // Snippet of code
|
||||||
|
Offset int // Offset in the file of Path (line for source/text files).
|
||||||
|
Type FileType // Type of file.
|
||||||
|
// TODO: add hash.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run represents a run.
|
||||||
|
type Run struct {
|
||||||
|
URL string
|
||||||
|
// TODO: add fields, e.g., Result=["success", "failure"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue represents an issue.
|
||||||
|
type Issue struct {
|
||||||
|
URL string
|
||||||
|
// TODO: add fields, e.g., state=[opened|closed]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeRequest represents a merge request.
|
||||||
|
type MergeRequest struct {
|
||||||
|
URL string
|
||||||
|
// TODO: add fields, e.g., State=["merged"|"closed"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tool represents a tool.
|
||||||
|
type Tool struct {
|
||||||
|
// Runs of the tool.
|
||||||
|
Runs []Run
|
||||||
|
// Issues created by the tool.
|
||||||
|
Issues []Issue
|
||||||
|
// Merges requests created by the tool.
|
||||||
|
MergeRequests []MergeRequest
|
||||||
|
Name string
|
||||||
|
URL string
|
||||||
|
Desc string
|
||||||
|
ConfigFiles []File
|
||||||
|
}
|
414
cmd/root.go
414
cmd/root.go
@ -55,22 +55,24 @@ var (
|
|||||||
rubygems string
|
rubygems string
|
||||||
showDetails bool
|
showDetails bool
|
||||||
policyFile string
|
policyFile string
|
||||||
|
rootCmd = &cobra.Command{
|
||||||
|
Use: scorecardUse,
|
||||||
|
Short: scorecardShort,
|
||||||
|
Long: scorecardLong,
|
||||||
|
Run: scorecardCmd,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
formatJSON = "json"
|
formatJSON = "json"
|
||||||
formatSarif = "sarif"
|
formatSarif = "sarif"
|
||||||
formatDefault = "default"
|
formatDefault = "default"
|
||||||
)
|
|
||||||
|
|
||||||
// These strings must be the same as the ones used in
|
// These strings must be the same as the ones used in
|
||||||
// checks.yaml for the "repos" field.
|
// checks.yaml for the "repos" field.
|
||||||
const (
|
|
||||||
repoTypeLocal = "local"
|
repoTypeLocal = "local"
|
||||||
repoTypeGitHub = "GitHub"
|
repoTypeGitHub = "GitHub"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
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] [--policy=file] or ./scorecard --{npm,pypi,rubygems}=<package_name>
|
[--show-details] [--policy=file] or ./scorecard --{npm,pypi,rubygems}=<package_name>
|
||||||
@ -78,6 +80,207 @@ const (
|
|||||||
scorecardShort = "Security Scorecards"
|
scorecardShort = "Security Scorecards"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//nolint:gochecknoinits
|
||||||
|
func init() {
|
||||||
|
// Add the zap flag manually
|
||||||
|
rootCmd.PersistentFlags().AddGoFlagSet(goflag.CommandLine)
|
||||||
|
rootCmd.Flags().StringVar(&repo, "repo", "", "repository to check")
|
||||||
|
rootCmd.Flags().StringVar(&local, "local", "", "local folder to check")
|
||||||
|
rootCmd.Flags().StringVar(
|
||||||
|
&npm, "npm", "",
|
||||||
|
"npm package to check, given that the npm package has a GitHub repository")
|
||||||
|
rootCmd.Flags().StringVar(
|
||||||
|
&pypi, "pypi", "",
|
||||||
|
"pypi package to check, given that the pypi package has a GitHub repository")
|
||||||
|
rootCmd.Flags().StringVar(
|
||||||
|
&rubygems, "rubygems", "",
|
||||||
|
"rubygems package to check, given that the rubygems package has a GitHub repository")
|
||||||
|
rootCmd.Flags().StringVar(&format, "format", formatDefault,
|
||||||
|
"output format. allowed values are [default, sarif, json]")
|
||||||
|
rootCmd.Flags().StringSliceVar(
|
||||||
|
&metaData, "metadata", []string{}, "metadata for the project. It can be multiple separated by commas")
|
||||||
|
rootCmd.Flags().BoolVar(&showDetails, "show-details", false, "show extra details about each check")
|
||||||
|
checkNames := []string{}
|
||||||
|
for checkName := range getAllChecks() {
|
||||||
|
checkNames = append(checkNames, checkName)
|
||||||
|
}
|
||||||
|
rootCmd.Flags().StringSliceVar(&checksToRun, "checks", []string{},
|
||||||
|
fmt.Sprintf("Checks to run. Possible values are: %s", strings.Join(checkNames, ",")))
|
||||||
|
rootCmd.Flags().StringVar(&policyFile, "policy", "", "policy to enforce")
|
||||||
|
|
||||||
|
var v6 bool
|
||||||
|
_, v6 = os.LookupEnv("SCORECARD_V6")
|
||||||
|
if v6 {
|
||||||
|
rootCmd.Flags().BoolVar(&raw, "raw", false, "generate raw results")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute runs the Scorecard commandline.
|
||||||
|
func Execute() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint: gocognit, gocyclo
|
||||||
|
func scorecardCmd(cmd *cobra.Command, args []string) {
|
||||||
|
// UPGRADEv4: remove.
|
||||||
|
var v4 bool
|
||||||
|
_, v4 = os.LookupEnv("SCORECARD_V4")
|
||||||
|
|
||||||
|
if format == formatSarif && !v4 {
|
||||||
|
log.Panic("sarif not supported yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
if policyFile != "" && !v4 {
|
||||||
|
log.Panic("policy not supported yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
if local != "" && !v4 {
|
||||||
|
log.Panic("--local option not supported yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
var v6 bool
|
||||||
|
_, v6 = os.LookupEnv("SCORECARD_V6")
|
||||||
|
if raw && !v6 {
|
||||||
|
log.Panic("--raw option not supported yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate format.
|
||||||
|
if !validateFormat(format) {
|
||||||
|
log.Panicf("unsupported format '%s'", format)
|
||||||
|
}
|
||||||
|
|
||||||
|
policy, err := readPolicy()
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("readPolicy: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the URI.
|
||||||
|
uri, err := getURI(repo, local)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set `repo` from package managers.
|
||||||
|
exists, gitRepo, err := fetchGitRepositoryFromPackageManagers(npm, pypi, rubygems)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
if err := cmd.Flags().Set("repo", gitRepo); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check that `repo` is set.
|
||||||
|
if err := cmd.MarkFlagRequired("repo"); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
logger, err := githubrepo.NewLogger(*logLevel)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
// nolint: errcheck
|
||||||
|
defer logger.Sync() // Flushes buffer, if any.
|
||||||
|
|
||||||
|
repoURI, repoClient, ossFuzzRepoClient, ciiClient, repoType, err := getRepoAccessors(ctx, uri, logger)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
defer repoClient.Close()
|
||||||
|
if ossFuzzRepoClient != nil {
|
||||||
|
defer ossFuzzRepoClient.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read docs.
|
||||||
|
checkDocs, err := docs.Read()
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("cannot read yaml file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
supportedChecks, err := getSupportedChecks(repoType, checkDocs)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("cannot read supported checks: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
enabledChecks, err := getEnabledChecks(policy, checksToRun, supportedChecks, repoType)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if format == formatDefault {
|
||||||
|
for checkName := range enabledChecks {
|
||||||
|
fmt.Fprintf(os.Stderr, "Starting [%s]\n", checkName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if raw && format != "json" {
|
||||||
|
log.Panicf("only json format is supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
repoResult, err := pkg.RunScorecards(ctx, repoURI, raw, enabledChecks, repoClient, ossFuzzRepoClient, ciiClient)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
repoResult.Metadata = append(repoResult.Metadata, metaData...)
|
||||||
|
|
||||||
|
// Sort them by name
|
||||||
|
sort.Slice(repoResult.Checks, func(i, j int) bool {
|
||||||
|
return repoResult.Checks[i].Name < repoResult.Checks[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
if format == formatDefault {
|
||||||
|
for checkName := range enabledChecks {
|
||||||
|
fmt.Fprintf(os.Stderr, "Finished [%s]\n", checkName)
|
||||||
|
}
|
||||||
|
fmt.Println("\nRESULTS\n-------")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch format {
|
||||||
|
case formatDefault:
|
||||||
|
err = repoResult.AsString(showDetails, *logLevel, checkDocs, os.Stdout)
|
||||||
|
case formatSarif:
|
||||||
|
// TODO: support config files and update checker.MaxResultScore.
|
||||||
|
err = repoResult.AsSARIF(showDetails, *logLevel, os.Stdout, checkDocs, policy)
|
||||||
|
case formatJSON:
|
||||||
|
if raw {
|
||||||
|
err = repoResult.AsRawJSON(os.Stdout)
|
||||||
|
} else {
|
||||||
|
err = repoResult.AsJSON2(showDetails, *logLevel, checkDocs, os.Stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
err = sce.WithMessage(sce.ErrScorecardInternal,
|
||||||
|
fmt.Sprintf("invalid format flag: %v. Expected [default, json]", format))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Panicf("Failed to output results: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchGitRepositoryFromPackageManagers(npm, pypi, rubygems string) (bool, string, error) {
|
||||||
|
if npm != "" {
|
||||||
|
gitRepo, err := fetchGitRepositoryFromNPM(npm)
|
||||||
|
return true, gitRepo, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if pypi != "" {
|
||||||
|
gitRepo, err := fetchGitRepositoryFromPYPI(pypi)
|
||||||
|
return true, gitRepo, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rubygems != "" {
|
||||||
|
gitRepo, err := fetchGitRepositoryFromRubyGems(rubygems)
|
||||||
|
return false, gitRepo, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func readPolicy() (*spol.ScorecardPolicy, error) {
|
func readPolicy() (*spol.ScorecardPolicy, error) {
|
||||||
if policyFile != "" {
|
if policyFile != "" {
|
||||||
data, err := os.ReadFile(policyFile)
|
data, err := os.ReadFile(policyFile)
|
||||||
@ -251,162 +454,6 @@ func getURI(repo, local string) (string, error) {
|
|||||||
return repo, nil
|
return repo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
|
||||||
Use: scorecardUse,
|
|
||||||
Short: scorecardShort,
|
|
||||||
Long: scorecardLong,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
// UPGRADEv4: remove.
|
|
||||||
var v4 bool
|
|
||||||
_, v4 = os.LookupEnv("SCORECARD_V4")
|
|
||||||
|
|
||||||
if format == formatSarif && !v4 {
|
|
||||||
log.Fatal("sarif not supported yet")
|
|
||||||
}
|
|
||||||
|
|
||||||
if policyFile != "" && !v4 {
|
|
||||||
log.Fatal("policy not supported yet")
|
|
||||||
}
|
|
||||||
|
|
||||||
if local != "" && !v4 {
|
|
||||||
log.Fatal("--local option not supported yet")
|
|
||||||
}
|
|
||||||
|
|
||||||
var v6 bool
|
|
||||||
_, v6 = os.LookupEnv("SCORECARD_V6")
|
|
||||||
if raw && !v6 {
|
|
||||||
log.Fatal("--raw option not supported yet")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate format.
|
|
||||||
if !validateFormat(format) {
|
|
||||||
log.Fatalf("unsupported format '%s'", format)
|
|
||||||
}
|
|
||||||
|
|
||||||
policy, err := readPolicy()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("readPolicy: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the URI.
|
|
||||||
uri, err := getURI(repo, local)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if npm != "" {
|
|
||||||
if git, err := fetchGitRepositoryFromNPM(npm); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
} else {
|
|
||||||
if err := cmd.Flags().Set("repo", git); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if pypi != "" {
|
|
||||||
if git, err := fetchGitRepositoryFromPYPI(pypi); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
} else {
|
|
||||||
if err := cmd.Flags().Set("repo", git); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if rubygems != "" {
|
|
||||||
if git, err := fetchGitRepositoryFromRubyGems(rubygems); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
} else {
|
|
||||||
if err := cmd.Flags().Set("repo", git); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := cmd.MarkFlagRequired("repo"); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
logger, err := githubrepo.NewLogger(*logLevel)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// nolint
|
|
||||||
defer logger.Sync() // Flushes buffer, if any.
|
|
||||||
|
|
||||||
repoURI, repoClient, ossFuzzRepoClient, ciiClient, repoType, err := getRepoAccessors(ctx, uri, logger)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer repoClient.Close()
|
|
||||||
if ossFuzzRepoClient != nil {
|
|
||||||
defer ossFuzzRepoClient.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read docs.
|
|
||||||
checkDocs, err := docs.Read()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("cannot read yaml file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
supportedChecks, err := getSupportedChecks(repoType, checkDocs)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("cannot read supported checks: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
enabledChecks, err := getEnabledChecks(policy, checksToRun, supportedChecks, repoType)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if format == formatDefault {
|
|
||||||
for checkName := range enabledChecks {
|
|
||||||
fmt.Fprintf(os.Stderr, "Starting [%s]\n", checkName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if raw && format != "json" {
|
|
||||||
log.Fatalf("only json format is supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
repoResult, err := pkg.RunScorecards(ctx, repoURI, raw, enabledChecks, repoClient, ossFuzzRepoClient, ciiClient)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
repoResult.Metadata = append(repoResult.Metadata, metaData...)
|
|
||||||
|
|
||||||
// Sort them by name
|
|
||||||
sort.Slice(repoResult.Checks, func(i, j int) bool {
|
|
||||||
return repoResult.Checks[i].Name < repoResult.Checks[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
if format == formatDefault {
|
|
||||||
for checkName := range enabledChecks {
|
|
||||||
fmt.Fprintf(os.Stderr, "Finished [%s]\n", checkName)
|
|
||||||
}
|
|
||||||
fmt.Println("\nRESULTS\n-------")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch format {
|
|
||||||
case formatDefault:
|
|
||||||
err = repoResult.AsString(showDetails, *logLevel, checkDocs, os.Stdout)
|
|
||||||
case formatSarif:
|
|
||||||
// TODO: support config files and update checker.MaxResultScore.
|
|
||||||
err = repoResult.AsSARIF(showDetails, *logLevel, os.Stdout, checkDocs, policy)
|
|
||||||
case formatJSON:
|
|
||||||
if raw {
|
|
||||||
err = repoResult.AsRawJSON(os.Stdout)
|
|
||||||
} else {
|
|
||||||
err = repoResult.AsJSON2(showDetails, *logLevel, checkDocs, os.Stdout)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
err = sce.WithMessage(sce.ErrScorecardInternal,
|
|
||||||
fmt.Sprintf("invalid format flag: %v. Expected [default, json]", format))
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to output results: %v", err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type npmSearchResults struct {
|
type npmSearchResults struct {
|
||||||
Objects []struct {
|
Objects []struct {
|
||||||
Package struct {
|
Package struct {
|
||||||
@ -429,14 +476,6 @@ type rubyGemsSearchResults struct {
|
|||||||
SourceCodeURI string `json:"source_code_uri"`
|
SourceCodeURI string `json:"source_code_uri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute runs the Scorecard commandline.
|
|
||||||
func Execute() {
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets the GitHub repository URL for the npm package.
|
// Gets the GitHub repository URL for the npm package.
|
||||||
//nolint:noctx
|
//nolint:noctx
|
||||||
func fetchGitRepositoryFromNPM(packageName string) (string, error) {
|
func fetchGitRepositoryFromNPM(packageName string) (string, error) {
|
||||||
@ -526,38 +565,3 @@ func enableCheck(checkName string, enabledChecks *checker.CheckNameToFnMap) bool
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gochecknoinits
|
|
||||||
func init() {
|
|
||||||
// Add the zap flag manually
|
|
||||||
rootCmd.PersistentFlags().AddGoFlagSet(goflag.CommandLine)
|
|
||||||
rootCmd.Flags().StringVar(&repo, "repo", "", "repository to check")
|
|
||||||
rootCmd.Flags().StringVar(&local, "local", "", "local folder to check")
|
|
||||||
rootCmd.Flags().StringVar(
|
|
||||||
&npm, "npm", "",
|
|
||||||
"npm package to check, given that the npm package has a GitHub repository")
|
|
||||||
rootCmd.Flags().StringVar(
|
|
||||||
&pypi, "pypi", "",
|
|
||||||
"pypi package to check, given that the pypi package has a GitHub repository")
|
|
||||||
rootCmd.Flags().StringVar(
|
|
||||||
&rubygems, "rubygems", "",
|
|
||||||
"rubygems package to check, given that the rubygems package has a GitHub repository")
|
|
||||||
rootCmd.Flags().StringVar(&format, "format", formatDefault,
|
|
||||||
"output format. allowed values are [default, sarif, json]")
|
|
||||||
rootCmd.Flags().StringSliceVar(
|
|
||||||
&metaData, "metadata", []string{}, "metadata for the project. It can be multiple separated by commas")
|
|
||||||
rootCmd.Flags().BoolVar(&showDetails, "show-details", false, "show extra details about each check")
|
|
||||||
checkNames := []string{}
|
|
||||||
for checkName := range getAllChecks() {
|
|
||||||
checkNames = append(checkNames, checkName)
|
|
||||||
}
|
|
||||||
rootCmd.Flags().StringSliceVar(&checksToRun, "checks", []string{},
|
|
||||||
fmt.Sprintf("Checks to run. Possible values are: %s", strings.Join(checkNames, ",")))
|
|
||||||
rootCmd.Flags().StringVar(&policyFile, "policy", "", "policy to enforce")
|
|
||||||
|
|
||||||
var v6 bool
|
|
||||||
_, v6 = os.LookupEnv("SCORECARD_V6")
|
|
||||||
if v6 {
|
|
||||||
rootCmd.Flags().BoolVar(&raw, "raw", false, "generate raw results")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user