[experimental] Add probe code and support for Tool-Update-Dependency (#2944)

* update

Signed-off-by: laurentsimon <laurentsimon@google.com>

* update

Signed-off-by: laurentsimon <laurentsimon@google.com>

* update

Signed-off-by: laurentsimon <laurentsimon@google.com>

* update

Signed-off-by: laurentsimon <laurentsimon@google.com>

* update

Signed-off-by: laurentsimon <laurentsimon@google.com>

* update

Signed-off-by: laurentsimon <laurentsimon@google.com>

* update

Signed-off-by: laurentsimon <laurentsimon@google.com>

* update

Signed-off-by: laurentsimon <laurentsimon@google.com>

---------

Signed-off-by: laurentsimon <laurentsimon@google.com>
This commit is contained in:
laurentsimon 2023-05-22 18:13:24 -07:00 committed by GitHub
parent f8d33f8b3e
commit 1a336d8087
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 757 additions and 244 deletions

View File

@ -193,3 +193,26 @@ func CreateRuntimeErrorResult(name string, e error) CheckResult {
Reason: e.Error(), // Note: message already accessible by caller thru `Error`. Reason: e.Error(), // Note: message already accessible by caller thru `Error`.
} }
} }
// LogFindings logs the list of findings.
func LogFindings(findings []finding.Finding, dl DetailLogger) error {
for i := range findings {
f := &findings[i]
switch f.Outcome {
case finding.OutcomeNegative:
dl.Warn(&LogMessage{
Finding: f,
})
case finding.OutcomePositive:
dl.Info(&LogMessage{
Finding: f,
})
default:
dl.Debug(&LogMessage{
Finding: f,
})
}
}
return nil
}

View File

@ -242,7 +242,6 @@ type SignedReleasesData struct {
// for the Dependency-Update-Tool check. // for the Dependency-Update-Tool check.
type DependencyUpdateToolData struct { type DependencyUpdateToolData struct {
// Tools contains a list of tools. // Tools contains a list of tools.
// Note: we only populate one entry at most.
Tools []Tool Tools []Tool
} }
@ -375,3 +374,24 @@ type TokenPermission struct {
Msg *string Msg *string
Type PermissionLevel Type PermissionLevel
} }
// Location generates location from a file.
func (f *File) Location() *finding.Location {
// TODO(2626): merge location and path.
if f == nil {
return nil
}
loc := &finding.Location{
Type: f.Type,
Path: f.Path,
LineStart: &f.Offset,
}
if f.EndOffset != 0 {
loc.LineEnd = &f.EndOffset
}
if f.Snippet != "" {
loc.Snippet = &f.Snippet
}
return loc
}

View File

@ -19,12 +19,13 @@ import (
"github.com/ossf/scorecard/v4/checks/evaluation" "github.com/ossf/scorecard/v4/checks/evaluation"
"github.com/ossf/scorecard/v4/checks/raw" "github.com/ossf/scorecard/v4/checks/raw"
sce "github.com/ossf/scorecard/v4/errors" sce "github.com/ossf/scorecard/v4/errors"
"github.com/ossf/scorecard/v4/probes"
) )
// CheckDependencyUpdateTool is the exported name for Automatic-Depdendency-Update. // CheckDependencyUpdateTool is the exported name for Automatic-Depdendency-Update.
const CheckDependencyUpdateTool = "Dependency-Update-Tool" const CheckDependencyUpdateTool = "Dependency-Update-Tool"
//nolint // nolint
func init() { func init() {
supportedRequestTypes := []checker.RequestType{ supportedRequestTypes := []checker.RequestType{
checker.FileBased, checker.FileBased,
@ -48,6 +49,13 @@ func DependencyUpdateTool(c *checker.CheckRequest) checker.CheckResult {
c.RawResults.DependencyUpdateToolResults = rawData c.RawResults.DependencyUpdateToolResults = rawData
} }
// Evaluate the probes.
findings, err := evaluateProbes(c, CheckDependencyUpdateTool, probes.DependencyToolUpdates)
if err != nil {
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
return checker.CreateRuntimeErrorResult(CheckDependencyUpdateTool, e)
}
// Return the score evaluation. // Return the score evaluation.
return evaluation.DependencyUpdateTool(CheckDependencyUpdateTool, c.Dlogger, &rawData) return evaluation.DependencyUpdateTool(CheckDependencyUpdateTool, findings)
} }

View File

@ -51,6 +51,7 @@ func TestDependencyUpdateTool(t *testing.T) {
CallSearchCommits: 0, CallSearchCommits: 0,
expected: scut.TestReturn{ expected: scut.TestReturn{
NumberOfInfo: 1, NumberOfInfo: 1,
NumberOfWarn: 3,
Score: 10, Score: 10,
}, },
}, },
@ -63,6 +64,7 @@ func TestDependencyUpdateTool(t *testing.T) {
CallSearchCommits: 0, CallSearchCommits: 0,
expected: scut.TestReturn{ expected: scut.TestReturn{
NumberOfInfo: 1, NumberOfInfo: 1,
NumberOfWarn: 3,
Score: 10, Score: 10,
}, },
}, },
@ -75,7 +77,7 @@ func TestDependencyUpdateTool(t *testing.T) {
SearchCommits: []clients.Commit{{Committer: clients.User{ID: 111111111}}}, SearchCommits: []clients.Commit{{Committer: clients.User{ID: 111111111}}},
CallSearchCommits: 1, CallSearchCommits: 1,
expected: scut.TestReturn{ expected: scut.TestReturn{
NumberOfWarn: 1, NumberOfWarn: 4,
}, },
}, },
{ {
@ -87,7 +89,7 @@ func TestDependencyUpdateTool(t *testing.T) {
SearchCommits: []clients.Commit{}, SearchCommits: []clients.Commit{},
CallSearchCommits: 1, CallSearchCommits: 1,
expected: scut.TestReturn{ expected: scut.TestReturn{
NumberOfWarn: 1, NumberOfWarn: 4,
}, },
}, },
@ -101,6 +103,7 @@ func TestDependencyUpdateTool(t *testing.T) {
CallSearchCommits: 1, CallSearchCommits: 1,
expected: scut.TestReturn{ expected: scut.TestReturn{
NumberOfInfo: 1, NumberOfInfo: 1,
NumberOfWarn: 3,
Score: 10, Score: 10,
}, },
}, },
@ -108,13 +111,14 @@ func TestDependencyUpdateTool(t *testing.T) {
name: "found in commits 2", name: "found in commits 2",
wantErr: false, wantErr: false,
files: []string{}, files: []string{},
SearchCommits: []clients.Commit{{Committer: clients.User{ID: 111111111}}, SearchCommits: []clients.Commit{
{Committer: clients.User{ID: 111111111}},
{Committer: clients.User{ID: dependabotID}}, {Committer: clients.User{ID: dependabotID}},
}, },
CallSearchCommits: 1, CallSearchCommits: 1,
expected: scut.TestReturn{ expected: scut.TestReturn{
NumberOfInfo: 1, NumberOfInfo: 1,
NumberOfWarn: 3,
Score: 10, Score: 10,
}, },
}, },
@ -125,12 +129,14 @@ func TestDependencyUpdateTool(t *testing.T) {
files: []string{ files: []string{
".github/foobar.yml", ".github/foobar.yml",
}, },
SearchCommits: []clients.Commit{{Committer: clients.User{ID: 111111111}}, SearchCommits: []clients.Commit{
{Committer: clients.User{ID: 111111111}},
{Committer: clients.User{ID: dependabotID}}, {Committer: clients.User{ID: dependabotID}},
}, },
CallSearchCommits: 1, CallSearchCommits: 1,
expected: scut.TestReturn{ expected: scut.TestReturn{
NumberOfInfo: 1, NumberOfInfo: 1,
NumberOfWarn: 3,
Score: 10, Score: 10,
}, },
}, },
@ -144,9 +150,11 @@ func TestDependencyUpdateTool(t *testing.T) {
mockRepo.EXPECT().ListFiles(gomock.Any()).Return(tt.files, nil) mockRepo.EXPECT().ListFiles(gomock.Any()).Return(tt.files, nil)
mockRepo.EXPECT().SearchCommits(gomock.Any()).Return(tt.SearchCommits, nil).Times(tt.CallSearchCommits) mockRepo.EXPECT().SearchCommits(gomock.Any()).Return(tt.SearchCommits, nil).Times(tt.CallSearchCommits)
dl := scut.TestDetailLogger{} dl := scut.TestDetailLogger{}
raw := checker.RawResults{}
c := &checker.CheckRequest{ c := &checker.CheckRequest{
RepoClient: mockRepo, RepoClient: mockRepo,
Dlogger: &dl, Dlogger: &dl,
RawResults: &raw,
} }
res := DependencyUpdateTool(c) res := DependencyUpdateTool(c)

View File

@ -15,51 +15,20 @@
package evaluation package evaluation
import ( import (
"fmt"
"github.com/ossf/scorecard/v4/checker" "github.com/ossf/scorecard/v4/checker"
sce "github.com/ossf/scorecard/v4/errors" "github.com/ossf/scorecard/v4/finding"
) )
// DependencyUpdateTool applies the score policy for the Dependency-Update-Tool check. // DependencyUpdateTool applies the score policy for the Dependency-Update-Tool check.
func DependencyUpdateTool(name string, dl checker.DetailLogger, func DependencyUpdateTool(name string,
r *checker.DependencyUpdateToolData, findings []finding.Finding,
) checker.CheckResult { ) checker.CheckResult {
if r == nil { for i := range findings {
e := sce.WithMessage(sce.ErrScorecardInternal, "empty raw data") f := &findings[i]
return checker.CreateRuntimeErrorResult(name, e) if f.Outcome == finding.OutcomePositive {
return checker.CreateMaxScoreResult(name, "update tool detected")
}
} }
// Apply the policy evaluation. return checker.CreateMinScoreResult(name, "no update tool detected")
if r.Tools == nil || len(r.Tools) == 0 {
dl.Warn(&checker.LogMessage{
Text: `Config file not detected in source location for dependabot, renovatebot, Sonatype Lift, or
PyUp (Python). We recommend setting this configuration in code so it can be easily verified by others.`,
})
return checker.CreateMinScoreResult(name, "no update tool detected")
}
// Validate the input.
if len(r.Tools) != 1 {
e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("found %d tools, expected 1", len(r.Tools)))
return checker.CreateRuntimeErrorResult(name, e)
}
if r.Tools[0].Files == nil {
e := sce.WithMessage(sce.ErrScorecardInternal, "Files are nil")
return checker.CreateRuntimeErrorResult(name, e)
}
// Iterate over all the files, since a Tool can contain multiple files.
for _, file := range r.Tools[0].Files {
dl.Info(&checker.LogMessage{
Path: file.Path,
Type: file.Type,
Offset: file.Offset,
Text: fmt.Sprintf("%s detected", r.Tools[0].Name),
})
}
// High score result.
return checker.CreateMaxScoreResult(name, "update tool detected")
} }

View File

@ -18,7 +18,6 @@ import (
"testing" "testing"
"github.com/ossf/scorecard/v4/checker" "github.com/ossf/scorecard/v4/checker"
sce "github.com/ossf/scorecard/v4/errors"
"github.com/ossf/scorecard/v4/finding" "github.com/ossf/scorecard/v4/finding"
scut "github.com/ossf/scorecard/v4/utests" scut "github.com/ossf/scorecard/v4/utests"
) )
@ -26,135 +25,91 @@ import (
func TestDependencyUpdateTool(t *testing.T) { func TestDependencyUpdateTool(t *testing.T) {
t.Parallel() t.Parallel()
//nolint //nolint
type args struct {
name string
dl checker.DetailLogger
r *checker.DependencyUpdateToolData
}
//nolint
tests := []struct { tests := []struct {
name string name string
args args findings []finding.Finding
want checker.CheckResult
err bool err bool
want checker.CheckResult
expected scut.TestReturn expected scut.TestReturn
}{ }{
{ {
name: "DependencyUpdateTool", name: "dependabot",
args: args{ findings: []finding.Finding{
name: "DependencyUpdateTool", {
dl: &scut.TestDetailLogger{}, Probe: "toolDependabotInstalled",
r: &checker.DependencyUpdateToolData{ Outcome: finding.OutcomePositive,
Tools: []checker.Tool{
{
Name: "DependencyUpdateTool",
},
},
},
},
want: checker.CheckResult{
Score: -1,
},
err: false,
expected: scut.TestReturn{
Error: sce.ErrScorecardInternal,
Score: -1,
},
},
{
name: "empty tool list",
args: args{
name: "DependencyUpdateTool",
dl: &scut.TestDetailLogger{},
r: &checker.DependencyUpdateToolData{
Tools: []checker.Tool{},
},
},
want: checker.CheckResult{
Score: 0,
Error: nil,
},
err: false,
expected: scut.TestReturn{
Score: 0,
NumberOfWarn: 1,
},
},
{
name: "Valid tool",
args: args{
name: "DependencyUpdateTool",
dl: &scut.TestDetailLogger{},
r: &checker.DependencyUpdateToolData{
Tools: []checker.Tool{
{
Name: "DependencyUpdateTool",
Files: []checker.File{
{
Path: "/etc/dependency-update-tool.conf",
Snippet: `
[dependency-update-tool]
enabled = true
`,
Type: finding.FileTypeSource,
},
},
},
},
}, },
}, },
want: checker.CheckResult{ want: checker.CheckResult{
Score: 10, Score: 10,
Error: nil,
}, },
expected: scut.TestReturn{
Error: nil,
Score: 10,
NumberOfInfo: 1,
},
err: false,
}, },
{ {
name: "more than one tool in the list", name: "renovate",
args: args{ findings: []finding.Finding{
name: "DependencyUpdateTool", {
dl: &scut.TestDetailLogger{}, Probe: "toolRenovateInstalled",
r: &checker.DependencyUpdateToolData{ Outcome: finding.OutcomePositive,
Tools: []checker.Tool{
{
Name: "DependencyUpdateTool",
},
{
Name: "DependencyUpdateTool",
},
},
}, },
}, },
want: checker.CheckResult{ want: checker.CheckResult{
Score: -1, Score: 10,
Error: nil,
}, },
expected: scut.TestReturn{
Error: sce.ErrScorecardInternal,
Score: -1,
},
err: false,
}, },
{ {
name: "Nil r", name: "pyup",
args: args{ findings: []finding.Finding{
name: "nil r", {
dl: &scut.TestDetailLogger{}, Probe: "toolPyUpInstalled",
Outcome: finding.OutcomePositive,
},
}, },
want: checker.CheckResult{ want: checker.CheckResult{
Score: -1, Score: 10,
},
},
{
name: "sonatype",
findings: []finding.Finding{
{
Probe: "toolSonatypeInstalled",
Outcome: finding.OutcomePositive,
},
},
want: checker.CheckResult{
Score: 10,
},
},
{
name: "none",
findings: []finding.Finding{
{
Probe: "toolDependabotInstalled",
Outcome: finding.OutcomeNegative,
},
{
Probe: "toolRenovateInstalled",
Outcome: finding.OutcomeNegative,
},
{
Probe: "toolPyUpInstalled",
Outcome: finding.OutcomeNegative,
},
{
Probe: "toolSonatypeInstalled",
Outcome: finding.OutcomeNegative,
},
},
want: checker.CheckResult{
Score: 0,
},
},
{
name: "empty tool list",
want: checker.CheckResult{
Score: 0,
Error: nil, Error: nil,
}, },
expected: scut.TestReturn{
Error: sce.ErrScorecardInternal,
Score: -1,
},
err: false,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -162,8 +117,7 @@ func TestDependencyUpdateTool(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Parallel() t.Parallel()
dl := scut.TestDetailLogger{} got := DependencyUpdateTool(tt.name, tt.findings)
got := DependencyUpdateTool(tt.args.name, &dl, tt.args.r)
if tt.want.Score != got.Score { if tt.want.Score != got.Score {
t.Errorf("DependencyUpdateTool() got Score = %v, want %v for %v", got.Score, tt.want.Score, tt.name) t.Errorf("DependencyUpdateTool() got Score = %v, want %v for %v", got.Score, tt.want.Score, tt.name)
} }
@ -171,10 +125,6 @@ func TestDependencyUpdateTool(t *testing.T) {
t.Errorf("DependencyUpdateTool() error = %v, want %v for %v", got.Error, tt.want.Error, tt.name) t.Errorf("DependencyUpdateTool() error = %v, want %v for %v", got.Error, tt.want.Error, tt.name)
return return
} }
if !scut.ValidateTestReturn(t, tt.name, &tt.expected, &got, &dl) {
t.Fatalf(tt.name)
}
}) })
} }
} }

View File

@ -126,13 +126,11 @@ var checkDependencyFileExists fileparser.DoWhileTrueOnFilename = func(name strin
}, },
}, },
}) })
default:
// Continue iterating.
return true, nil
} }
// We found a file, no need to continue iterating. // Continue iterating, even if we have found a tool.
return false, nil // It's needed for all probes results to be populated.
return true, nil
} }
func asPointer(s string) *string { func asPointer(s string) *string {

View File

@ -26,115 +26,84 @@ import (
func Test_checkDependencyFileExists(t *testing.T) { func Test_checkDependencyFileExists(t *testing.T) {
t.Parallel() t.Parallel()
//nolint
type args struct {
name string
data *[]checker.Tool
}
//nolint //nolint
tests := []struct { tests := []struct {
name string name string
args args path string
want bool want bool
wantErr bool wantErr bool
}{ }{
{ {
name: "check dependency file exists", name: ".github/dependabot.yml",
args: args{ path: ".github/dependabot.yml",
name: ".github/dependabot.yml",
data: &[]checker.Tool{},
},
want: false,
wantErr: false,
},
{
name: ".other",
args: args{
name: ".other",
data: &[]checker.Tool{},
},
want: true, want: true,
wantErr: false, wantErr: false,
}, },
{ {
name: ".github/renovate.json", name: ".github/dependabot.yaml",
args: args{ path: ".github/dependabot.yaml",
name: ".github/renovate.json", want: true,
data: &[]checker.Tool{}, wantErr: false,
}, },
{
name: ".other",
path: ".other",
want: false, want: false,
wantErr: false, wantErr: false,
}, },
{ {
name: ".github/renovate.json5", name: ".github/renovate.json",
args: args{ path: ".github/renovate.json",
name: ".github/renovate.json5", want: true,
data: &[]checker.Tool{},
},
want: false,
wantErr: false, wantErr: false,
}, },
{ {
name: ".renovaterc.json", name: ".github/renovate.json5",
args: args{ path: ".github/renovate.json5",
name: ".renovaterc.json", want: true,
data: &[]checker.Tool{},
},
want: false,
wantErr: false, wantErr: false,
}, },
{ {
name: "renovate.json", name: ".renovaterc.json",
args: args{ path: ".renovaterc.json",
name: "renovate.json", want: true,
data: &[]checker.Tool{},
},
want: false,
wantErr: false, wantErr: false,
}, },
{ {
name: "renovate.json5", name: "renovate.json",
args: args{ path: "renovate.json",
name: "renovate.json5", want: true,
data: &[]checker.Tool{},
},
want: false,
wantErr: false, wantErr: false,
}, },
{ {
name: ".renovaterc", name: "renovate.json5",
args: args{ path: "renovate.json5",
name: ".renovaterc", want: true,
data: &[]checker.Tool{},
},
want: false,
wantErr: false, wantErr: false,
}, },
{ {
name: ".pyup.yml", name: ".renovaterc",
args: args{ path: ".renovaterc",
name: ".pyup.yml", want: true,
data: &[]checker.Tool{},
},
want: false,
wantErr: false, wantErr: false,
}, },
{ {
name: ".lift.toml", name: ".pyup.yml",
args: args{ path: ".pyup.yml",
name: ".lift.toml", want: true,
data: &[]checker.Tool{},
},
want: false,
wantErr: false, wantErr: false,
}, },
{ {
name: ".lift/config.toml", name: ".lift.toml",
args: args{ path: ".lift.toml",
name: ".lift/config.toml", want: true,
data: &[]checker.Tool{}, wantErr: false,
}, },
want: false, {
name: ".lift/config.toml",
path: ".lift/config.toml",
want: true,
wantErr: false, wantErr: false,
}, },
} }
@ -142,13 +111,17 @@ func Test_checkDependencyFileExists(t *testing.T) {
tt := tt tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Parallel() t.Parallel()
got, err := checkDependencyFileExists(tt.args.name, tt.args.data) results := []checker.Tool{}
cont, err := checkDependencyFileExists(tt.path, &results)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("checkDependencyFileExists() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("checkDependencyFileExists() error = %v, wantErr %v", err, tt.wantErr)
return return
} }
if got != tt.want { if !cont {
t.Errorf("checkDependencyFileExists() = %v, want %v for test %v", got, tt.want, tt.name) t.Errorf("continue is false for %v", tt.name)
}
if tt.want != (len(results) == 1) {
t.Errorf("checkDependencyFileExists() = %v, want %v for test %v", len(results), tt.want, tt.name)
} }
}) })
} }

41
checks/run_probes.go Normal file
View File

@ -0,0 +1,41 @@
// Copyright 2023 OpenSSF 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 checks
import (
"fmt"
"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/finding"
"github.com/ossf/scorecard/v4/probes"
"github.com/ossf/scorecard/v4/probes/zrunner"
)
// evaluateProbes runs the probes in probesToRun and logs its findings.
func evaluateProbes(c *checker.CheckRequest, checkName string,
probesToRun []probes.ProbeImpl,
) ([]finding.Finding, error) {
// Run the probes.
findings, err := zrunner.Run(c.RawResults, probesToRun)
if err != nil {
return nil, fmt.Errorf("zrunner.Run: %w", err)
}
// Log the findings.
if err := checker.LogFindings(findings, c.Dlogger); err != nil {
return nil, fmt.Errorf("LogFindings: %w", err)
}
return findings, nil
}

View File

@ -39,16 +39,18 @@ var _ = Describe("E2E TEST:"+checks.CheckDependencyUpdateTool, func() {
err = repoClient.InitRepo(repo, clients.HeadSHA, 0) err = repoClient.InitRepo(repo, clients.HeadSHA, 0)
Expect(err).Should(BeNil()) Expect(err).Should(BeNil())
raw := checker.RawResults{}
req := checker.CheckRequest{ req := checker.CheckRequest{
Ctx: context.Background(), Ctx: context.Background(),
RepoClient: repoClient, RepoClient: repoClient,
Repo: repo, Repo: repo,
Dlogger: &dl, Dlogger: &dl,
RawResults: &raw,
} }
expected := scut.TestReturn{ expected := scut.TestReturn{
Error: nil, Error: nil,
Score: checker.MaxResultScore, Score: checker.MaxResultScore,
NumberOfWarn: 0, NumberOfWarn: 3,
NumberOfInfo: 1, NumberOfInfo: 1,
NumberOfDebug: 0, NumberOfDebug: 0,
} }
@ -66,16 +68,18 @@ var _ = Describe("E2E TEST:"+checks.CheckDependencyUpdateTool, func() {
err = repoClient.InitRepo(repo, clients.HeadSHA, 0) err = repoClient.InitRepo(repo, clients.HeadSHA, 0)
Expect(err).Should(BeNil()) Expect(err).Should(BeNil())
raw := checker.RawResults{}
req := checker.CheckRequest{ req := checker.CheckRequest{
Ctx: context.Background(), Ctx: context.Background(),
RepoClient: repoClient, RepoClient: repoClient,
Repo: repo, Repo: repo,
Dlogger: &dl, Dlogger: &dl,
RawResults: &raw,
} }
expected := scut.TestReturn{ expected := scut.TestReturn{
Error: nil, Error: nil,
Score: checker.MaxResultScore, Score: checker.MaxResultScore,
NumberOfWarn: 0, NumberOfWarn: 3,
NumberOfInfo: 1, NumberOfInfo: 1,
NumberOfDebug: 0, NumberOfDebug: 0,
} }

59
probes/entries.go Normal file
View File

@ -0,0 +1,59 @@
// Copyright 2023 OpenSSF 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 probes
import (
"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/finding"
"github.com/ossf/scorecard/v4/probes/toolDependabotInstalled"
"github.com/ossf/scorecard/v4/probes/toolPyUpInstalled"
"github.com/ossf/scorecard/v4/probes/toolRenovateInstalled"
"github.com/ossf/scorecard/v4/probes/toolSonatypeLiftInstalled"
)
// ProbeImpl is the implementation of a probe.
type ProbeImpl func(*checker.RawResults) ([]finding.Finding, string, error)
var (
// All represents all the probes.
All []ProbeImpl
// DependencyToolUpdates is all the probes for the
// DpendencyUpdateTool check.
DependencyToolUpdates = []ProbeImpl{
toolRenovateInstalled.Run,
toolDependabotInstalled.Run,
toolPyUpInstalled.Run,
toolSonatypeLiftInstalled.Run,
}
)
//nolint:gochecknoinits
func init() {
All = concatMultipleProbes([][]ProbeImpl{
DependencyToolUpdates,
})
}
func concatMultipleProbes(slices [][]ProbeImpl) []ProbeImpl {
var totalLen int
for _, s := range slices {
totalLen += len(s)
}
tmp := make([]ProbeImpl, 0, totalLen)
for _, s := range slices {
tmp = append(tmp, s...)
}
return tmp
}

View File

@ -0,0 +1,32 @@
# Copyright 2023 OpenSSF 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.
id: toolDependabotInstalled
short: Check that Dependabot is enabled
motivation: >
Out-of-date dependencies make a project vulnerable to known flaws and prone to attacks.
Dependabot automates the process of updating dependencies by scanning for outdated or insecure requirements, and opening a pull request to update them if found.
implementation: >
The implemtation looks for the presence of files named ".github/dependabot.yml" or ".github/dependabot.yaml". If none of these files are found,
the implementation checks whether commits are authored by Dependabot. If none of these succeed, Dependabot is not installed.
NOTE: if the configuration files are found, the probe does not ensure that the Dependabot is run or that the Dependabot's pull requests are merged.
outcome:
- If dependendabot is installed, the probe returns OutcomePositive (1)
- If dependendabot is not installed, the probe returns OutcomeNegative (0)
remediation:
effort: Low
text:
- Follow the instructions from https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates.
markdown:
- Follow the instructions from [the official documentation](https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates).

View File

@ -0,0 +1,53 @@
// Copyright 2022 OpenSSF 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.
// nolint:stylecheck
package toolDependabotInstalled
import (
"embed"
"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/finding"
"github.com/ossf/scorecard/v4/probes/utils"
)
//go:embed *.yml
var fs embed.FS
const probe = "toolDependabotInstalled"
type dependabot struct{}
func (t dependabot) Name() string {
return "Dependabot"
}
func (t dependabot) Matches(tool *checker.Tool) bool {
return t.Name() == tool.Name
}
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
tools := raw.DependencyUpdateToolResults.Tools
var matcher dependabot
// Check whether Dependabot tool is installed on the repo,
// and create the corresponding findings.
//nolint:wrapcheck
return utils.ToolsRun(tools, fs, probe,
// Tool found will generate a positive result.
finding.OutcomePositive,
// Tool not found will generate a negative result.
finding.OutcomeNegative,
matcher)
}

View File

@ -0,0 +1,32 @@
# Copyright 2023 OpenSSF 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.
id: toolPyUpInstalled
short: Check that PyUp is installed.
motivation: >
Out-of-date dependencies make a project vulnerable to known flaws and prone to attacks.
PyUp automates the process of updating dependencies by scanning for outdated or insecure requirements, and opening a pull request to update them if found.
implementation: >
The implementation looks for the presence of a file named ".pyup.yml".
If the file is not found, PyUp is not installed.
NOTE: the implementation does not ensure that PyUp is run or that PyUp's pull requests are merged.
outcome:
- If PyUp is installed, the probe returns OutcomePositive (1)
- If PyUp is not installed, the probe returns OutcomeNegative (0)
remediation:
effort: Low
text:
- Follow the instructions from https://docs.pyup.io/docs.
markdown:
- Follow the instructions from [the official documentation](https://docs.pyup.io/docs).

View File

@ -0,0 +1,53 @@
// Copyright 2022 OpenSSF 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.
// nolint:stylecheck
package toolPyUpInstalled
import (
"embed"
"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/finding"
"github.com/ossf/scorecard/v4/probes/utils"
)
//go:embed *.yml
var fs embed.FS
const probe = "toolPyUpInstalled"
type pyup struct{}
func (t pyup) Name() string {
return "PyUp"
}
func (t pyup) Matches(tool *checker.Tool) bool {
return t.Name() == tool.Name
}
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
tools := raw.DependencyUpdateToolResults.Tools
var matcher pyup
// Check whether PyUp tool is installed on the repo,
// and create the corresponding findings.
//nolint:wrapcheck
return utils.ToolsRun(tools, fs, probe,
// Tool found will generate a positive result.
finding.OutcomePositive,
// Tool not found will generate a negative result.
finding.OutcomeNegative,
matcher)
}

View File

@ -0,0 +1,32 @@
# Copyright 2023 OpenSSF 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.
id: toolRenovateInstalled
short: Check that Renovate bot is installed.
motivation: >
Out-of-date dependencies make a project vulnerable to known flaws and prone to attacks.
Renovate automates the process of updating dependencies by scanning for outdated or insecure requirements, and opening a pull request to update them if found.
implementation: >
The implementation looks for the presence of files named ".github/renovate.json", ".github/renovate.json5", ".renovaterc.json" or. "renovate.json".
If none of these files are found, Renovate is not installed.
NOTE: the implementation does not ensure that Renovate is run or that Renovate's pull requests are merged.
outcome:
- If Renovate is installed, the probe returns OutcomePositive (1)
- If Renovate is not installed, the probe returns OutcomeNegative (0)
remediation:
effort: Low
text:
- Follow the instructions from https://docs.renovatebot.com/configuration-options/.
markdown:
- Follow the instructions from [the official documentation](https://docs.renovatebot.com/configuration-options/).

View File

@ -0,0 +1,53 @@
// Copyright 2022 OpenSSF 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.
// nolint:stylecheck
package toolRenovateInstalled
import (
"embed"
"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/finding"
"github.com/ossf/scorecard/v4/probes/utils"
)
//go:embed *.yml
var fs embed.FS
const probe = "toolRenovateInstalled"
type renovate struct{}
func (t renovate) Name() string {
return "RenovateBot"
}
func (t renovate) Matches(tool *checker.Tool) bool {
return t.Name() == tool.Name
}
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
tools := raw.DependencyUpdateToolResults.Tools
var matcher renovate
// Check whether Renovate tool is installed on the repo,
// and create the corresponding findings.
//nolint:wrapcheck
return utils.ToolsRun(tools, fs, probe,
// Tool found will generate a positive result.
finding.OutcomePositive,
// Tool not found will generate a negative result.
finding.OutcomeNegative,
matcher)
}

View File

@ -0,0 +1,32 @@
# Copyright 2023 OpenSSF 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.
id: toolSonatypeLiftInstalled
short: Check that Sonatype Lyft is installed.
motivation: >
Out-of-date dependencies make a project vulnerable to known flaws and prone to attacks.
Sonatype Lyft automates the process of updating dependencies by scanning for outdated or insecure requirements, and opening a pull request to update them if found.
implementation: >
The implementation looks for the presence of files named ".lift.toml" or ".lift/config.toml".
If none of these files are found, Sonatype Lyft is not installed.
NOTE: the implementation does not ensure that Sonatype Lyft is run or that Sonatype Lyft's pull requests are merged.
outcome:
- If Sonatype Lyft is installed, the probe returns OutcomePositive (1)
- If Sonatype Lyft is not installed, the probe returns OutcomeNegative (0)
remediation:
effort: Low
text:
- Follow the instructions from https://help.sonatype.com/lift/getting-started.
markdown:
- Follow the instructions from [the official documentation](https://help.sonatype.com/lift/getting-started).

View File

@ -0,0 +1,53 @@
// Copyright 2022 OpenSSF 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.
// nolint:stylecheck
package toolSonatypeLiftInstalled
import (
"embed"
"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/finding"
"github.com/ossf/scorecard/v4/probes/utils"
)
//go:embed *.yml
var fs embed.FS
const probe = "toolSonatypeLiftInstalled"
type sonatypeLyft struct{}
func (t sonatypeLyft) Name() string {
return "Sonatype Lift"
}
func (t sonatypeLyft) Matches(tool *checker.Tool) bool {
return t.Name() == tool.Name
}
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
tools := raw.DependencyUpdateToolResults.Tools
var matcher sonatypeLyft
// Check whether Sona Lyft tool is installed on the repo,
// and create the corresponding findings.
//nolint:wrapcheck
return utils.ToolsRun(tools, fs, probe,
// Tool found will generate a positive result.
finding.OutcomePositive,
// Tool not found will generate a negative result.
finding.OutcomeNegative,
matcher)
}

69
probes/utils/tools.go Normal file
View File

@ -0,0 +1,69 @@
// Copyright 2023 OpenSSF 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 utils
import (
"embed"
"fmt"
"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/finding"
)
type toolMatcher interface {
Name() string
Matches(*checker.Tool) bool
}
// ToolsRun runs the probe for a tool.
// The function iterates thru the raw results and searches for a tool of interest that is used on a repository.
// The function uses 'matcher' to identify the tool of interest.
// If a tool is used in the repository, it creates a finding with the 'foundOutcome'.
// If not, it returns a finding with outcome 'notFoundOutcome'.
func ToolsRun(tools []checker.Tool, fs embed.FS, probeID string,
foundOutcome, notFoundOutcome finding.Outcome, matcher toolMatcher,
) ([]finding.Finding, string, error) {
var findings []finding.Finding
for i := range tools {
tool := &tools[i]
if !matcher.Matches(tool) {
continue
}
var loc *finding.Location
if len(tool.Files) > 0 {
loc = tool.Files[0].Location()
}
f, err := finding.NewWith(fs, probeID, fmt.Sprintf("tool '%s' is used", tool.Name),
loc, foundOutcome)
if err != nil {
return nil, probeID, fmt.Errorf("create finding: %w", err)
}
findings = append(findings, *f)
}
// No tools found.
if len(findings) == 0 {
f, err := finding.NewWith(fs, probeID, fmt.Sprintf("tool '%s' is not used", matcher.Name()),
nil, notFoundOutcome)
if err != nil {
return nil, probeID, fmt.Errorf("create finding: %w", err)
}
findings = append(findings, *f)
}
return findings, probeID, nil
}

51
probes/zrunner/runner.go Normal file
View File

@ -0,0 +1,51 @@
// Copyright 2023 OpenSSF 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 zrunner
import (
"errors"
"fmt"
"github.com/ossf/scorecard/v4/checker"
serrors "github.com/ossf/scorecard/v4/errors"
"github.com/ossf/scorecard/v4/finding"
"github.com/ossf/scorecard/v4/probes"
)
var errProbeRun = errors.New("probe run failure")
// Run runs the probes in probesToRun.
func Run(raw *checker.RawResults, probesToRun []probes.ProbeImpl) ([]finding.Finding, error) {
var results []finding.Finding
var errs []error
for _, probeFunc := range probesToRun {
findings, probeID, err := probeFunc(raw)
if err != nil {
errs = append(errs, err)
results = append(results,
finding.Finding{
Probe: probeID,
Outcome: finding.OutcomeError,
Message: serrors.WithMessage(serrors.ErrScorecardInternal, err.Error()).Error(),
})
continue
}
results = append(results, findings...)
}
if len(errs) > 0 {
return results, fmt.Errorf("%w: %v", errProbeRun, errs)
}
return results, nil
}