mirror of
https://github.com/ossf/scorecard.git
synced 2024-08-15 03:10:33 +03:00
✨ Raw results for Pinned-Dependencies (#1932)
* backup * update * update * draft * updates * updates * updates * updates * fix * linter * updates * updates * updates * updates * updates * updates * updates * linter * comments * linter * linter * tests * updates * updates * tests
This commit is contained in:
parent
23523f6d09
commit
4bd3391a36
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@ -879,5 +879,5 @@ jobs:
|
||||
run: |
|
||||
go env -w GOFLAGS=-mod=mod
|
||||
go install github.com/google/addlicense@2fe3ee94479d08be985a84861de4e6b06a1c7208
|
||||
addlicense -ignore "**/script-empty.sh" -ignore "pkg/testdata/**" -ignore "checks/testdata/**" -l apache -c 'Security Scorecard Authors' -v *
|
||||
addlicense -ignore "**/script-empty.sh" -ignore "testdata/**" -ignore "**/testdata/**" -l apache -c 'Security Scorecard Authors' -v *
|
||||
git diff --exit-code
|
||||
|
@ -31,6 +31,7 @@ type RawResults struct {
|
||||
DependencyUpdateToolResults DependencyUpdateToolData
|
||||
BranchProtectionResults BranchProtectionsData
|
||||
CodeReviewResults CodeReviewData
|
||||
PinningDependenciesResults PinningDependenciesData
|
||||
WebhookResults WebhooksData
|
||||
ContributorsResults ContributorsData
|
||||
MaintainedResults MaintainedData
|
||||
@ -64,6 +65,42 @@ type Package struct {
|
||||
Runs []Run
|
||||
}
|
||||
|
||||
// DependencyUseType reprensets a type of dependency use.
|
||||
type DependencyUseType string
|
||||
|
||||
const (
|
||||
// DependencyUseTypeGHAction is an action.
|
||||
DependencyUseTypeGHAction DependencyUseType = "GitHubAction"
|
||||
// DependencyUseTypeDockerfileContainerImage a container image used via FROM.
|
||||
DependencyUseTypeDockerfileContainerImage DependencyUseType = "containerImage"
|
||||
// DependencyUseTypeDownloadThenRun is a download followed by a run.
|
||||
DependencyUseTypeDownloadThenRun DependencyUseType = "downloadThenRun"
|
||||
// DependencyUseTypeGoCommand is a go command.
|
||||
DependencyUseTypeGoCommand DependencyUseType = "goCommand"
|
||||
// DependencyUseTypeChocoCommand is a choco command.
|
||||
DependencyUseTypeChocoCommand DependencyUseType = "chocoCommand"
|
||||
// DependencyUseTypeNpmCommand is an npm command.
|
||||
DependencyUseTypeNpmCommand DependencyUseType = "npmCommand"
|
||||
// DependencyUseTypePipCommand is a pipp command.
|
||||
DependencyUseTypePipCommand DependencyUseType = "pipCommand"
|
||||
)
|
||||
|
||||
// PinningDependenciesData represents pinned dependency data.
|
||||
type PinningDependenciesData struct {
|
||||
Dependencies []Dependency
|
||||
}
|
||||
|
||||
// Dependency represents a dependency.
|
||||
type Dependency struct {
|
||||
// TODO: unique dependency name.
|
||||
// TODO: Job *WorkflowJob
|
||||
Name *string
|
||||
PinnedAt *string
|
||||
Location *File
|
||||
Msg *string // Only for debug messages.
|
||||
Type DependencyUseType
|
||||
}
|
||||
|
||||
// MaintainedData contains the raw results
|
||||
// for the Maintained check.
|
||||
type MaintainedData struct {
|
||||
@ -165,10 +202,11 @@ type ArchivedStatus struct {
|
||||
|
||||
// File represents a file.
|
||||
type File struct {
|
||||
Path string
|
||||
Snippet string // Snippet of code
|
||||
Offset uint // Offset in the file of Path (line for source/text files).
|
||||
Type FileType // Type of file.
|
||||
Path string
|
||||
Snippet string // Snippet of code
|
||||
Offset uint // Offset in the file of Path (line for source/text files).
|
||||
EndOffset uint // End of offset in the file, e.g. if the command spans multiple lines.
|
||||
Type FileType // Type of file.
|
||||
// TODO: add hash.
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errInternalInvalidDockerFile = errors.New("invalid Dockerfile")
|
||||
errInvalidGitHubWorkflow = errors.New("invalid GitHub workflow")
|
||||
errInternalNameCannotBeEmpty = errors.New("name cannot be empty")
|
||||
errInternalCheckFuncCannotBeNil = errors.New("checkFunc cannot be nil")
|
||||
|
292
checks/evaluation/pinned_dependencies.go
Normal file
292
checks/evaluation/pinned_dependencies.go
Normal file
@ -0,0 +1,292 @@
|
||||
// Copyright 2021 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 evaluation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/checks/fileparser"
|
||||
sce "github.com/ossf/scorecard/v4/errors"
|
||||
"github.com/ossf/scorecard/v4/remediation"
|
||||
)
|
||||
|
||||
var errInvalidValue = errors.New("invalid value")
|
||||
|
||||
type pinnedResult int
|
||||
|
||||
const (
|
||||
pinnedUndefined pinnedResult = iota
|
||||
pinned
|
||||
notPinned
|
||||
)
|
||||
|
||||
// Structure to host information about pinned github
|
||||
// or third party dependencies.
|
||||
type worklowPinningResult struct {
|
||||
thirdParties pinnedResult
|
||||
gitHubOwned pinnedResult
|
||||
}
|
||||
|
||||
// PinningDependencies applies the score policy for the Pinned-Dependencies check.
|
||||
func PinningDependencies(name string, dl checker.DetailLogger,
|
||||
r *checker.PinningDependenciesData,
|
||||
) checker.CheckResult {
|
||||
if r == nil {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, "empty raw data")
|
||||
return checker.CreateRuntimeErrorResult(name, e)
|
||||
}
|
||||
|
||||
var wp worklowPinningResult
|
||||
pr := make(map[checker.DependencyUseType]pinnedResult)
|
||||
|
||||
for i := range r.Dependencies {
|
||||
rr := r.Dependencies[i]
|
||||
if rr.Location == nil {
|
||||
if rr.Msg == nil {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, "empty File field")
|
||||
return checker.CreateRuntimeErrorResult(name, e)
|
||||
}
|
||||
dl.Debug(&checker.LogMessage{
|
||||
Text: *rr.Msg,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if rr.Msg != nil {
|
||||
dl.Debug(&checker.LogMessage{
|
||||
Path: rr.Location.Path,
|
||||
Type: rr.Location.Type,
|
||||
Offset: rr.Location.Offset,
|
||||
EndOffset: rr.Location.EndOffset,
|
||||
Text: *rr.Msg,
|
||||
Snippet: rr.Location.Snippet,
|
||||
})
|
||||
} else {
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Path: rr.Location.Path,
|
||||
Type: rr.Location.Type,
|
||||
Offset: rr.Location.Offset,
|
||||
EndOffset: rr.Location.EndOffset,
|
||||
Text: generateText(&rr),
|
||||
Snippet: rr.Location.Snippet,
|
||||
Remediation: generateRemediation(&rr),
|
||||
})
|
||||
|
||||
// Update the pinning status.
|
||||
updatePinningResults(&rr, &wp, pr)
|
||||
}
|
||||
}
|
||||
|
||||
// Generate scores and Info results.
|
||||
// GitHub actions.
|
||||
actionScore, err := createReturnForIsGitHubActionsWorkflowPinned(wp, dl)
|
||||
if err != nil {
|
||||
return checker.CreateRuntimeErrorResult(name, err)
|
||||
}
|
||||
|
||||
// Docker files.
|
||||
dockerFromScore, err := createReturnForIsDockerfilePinned(pr, dl)
|
||||
if err != nil {
|
||||
return checker.CreateRuntimeErrorResult(name, err)
|
||||
}
|
||||
|
||||
// Docker downloads.
|
||||
dockerDownloadScore, err := createReturnForIsDockerfileFreeOfInsecureDownloads(pr, dl)
|
||||
if err != nil {
|
||||
return checker.CreateRuntimeErrorResult(name, err)
|
||||
}
|
||||
|
||||
// Script downloads.
|
||||
scriptScore, err := createReturnForIsShellScriptFreeOfInsecureDownloads(pr, dl)
|
||||
if err != nil {
|
||||
return checker.CreateRuntimeErrorResult(name, err)
|
||||
}
|
||||
|
||||
// Scores may be inconclusive.
|
||||
actionScore = maxScore(0, actionScore)
|
||||
dockerFromScore = maxScore(0, dockerFromScore)
|
||||
dockerDownloadScore = maxScore(0, dockerDownloadScore)
|
||||
scriptScore = maxScore(0, scriptScore)
|
||||
|
||||
score := checker.AggregateScores(actionScore, dockerFromScore,
|
||||
dockerDownloadScore, scriptScore)
|
||||
|
||||
if score == checker.MaxResultScore {
|
||||
return checker.CreateMaxScoreResult(name, "all dependencies are pinned")
|
||||
}
|
||||
|
||||
return checker.CreateProportionalScoreResult(name,
|
||||
"dependency not pinned by hash detected", score, checker.MaxResultScore)
|
||||
}
|
||||
|
||||
func generateRemediation(rr *checker.Dependency) *checker.Remediation {
|
||||
if rr.Type == checker.DependencyUseTypeGHAction {
|
||||
return remediation.CreateWorkflowPinningRemediation(rr.Location.Path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updatePinningResults(rr *checker.Dependency,
|
||||
wp *worklowPinningResult, pr map[checker.DependencyUseType]pinnedResult,
|
||||
) {
|
||||
if rr.Type == checker.DependencyUseTypeGHAction {
|
||||
// Note: `Snippet` contains `action/name@xxx`, so we cna use it to infer
|
||||
// if it's a GitHub-owned action or not.
|
||||
gitHubOwned := fileparser.IsGitHubOwnedAction(rr.Location.Snippet)
|
||||
addWorkflowPinnedResult(wp, false, gitHubOwned)
|
||||
return
|
||||
}
|
||||
|
||||
// Update other result types.
|
||||
var p pinnedResult
|
||||
addPinnedResult(&p, false)
|
||||
pr[rr.Type] = p
|
||||
}
|
||||
|
||||
func generateText(rr *checker.Dependency) string {
|
||||
if rr.Type == checker.DependencyUseTypeGHAction {
|
||||
// Check if we are dealing with a GitHub action or a third-party one.
|
||||
gitHubOwned := fileparser.IsGitHubOwnedAction(rr.Location.Snippet)
|
||||
owner := generateOwnerToDisplay(gitHubOwned)
|
||||
return fmt.Sprintf("%s %s not pinned by hash", owner, rr.Type)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s not pinned by hash", rr.Type)
|
||||
}
|
||||
|
||||
func generateOwnerToDisplay(gitHubOwned bool) string {
|
||||
if gitHubOwned {
|
||||
return "GitHub-owned"
|
||||
}
|
||||
return "third-party"
|
||||
}
|
||||
|
||||
// TODO(laurent): need to support GCB pinning.
|
||||
//nolint
|
||||
func maxScore(s1, s2 int) int {
|
||||
if s1 > s2 {
|
||||
return s1
|
||||
}
|
||||
return s2
|
||||
}
|
||||
|
||||
// For the 'to' param, true means the file is pinning dependencies (or there are no dependencies),
|
||||
// false means there are unpinned dependencies.
|
||||
func addPinnedResult(r *pinnedResult, to bool) {
|
||||
// If the result is `notPinned`, we keep it.
|
||||
// In other cases, we always update the result.
|
||||
if *r == notPinned {
|
||||
return
|
||||
}
|
||||
|
||||
switch to {
|
||||
case true:
|
||||
*r = pinned
|
||||
case false:
|
||||
*r = notPinned
|
||||
}
|
||||
}
|
||||
|
||||
func addWorkflowPinnedResult(w *worklowPinningResult, to, isGitHub bool) {
|
||||
if isGitHub {
|
||||
addPinnedResult(&w.gitHubOwned, to)
|
||||
} else {
|
||||
addPinnedResult(&w.thirdParties, to)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the result for scripts.
|
||||
func createReturnForIsShellScriptFreeOfInsecureDownloads(pr map[checker.DependencyUseType]pinnedResult,
|
||||
dl checker.DetailLogger,
|
||||
) (int, error) {
|
||||
return createReturnValues(pr, checker.DependencyUseTypeDownloadThenRun,
|
||||
"no insecure (not pinned by hash) dependency downloads found in shell scripts",
|
||||
dl)
|
||||
}
|
||||
|
||||
// Create the result for docker containers.
|
||||
func createReturnForIsDockerfilePinned(pr map[checker.DependencyUseType]pinnedResult,
|
||||
dl checker.DetailLogger,
|
||||
) (int, error) {
|
||||
return createReturnValues(pr, checker.DependencyUseTypeDockerfileContainerImage,
|
||||
"Dockerfile dependencies are pinned",
|
||||
dl)
|
||||
}
|
||||
|
||||
// Create the result for docker commands.
|
||||
func createReturnForIsDockerfileFreeOfInsecureDownloads(pr map[checker.DependencyUseType]pinnedResult,
|
||||
dl checker.DetailLogger,
|
||||
) (int, error) {
|
||||
return createReturnValues(pr, checker.DependencyUseTypeDownloadThenRun,
|
||||
"no insecure (not pinned by hash) dependency downloads found in Dockerfiles",
|
||||
dl)
|
||||
}
|
||||
|
||||
func createReturnValues(pr map[checker.DependencyUseType]pinnedResult,
|
||||
t checker.DependencyUseType, infoMsg string,
|
||||
dl checker.DetailLogger,
|
||||
) (int, error) {
|
||||
// Note: we don't check if the entry exists,
|
||||
// as it will have the default value which is handled in the switch statement.
|
||||
//nolint
|
||||
r, _ := pr[t]
|
||||
switch r {
|
||||
default:
|
||||
return checker.InconclusiveResultScore, fmt.Errorf("%w: %v", errInvalidValue, r)
|
||||
case pinned, pinnedUndefined:
|
||||
dl.Info(&checker.LogMessage{
|
||||
Text: infoMsg,
|
||||
})
|
||||
return checker.MaxResultScore, nil
|
||||
case notPinned:
|
||||
// No logging needed as it's done by the checks.
|
||||
return checker.MinResultScore, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Create the result.
|
||||
func createReturnForIsGitHubActionsWorkflowPinned(wp worklowPinningResult, dl checker.DetailLogger) (int, error) {
|
||||
return createReturnValuesForGitHubActionsWorkflowPinned(wp,
|
||||
fmt.Sprintf("%ss are pinned", checker.DependencyUseTypeGHAction),
|
||||
dl)
|
||||
}
|
||||
|
||||
func createReturnValuesForGitHubActionsWorkflowPinned(r worklowPinningResult, infoMsg string,
|
||||
dl checker.DetailLogger,
|
||||
) (int, error) {
|
||||
score := checker.MinResultScore
|
||||
|
||||
if r.gitHubOwned != notPinned {
|
||||
score += 2
|
||||
dl.Info(&checker.LogMessage{
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: checker.OffsetDefault,
|
||||
Text: fmt.Sprintf("%s %s", "GitHub-owned", infoMsg),
|
||||
})
|
||||
}
|
||||
|
||||
if r.thirdParties != notPinned {
|
||||
score += 8
|
||||
dl.Info(&checker.LogMessage{
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: checker.OffsetDefault,
|
||||
Text: fmt.Sprintf("%s %s", "Third-party", infoMsg),
|
||||
})
|
||||
}
|
||||
|
||||
return score, nil
|
||||
}
|
312
checks/evaluation/pinned_dependencies_test.go
Normal file
312
checks/evaluation/pinned_dependencies_test.go
Normal file
@ -0,0 +1,312 @@
|
||||
// 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 evaluation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
scut "github.com/ossf/scorecard/v4/utests"
|
||||
)
|
||||
|
||||
func Test_createReturnValuesForGitHubActionsWorkflowPinned(t *testing.T) {
|
||||
t.Parallel()
|
||||
//nolint
|
||||
type args struct {
|
||||
r worklowPinningResult
|
||||
infoMsg string
|
||||
dl checker.DetailLogger
|
||||
}
|
||||
//nolint
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "both actions workflow pinned",
|
||||
args: args{
|
||||
r: worklowPinningResult{
|
||||
thirdParties: 1,
|
||||
gitHubOwned: 1,
|
||||
},
|
||||
dl: &scut.TestDetailLogger{},
|
||||
},
|
||||
want: 10,
|
||||
},
|
||||
{
|
||||
name: "github actions workflow pinned",
|
||||
args: args{
|
||||
r: worklowPinningResult{
|
||||
thirdParties: 2,
|
||||
gitHubOwned: 2,
|
||||
},
|
||||
dl: &scut.TestDetailLogger{},
|
||||
},
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "error in github actions workflow pinned",
|
||||
args: args{
|
||||
r: worklowPinningResult{
|
||||
thirdParties: 2,
|
||||
gitHubOwned: 2,
|
||||
},
|
||||
dl: &scut.TestDetailLogger{},
|
||||
},
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt // Re-initializing variable so it is not changed while executing the closure below
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got, err := createReturnValuesForGitHubActionsWorkflowPinned(tt.args.r, tt.args.infoMsg, tt.args.dl)
|
||||
if err != nil {
|
||||
t.Errorf("error during createReturnValuesForGitHubActionsWorkflowPinned: %v", err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("createReturnValuesForGitHubActionsWorkflowPinned() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func asPointer(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func Test_PinningDependencies(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dependencies []checker.Dependency
|
||||
expected scut.TestReturn
|
||||
}{
|
||||
{
|
||||
name: "download then run pinned debug",
|
||||
dependencies: []checker.Dependency{
|
||||
{
|
||||
Location: &checker.File{},
|
||||
Msg: asPointer("some message"),
|
||||
Type: checker.DependencyUseTypeDownloadThenRun,
|
||||
},
|
||||
},
|
||||
expected: scut.TestReturn{
|
||||
Error: nil,
|
||||
Score: checker.MaxResultScore,
|
||||
NumberOfWarn: 0,
|
||||
NumberOfInfo: 5,
|
||||
NumberOfDebug: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "download then run pinned debug and warn",
|
||||
dependencies: []checker.Dependency{
|
||||
{
|
||||
Location: &checker.File{},
|
||||
Msg: asPointer("some message"),
|
||||
Type: checker.DependencyUseTypeDownloadThenRun,
|
||||
},
|
||||
{
|
||||
Location: &checker.File{},
|
||||
Type: checker.DependencyUseTypeDownloadThenRun,
|
||||
},
|
||||
},
|
||||
expected: scut.TestReturn{
|
||||
Error: nil,
|
||||
Score: 5,
|
||||
NumberOfWarn: 1,
|
||||
NumberOfInfo: 3,
|
||||
NumberOfDebug: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "various wanrings",
|
||||
dependencies: []checker.Dependency{
|
||||
{
|
||||
Location: &checker.File{},
|
||||
Type: checker.DependencyUseTypePipCommand,
|
||||
},
|
||||
{
|
||||
Location: &checker.File{},
|
||||
Type: checker.DependencyUseTypeDownloadThenRun,
|
||||
},
|
||||
{
|
||||
Location: &checker.File{},
|
||||
Type: checker.DependencyUseTypeDockerfileContainerImage,
|
||||
},
|
||||
{
|
||||
Location: &checker.File{},
|
||||
Msg: asPointer("debug message"),
|
||||
},
|
||||
},
|
||||
expected: scut.TestReturn{
|
||||
Error: nil,
|
||||
Score: 2,
|
||||
NumberOfWarn: 3,
|
||||
NumberOfInfo: 2,
|
||||
NumberOfDebug: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dl := scut.TestDetailLogger{}
|
||||
actual := PinningDependencies("checkname", &dl,
|
||||
&checker.PinningDependenciesData{
|
||||
Dependencies: tt.dependencies,
|
||||
})
|
||||
|
||||
if !scut.ValidateTestReturn(t, tt.name, &tt.expected, &actual, &dl) {
|
||||
t.Fail()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_createReturnValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
pr map[checker.DependencyUseType]pinnedResult
|
||||
dl *scut.TestDetailLogger
|
||||
t checker.DependencyUseType
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "returns 10 if no error and no pinnedResult",
|
||||
args: args{
|
||||
t: checker.DependencyUseTypeDownloadThenRun,
|
||||
dl: &scut.TestDetailLogger{},
|
||||
},
|
||||
want: 10,
|
||||
},
|
||||
{
|
||||
name: "returns 10 if pinned undefined",
|
||||
args: args{
|
||||
t: checker.DependencyUseTypeDownloadThenRun,
|
||||
pr: map[checker.DependencyUseType]pinnedResult{
|
||||
checker.DependencyUseTypeDownloadThenRun: pinnedUndefined,
|
||||
},
|
||||
dl: &scut.TestDetailLogger{},
|
||||
},
|
||||
want: 10,
|
||||
},
|
||||
{
|
||||
name: "returns 10 if pinned",
|
||||
args: args{
|
||||
t: checker.DependencyUseTypeDownloadThenRun,
|
||||
pr: map[checker.DependencyUseType]pinnedResult{
|
||||
checker.DependencyUseTypeDownloadThenRun: pinned,
|
||||
},
|
||||
dl: &scut.TestDetailLogger{},
|
||||
},
|
||||
want: 10,
|
||||
},
|
||||
{
|
||||
name: "returns 0 if unpinned",
|
||||
args: args{
|
||||
t: checker.DependencyUseTypeDownloadThenRun,
|
||||
pr: map[checker.DependencyUseType]pinnedResult{
|
||||
checker.DependencyUseTypeDownloadThenRun: notPinned,
|
||||
},
|
||||
dl: &scut.TestDetailLogger{},
|
||||
},
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got, err := createReturnValues(tt.args.pr, tt.args.t, "some message", tt.args.dl)
|
||||
if err != nil {
|
||||
t.Errorf("error during createReturnValues: %v", err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("createReturnValues() = %v, want %v", got, tt.want)
|
||||
}
|
||||
|
||||
if tt.want < 10 {
|
||||
return
|
||||
}
|
||||
|
||||
isExpectedLog := func(logMessage checker.LogMessage, logType checker.DetailType) bool {
|
||||
return logMessage.Text == "some message" && logType == checker.DetailInfo
|
||||
}
|
||||
if !scut.ValidateLogMessage(isExpectedLog, tt.args.dl) {
|
||||
t.Errorf("test failed: log message not present: %+v", "some message")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_maxScore(t *testing.T) {
|
||||
t.Parallel()
|
||||
type args struct {
|
||||
s1 int
|
||||
s2 int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "returns s1 if s1 is greater than s2",
|
||||
args: args{
|
||||
s1: 10,
|
||||
s2: 5,
|
||||
},
|
||||
want: 10,
|
||||
},
|
||||
{
|
||||
name: "returns s2 if s2 is greater than s1",
|
||||
args: args{
|
||||
s1: 5,
|
||||
s2: 10,
|
||||
},
|
||||
want: 10,
|
||||
},
|
||||
{
|
||||
name: "returns s1 if s1 is equal to s2",
|
||||
args: args{
|
||||
s1: 10,
|
||||
s2: 10,
|
||||
},
|
||||
want: 10,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := maxScore(tt.args.s1, tt.args.s2); got != tt.want {
|
||||
t.Errorf("maxScore() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -15,716 +15,47 @@
|
||||
package checks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||
"github.com/rhysd/actionlint"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/checks/fileparser"
|
||||
"github.com/ossf/scorecard/v4/checks/evaluation"
|
||||
"github.com/ossf/scorecard/v4/checks/raw"
|
||||
sce "github.com/ossf/scorecard/v4/errors"
|
||||
"github.com/ossf/scorecard/v4/remediation"
|
||||
)
|
||||
|
||||
// CheckPinnedDependencies is the registered name for FrozenDeps.
|
||||
const CheckPinnedDependencies = "Pinned-Dependencies"
|
||||
|
||||
// Structure to host information about pinned github
|
||||
// or third party dependencies.
|
||||
type worklowPinningResult struct {
|
||||
thirdParties pinnedResult
|
||||
gitHubOwned pinnedResult
|
||||
}
|
||||
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
supportedRequestTypes := []checker.RequestType{
|
||||
checker.FileBased,
|
||||
checker.CommitBased,
|
||||
}
|
||||
if err := registerCheck(CheckPinnedDependencies, PinnedDependencies, supportedRequestTypes); err != nil {
|
||||
if err := registerCheck(CheckPinnedDependencies, PinningDependencies, supportedRequestTypes); err != nil {
|
||||
// This should never happen.
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// PinnedDependencies will check the repository if it contains frozen dependecies.
|
||||
func PinnedDependencies(c *checker.CheckRequest) checker.CheckResult {
|
||||
// Lock file.
|
||||
/* WARNING: this code is inherently incorrect:
|
||||
- does not differentiate between libs and main
|
||||
- only looks at root folder.
|
||||
=> disabling to avoid false positives.
|
||||
lockScore, lockErr := isPackageManagerLockFilePresent(c)
|
||||
if lockErr != nil {
|
||||
return checker.CreateRuntimeErrorResult(CheckPinnedDependencies, lockErr)
|
||||
}
|
||||
*/
|
||||
|
||||
if remErr := remdiationSetup(c); remErr != nil {
|
||||
return checker.CreateRuntimeErrorResult(CheckPinnedDependencies, remErr)
|
||||
// PinningDependencies will check the repository for its use of dependencies.
|
||||
func PinningDependencies(c *checker.CheckRequest) checker.CheckResult {
|
||||
if err := remediation.Setup(c); err != nil {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("remdiationSetup: %v", err))
|
||||
return checker.CreateRuntimeErrorResult(CheckPinnedDependencies, e)
|
||||
}
|
||||
|
||||
// GitHub actions.
|
||||
actionScore, actionErr := isGitHubActionsWorkflowPinned(c)
|
||||
if actionErr != nil {
|
||||
return checker.CreateRuntimeErrorResult(CheckPinnedDependencies, actionErr)
|
||||
}
|
||||
|
||||
// Docker files.
|
||||
dockerFromScore, dockerFromErr := isDockerfilePinned(c)
|
||||
if dockerFromErr != nil {
|
||||
return checker.CreateRuntimeErrorResult(CheckPinnedDependencies, dockerFromErr)
|
||||
}
|
||||
|
||||
// Docker downloads.
|
||||
dockerDownloadScore, dockerDownloadErr := isDockerfileFreeOfInsecureDownloads(c)
|
||||
if dockerDownloadErr != nil {
|
||||
return checker.CreateRuntimeErrorResult(CheckPinnedDependencies, dockerDownloadErr)
|
||||
}
|
||||
|
||||
// Script downloads.
|
||||
scriptScore, scriptError := isShellScriptFreeOfInsecureDownloads(c)
|
||||
if scriptError != nil {
|
||||
return checker.CreateRuntimeErrorResult(CheckPinnedDependencies, scriptError)
|
||||
}
|
||||
|
||||
// Action script downloads.
|
||||
actionScriptScore, actionScriptError := isGitHubWorkflowScriptFreeOfInsecureDownloads(c)
|
||||
if actionScriptError != nil {
|
||||
return checker.CreateRuntimeErrorResult(CheckPinnedDependencies, actionScriptError)
|
||||
}
|
||||
|
||||
// Scores may be inconclusive.
|
||||
actionScore = maxScore(0, actionScore)
|
||||
dockerFromScore = maxScore(0, dockerFromScore)
|
||||
dockerDownloadScore = maxScore(0, dockerDownloadScore)
|
||||
scriptScore = maxScore(0, scriptScore)
|
||||
actionScriptScore = maxScore(0, actionScriptScore)
|
||||
score := checker.AggregateScores(actionScore, dockerFromScore,
|
||||
dockerDownloadScore, scriptScore, actionScriptScore)
|
||||
|
||||
if score == checker.MaxResultScore {
|
||||
return checker.CreateMaxScoreResult(CheckPinnedDependencies, "all dependencies are pinned")
|
||||
}
|
||||
return checker.CreateProportionalScoreResult(CheckPinnedDependencies,
|
||||
"dependency not pinned by hash detected", score, checker.MaxResultScore)
|
||||
}
|
||||
|
||||
// TODO(laurent): need to support GCB pinning.
|
||||
//nolint
|
||||
func maxScore(s1, s2 int) int {
|
||||
if s1 > s2 {
|
||||
return s1
|
||||
}
|
||||
return s2
|
||||
}
|
||||
|
||||
type pinnedResult int
|
||||
|
||||
const (
|
||||
pinnedUndefined pinnedResult = iota
|
||||
pinned
|
||||
notPinned
|
||||
)
|
||||
|
||||
// For the 'to' param, true means the file is pinning dependencies (or there are no dependencies),
|
||||
// false means there are unpinned dependencies.
|
||||
func addPinnedResult(r *pinnedResult, to bool) {
|
||||
// If the result is `notPinned`, we keep it.
|
||||
// In other cases, we always update the result.
|
||||
if *r == notPinned {
|
||||
return
|
||||
}
|
||||
|
||||
switch to {
|
||||
case true:
|
||||
*r = pinned
|
||||
case false:
|
||||
*r = notPinned
|
||||
}
|
||||
}
|
||||
|
||||
func dataAsWorkflowResultPointer(data interface{}) *worklowPinningResult {
|
||||
pdata, ok := data.(*worklowPinningResult)
|
||||
if !ok {
|
||||
// panic if it is not correct type
|
||||
panic("type need to be of worklowPinningResult")
|
||||
}
|
||||
return pdata
|
||||
}
|
||||
|
||||
func dataAsResultPointer(data interface{}) *pinnedResult {
|
||||
pdata, ok := data.(*pinnedResult)
|
||||
if !ok {
|
||||
// This never happens.
|
||||
panic("invalid type")
|
||||
}
|
||||
return pdata
|
||||
}
|
||||
|
||||
func dataAsDetailLogger(data interface{}) checker.DetailLogger {
|
||||
pdata, ok := data.(checker.DetailLogger)
|
||||
if !ok {
|
||||
// This never happens.
|
||||
panic("invalid type")
|
||||
}
|
||||
return pdata
|
||||
}
|
||||
|
||||
func createReturnValuesForGitHubActionsWorkflowPinned(r worklowPinningResult, infoMsg string,
|
||||
dl checker.DetailLogger, err error,
|
||||
) (int, error) {
|
||||
rawData, err := raw.PinningDependencies(c)
|
||||
if err != nil {
|
||||
return checker.InconclusiveResultScore, err
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
return checker.CreateRuntimeErrorResult(CheckPinnedDependencies, e)
|
||||
}
|
||||
|
||||
score := checker.MinResultScore
|
||||
|
||||
if r.gitHubOwned != notPinned {
|
||||
score += 2
|
||||
dl.Info(&checker.LogMessage{
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: checker.OffsetDefault,
|
||||
Text: fmt.Sprintf("%s %s", "GitHub-owned", infoMsg),
|
||||
})
|
||||
// Set the raw results.
|
||||
if c.RawResults != nil {
|
||||
c.RawResults.PinningDependenciesResults = rawData
|
||||
}
|
||||
|
||||
if r.thirdParties != notPinned {
|
||||
score += 8
|
||||
dl.Info(&checker.LogMessage{
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: checker.OffsetDefault,
|
||||
Text: fmt.Sprintf("%s %s", "Third-party", infoMsg),
|
||||
})
|
||||
}
|
||||
|
||||
return score, nil
|
||||
}
|
||||
|
||||
func createReturnValues(r pinnedResult, infoMsg string, dl checker.DetailLogger, err error) (int, error) {
|
||||
if err != nil {
|
||||
return checker.InconclusiveResultScore, err
|
||||
}
|
||||
|
||||
switch r {
|
||||
default:
|
||||
panic("invalid value")
|
||||
case pinned, pinnedUndefined:
|
||||
dl.Info(&checker.LogMessage{
|
||||
Text: infoMsg,
|
||||
})
|
||||
return checker.MaxResultScore, nil
|
||||
case notPinned:
|
||||
// No logging needed as it's done by the checks.
|
||||
return checker.MinResultScore, nil
|
||||
}
|
||||
}
|
||||
|
||||
func isShellScriptFreeOfInsecureDownloads(c *checker.CheckRequest) (int, error) {
|
||||
var r pinnedResult
|
||||
err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
|
||||
Pattern: "*",
|
||||
CaseSensitive: false,
|
||||
}, validateShellScriptIsFreeOfInsecureDownloads, c.Dlogger, &r)
|
||||
return createReturnForIsShellScriptFreeOfInsecureDownloads(r, c.Dlogger, err)
|
||||
}
|
||||
|
||||
func createReturnForIsShellScriptFreeOfInsecureDownloads(r pinnedResult,
|
||||
dl checker.DetailLogger, err error,
|
||||
) (int, error) {
|
||||
return createReturnValues(r,
|
||||
"no insecure (not pinned by hash) dependency downloads found in shell scripts",
|
||||
dl, err)
|
||||
}
|
||||
|
||||
var validateShellScriptIsFreeOfInsecureDownloads fileparser.DoWhileTrueOnFileContent = func(
|
||||
pathfn string,
|
||||
content []byte,
|
||||
args ...interface{},
|
||||
) (bool, error) {
|
||||
if len(args) != 2 {
|
||||
return false, fmt.Errorf(
|
||||
"validateShellScriptIsFreeOfInsecureDownloads requires exactly 2 arguments: %w", errInvalidArgLength)
|
||||
}
|
||||
|
||||
pdata := dataAsResultPointer(args[1])
|
||||
dl := dataAsDetailLogger(args[0])
|
||||
|
||||
// Validate the file type.
|
||||
if !isSupportedShellScriptFile(pathfn, content) {
|
||||
addPinnedResult(pdata, true)
|
||||
return true, nil
|
||||
}
|
||||
r, err := validateShellFile(pathfn, 0, 0 /*unknown*/, content, map[string]bool{}, dl)
|
||||
if err != nil {
|
||||
// Ignore parsing errors.
|
||||
if errors.Is(err, sce.ErrorShellParsing) {
|
||||
addPinnedResult(pdata, true)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
addPinnedResult(pdata, r)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func isDockerfileFreeOfInsecureDownloads(c *checker.CheckRequest) (int, error) {
|
||||
var r pinnedResult
|
||||
err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
|
||||
Pattern: "*Dockerfile*",
|
||||
CaseSensitive: false,
|
||||
}, validateDockerfileIsFreeOfInsecureDownloads, c.Dlogger, &r)
|
||||
return createReturnForIsDockerfileFreeOfInsecureDownloads(r, c.Dlogger, err)
|
||||
}
|
||||
|
||||
// Create the result.
|
||||
func createReturnForIsDockerfileFreeOfInsecureDownloads(r pinnedResult,
|
||||
dl checker.DetailLogger, err error,
|
||||
) (int, error) {
|
||||
return createReturnValues(r,
|
||||
"no insecure (not pinned by hash) dependency downloads found in Dockerfiles",
|
||||
dl, err)
|
||||
}
|
||||
|
||||
func isDockerfile(pathfn string, content []byte) bool {
|
||||
if strings.HasSuffix(pathfn, ".go") ||
|
||||
strings.HasSuffix(pathfn, ".c") ||
|
||||
strings.HasSuffix(pathfn, ".cpp") ||
|
||||
strings.HasSuffix(pathfn, ".rs") ||
|
||||
strings.HasSuffix(pathfn, ".js") ||
|
||||
strings.HasSuffix(pathfn, ".py") ||
|
||||
strings.HasSuffix(pathfn, ".pyc") ||
|
||||
strings.HasSuffix(pathfn, ".java") ||
|
||||
isShellScriptFile(pathfn, content) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var validateDockerfileIsFreeOfInsecureDownloads fileparser.DoWhileTrueOnFileContent = func(
|
||||
pathfn string,
|
||||
content []byte,
|
||||
args ...interface{},
|
||||
) (bool, error) {
|
||||
if len(args) != 2 {
|
||||
return false, fmt.Errorf(
|
||||
"validateDockerfileIsFreeOfInsecureDownloads requires exactly 2 arguments: %w", errInvalidArgLength)
|
||||
}
|
||||
pdata := dataAsResultPointer(args[1])
|
||||
dl := dataAsDetailLogger(args[0])
|
||||
|
||||
// Return early if this is not a docker file.
|
||||
if !isDockerfile(pathfn, content) {
|
||||
addPinnedResult(pdata, true)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if !fileparser.CheckFileContainsCommands(content, "#") {
|
||||
addPinnedResult(pdata, true)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
contentReader := strings.NewReader(string(content))
|
||||
res, err := parser.Parse(contentReader)
|
||||
if err != nil {
|
||||
return false, sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("%v: %v", errInternalInvalidDockerFile, err))
|
||||
}
|
||||
|
||||
// Walk the Dockerfile's AST.
|
||||
taintedFiles := make(map[string]bool)
|
||||
for i := range res.AST.Children {
|
||||
var bytes []byte
|
||||
|
||||
child := res.AST.Children[i]
|
||||
cmdType := child.Value
|
||||
|
||||
// Only look for the 'RUN' command.
|
||||
if cmdType != "RUN" {
|
||||
continue
|
||||
}
|
||||
|
||||
var valueList []string
|
||||
for n := child.Next; n != nil; n = n.Next {
|
||||
valueList = append(valueList, n.Value)
|
||||
}
|
||||
|
||||
if len(valueList) == 0 {
|
||||
return false, sce.WithMessage(sce.ErrScorecardInternal, errInternalInvalidDockerFile.Error())
|
||||
}
|
||||
|
||||
// Build a file content.
|
||||
cmd := strings.Join(valueList, " ")
|
||||
bytes = append(bytes, cmd...)
|
||||
r, err := validateShellFile(pathfn, uint(child.StartLine)-1, uint(child.EndLine)-1,
|
||||
bytes, taintedFiles, dl)
|
||||
if err != nil {
|
||||
// Ignore parsing errors.
|
||||
if errors.Is(err, sce.ErrorShellParsing) {
|
||||
addPinnedResult(pdata, true)
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
addPinnedResult(pdata, r)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func isDockerfilePinned(c *checker.CheckRequest) (int, error) {
|
||||
var r pinnedResult
|
||||
err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
|
||||
Pattern: "*Dockerfile*",
|
||||
CaseSensitive: false,
|
||||
}, validateDockerfileIsPinned, c.Dlogger, &r)
|
||||
return createReturnForIsDockerfilePinned(r, c.Dlogger, err)
|
||||
}
|
||||
|
||||
// Create the result.
|
||||
func createReturnForIsDockerfilePinned(r pinnedResult, dl checker.DetailLogger, err error) (int, error) {
|
||||
return createReturnValues(r,
|
||||
"Dockerfile dependencies are pinned",
|
||||
dl, err)
|
||||
}
|
||||
|
||||
var validateDockerfileIsPinned fileparser.DoWhileTrueOnFileContent = func(
|
||||
pathfn string,
|
||||
content []byte,
|
||||
args ...interface{},
|
||||
) (bool, error) {
|
||||
// Users may use various names, e.g.,
|
||||
// Dockerfile.aarch64, Dockerfile.template, Dockerfile_template, dockerfile, Dockerfile-name.template
|
||||
|
||||
if len(args) != 2 {
|
||||
return false, fmt.Errorf(
|
||||
"validateDockerfileIsPinned requires exactly 2 arguments: %w", errInvalidArgLength)
|
||||
}
|
||||
pdata := dataAsResultPointer(args[1])
|
||||
dl := dataAsDetailLogger(args[0])
|
||||
// Return early if this is not a dockerfile.
|
||||
if !isDockerfile(pathfn, content) {
|
||||
addPinnedResult(pdata, true)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if !fileparser.CheckFileContainsCommands(content, "#") {
|
||||
addPinnedResult(pdata, true)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if fileparser.IsTemplateFile(pathfn) {
|
||||
addPinnedResult(pdata, true)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// We have what looks like a docker file.
|
||||
// Let's interpret the content as utf8-encoded strings.
|
||||
contentReader := strings.NewReader(string(content))
|
||||
// The dependency must be pinned by sha256 hash, e.g.,
|
||||
// FROM something@sha256:${ARG},
|
||||
// FROM something:@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
|
||||
regex := regexp.MustCompile(`.*@sha256:([a-f\d]{64}|\${.*})`)
|
||||
|
||||
ret := true
|
||||
pinnedAsNames := make(map[string]bool)
|
||||
res, err := parser.Parse(contentReader)
|
||||
if err != nil {
|
||||
return false, sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("%v: %v", errInternalInvalidDockerFile, err))
|
||||
}
|
||||
|
||||
for _, child := range res.AST.Children {
|
||||
cmdType := child.Value
|
||||
if cmdType != "FROM" {
|
||||
continue
|
||||
}
|
||||
|
||||
var valueList []string
|
||||
for n := child.Next; n != nil; n = n.Next {
|
||||
valueList = append(valueList, n.Value)
|
||||
}
|
||||
|
||||
switch {
|
||||
// scratch is no-op.
|
||||
case len(valueList) > 0 && strings.EqualFold(valueList[0], "scratch"):
|
||||
continue
|
||||
|
||||
// FROM name AS newname.
|
||||
case len(valueList) == 3 && strings.EqualFold(valueList[1], "as"):
|
||||
name := valueList[0]
|
||||
asName := valueList[2]
|
||||
// Check if the name is pinned.
|
||||
// (1): name = <>@sha245:hash
|
||||
// (2): name = XXX where XXX was pinned
|
||||
pinned := pinnedAsNames[name]
|
||||
if pinned || regex.MatchString(name) {
|
||||
// Record the asName.
|
||||
pinnedAsNames[asName] = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Not pinned.
|
||||
ret = false
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: uint(child.StartLine),
|
||||
EndOffset: uint(child.EndLine),
|
||||
Text: "docker image not pinned by hash",
|
||||
Snippet: child.Original,
|
||||
})
|
||||
|
||||
// FROM name.
|
||||
case len(valueList) == 1:
|
||||
name := valueList[0]
|
||||
pinned := pinnedAsNames[name]
|
||||
if !pinned && !regex.MatchString(name) {
|
||||
ret = false
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: uint(child.StartLine),
|
||||
EndOffset: uint(child.EndLine),
|
||||
Text: "docker image not pinned by hash",
|
||||
Snippet: child.Original,
|
||||
})
|
||||
}
|
||||
|
||||
default:
|
||||
// That should not happen.
|
||||
return false, sce.WithMessage(sce.ErrScorecardInternal, errInternalInvalidDockerFile.Error())
|
||||
}
|
||||
}
|
||||
|
||||
//nolint
|
||||
// The file need not have a FROM statement,
|
||||
// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/tools/dockerfiles/partials/jupyter.partial.Dockerfile.
|
||||
|
||||
addPinnedResult(pdata, ret)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func isGitHubWorkflowScriptFreeOfInsecureDownloads(c *checker.CheckRequest) (int, error) {
|
||||
var r pinnedResult
|
||||
err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
|
||||
Pattern: ".github/workflows/*",
|
||||
CaseSensitive: false,
|
||||
}, validateGitHubWorkflowIsFreeOfInsecureDownloads, c.Dlogger, &r)
|
||||
return createReturnForIsGitHubWorkflowScriptFreeOfInsecureDownloads(r, c.Dlogger, err)
|
||||
}
|
||||
|
||||
// Create the result.
|
||||
func createReturnForIsGitHubWorkflowScriptFreeOfInsecureDownloads(r pinnedResult,
|
||||
dl checker.DetailLogger, err error,
|
||||
) (int, error) {
|
||||
return createReturnValues(r,
|
||||
"no insecure (not pinned by hash) dependency downloads found in GitHub workflows",
|
||||
dl, err)
|
||||
}
|
||||
|
||||
// validateGitHubWorkflowIsFreeOfInsecureDownloads checks if the workflow file downloads dependencies that are unpinned.
|
||||
// Returns true if the check should continue executing after this file.
|
||||
var validateGitHubWorkflowIsFreeOfInsecureDownloads fileparser.DoWhileTrueOnFileContent = func(
|
||||
pathfn string,
|
||||
content []byte,
|
||||
args ...interface{},
|
||||
) (bool, error) {
|
||||
if !fileparser.IsWorkflowFile(pathfn) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if len(args) != 2 {
|
||||
return false, fmt.Errorf(
|
||||
"validateGitHubWorkflowIsFreeOfInsecureDownloads requires exactly 2 arguments: %w", errInvalidArgLength)
|
||||
}
|
||||
pdata := dataAsResultPointer(args[1])
|
||||
dl := dataAsDetailLogger(args[0])
|
||||
|
||||
if !fileparser.CheckFileContainsCommands(content, "#") {
|
||||
addPinnedResult(pdata, true)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
workflow, errs := actionlint.Parse(content)
|
||||
if len(errs) > 0 && workflow == nil {
|
||||
// actionlint is a linter, so it will return errors when the yaml file does not meet its linting standards.
|
||||
// Often we don't care about these errors.
|
||||
return false, fileparser.FormatActionlintError(errs)
|
||||
}
|
||||
|
||||
githubVarRegex := regexp.MustCompile(`{{[^{}]*}}`)
|
||||
for jobName, job := range workflow.Jobs {
|
||||
jobName := jobName
|
||||
job := job
|
||||
if len(fileparser.GetJobName(job)) > 0 {
|
||||
jobName = fileparser.GetJobName(job)
|
||||
}
|
||||
taintedFiles := make(map[string]bool)
|
||||
for _, step := range job.Steps {
|
||||
step := step
|
||||
if !fileparser.IsStepExecKind(step, actionlint.ExecKindRun) {
|
||||
continue
|
||||
}
|
||||
|
||||
execRun, ok := step.Exec.(*actionlint.ExecRun)
|
||||
if !ok {
|
||||
stepName := fileparser.GetStepName(step)
|
||||
return false, sce.WithMessage(sce.ErrScorecardInternal,
|
||||
fmt.Sprintf("unable to parse step '%v' for job '%v'", jobName, stepName))
|
||||
}
|
||||
|
||||
if execRun == nil || execRun.Run == nil {
|
||||
// Cannot check further, continue.
|
||||
continue
|
||||
}
|
||||
|
||||
run := execRun.Run.Value
|
||||
// https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun.
|
||||
shell, err := fileparser.GetShellForStep(step, job)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Skip unsupported shells. We don't support Windows shells or some Unix shells.
|
||||
if !isSupportedShell(shell) {
|
||||
continue
|
||||
}
|
||||
|
||||
// We replace the `${{ github.variable }}` to avoid shell parsing failures.
|
||||
script := githubVarRegex.ReplaceAll([]byte(run), []byte("GITHUB_REDACTED_VAR"))
|
||||
validated, err := validateShellFile(pathfn, uint(execRun.Run.Pos.Line), uint(execRun.Run.Pos.Line),
|
||||
script, taintedFiles, dl)
|
||||
if err != nil {
|
||||
// Ignore parsing errors.
|
||||
if errors.Is(err, sce.ErrorShellParsing) {
|
||||
addPinnedResult(pdata, true)
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
addPinnedResult(pdata, validated)
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Check pinning of github actions in workflows.
|
||||
func isGitHubActionsWorkflowPinned(c *checker.CheckRequest) (int, error) {
|
||||
var r worklowPinningResult
|
||||
err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
|
||||
Pattern: ".github/workflows/*",
|
||||
CaseSensitive: true,
|
||||
}, validateGitHubActionWorkflow, c.Dlogger, &r)
|
||||
return createReturnForIsGitHubActionsWorkflowPinned(r, c.Dlogger, err)
|
||||
}
|
||||
|
||||
// Create the result.
|
||||
func createReturnForIsGitHubActionsWorkflowPinned(r worklowPinningResult, dl checker.DetailLogger,
|
||||
err error,
|
||||
) (int, error) {
|
||||
return createReturnValuesForGitHubActionsWorkflowPinned(r,
|
||||
"actions are pinned",
|
||||
dl, err)
|
||||
}
|
||||
|
||||
func generateOwnerToDisplay(gitHubOwned bool) string {
|
||||
if gitHubOwned {
|
||||
return "GitHub-owned"
|
||||
}
|
||||
return "third-party"
|
||||
}
|
||||
|
||||
// validateGitHubActionWorkflow checks if the workflow file contains unpinned actions. Returns true if the check
|
||||
// should continue executing after this file.
|
||||
var validateGitHubActionWorkflow fileparser.DoWhileTrueOnFileContent = func(
|
||||
pathfn string,
|
||||
content []byte,
|
||||
args ...interface{},
|
||||
) (bool, error) {
|
||||
if !fileparser.IsWorkflowFile(pathfn) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if len(args) != 2 {
|
||||
return false, fmt.Errorf(
|
||||
"validateGitHubActionWorkflow requires exactly 2 arguments: %w", errInvalidArgLength)
|
||||
}
|
||||
pdata := dataAsWorkflowResultPointer(args[1])
|
||||
dl := dataAsDetailLogger(args[0])
|
||||
|
||||
if !fileparser.CheckFileContainsCommands(content, "#") {
|
||||
addWorkflowPinnedResult(pdata, true, true)
|
||||
addWorkflowPinnedResult(pdata, true, true)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
workflow, errs := actionlint.Parse(content)
|
||||
if len(errs) > 0 && workflow == nil {
|
||||
// actionlint is a linter, so it will return errors when the yaml file does not meet its linting standards.
|
||||
// Often we don't care about these errors.
|
||||
return false, fileparser.FormatActionlintError(errs)
|
||||
}
|
||||
|
||||
hashRegex := regexp.MustCompile(`^.*@[a-f\d]{40,}`)
|
||||
for jobName, job := range workflow.Jobs {
|
||||
jobName := jobName
|
||||
job := job
|
||||
if len(fileparser.GetJobName(job)) > 0 {
|
||||
jobName = fileparser.GetJobName(job)
|
||||
}
|
||||
for _, step := range job.Steps {
|
||||
if !fileparser.IsStepExecKind(step, actionlint.ExecKindAction) {
|
||||
continue
|
||||
}
|
||||
execAction, ok := step.Exec.(*actionlint.ExecAction)
|
||||
if !ok {
|
||||
stepName := fileparser.GetStepName(step)
|
||||
return false, sce.WithMessage(sce.ErrScorecardInternal,
|
||||
fmt.Sprintf("unable to parse step '%v' for job '%v'", jobName, stepName))
|
||||
}
|
||||
|
||||
if execAction == nil || execAction.Uses == nil {
|
||||
// Cannot check further, continue.
|
||||
continue
|
||||
}
|
||||
|
||||
// nolint:lll
|
||||
// Check whether this is an action defined in the same repo,
|
||||
// https://docs.github.com/en/actions/learn-github-actions/finding-and-customizing-actions#referencing-an-action-in-the-same-repository-where-a-workflow-file-uses-the-action.
|
||||
if strings.HasPrefix(execAction.Uses.Value, "./") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if we are dealing with a GitHub action or a third-party one.
|
||||
gitHubOwned := fileparser.IsGitHubOwnedAction(execAction.Uses.Value)
|
||||
owner := generateOwnerToDisplay(gitHubOwned)
|
||||
|
||||
// Ensure a hash at least as large as SHA1 is used (40 hex characters).
|
||||
// Example: action-name@hash
|
||||
match := hashRegex.MatchString(execAction.Uses.Value)
|
||||
if !match {
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Path: pathfn, Type: checker.FileTypeSource,
|
||||
Offset: uint(execAction.Uses.Pos.Line),
|
||||
EndOffset: uint(execAction.Uses.Pos.Line), // `Uses` always span a single line.
|
||||
Snippet: execAction.Uses.Value,
|
||||
Text: fmt.Sprintf("%s action not pinned by hash", owner),
|
||||
Remediation: createWorkflowPinningRemediation(pathfn),
|
||||
})
|
||||
}
|
||||
|
||||
addWorkflowPinnedResult(pdata, match, gitHubOwned)
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func addWorkflowPinnedResult(w *worklowPinningResult, to, isGitHub bool) {
|
||||
if isGitHub {
|
||||
addPinnedResult(&w.gitHubOwned, to)
|
||||
} else {
|
||||
addPinnedResult(&w.thirdParties, to)
|
||||
}
|
||||
return evaluation.PinningDependencies(CheckPinnedDependencies, c.Dlogger, &rawData)
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -19,8 +19,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errInternalCommitishNil = errors.New("commitish is nil")
|
||||
errInvalidArgType = errors.New("invalid arg type")
|
||||
errInvalidArgLength = errors.New("invalid arg length")
|
||||
errInvalidGitHubWorkflow = errors.New("invalid GitHub workflow")
|
||||
errInternalInvalidDockerFile = errors.New("invalid Dockerfile")
|
||||
errInternalCommitishNil = errors.New("commitish is nil")
|
||||
errInvalidArgType = errors.New("invalid arg type")
|
||||
errInvalidArgLength = errors.New("invalid arg length")
|
||||
errInvalidGitHubWorkflow = errors.New("invalid GitHub workflow")
|
||||
)
|
||||
|
499
checks/raw/pinned_dependencies.go
Normal file
499
checks/raw/pinned_dependencies.go
Normal file
@ -0,0 +1,499 @@
|
||||
// Copyright 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 raw
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||
"github.com/rhysd/actionlint"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/checks/fileparser"
|
||||
sce "github.com/ossf/scorecard/v4/errors"
|
||||
)
|
||||
|
||||
// PinningDependencies checks for (un)pinned dependencies.
|
||||
func PinningDependencies(c *checker.CheckRequest) (checker.PinningDependenciesData, error) {
|
||||
var results checker.PinningDependenciesData
|
||||
// GitHub actions.
|
||||
if err := collectGitHubActionsWorkflowPinning(c, &results); err != nil {
|
||||
return checker.PinningDependenciesData{}, err
|
||||
}
|
||||
|
||||
// // Docker files.
|
||||
if err := collectDockerfilePinning(c, &results); err != nil {
|
||||
return checker.PinningDependenciesData{}, err
|
||||
}
|
||||
|
||||
// Docker downloads.
|
||||
if err := collectDockerfileInsecureDownloads(c, &results); err != nil {
|
||||
return checker.PinningDependenciesData{}, err
|
||||
}
|
||||
|
||||
// Script downloads.
|
||||
if err := collectShellScriptInsecureDownloads(c, &results); err != nil {
|
||||
return checker.PinningDependenciesData{}, err
|
||||
}
|
||||
|
||||
// Action script downloads.
|
||||
if err := collectGitHubWorkflowScriptInsecureDownloads(c, &results); err != nil {
|
||||
return checker.PinningDependenciesData{}, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func dataAsPinnedDependenciesPointer(data interface{}) *checker.PinningDependenciesData {
|
||||
pdata, ok := data.(*checker.PinningDependenciesData)
|
||||
if !ok {
|
||||
// panic if it is not correct type
|
||||
panic(fmt.Sprintf("expected type PinningDependenciesData, got %v", reflect.TypeOf(data)))
|
||||
}
|
||||
return pdata
|
||||
}
|
||||
|
||||
func collectShellScriptInsecureDownloads(c *checker.CheckRequest, r *checker.PinningDependenciesData) error {
|
||||
return fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
|
||||
Pattern: "*",
|
||||
CaseSensitive: false,
|
||||
}, validateShellScriptIsFreeOfInsecureDownloads, r)
|
||||
}
|
||||
|
||||
var validateShellScriptIsFreeOfInsecureDownloads fileparser.DoWhileTrueOnFileContent = func(
|
||||
pathfn string,
|
||||
content []byte,
|
||||
args ...interface{},
|
||||
) (bool, error) {
|
||||
if len(args) != 1 {
|
||||
return false, fmt.Errorf(
|
||||
"validateShellScriptIsFreeOfInsecureDownloads requires exactly 1 arguments: got %v: %w",
|
||||
len(args), errInvalidArgLength)
|
||||
}
|
||||
|
||||
pdata := dataAsPinnedDependenciesPointer(args[0])
|
||||
|
||||
// Validate the file type.
|
||||
if !isSupportedShellScriptFile(pathfn, content) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if err := validateShellFile(pathfn, 0, 0, content, map[string]bool{}, pdata); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func collectDockerfileInsecureDownloads(c *checker.CheckRequest, r *checker.PinningDependenciesData) error {
|
||||
return fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
|
||||
Pattern: "*Dockerfile*",
|
||||
CaseSensitive: false,
|
||||
}, validateDockerfileInsecureDownloads, r)
|
||||
}
|
||||
|
||||
var validateDockerfileInsecureDownloads fileparser.DoWhileTrueOnFileContent = func(
|
||||
pathfn string,
|
||||
content []byte,
|
||||
args ...interface{},
|
||||
) (bool, error) {
|
||||
if len(args) != 1 {
|
||||
return false, fmt.Errorf(
|
||||
"validateDockerfileInsecureDownloads requires exactly 1 arguments: got %v: %w",
|
||||
len(args), errInvalidArgLength)
|
||||
}
|
||||
|
||||
pdata := dataAsPinnedDependenciesPointer(args[0])
|
||||
|
||||
// Return early if this is not a docker file.
|
||||
if !isDockerfile(pathfn, content) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if !fileparser.CheckFileContainsCommands(content, "#") {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
contentReader := strings.NewReader(string(content))
|
||||
res, err := parser.Parse(contentReader)
|
||||
if err != nil {
|
||||
return false, sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("%v: %v", errInternalInvalidDockerFile, err))
|
||||
}
|
||||
|
||||
// Walk the Dockerfile's AST.
|
||||
taintedFiles := make(map[string]bool)
|
||||
for i := range res.AST.Children {
|
||||
var bytes []byte
|
||||
|
||||
child := res.AST.Children[i]
|
||||
cmdType := child.Value
|
||||
|
||||
// Only look for the 'RUN' command.
|
||||
if cmdType != "RUN" {
|
||||
continue
|
||||
}
|
||||
|
||||
var valueList []string
|
||||
for n := child.Next; n != nil; n = n.Next {
|
||||
valueList = append(valueList, n.Value)
|
||||
}
|
||||
|
||||
if len(valueList) == 0 {
|
||||
return false, sce.WithMessage(sce.ErrScorecardInternal, errInternalInvalidDockerFile.Error())
|
||||
}
|
||||
|
||||
// Build a file content.
|
||||
cmd := strings.Join(valueList, " ")
|
||||
bytes = append(bytes, cmd...)
|
||||
if err := validateShellFile(pathfn, uint(child.StartLine)-1, uint(child.EndLine)-1,
|
||||
bytes, taintedFiles, pdata); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func isDockerfile(pathfn string, content []byte) bool {
|
||||
if strings.HasSuffix(pathfn, ".go") ||
|
||||
strings.HasSuffix(pathfn, ".c") ||
|
||||
strings.HasSuffix(pathfn, ".cpp") ||
|
||||
strings.HasSuffix(pathfn, ".rs") ||
|
||||
strings.HasSuffix(pathfn, ".js") ||
|
||||
strings.HasSuffix(pathfn, ".py") ||
|
||||
strings.HasSuffix(pathfn, ".pyc") ||
|
||||
strings.HasSuffix(pathfn, ".java") ||
|
||||
isShellScriptFile(pathfn, content) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func collectDockerfilePinning(c *checker.CheckRequest, r *checker.PinningDependenciesData) error {
|
||||
return fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
|
||||
Pattern: "*Dockerfile*",
|
||||
CaseSensitive: false,
|
||||
}, validateDockerfilesPinning, r)
|
||||
}
|
||||
|
||||
var validateDockerfilesPinning fileparser.DoWhileTrueOnFileContent = func(
|
||||
pathfn string,
|
||||
content []byte,
|
||||
args ...interface{},
|
||||
) (bool, error) {
|
||||
// Users may use various names, e.g.,
|
||||
// Dockerfile.aarch64, Dockerfile.template, Dockerfile_template, dockerfile, Dockerfile-name.template
|
||||
|
||||
if len(args) != 1 {
|
||||
return false, fmt.Errorf(
|
||||
"validateDockerfilesPinning requires exactly 2 arguments: got %v: %w", len(args), errInvalidArgLength)
|
||||
}
|
||||
pdata := dataAsPinnedDependenciesPointer(args[0])
|
||||
|
||||
// Return early if this is not a dockerfile.
|
||||
if !isDockerfile(pathfn, content) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if !fileparser.CheckFileContainsCommands(content, "#") {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if fileparser.IsTemplateFile(pathfn) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// We have what looks like a docker file.
|
||||
// Let's interpret the content as utf8-encoded strings.
|
||||
contentReader := strings.NewReader(string(content))
|
||||
// The dependency must be pinned by sha256 hash, e.g.,
|
||||
// FROM something@sha256:${ARG},
|
||||
// FROM something:@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
|
||||
regex := regexp.MustCompile(`.*@sha256:([a-f\d]{64}|\${.*})`)
|
||||
|
||||
pinnedAsNames := make(map[string]bool)
|
||||
res, err := parser.Parse(contentReader)
|
||||
if err != nil {
|
||||
return false, sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("%v: %v", errInternalInvalidDockerFile, err))
|
||||
}
|
||||
|
||||
for _, child := range res.AST.Children {
|
||||
cmdType := child.Value
|
||||
if cmdType != "FROM" {
|
||||
continue
|
||||
}
|
||||
|
||||
var valueList []string
|
||||
for n := child.Next; n != nil; n = n.Next {
|
||||
valueList = append(valueList, n.Value)
|
||||
}
|
||||
|
||||
switch {
|
||||
// scratch is no-op.
|
||||
case len(valueList) > 0 && strings.EqualFold(valueList[0], "scratch"):
|
||||
continue
|
||||
|
||||
// FROM name AS newname.
|
||||
case len(valueList) == 3 && strings.EqualFold(valueList[1], "as"):
|
||||
name := valueList[0]
|
||||
asName := valueList[2]
|
||||
// Check if the name is pinned.
|
||||
// (1): name = <>@sha245:hash
|
||||
// (2): name = XXX where XXX was pinned
|
||||
pinned := pinnedAsNames[name]
|
||||
if pinned || regex.MatchString(name) {
|
||||
// Record the asName.
|
||||
pinnedAsNames[asName] = true
|
||||
continue
|
||||
}
|
||||
|
||||
pdata.Dependencies = append(pdata.Dependencies,
|
||||
checker.Dependency{
|
||||
Location: &checker.File{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: uint(child.StartLine),
|
||||
EndOffset: uint(child.EndLine),
|
||||
Snippet: child.Original,
|
||||
},
|
||||
Name: asPointer(name),
|
||||
PinnedAt: asPointer(asName),
|
||||
Type: checker.DependencyUseTypeDockerfileContainerImage,
|
||||
},
|
||||
)
|
||||
|
||||
// FROM name.
|
||||
case len(valueList) == 1:
|
||||
name := valueList[0]
|
||||
pinned := pinnedAsNames[name]
|
||||
if !pinned && !regex.MatchString(name) {
|
||||
dep := checker.Dependency{
|
||||
Location: &checker.File{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: uint(child.StartLine),
|
||||
EndOffset: uint(child.EndLine),
|
||||
Snippet: child.Original,
|
||||
},
|
||||
Type: checker.DependencyUseTypeDockerfileContainerImage,
|
||||
}
|
||||
parts := strings.SplitN(name, ":", 2)
|
||||
if len(parts) > 0 {
|
||||
dep.Name = asPointer(parts[0])
|
||||
if len(parts) > 1 {
|
||||
dep.PinnedAt = asPointer(parts[1])
|
||||
}
|
||||
}
|
||||
pdata.Dependencies = append(pdata.Dependencies, dep)
|
||||
}
|
||||
|
||||
default:
|
||||
// That should not happen.
|
||||
return false, sce.WithMessage(sce.ErrScorecardInternal, errInternalInvalidDockerFile.Error())
|
||||
}
|
||||
}
|
||||
|
||||
//nolint
|
||||
// The file need not have a FROM statement,
|
||||
// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/tools/dockerfiles/partials/jupyter.partial.Dockerfile.
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func collectGitHubWorkflowScriptInsecureDownloads(c *checker.CheckRequest, r *checker.PinningDependenciesData) error {
|
||||
return fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
|
||||
Pattern: ".github/workflows/*",
|
||||
CaseSensitive: false,
|
||||
}, validateGitHubWorkflowIsFreeOfInsecureDownloads, r)
|
||||
}
|
||||
|
||||
// validateGitHubWorkflowIsFreeOfInsecureDownloads checks if the workflow file downloads dependencies that are unpinned.
|
||||
// Returns true if the check should continue executing after this file.
|
||||
var validateGitHubWorkflowIsFreeOfInsecureDownloads fileparser.DoWhileTrueOnFileContent = func(
|
||||
pathfn string,
|
||||
content []byte,
|
||||
args ...interface{},
|
||||
) (bool, error) {
|
||||
if !fileparser.IsWorkflowFile(pathfn) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if len(args) != 1 {
|
||||
return false, fmt.Errorf(
|
||||
"validateGitHubWorkflowIsFreeOfInsecureDownloads requires exactly 1 arguments: got %v: %w",
|
||||
len(args), errInvalidArgLength)
|
||||
}
|
||||
|
||||
pdata := dataAsPinnedDependenciesPointer(args[0])
|
||||
if !fileparser.CheckFileContainsCommands(content, "#") {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
workflow, errs := actionlint.Parse(content)
|
||||
if len(errs) > 0 && workflow == nil {
|
||||
// actionlint is a linter, so it will return errors when the yaml file does not meet its linting standards.
|
||||
// Often we don't care about these errors.
|
||||
return false, fileparser.FormatActionlintError(errs)
|
||||
}
|
||||
|
||||
githubVarRegex := regexp.MustCompile(`{{[^{}]*}}`)
|
||||
for jobName, job := range workflow.Jobs {
|
||||
jobName := jobName
|
||||
job := job
|
||||
if len(fileparser.GetJobName(job)) > 0 {
|
||||
jobName = fileparser.GetJobName(job)
|
||||
}
|
||||
taintedFiles := make(map[string]bool)
|
||||
for _, step := range job.Steps {
|
||||
step := step
|
||||
if !fileparser.IsStepExecKind(step, actionlint.ExecKindRun) {
|
||||
continue
|
||||
}
|
||||
|
||||
execRun, ok := step.Exec.(*actionlint.ExecRun)
|
||||
if !ok {
|
||||
stepName := fileparser.GetStepName(step)
|
||||
return false, sce.WithMessage(sce.ErrScorecardInternal,
|
||||
fmt.Sprintf("unable to parse step '%v' for job '%v'", jobName, stepName))
|
||||
}
|
||||
|
||||
if execRun == nil || execRun.Run == nil {
|
||||
// Cannot check further, continue.
|
||||
continue
|
||||
}
|
||||
|
||||
run := execRun.Run.Value
|
||||
// https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun.
|
||||
shell, err := fileparser.GetShellForStep(step, job)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Skip unsupported shells. We don't support Windows shells or some Unix shells.
|
||||
if !isSupportedShell(shell) {
|
||||
continue
|
||||
}
|
||||
|
||||
// We replace the `${{ github.variable }}` to avoid shell parsing failures.
|
||||
script := githubVarRegex.ReplaceAll([]byte(run), []byte("GITHUB_REDACTED_VAR"))
|
||||
if err := validateShellFile(pathfn, uint(execRun.Run.Pos.Line), uint(execRun.Run.Pos.Line),
|
||||
script, taintedFiles, pdata); err != nil {
|
||||
pdata.Dependencies = append(pdata.Dependencies, checker.Dependency{
|
||||
Msg: asPointer(err.Error()),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Check pinning of github actions in workflows.
|
||||
func collectGitHubActionsWorkflowPinning(c *checker.CheckRequest, r *checker.PinningDependenciesData) error {
|
||||
return fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
|
||||
Pattern: ".github/workflows/*",
|
||||
CaseSensitive: true,
|
||||
}, validateGitHubActionWorkflow, r)
|
||||
}
|
||||
|
||||
// validateGitHubActionWorkflow checks if the workflow file contains unpinned actions. Returns true if the check
|
||||
// should continue executing after this file.
|
||||
var validateGitHubActionWorkflow fileparser.DoWhileTrueOnFileContent = func(
|
||||
pathfn string,
|
||||
content []byte,
|
||||
args ...interface{},
|
||||
) (bool, error) {
|
||||
if !fileparser.IsWorkflowFile(pathfn) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if len(args) != 1 {
|
||||
return false, fmt.Errorf(
|
||||
"validateGitHubActionWorkflow requires exactly 1 arguments: got %v: %w", len(args), errInvalidArgLength)
|
||||
}
|
||||
pdata := dataAsPinnedDependenciesPointer(args[0])
|
||||
|
||||
if !fileparser.CheckFileContainsCommands(content, "#") {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
workflow, errs := actionlint.Parse(content)
|
||||
if len(errs) > 0 && workflow == nil {
|
||||
// actionlint is a linter, so it will return errors when the yaml file does not meet its linting standards.
|
||||
// Often we don't care about these errors.
|
||||
return false, fileparser.FormatActionlintError(errs)
|
||||
}
|
||||
|
||||
hashRegex := regexp.MustCompile(`^.*@[a-f\d]{40,}`)
|
||||
for jobName, job := range workflow.Jobs {
|
||||
jobName := jobName
|
||||
job := job
|
||||
if len(fileparser.GetJobName(job)) > 0 {
|
||||
jobName = fileparser.GetJobName(job)
|
||||
}
|
||||
for _, step := range job.Steps {
|
||||
if !fileparser.IsStepExecKind(step, actionlint.ExecKindAction) {
|
||||
continue
|
||||
}
|
||||
|
||||
execAction, ok := step.Exec.(*actionlint.ExecAction)
|
||||
if !ok {
|
||||
stepName := fileparser.GetStepName(step)
|
||||
return false, sce.WithMessage(sce.ErrScorecardInternal,
|
||||
fmt.Sprintf("unable to parse step '%v' for job '%v'", jobName, stepName))
|
||||
}
|
||||
|
||||
if execAction == nil || execAction.Uses == nil {
|
||||
// Cannot check further, continue.
|
||||
continue
|
||||
}
|
||||
|
||||
// nolint:lll
|
||||
// Check whether this is an action defined in the same repo,
|
||||
// https://docs.github.com/en/actions/learn-github-actions/finding-and-customizing-actions#referencing-an-action-in-the-same-repository-where-a-workflow-file-uses-the-action.
|
||||
if strings.HasPrefix(execAction.Uses.Value, "./") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure a hash at least as large as SHA1 is used (40 hex characters).
|
||||
// Example: action-name@hash
|
||||
match := hashRegex.MatchString(execAction.Uses.Value)
|
||||
if !match {
|
||||
dep := checker.Dependency{
|
||||
Location: &checker.File{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: uint(execAction.Uses.Pos.Line),
|
||||
EndOffset: uint(execAction.Uses.Pos.Line), // `Uses` always span a single line.
|
||||
Snippet: execAction.Uses.Value,
|
||||
},
|
||||
Type: checker.DependencyUseTypeGHAction,
|
||||
}
|
||||
parts := strings.SplitN(execAction.Uses.Value, "@", 2)
|
||||
if len(parts) > 0 {
|
||||
dep.Name = asPointer(parts[0])
|
||||
if len(parts) > 1 {
|
||||
dep.PinnedAt = asPointer(parts[1])
|
||||
}
|
||||
}
|
||||
pdata.Dependencies = append(pdata.Dependencies, dep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
1192
checks/raw/pinned_dependencies_test.go
Normal file
1192
checks/raw/pinned_dependencies_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package checks
|
||||
package raw
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@ -292,47 +292,51 @@ func getLine(startLine, endLine uint, node syntax.Node) (uint, uint) {
|
||||
startLine + node.Pos().Line()
|
||||
}
|
||||
|
||||
func isFetchPipeExecute(startLine, endLine uint, node syntax.Node, cmd, pathfn string,
|
||||
dl checker.DetailLogger,
|
||||
) bool {
|
||||
func collectFetchPipeExecute(startLine, endLine uint, node syntax.Node, cmd, pathfn string,
|
||||
r *checker.PinningDependenciesData,
|
||||
) {
|
||||
// BinaryCmd {Op=|, X=CallExpr{Args={curl, -s, url}}, Y=CallExpr{Args={bash,}}}.
|
||||
bc, ok := node.(*syntax.BinaryCmd)
|
||||
if !ok {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
// Look for the pipe operator.
|
||||
if !strings.EqualFold(bc.Op.String(), "|") {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
leftStmt, ok := extractCommand(bc.X.Cmd)
|
||||
if !ok {
|
||||
return false
|
||||
return
|
||||
}
|
||||
rightStmt, ok := extractCommand(bc.Y.Cmd)
|
||||
if !ok {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
if !isDownloadUtility(leftStmt) {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
if !isInterpreter(rightStmt) {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
startLine, endLine = getLine(startLine, endLine, node)
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: startLine,
|
||||
EndOffset: endLine,
|
||||
Snippet: cmd,
|
||||
Text: "insecure (not pinned by hash) download detected",
|
||||
})
|
||||
return true
|
||||
|
||||
r.Dependencies = append(r.Dependencies,
|
||||
checker.Dependency{
|
||||
Location: &checker.File{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: startLine,
|
||||
EndOffset: endLine,
|
||||
Snippet: cmd,
|
||||
},
|
||||
Type: checker.DependencyUseTypeDownloadThenRun,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func getRedirectFile(red []*syntax.Redirect) (string, bool) {
|
||||
@ -356,36 +360,36 @@ func getRedirectFile(red []*syntax.Redirect) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
func isExecuteFiles(startLine, endLine uint, node syntax.Node, cmd, pathfn string, files map[string]bool,
|
||||
dl checker.DetailLogger,
|
||||
) bool {
|
||||
func collectExecuteFiles(startLine, endLine uint, node syntax.Node, cmd, pathfn string, files map[string]bool,
|
||||
r *checker.PinningDependenciesData,
|
||||
) {
|
||||
ce, ok := node.(*syntax.CallExpr)
|
||||
if !ok {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
c, ok := extractCommand(ce)
|
||||
if !ok {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
ok = false
|
||||
startLine, endLine = getLine(startLine, endLine, node)
|
||||
for fn := range files {
|
||||
if isInterpreterWithFile(c, fn) || isExecuteFile(c, fn) {
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: startLine,
|
||||
EndOffset: endLine,
|
||||
Snippet: cmd,
|
||||
Text: "insecure (not pinned by hash) download-then-run",
|
||||
})
|
||||
ok = true
|
||||
r.Dependencies = append(r.Dependencies,
|
||||
checker.Dependency{
|
||||
Location: &checker.File{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: startLine,
|
||||
EndOffset: endLine,
|
||||
Snippet: cmd,
|
||||
},
|
||||
Type: checker.DependencyUseTypeDownloadThenRun,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// Npm install docs are here.
|
||||
@ -607,75 +611,93 @@ func isChocoUnpinnedDownload(cmd []string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func isUnpinnedPakageManagerDownload(startLine, endLine uint, node syntax.Node,
|
||||
cmd, pathfn string, dl checker.DetailLogger,
|
||||
) bool {
|
||||
func collectUnpinnedPakageManagerDownload(startLine, endLine uint, node syntax.Node,
|
||||
cmd, pathfn string, r *checker.PinningDependenciesData,
|
||||
) {
|
||||
ce, ok := node.(*syntax.CallExpr)
|
||||
if !ok {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
c, ok := extractCommand(ce)
|
||||
if !ok {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
startLine, endLine = getLine(startLine, endLine, node)
|
||||
|
||||
// Go get/install.
|
||||
if isGoUnpinnedDownload(c) {
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: startLine,
|
||||
EndOffset: endLine,
|
||||
Snippet: cmd,
|
||||
Text: "go installation not pinned by hash",
|
||||
})
|
||||
return true
|
||||
r.Dependencies = append(r.Dependencies,
|
||||
checker.Dependency{
|
||||
Location: &checker.File{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: startLine,
|
||||
EndOffset: endLine,
|
||||
Snippet: cmd,
|
||||
},
|
||||
Type: checker.DependencyUseTypeGoCommand,
|
||||
},
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Pip install.
|
||||
if isPipUnpinnedDownload(c) {
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: startLine,
|
||||
EndOffset: endLine,
|
||||
Snippet: cmd,
|
||||
Text: "pip installation not pinned by hash",
|
||||
})
|
||||
return true
|
||||
r.Dependencies = append(r.Dependencies,
|
||||
checker.Dependency{
|
||||
Location: &checker.File{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: startLine,
|
||||
EndOffset: endLine,
|
||||
Snippet: cmd,
|
||||
},
|
||||
Type: checker.DependencyUseTypePipCommand,
|
||||
},
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Npm install.
|
||||
if isNpmUnpinnedDownload(c) {
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: startLine,
|
||||
EndOffset: endLine,
|
||||
Snippet: cmd,
|
||||
Text: "npm installation not pinned by hash",
|
||||
})
|
||||
return true
|
||||
r.Dependencies = append(r.Dependencies,
|
||||
checker.Dependency{
|
||||
Location: &checker.File{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: startLine,
|
||||
EndOffset: endLine,
|
||||
Snippet: cmd,
|
||||
},
|
||||
Type: checker.DependencyUseTypeNpmCommand,
|
||||
},
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Choco install.
|
||||
if isChocoUnpinnedDownload(c) {
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: startLine,
|
||||
EndOffset: endLine,
|
||||
Snippet: cmd,
|
||||
Text: "choco installation not pinned by hash",
|
||||
})
|
||||
return true
|
||||
r.Dependencies = append(r.Dependencies,
|
||||
checker.Dependency{
|
||||
Location: &checker.File{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: startLine,
|
||||
EndOffset: endLine,
|
||||
Snippet: cmd,
|
||||
},
|
||||
Type: checker.DependencyUseTypeChocoCommand,
|
||||
},
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
// TODO(laurent): add other package managers.
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func recordFetchFileFromNode(node syntax.Node) (pathfn string, ok bool, err error) {
|
||||
@ -701,69 +723,72 @@ func recordFetchFileFromNode(node syntax.Node) (pathfn string, ok bool, err erro
|
||||
return fn, true, nil
|
||||
}
|
||||
|
||||
func isFetchProcSubsExecute(startLine, endLine uint, node syntax.Node, cmd, pathfn string,
|
||||
dl checker.DetailLogger,
|
||||
) bool {
|
||||
func collectFetchProcSubsExecute(startLine, endLine uint, node syntax.Node, cmd, pathfn string,
|
||||
r *checker.PinningDependenciesData,
|
||||
) {
|
||||
ce, ok := node.(*syntax.CallExpr)
|
||||
if !ok {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
c, ok := extractCommand(ce)
|
||||
if !ok {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
if !isInterpreter(c) {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
// Now parse the process substitution part.
|
||||
// Example: `bash <(wget -qO- http://website.com/my-script.sh)`.
|
||||
l := 2
|
||||
if len(ce.Args) < l {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
parts := ce.Args[1].Parts
|
||||
if len(parts) != 1 {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
part := parts[0]
|
||||
p, ok := part.(*syntax.ProcSubst)
|
||||
if !ok {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.EqualFold(p.Op.String(), "<(") {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
if len(p.Stmts) == 0 {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
c, ok = extractCommand(p.Stmts[0].Cmd)
|
||||
if !ok {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
if !isDownloadUtility(c) {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
startLine, endLine = getLine(startLine, endLine, node)
|
||||
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: startLine,
|
||||
EndOffset: endLine,
|
||||
Snippet: cmd,
|
||||
Text: "insecure (not pinned by hash) download-then-run",
|
||||
})
|
||||
return true
|
||||
r.Dependencies = append(r.Dependencies,
|
||||
checker.Dependency{
|
||||
Location: &checker.File{
|
||||
Path: pathfn,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: startLine,
|
||||
EndOffset: endLine,
|
||||
Snippet: cmd,
|
||||
},
|
||||
Type: checker.DependencyUseTypeDownloadThenRun,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func isCommand(cmd []string, b string) bool {
|
||||
@ -840,19 +865,18 @@ func nodeToString(p *syntax.Printer, node syntax.Node) (string, error) {
|
||||
}
|
||||
|
||||
func validateShellFileAndRecord(pathfn string, startLine, endLine uint, content []byte, files map[string]bool,
|
||||
dl checker.DetailLogger,
|
||||
) (bool, error) {
|
||||
r *checker.PinningDependenciesData,
|
||||
) error {
|
||||
in := strings.NewReader(string(content))
|
||||
f, err := syntax.NewParser().Parse(in, pathfn)
|
||||
if err != nil {
|
||||
// Note: this is caught by internal caller and only printed
|
||||
// to avoid failing on shell scripts that our parser does not understand.
|
||||
// Example: https://github.com/openssl/openssl/blob/master/util/shlib_wrap.sh.in
|
||||
return false, sce.WithMessage(sce.ErrorShellParsing, err.Error())
|
||||
return sce.WithMessage(sce.ErrorShellParsing, err.Error())
|
||||
}
|
||||
|
||||
printer := syntax.NewPrinter()
|
||||
validated := true
|
||||
|
||||
syntax.Walk(f, func(node syntax.Node) bool {
|
||||
cmdStr, e := nodeToString(printer, node)
|
||||
@ -869,9 +893,8 @@ func validateShellFileAndRecord(pathfn string, startLine, endLine uint, content
|
||||
// nolinter
|
||||
if ok && isShellInterpreterOrCommand([]string{i}) {
|
||||
start, end := getLine(startLine, endLine, node)
|
||||
ok, e := validateShellFileAndRecord(pathfn, start, end,
|
||||
[]byte(c), files, dl)
|
||||
validated = ok
|
||||
e := validateShellFileAndRecord(pathfn, start, end,
|
||||
[]byte(c), files, r)
|
||||
if e != nil {
|
||||
err = e
|
||||
return true
|
||||
@ -879,25 +902,18 @@ func validateShellFileAndRecord(pathfn string, startLine, endLine uint, content
|
||||
}
|
||||
|
||||
// `curl | bash` (supports `sudo`).
|
||||
if isFetchPipeExecute(startLine, endLine, node, cmdStr, pathfn, dl) {
|
||||
validated = false
|
||||
}
|
||||
collectFetchPipeExecute(startLine, endLine, node, cmdStr, pathfn, r)
|
||||
|
||||
// Check if we're calling a file we previously downloaded.
|
||||
// Includes `curl > /tmp/file [&&|;] [bash] /tmp/file`
|
||||
if isExecuteFiles(startLine, endLine, node, cmdStr, pathfn, files, dl) {
|
||||
validated = false
|
||||
}
|
||||
collectExecuteFiles(startLine, endLine, node, cmdStr, pathfn, files, r)
|
||||
|
||||
// `bash <(wget -qO- http://website.com/my-script.sh)`. (supports `sudo`).
|
||||
if isFetchProcSubsExecute(startLine, endLine, node, cmdStr, pathfn, dl) {
|
||||
validated = false
|
||||
}
|
||||
collectFetchProcSubsExecute(startLine, endLine, node, cmdStr, pathfn, r)
|
||||
|
||||
// Package manager's unpinned installs.
|
||||
if isUnpinnedPakageManagerDownload(startLine, endLine, node, cmdStr, pathfn, dl) {
|
||||
validated = false
|
||||
}
|
||||
collectUnpinnedPakageManagerDownload(startLine, endLine, node, cmdStr, pathfn, r)
|
||||
|
||||
// TODO(laurent): add check for cat file | bash.
|
||||
// TODO(laurent): detect downloads of zip/tar files containing scripts.
|
||||
// TODO(laurent): detect command being an env variable.
|
||||
@ -916,7 +932,7 @@ func validateShellFileAndRecord(pathfn string, startLine, endLine uint, content
|
||||
return true
|
||||
})
|
||||
|
||||
return validated, err
|
||||
return err
|
||||
}
|
||||
|
||||
// The functions below are the only ones that should be called by other files.
|
||||
@ -991,14 +1007,7 @@ func isMatchingShellScriptFile(pathfn string, content []byte, shellsToMatch []st
|
||||
}
|
||||
|
||||
func validateShellFile(pathfn string, startLine, endLine uint,
|
||||
content []byte, taintedFiles map[string]bool, dl checker.DetailLogger,
|
||||
) (bool, error) {
|
||||
r, err := validateShellFileAndRecord(pathfn, startLine, endLine, content, taintedFiles, dl)
|
||||
if err != nil {
|
||||
// Print this particular error.
|
||||
dl.Debug(&checker.LogMessage{
|
||||
Text: err.Error(),
|
||||
})
|
||||
}
|
||||
return r, err
|
||||
content []byte, taintedFiles map[string]bool, r *checker.PinningDependenciesData,
|
||||
) error {
|
||||
return validateShellFileAndRecord(pathfn, startLine, endLine, content, taintedFiles, r)
|
||||
}
|
@ -12,13 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package checks
|
||||
package raw
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
scut "github.com/ossf/scorecard/v4/utests"
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
)
|
||||
|
||||
func TestIsSupportedShellScriptFile(t *testing.T) {
|
||||
@ -99,8 +99,9 @@ func TestValidateShellFile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("cannot read file: %v", err)
|
||||
}
|
||||
dl := scut.TestDetailLogger{}
|
||||
_, err = validateShellFile(filename, 0, 0, content, map[string]bool{}, &dl)
|
||||
|
||||
var r checker.PinningDependenciesData
|
||||
err = validateShellFile(filename, 0, 0, content, map[string]bool{}, &r)
|
||||
if err == nil {
|
||||
t.Errorf("failed to detect shell parsing error: %v", err)
|
||||
}
|
@ -72,10 +72,6 @@ func createWorkflowPermissionRemediation(filepath string) *checker.Remediation {
|
||||
return createWorkflowRemediation(filepath, "permissions")
|
||||
}
|
||||
|
||||
func createWorkflowPinningRemediation(filepath string) *checker.Remediation {
|
||||
return createWorkflowRemediation(filepath, "pin")
|
||||
}
|
||||
|
||||
func createWorkflowRemediation(path, t string) *checker.Remediation {
|
||||
p := strings.TrimPrefix(path, ".github/workflows/")
|
||||
if remediationBranch == "" || remediationRepo == "" {
|
||||
|
@ -50,12 +50,12 @@ var _ = Describe("E2E TEST:"+checks.CheckPinnedDependencies, func() {
|
||||
}
|
||||
expected := scut.TestReturn{
|
||||
Error: nil,
|
||||
Score: 3,
|
||||
Score: 2,
|
||||
NumberOfWarn: 139,
|
||||
NumberOfInfo: 2,
|
||||
NumberOfInfo: 1,
|
||||
NumberOfDebug: 0,
|
||||
}
|
||||
result := checks.PinnedDependencies(&req)
|
||||
result := checks.PinningDependencies(&req)
|
||||
Expect(scut.ValidateTestReturn(nil, "dependencies check", &expected, &result, &dl)).Should(BeTrue())
|
||||
Expect(repoClient.Close()).Should(BeNil())
|
||||
})
|
||||
@ -75,12 +75,12 @@ var _ = Describe("E2E TEST:"+checks.CheckPinnedDependencies, func() {
|
||||
}
|
||||
expected := scut.TestReturn{
|
||||
Error: nil,
|
||||
Score: 3,
|
||||
Score: 2,
|
||||
NumberOfWarn: 139,
|
||||
NumberOfInfo: 2,
|
||||
NumberOfInfo: 1,
|
||||
NumberOfDebug: 0,
|
||||
}
|
||||
result := checks.PinnedDependencies(&req)
|
||||
result := checks.PinningDependencies(&req)
|
||||
Expect(scut.ValidateTestReturn(nil, "dependencies check", &expected, &result, &dl)).Should(BeTrue())
|
||||
Expect(repoClient.Close()).Should(BeNil())
|
||||
})
|
||||
@ -111,12 +111,12 @@ var _ = Describe("E2E TEST:"+checks.CheckPinnedDependencies, func() {
|
||||
}
|
||||
expected := scut.TestReturn{
|
||||
Error: nil,
|
||||
Score: 3,
|
||||
Score: 2,
|
||||
NumberOfWarn: 139,
|
||||
NumberOfInfo: 2,
|
||||
NumberOfInfo: 1,
|
||||
NumberOfDebug: 0,
|
||||
}
|
||||
result := checks.PinnedDependencies(&req)
|
||||
result := checks.PinningDependencies(&req)
|
||||
Expect(scut.ValidateTestReturn(nil, "dependencies check", &expected, &result, &dl)).Should(BeTrue())
|
||||
Expect(x.Close()).Should(BeNil())
|
||||
})
|
||||
|
69
go.sum
69
go.sum
@ -22,7 +22,6 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
@ -60,7 +59,6 @@ cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSi
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/firestore v1.4.0/go.mod h1:NjjGEnxCS3CAKYp+vmALu20QzcqasGodQp48WxJGAYc=
|
||||
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
|
||||
cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c=
|
||||
cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw=
|
||||
@ -75,7 +73,6 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/pubsub v1.9.0/go.mod h1:G3o6/kJvEMIEAN5urdkaP4be49WQsjNiykBIto9LFtY=
|
||||
cloud.google.com/go/pubsub v1.19.0/go.mod h1:/O9kmSe9bb9KRnIAWkzmqhPjHo6LtzGOBYd/kr06XSs=
|
||||
cloud.google.com/go/pubsub v1.21.1 h1:ghu6wlm6WouITmmuwkxGG+6vNRXDaPdAjqLcRdsw3EQ=
|
||||
cloud.google.com/go/pubsub v1.21.1/go.mod h1:u3XGeMBOBCIQLcxNzy14Svz88ZFS8vI250uDgIAQDSQ=
|
||||
@ -85,7 +82,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7nillojlL5Ho=
|
||||
cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKuqnZI01LAA=
|
||||
cloud.google.com/go/storage v1.22.0 h1:NUV0NNp9nkBuW66BFRLuMgldN60C57ET3dhbwLIYio8=
|
||||
cloud.google.com/go/storage v1.22.0/go.mod h1:GbaLEoMqbVm6sx3Z0R++gSiBlgMv6yUi2q1DeGFKQgE=
|
||||
@ -97,7 +93,6 @@ contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod
|
||||
contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0=
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw=
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.13.10/go.mod h1:I5htMbyta491eUxufwwZPQdcKvvgzMB4O9ni41YnIM8=
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.13.12 h1:bjBKzIf7/TAkxd7L2utGaLM78bmUWlCval5K9UeElbY=
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.13.12/go.mod h1:mmxnWlrvrFdpiOHOhxBaVi1rkc0WOqhgfknj4Yg0SeQ=
|
||||
@ -110,8 +105,6 @@ git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqbl
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg=
|
||||
github.com/AkihiroSuda/containerd-fuse-overlayfs v1.0.0/go.mod h1:0mMDvQFeLbbn1Wy8P2j3hwFhqBq+FKn8OZPno8WLmp8=
|
||||
github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU=
|
||||
github.com/Azure/azure-amqp-common-go/v3 v3.0.1/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0=
|
||||
github.com/Azure/azure-amqp-common-go/v3 v3.1.0/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0=
|
||||
github.com/Azure/azure-amqp-common-go/v3 v3.2.1/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI=
|
||||
github.com/Azure/azure-amqp-common-go/v3 v3.2.2/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI=
|
||||
github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
|
||||
@ -122,24 +115,18 @@ github.com/Azure/azure-sdk-for-go v19.1.1+incompatible/go.mod h1:9XXNKU+eRnpl9mo
|
||||
github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v37.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v38.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v42.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v49.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v59.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
|
||||
github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0=
|
||||
github.com/Azure/azure-service-bus-go v0.10.7/go.mod h1:o5z/3lDG1iT/T/G7vgIwIqVDTx9Qa2wndf5OdzSzpF8=
|
||||
github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU=
|
||||
github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0=
|
||||
github.com/Azure/azure-storage-blob-go v0.13.0/go.mod h1:pA9kNqtjUeQF2zOSu4s//nUdBD+e64lEuc4sVnuOfNs=
|
||||
github.com/Azure/azure-storage-blob-go v0.14.0 h1:1BCg74AmVdYwO3dlKwtFU1V0wU2PZdREkXvAmZJRUlM=
|
||||
github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck=
|
||||
github.com/Azure/go-amqp v0.13.0/go.mod h1:qj+o8xPCz9tMSbQ83Vp8boHahuRDl5mkNHyt1xlxUTs=
|
||||
github.com/Azure/go-amqp v0.13.1/go.mod h1:qj+o8xPCz9tMSbQ83Vp8boHahuRDl5mkNHyt1xlxUTs=
|
||||
github.com/Azure/go-amqp v0.16.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
|
||||
github.com/Azure/go-amqp v0.16.4/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
@ -154,10 +141,6 @@ github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8
|
||||
github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630=
|
||||
github.com/Azure/go-autorest/autorest v0.10.2/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630=
|
||||
github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
|
||||
github.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
|
||||
github.com/Azure/go-autorest/autorest v0.11.7/go.mod h1:V6p3pKZx1KKkJubbxnDWrzNhEIfOy/pTGasLqzHIPHs=
|
||||
github.com/Azure/go-autorest/autorest v0.11.9/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
|
||||
github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
|
||||
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
|
||||
github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
|
||||
github.com/Azure/go-autorest/autorest v0.11.22/go.mod h1:BAWYUWGPEtKPzjVkp0Q6an0MJcJDsoh5Z1BFAEFs4Xs=
|
||||
@ -167,15 +150,11 @@ github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMl
|
||||
github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.8.3/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.4/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.6/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.17/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.3/go.mod h1:4bJZhUhcq8LB20TruwHbAQsmUs2Xh+QR7utuJpLXX3A=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.9/go.mod h1:hg3/1yw0Bq87O3KvvnJoAh34/0zbP7SFizX/qN5JvjU=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
|
||||
@ -192,7 +171,6 @@ github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsI
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.0/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
@ -204,7 +182,6 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
|
||||
github.com/Djarvur/go-err113 v0.0.0-20200410182137-af658d038157/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
|
||||
github.com/Djarvur/go-err113 v0.1.0/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
|
||||
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo=
|
||||
github.com/GoogleCloudPlatform/cloudsql-proxy v1.19.1/go.mod h1:+yYmuKqcBVkgRePGpUhTA9OEg0XsnFE96eZ6nJ2yCQM=
|
||||
github.com/GoogleCloudPlatform/cloudsql-proxy v1.29.0/go.mod h1:spvB9eLJH9dutlbPSRmHvSXXHOwGRyeXh1jVdquA2G8=
|
||||
github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
@ -287,11 +264,9 @@ github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi
|
||||
github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.31.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.36.1/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.43.31 h1:yJZIr8nMV1hXjAvvOLUFqZRJcHV7udPQBfhJqawDzI0=
|
||||
github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||
@ -354,9 +329,6 @@ github.com/bombsimon/wsl/v2 v2.2.0/go.mod h1:Azh8c3XGEJl9LyX0/sFC+CKMc7Ssgua0g+6
|
||||
github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
|
||||
github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I=
|
||||
github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug=
|
||||
github.com/bradleyfalzon/ghinstallation/v2 v2.0.4 h1:tXKVfhE7FcSkhkv0UwkLvPDeZ4kz6OXd0PKPlFqf81M=
|
||||
github.com/bradleyfalzon/ghinstallation/v2 v2.0.4/go.mod h1:B40qPqJxWE0jDZgOR1JmaMy+4AY1eBP+IByOvqyAKp0=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
@ -560,7 +532,6 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU=
|
||||
github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
@ -818,7 +789,6 @@ github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bz
|
||||
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
|
||||
github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
|
||||
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
|
||||
github.com/gomodule/redigo v1.8.4/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
@ -847,9 +817,6 @@ github.com/google/go-containerregistry v0.9.0/go.mod h1:9eq4BnSufyT1kHNffX+vSXVo
|
||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
|
||||
github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E=
|
||||
github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II=
|
||||
github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
|
||||
github.com/google/go-github/v38 v38.1.0 h1:C6h1FkaITcBFK7gAmq4eFzt6gbhEhk7L5z6R3Uva+po=
|
||||
github.com/google/go-github/v38 v38.1.0/go.mod h1:cStvrz/7nFr0FoENgG6GLbp53WaelXucT+BBz/3VKx4=
|
||||
github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27ueww4gg=
|
||||
@ -858,11 +825,9 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
|
||||
github.com/google/go-replayers/grpcreplay v1.0.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
|
||||
github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE=
|
||||
github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk=
|
||||
github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
|
||||
github.com/google/go-replayers/httpreplay v0.1.2/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
|
||||
github.com/google/go-replayers/httpreplay v1.1.1 h1:H91sIMlt1NZzN7R+/ASswyouLJfW0WLW7fhyUFvDEkY=
|
||||
github.com/google/go-replayers/httpreplay v1.1.1/go.mod h1:gN9GeLIs7l6NUoVaSSnv2RiqK1NiwAmD0MrKeC9IIks=
|
||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
@ -885,7 +850,6 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
@ -945,7 +909,6 @@ github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.m
|
||||
github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s=
|
||||
@ -1133,7 +1096,6 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
|
||||
@ -1209,7 +1171,6 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/buildkit v0.8.1/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ=
|
||||
github.com/moby/buildkit v0.10.3 h1:/dGykD8FW+H4p++q5+KqKEo6gAkYKyBQHdawdjVwVAU=
|
||||
@ -1250,7 +1211,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c=
|
||||
github.com/naveensrinivasan/httpcache v1.2.2/go.mod h1:gpEVVjcTYZA3F1tqYkLqbNvZuf380rhUDaV5OZpyQ88=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
github.com/networkplumbing/go-nft v0.2.0/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
@ -1273,7 +1233,6 @@ github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.15.2/go.mod h1:Dd6YFfwBW84ETqqtL0CPyPXillHgY6XhQH3uuCCTr/o=
|
||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
@ -1290,7 +1249,6 @@ github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT
|
||||
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
|
||||
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
@ -1332,8 +1290,6 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/ossf/scorecard v1.2.0 h1:Gf12BN29RZDDSev0suW/DwJyhYWH1XHsIqSmpCChgsE=
|
||||
github.com/ossf/scorecard v1.2.0/go.mod h1:hc0zwnXi2NHq2aru8A/NoNZ9H+DqZZlYbmOw7jjHi/Q=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
@ -1451,7 +1407,6 @@ github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lz
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa h1:jozR3igKlnYCj9IVHOVump59bp07oIRoLQ/CcjMYIUA=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
@ -1522,7 +1477,6 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
|
||||
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
|
||||
@ -1682,12 +1636,10 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
gocloud.dev v0.19.0/go.mod h1:SmKwiR8YwIMMJvQBKLsC3fHNyMwXLw3PMDO+VVteJMI=
|
||||
gocloud.dev v0.22.0/go.mod h1:z3jKIQ0Es9LALVZFQ3wOvwqAsSLq1R5c/2RdmghDucw=
|
||||
gocloud.dev v0.25.0 h1:Y7vDq8xj7SyM848KXf32Krda2e6jQ4CLh/mTeCSqXtk=
|
||||
gocloud.dev v0.25.0/go.mod h1:7HegHVCYZrMiU3IE1qtnzf/vRrDwLYnRNR3EhWX8x9Y=
|
||||
golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4=
|
||||
@ -1700,7 +1652,6 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@ -1818,7 +1769,6 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
@ -1862,7 +1812,6 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201203001011-0b49973bad19/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
@ -2099,7 +2048,6 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@ -2143,18 +2091,12 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201202200335-bef1c476418a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201203202102-a1a1cbeaa516/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
@ -2188,7 +2130,6 @@ google.golang.org/api v0.6.1-0.20190607001116-5213b8090861/go.mod h1:btoxGiFvQNV
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
@ -2202,8 +2143,6 @@ google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.31.0/go.mod h1:CL+9IBCa2WWU6gRuBWaKqGWLFFwbEUXkfeMkHLQWYWo=
|
||||
google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
@ -2239,7 +2178,6 @@ google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
@ -2284,15 +2222,11 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200914193844-75d14daec038/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201203001206-6486ece9c497/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
@ -2369,7 +2303,6 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
@ -2410,7 +2343,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
@ -2551,7 +2483,6 @@ mvdan.cc/sh/v3 v3.5.1 h1:hmP3UOw4f+EYexsJjFxvU38+kn+V/s2CclXHanIBkmQ=
|
||||
mvdan.cc/sh/v3 v3.5.1/go.mod h1:1JcoyAKm1lZw/2bZje/iYKWicU/KMd0rsyJeKHnsK4E=
|
||||
mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw=
|
||||
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7/go.mod h1:HGC5lll35J70Y5v7vCGb9oLhHoScFwkHDJm/05RdSTc=
|
||||
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
@ -40,10 +40,10 @@ type jsonScorecardRawResult struct {
|
||||
|
||||
// TODO: separate each check extraction into its own file.
|
||||
type jsonFile struct {
|
||||
Snippet *string `json:"snippet,omitempty"`
|
||||
Path string `json:"path"`
|
||||
// TODO: change to an uint.
|
||||
Offset int `json:"offset,omitempty"`
|
||||
Snippet *string `json:"snippet,omitempty"`
|
||||
Path string `json:"path"`
|
||||
Offset uint `json:"offset,omitempty"`
|
||||
EndOffset uint `json:"endOffset,omitempty"`
|
||||
}
|
||||
|
||||
type jsonTool struct {
|
||||
@ -193,6 +193,19 @@ type jsonRun struct {
|
||||
// TODO: add fields, e.g., Result=["success", "failure"]
|
||||
}
|
||||
|
||||
type jsonPinningDependenciesData struct {
|
||||
Dependencies []jsonDependency `json:"dependencies"`
|
||||
}
|
||||
|
||||
type jsonDependency struct {
|
||||
// TODO: unique dependency name.
|
||||
// TODO: Job *WorkflowJob
|
||||
Location *jsonFile `json:"location"`
|
||||
Name *string `json:"name"`
|
||||
PinnedAt *string `json:"pinnedAt"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
//nolint
|
||||
type jsonRawResults struct {
|
||||
// Workflow results.
|
||||
@ -220,7 +233,7 @@ type jsonRawResults struct {
|
||||
// structure for it.
|
||||
Contributors jsonContributors `json:"Contributors"`
|
||||
// Commits.
|
||||
DefaultBranchCommits []jsonDefaultBranchCommit `json:"defaultBrancCommits"`
|
||||
DefaultBranchCommits []jsonDefaultBranchCommit `json:"defaultBranchCommits"`
|
||||
// Archived status of the repo.
|
||||
ArchivedStatus jsonArchivedStatus `json:"archived"`
|
||||
// Fuzzers.
|
||||
@ -229,6 +242,8 @@ type jsonRawResults struct {
|
||||
Releases []jsonRelease `json:"releases"`
|
||||
// Packages.
|
||||
Packages []jsonPackage `json:"packages"`
|
||||
// Dependency pinning.
|
||||
DependencyPinning jsonPinningDependenciesData `json:"dependencyPinning"`
|
||||
}
|
||||
|
||||
func (r *jsonScorecardRawResult) addPackagingRawResults(pk *checker.PackagingData) error {
|
||||
@ -248,7 +263,7 @@ func (r *jsonScorecardRawResult) addPackagingRawResults(pk *checker.PackagingDat
|
||||
|
||||
jpk.File = &jsonFile{
|
||||
Path: p.File.Path,
|
||||
Offset: int(p.File.Offset),
|
||||
Offset: p.File.Offset,
|
||||
// TODO: Snippet
|
||||
}
|
||||
|
||||
@ -265,14 +280,44 @@ func (r *jsonScorecardRawResult) addPackagingRawResults(pk *checker.PackagingDat
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:unparam
|
||||
func (r *jsonScorecardRawResult) addDependencyPinningRawResults(pd *checker.PinningDependenciesData) error {
|
||||
r.Results.DependencyPinning = jsonPinningDependenciesData{}
|
||||
for i := range pd.Dependencies {
|
||||
rr := pd.Dependencies[i]
|
||||
if rr.Location == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
v := jsonDependency{
|
||||
Location: &jsonFile{
|
||||
Path: rr.Location.Path,
|
||||
Offset: rr.Location.Offset,
|
||||
EndOffset: rr.Location.EndOffset,
|
||||
},
|
||||
Name: rr.Name,
|
||||
PinnedAt: rr.PinnedAt,
|
||||
Type: string(rr.Type),
|
||||
}
|
||||
|
||||
if rr.Location.Snippet != "" {
|
||||
v.Location.Snippet = &rr.Location.Snippet
|
||||
}
|
||||
|
||||
r.Results.DependencyPinning.Dependencies = append(r.Results.DependencyPinning.Dependencies, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:unparam
|
||||
func (r *jsonScorecardRawResult) addDangerousWorkflowRawResults(df *checker.DangerousWorkflowData) error {
|
||||
r.Results.Workflows = []jsonWorkflow{}
|
||||
for _, e := range df.Workflows {
|
||||
v := jsonWorkflow{
|
||||
File: &jsonFile{
|
||||
Path: e.File.Path,
|
||||
Offset: int(e.File.Offset),
|
||||
Path: e.File.Path,
|
||||
Offset: e.File.Offset,
|
||||
EndOffset: e.File.EndOffset,
|
||||
},
|
||||
Type: string(e.Type),
|
||||
}
|
||||
@ -616,6 +661,11 @@ func (r *jsonScorecardRawResult) fillJSONRawResults(raw *checker.RawResults) err
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
}
|
||||
|
||||
// DependencyPinning.
|
||||
if err := r.addDependencyPinningRawResults(&raw.PinningDependenciesResults); err != nil {
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
}
|
||||
|
||||
// CII-Best-Practices.
|
||||
if err := r.addOssfBestPracticesRawResults(&raw.CIIBestPracticesResults); err != nil {
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
|
96
remediation/remediations.go
Normal file
96
remediation/remediations.go
Normal file
@ -0,0 +1,96 @@
|
||||
// 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 remediation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/clients"
|
||||
)
|
||||
|
||||
var (
|
||||
branch string
|
||||
repo string
|
||||
once *sync.Once
|
||||
setupErr error
|
||||
)
|
||||
|
||||
var errInvalidArg = errors.New("invalid argument")
|
||||
|
||||
var (
|
||||
workflowText = "update your workflow using https://app.stepsecurity.io/secureworkflow/%s/%s/%s?enable=%s"
|
||||
//nolint
|
||||
workflowMarkdown = "update your workflow using [https://app.stepsecurity.io](https://app.stepsecurity.io/secureworkflow/%s/%s/%s?enable=%s)"
|
||||
)
|
||||
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
once = new(sync.Once)
|
||||
}
|
||||
|
||||
// Setup sets up remediation code.
|
||||
func Setup(c *checker.CheckRequest) error {
|
||||
once.Do(func() {
|
||||
// Get the branch for remediation.
|
||||
b, err := c.RepoClient.GetDefaultBranch()
|
||||
if err != nil {
|
||||
if !errors.Is(err, clients.ErrUnsupportedFeature) {
|
||||
setupErr = err
|
||||
}
|
||||
return
|
||||
}
|
||||
if b.Name != nil {
|
||||
branch = *b.Name
|
||||
uri := c.Repo.URI()
|
||||
parts := strings.Split(uri, "/")
|
||||
if len(parts) != 3 {
|
||||
setupErr = fmt.Errorf("%w: enpty: %s", errInvalidArg, uri)
|
||||
return
|
||||
}
|
||||
repo = fmt.Sprintf("%s/%s", parts[1], parts[2])
|
||||
}
|
||||
})
|
||||
|
||||
return setupErr
|
||||
}
|
||||
|
||||
// CreateWorkflowPermissionRemediation create remediation for workflow permissions.
|
||||
func CreateWorkflowPermissionRemediation(filepath string) *checker.Remediation {
|
||||
return createWorkflowRemediation(filepath, "permissions")
|
||||
}
|
||||
|
||||
// CreateWorkflowPinningRemediation create remediaiton for pinninn GH Actions.
|
||||
func CreateWorkflowPinningRemediation(filepath string) *checker.Remediation {
|
||||
return createWorkflowRemediation(filepath, "pin")
|
||||
}
|
||||
|
||||
func createWorkflowRemediation(path, t string) *checker.Remediation {
|
||||
p := strings.TrimPrefix(path, ".github/workflows/")
|
||||
if branch == "" || repo == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
text := fmt.Sprintf(workflowText, repo, p, branch, t)
|
||||
markdown := fmt.Sprintf(workflowMarkdown, repo, p, branch, t)
|
||||
|
||||
return &checker.Remediation{
|
||||
HelpText: text,
|
||||
HelpMarkdown: markdown,
|
||||
}
|
||||
}
|
@ -118,6 +118,18 @@ func ValidateTestReturn(
|
||||
return true
|
||||
}
|
||||
|
||||
// ValidatePinningDependencies tests that at least one entry returns true for isExpectedMessage.
|
||||
func ValidatePinningDependencies(isExpectedDependency func(checker.Dependency) bool,
|
||||
r *checker.PinningDependenciesData,
|
||||
) bool {
|
||||
for _, dep := range r.Dependencies {
|
||||
if isExpectedDependency(dep) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidateLogMessage tests that at least one log message returns true for isExpectedMessage.
|
||||
func ValidateLogMessage(isExpectedMessage func(checker.LogMessage, checker.DetailType) bool,
|
||||
dl *TestDetailLogger,
|
||||
|
Loading…
Reference in New Issue
Block a user