mirror of
https://github.com/ossf/scorecard.git
synced 2024-09-11 00:45:36 +03:00
✨ Add raw results for Token-Permissions (#1912)
* draft * update * update * draft * updates * update * update * update * update * update * update * update * update * e2e test for empty repo * update * rename structure * update
This commit is contained in:
parent
2b8c7b4b88
commit
838f62f65a
@ -20,7 +20,7 @@ import (
|
||||
|
||||
// RawResults contains results before a policy
|
||||
// is applied.
|
||||
//nolint
|
||||
// nolint
|
||||
type RawResults struct {
|
||||
PackagingResults PackagingData
|
||||
CIIBestPracticesResults CIIBestPracticesData
|
||||
@ -38,6 +38,7 @@ type RawResults struct {
|
||||
SignedReleasesResults SignedReleasesData
|
||||
FuzzingResults FuzzingData
|
||||
LicenseResults LicenseData
|
||||
TokenPermissionsResults TokenPermissionsData
|
||||
}
|
||||
|
||||
// FuzzingData represents different fuzzing done.
|
||||
@ -243,3 +244,46 @@ type WorkflowJob struct {
|
||||
Name *string
|
||||
ID *string
|
||||
}
|
||||
|
||||
// TokenPermissionsData represents data about a permission failure.
|
||||
type TokenPermissionsData struct {
|
||||
TokenPermissions []TokenPermission
|
||||
}
|
||||
|
||||
// PermissionLocation represents a declaration type.
|
||||
type PermissionLocation string
|
||||
|
||||
const (
|
||||
// PermissionLocationTop is top-level workflow permission.
|
||||
PermissionLocationTop PermissionLocation = "topLevel"
|
||||
// PermissionLocationJob is job-level workflow permission.
|
||||
PermissionLocationJob PermissionLocation = "jobLevel"
|
||||
)
|
||||
|
||||
// PermissionLevel represents a permission type.
|
||||
type PermissionLevel string
|
||||
|
||||
const (
|
||||
// PermissionLevelUndeclared is an undecleared permission.
|
||||
PermissionLevelUndeclared PermissionLevel = "undeclared"
|
||||
// PermissionLevelWrite is a permission set to `write` for a permission we consider potentially dangerous.
|
||||
PermissionLevelWrite PermissionLevel = "write"
|
||||
// PermissionLevelRead is a permission set to `read`.
|
||||
PermissionLevelRead PermissionLevel = "read"
|
||||
// PermissionLevelNone is a permission set to `none`.
|
||||
PermissionLevelNone PermissionLevel = "none"
|
||||
// PermissionLevelUnknown is for other kinds of alerts, mostly to support debug messages.
|
||||
// TODO: remove it once we have implemented severity (#1874).
|
||||
PermissionLevelUnknown PermissionLevel = "unknown"
|
||||
)
|
||||
|
||||
// TokenPermission defines a token permission result.
|
||||
type TokenPermission struct {
|
||||
Job *WorkflowJob
|
||||
LocationType *PermissionLocation
|
||||
Name *string
|
||||
Value *string
|
||||
File *File
|
||||
Msg *string
|
||||
Type PermissionLevel
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ func TestReleaseAndDevBranchProtected(t *testing.T) {
|
||||
|
||||
rel1 := "release/v.1"
|
||||
sha := "8fb3cb86082b17144a80402f5367ae65f06083bd"
|
||||
//nolint:goconst
|
||||
main := "main"
|
||||
trueVal := true
|
||||
falseVal := false
|
||||
|
@ -19,10 +19,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidGitHubWorkflow = errors.New("invalid GitHub workflow")
|
||||
errInternalNameCannotBeEmpty = errors.New("name cannot be empty")
|
||||
errInternalCheckFuncCannotBeNil = errors.New("checkFunc cannot be nil")
|
||||
// TODO(#1245): these should be moved under `raw` package after migration.
|
||||
errInvalidArgType = errors.New("invalid arg type")
|
||||
errInvalidArgLength = errors.New("invalid arg length")
|
||||
)
|
||||
|
286
checks/evaluation/permissions.go
Normal file
286
checks/evaluation/permissions.go
Normal file
@ -0,0 +1,286 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
sce "github.com/ossf/scorecard/v4/errors"
|
||||
"github.com/ossf/scorecard/v4/remediation"
|
||||
)
|
||||
|
||||
type permissions struct {
|
||||
topLevelWritePermissions map[string]bool
|
||||
jobLevelWritePermissions map[string]bool
|
||||
}
|
||||
|
||||
// TokenPermissions applies the score policy for the Token-Permissions check.
|
||||
func TokenPermissions(name string, dl checker.DetailLogger, r *checker.TokenPermissionsData) checker.CheckResult {
|
||||
if r == nil {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, "empty raw data")
|
||||
return checker.CreateRuntimeErrorResult(name, e)
|
||||
}
|
||||
|
||||
score, err := applyScorePolicy(r, dl)
|
||||
if err != nil {
|
||||
return checker.CreateRuntimeErrorResult(name, err)
|
||||
}
|
||||
|
||||
if score != checker.MaxResultScore {
|
||||
return checker.CreateResultWithScore(name,
|
||||
"non read-only tokens detected in GitHub workflows", score)
|
||||
}
|
||||
|
||||
return checker.CreateMaxScoreResult(name,
|
||||
"tokens are read-only in GitHub workflows")
|
||||
}
|
||||
|
||||
func applyScorePolicy(results *checker.TokenPermissionsData, dl checker.DetailLogger) (int, error) {
|
||||
// See list https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/.
|
||||
// Note: there are legitimate reasons to use some of the permissions like checks, deployments, etc.
|
||||
// in CI/CD systems https://docs.travis-ci.com/user/github-oauth-scopes/.
|
||||
|
||||
hm := make(map[string]permissions)
|
||||
|
||||
for _, r := range results.TokenPermissions {
|
||||
var msg checker.LogMessage
|
||||
|
||||
if r.File != nil {
|
||||
msg.Path = r.File.Path
|
||||
msg.Offset = r.File.Offset
|
||||
msg.Type = r.File.Type
|
||||
msg.Snippet = r.File.Snippet
|
||||
|
||||
if msg.Path != "" {
|
||||
msg.Remediation = remediation.CreateWorkflowPermissionRemediation(r.File.Path)
|
||||
}
|
||||
}
|
||||
|
||||
text, err := createMessage(r)
|
||||
if err != nil {
|
||||
return checker.MinResultScore, err
|
||||
}
|
||||
msg.Text = text
|
||||
|
||||
switch r.Type {
|
||||
case checker.PermissionLevelNone, checker.PermissionLevelRead:
|
||||
dl.Info(&msg)
|
||||
case checker.PermissionLevelUnknown:
|
||||
dl.Debug(&msg)
|
||||
|
||||
case checker.PermissionLevelUndeclared:
|
||||
if r.LocationType == nil {
|
||||
return checker.InconclusiveResultScore,
|
||||
sce.WithMessage(sce.ErrScorecardInternal, "locationType is nil")
|
||||
}
|
||||
|
||||
// We warn only for top-level.
|
||||
if *r.LocationType == checker.PermissionLocationTop {
|
||||
dl.Warn(&msg)
|
||||
} else {
|
||||
dl.Debug(&msg)
|
||||
}
|
||||
|
||||
// Group results by workflow name for score computation.
|
||||
if err := updateWorkflowHashMap(hm, r); err != nil {
|
||||
return checker.InconclusiveResultScore, err
|
||||
}
|
||||
|
||||
case checker.PermissionLevelWrite:
|
||||
dl.Warn(&msg)
|
||||
|
||||
// Group results by workflow name for score computation.
|
||||
if err := updateWorkflowHashMap(hm, r); err != nil {
|
||||
return checker.InconclusiveResultScore, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return calculateScore(hm), nil
|
||||
}
|
||||
|
||||
func recordPermissionWrite(hm map[string]permissions, path string,
|
||||
locType checker.PermissionLocation, permName *string,
|
||||
) {
|
||||
if _, exists := hm[path]; !exists {
|
||||
hm[path] = permissions{
|
||||
topLevelWritePermissions: make(map[string]bool),
|
||||
jobLevelWritePermissions: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// Select the hash map to update.
|
||||
m := hm[path].jobLevelWritePermissions
|
||||
if locType == checker.PermissionLocationTop {
|
||||
m = hm[path].topLevelWritePermissions
|
||||
}
|
||||
|
||||
// Set the permission name to record.
|
||||
name := "all"
|
||||
if permName != nil && *permName != "" {
|
||||
name = *permName
|
||||
}
|
||||
m[name] = true
|
||||
}
|
||||
|
||||
func updateWorkflowHashMap(hm map[string]permissions, t checker.TokenPermission) error {
|
||||
if t.LocationType == nil {
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, "locationType is nil")
|
||||
}
|
||||
|
||||
if t.File == nil || t.File.Path == "" {
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, "path is not set")
|
||||
}
|
||||
|
||||
if t.Type != checker.PermissionLevelWrite &&
|
||||
t.Type != checker.PermissionLevelUndeclared {
|
||||
return nil
|
||||
}
|
||||
|
||||
recordPermissionWrite(hm, t.File.Path, *t.LocationType, t.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createMessage(t checker.TokenPermission) (string, error) {
|
||||
// By default, use the message already present.
|
||||
if t.Msg != nil {
|
||||
return *t.Msg, nil
|
||||
}
|
||||
|
||||
// Ensure there's no implementation bug.
|
||||
if t.LocationType == nil {
|
||||
return "", sce.WithMessage(sce.ErrScorecardInternal, "locationType is nil")
|
||||
}
|
||||
|
||||
// Use a different message depending on the type.
|
||||
if t.Type == checker.PermissionLevelUndeclared {
|
||||
return fmt.Sprintf("no %s permission defined", *t.LocationType), nil
|
||||
}
|
||||
|
||||
if t.Value == nil {
|
||||
return "", sce.WithMessage(sce.ErrScorecardInternal, "Value fields is nil")
|
||||
}
|
||||
|
||||
if t.Name == nil {
|
||||
return fmt.Sprintf("%s permissions set to '%v'", *t.LocationType,
|
||||
*t.Value), nil
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s '%v' permission set to '%v'", *t.LocationType,
|
||||
*t.Name, *t.Value), nil
|
||||
}
|
||||
|
||||
// Calculate the score.
|
||||
func calculateScore(result map[string]permissions) int {
|
||||
// See list https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/.
|
||||
// Note: there are legitimate reasons to use some of the permissions like checks, deployments, etc.
|
||||
// in CI/CD systems https://docs.travis-ci.com/user/github-oauth-scopes/.
|
||||
|
||||
// Start with a perfect score.
|
||||
score := float32(checker.MaxResultScore)
|
||||
|
||||
// Retrieve the overall results.
|
||||
for _, perms := range result {
|
||||
// If no top level permissions are defined, all the permissions
|
||||
// are enabled by default. In this case,
|
||||
if permissionIsPresentInTopLevel(perms, "all") {
|
||||
if permissionIsPresentInRunLevel(perms, "all") {
|
||||
// ... give lowest score if no run level permissions are defined either.
|
||||
return checker.MinResultScore
|
||||
}
|
||||
// ... reduce score if run level permissions are defined.
|
||||
score -= 0.5
|
||||
}
|
||||
|
||||
// status: https://docs.github.com/en/rest/reference/repos#statuses.
|
||||
// May allow an attacker to change the result of pre-submit and get a PR merged.
|
||||
// Low risk: -0.5.
|
||||
if permissionIsPresent(perms, "statuses") {
|
||||
score -= 0.5
|
||||
}
|
||||
|
||||
// checks.
|
||||
// May allow an attacker to edit checks to remove pre-submit and introduce a bug.
|
||||
// Low risk: -0.5.
|
||||
if permissionIsPresent(perms, "checks") {
|
||||
score -= 0.5
|
||||
}
|
||||
|
||||
// secEvents.
|
||||
// May allow attacker to read vuln reports before patch available.
|
||||
// Low risk: -1
|
||||
if permissionIsPresent(perms, "security-events") {
|
||||
score--
|
||||
}
|
||||
|
||||
// deployments: https://docs.github.com/en/rest/reference/repos#deployments.
|
||||
// May allow attacker to charge repo owner by triggering VM runs,
|
||||
// and tiny chance an attacker can trigger a remote
|
||||
// service with code they own if server accepts code/location var unsanitized.
|
||||
// Low risk: -1
|
||||
if permissionIsPresent(perms, "deployments") {
|
||||
score--
|
||||
}
|
||||
|
||||
// contents.
|
||||
// Allows attacker to commit unreviewed code.
|
||||
// High risk: -10
|
||||
if permissionIsPresent(perms, "contents") {
|
||||
score -= checker.MaxResultScore
|
||||
}
|
||||
|
||||
// packages: https://docs.github.com/en/packages/learn-github-packages/about-permissions-for-github-packages.
|
||||
// Allows attacker to publish packages.
|
||||
// High risk: -10
|
||||
if permissionIsPresent(perms, "packages") {
|
||||
score -= checker.MaxResultScore
|
||||
}
|
||||
|
||||
// actions.
|
||||
// May allow an attacker to steal GitHub secrets by approving to run an action that needs approval.
|
||||
// High risk: -10
|
||||
if permissionIsPresent(perms, "actions") {
|
||||
score -= checker.MaxResultScore
|
||||
}
|
||||
|
||||
if score < checker.MinResultScore {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// We're done, calculate the final score.
|
||||
if score < checker.MinResultScore {
|
||||
return checker.MinResultScore
|
||||
}
|
||||
|
||||
return int(score)
|
||||
}
|
||||
|
||||
func permissionIsPresent(perms permissions, name string) bool {
|
||||
return permissionIsPresentInTopLevel(perms, name) ||
|
||||
permissionIsPresentInRunLevel(perms, name)
|
||||
}
|
||||
|
||||
func permissionIsPresentInTopLevel(perms permissions, name string) bool {
|
||||
_, ok := perms.topLevelWritePermissions[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func permissionIsPresentInRunLevel(perms permissions, name string) bool {
|
||||
_, ok := perms.jobLevelWritePermissions[name]
|
||||
return ok
|
||||
}
|
@ -336,9 +336,8 @@ type JobMatchResult struct {
|
||||
File checker.File
|
||||
}
|
||||
|
||||
// RawAnyJobsMatch returns true if any of the jobs have a match in the given workflow.
|
||||
// TODO: Rename after migraiton is complete.
|
||||
func RawAnyJobsMatch(workflow *actionlint.Workflow, jobMatchers []JobMatcher, fp string,
|
||||
// AnyJobsMatch returns true if any of the jobs have a match in the given workflow.
|
||||
func AnyJobsMatch(workflow *actionlint.Workflow, jobMatchers []JobMatcher, fp string,
|
||||
logMsgNoMatch string,
|
||||
) (JobMatchResult, bool) {
|
||||
for _, job := range workflow.Jobs {
|
||||
@ -368,39 +367,19 @@ func RawAnyJobsMatch(workflow *actionlint.Workflow, jobMatchers []JobMatcher, fp
|
||||
}, false
|
||||
}
|
||||
|
||||
// AnyJobsMatch returns true if any of the jobs have a match in the given workflow.
|
||||
func AnyJobsMatch(workflow *actionlint.Workflow, jobMatchers []JobMatcher, fp string, dl checker.DetailLogger,
|
||||
logMsgNoMatch string,
|
||||
) bool {
|
||||
for _, job := range workflow.Jobs {
|
||||
for _, matcher := range jobMatchers {
|
||||
if !matcher.matches(job) {
|
||||
continue
|
||||
}
|
||||
|
||||
dl.Info(&checker.LogMessage{
|
||||
Path: fp,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: GetLineNumber(job.Pos),
|
||||
Text: matcher.LogText,
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
dl.Debug(&checker.LogMessage{
|
||||
Path: fp,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: checker.OffsetDefault,
|
||||
Text: logMsgNoMatch,
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
// matches returns true if the job matches the job matcher.
|
||||
func (m *JobMatcher) matches(job *actionlint.Job) bool {
|
||||
for _, stepToMatch := range m.Steps {
|
||||
hasMatch := false
|
||||
|
||||
// First look for re-usable workflow calls.
|
||||
if job.WorkflowCall != nil &&
|
||||
job.WorkflowCall.Uses != nil &&
|
||||
strings.HasPrefix(job.WorkflowCall.Uses.Value, stepToMatch.Uses+"@") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Second looks for steps in the job.
|
||||
for _, step := range job.Steps {
|
||||
if stepsMatch(stepToMatch, step) {
|
||||
hasMatch = true
|
||||
@ -455,3 +434,136 @@ func stepsMatch(stepToMatch *JobMatcherStep, step *actionlint.Step) bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsPackagingWorkflow checks for a packaging workflow.
|
||||
func IsPackagingWorkflow(workflow *actionlint.Workflow, fp string) (JobMatchResult, bool) {
|
||||
jobMatchers := []JobMatcher{
|
||||
{
|
||||
Steps: []*JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-node",
|
||||
With: map[string]string{"registry-url": "https://registry.npmjs.org"},
|
||||
},
|
||||
{
|
||||
Run: "npm.*publish",
|
||||
},
|
||||
},
|
||||
LogText: "candidate node publishing workflow using npm",
|
||||
},
|
||||
{
|
||||
// Java packages with maven.
|
||||
Steps: []*JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-java",
|
||||
},
|
||||
{
|
||||
Run: "mvn.*deploy",
|
||||
},
|
||||
},
|
||||
LogText: "candidate java publishing workflow using maven",
|
||||
},
|
||||
{
|
||||
// Java packages with gradle.
|
||||
Steps: []*JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-java",
|
||||
},
|
||||
{
|
||||
Run: "gradle.*publish",
|
||||
},
|
||||
},
|
||||
LogText: "candidate java publishing workflow using gradle",
|
||||
},
|
||||
{
|
||||
// Ruby packages.
|
||||
Steps: []*JobMatcherStep{
|
||||
{
|
||||
Run: "gem.*push",
|
||||
},
|
||||
},
|
||||
LogText: "candidate ruby publishing workflow using gem",
|
||||
},
|
||||
{
|
||||
// NuGet packages.
|
||||
Steps: []*JobMatcherStep{
|
||||
{
|
||||
Run: "nuget.*push",
|
||||
},
|
||||
},
|
||||
LogText: "candidate nuget publishing workflow",
|
||||
},
|
||||
{
|
||||
// Docker packages.
|
||||
Steps: []*JobMatcherStep{
|
||||
{
|
||||
Run: "docker.*push",
|
||||
},
|
||||
},
|
||||
LogText: "candidate docker publishing workflow",
|
||||
},
|
||||
{
|
||||
// Docker packages.
|
||||
Steps: []*JobMatcherStep{
|
||||
{
|
||||
Uses: "docker/build-push-action",
|
||||
},
|
||||
},
|
||||
LogText: "candidate docker publishing workflow",
|
||||
},
|
||||
{
|
||||
// Python packages.
|
||||
Steps: []*JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-python",
|
||||
},
|
||||
{
|
||||
Uses: "pypa/gh-action-pypi-publish",
|
||||
},
|
||||
},
|
||||
LogText: "candidate python publishing workflow using pypi",
|
||||
},
|
||||
{
|
||||
// Python packages.
|
||||
// This is a custom Python packaging workflow based on semantic versioning.
|
||||
// TODO(#1642): accept custom workflows through a separate configuration.
|
||||
Steps: []*JobMatcherStep{
|
||||
{
|
||||
Uses: "relekang/python-semantic-release",
|
||||
},
|
||||
},
|
||||
LogText: "candidate python publishing workflow using python-semantic-release",
|
||||
},
|
||||
{
|
||||
// Go packages.
|
||||
Steps: []*JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-go",
|
||||
},
|
||||
{
|
||||
Uses: "goreleaser/goreleaser-action",
|
||||
},
|
||||
},
|
||||
LogText: "candidate golang publishing workflow",
|
||||
},
|
||||
{
|
||||
// Rust packages. https://doc.rust-lang.org/cargo/reference/publishing.html
|
||||
Steps: []*JobMatcherStep{
|
||||
{
|
||||
Run: "cargo.*publish",
|
||||
},
|
||||
},
|
||||
LogText: "candidate rust publishing workflow using cargo",
|
||||
},
|
||||
{
|
||||
// Ko container action. https://github.com/google/ko
|
||||
Steps: []*JobMatcherStep{
|
||||
{
|
||||
Uses: "imjasonh/setup-ko",
|
||||
},
|
||||
},
|
||||
LogText: "candidate container publishing workflow using ko",
|
||||
},
|
||||
}
|
||||
|
||||
return AnyJobsMatch(workflow, jobMatchers, fp, "not a publishing workflow")
|
||||
}
|
||||
|
@ -15,42 +15,15 @@
|
||||
package checks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// CheckTokenPermissions is the exported name for Token-Permissions check.
|
||||
const (
|
||||
CheckTokenPermissions = "Token-Permissions"
|
||||
jobLevelPermission = "job level"
|
||||
topLevelPermission = "top level"
|
||||
)
|
||||
|
||||
type permission string
|
||||
|
||||
const (
|
||||
permissionAll = permission("all")
|
||||
permissionStatuses = permission("statuses")
|
||||
permissionChecks = permission("checks")
|
||||
permissionSecurityEvents = permission("security-events")
|
||||
permissionDeployments = permission("deployments")
|
||||
permissionContents = permission("contents")
|
||||
permissionPackages = permission("packages")
|
||||
permissionActions = permission("actions")
|
||||
)
|
||||
|
||||
var permissionsOfInterest = []permission{
|
||||
permissionStatuses, permissionChecks,
|
||||
permissionSecurityEvents, permissionDeployments,
|
||||
permissionContents, permissionPackages, permissionActions,
|
||||
}
|
||||
const CheckTokenPermissions = "Token-Permissions"
|
||||
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
@ -64,678 +37,24 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// Holds stateful data to pass thru callbacks.
|
||||
// Each field correpsonds to a GitHub permission type, and
|
||||
// will hold true if declared non-write, false otherwise.
|
||||
type permissions struct {
|
||||
topLevelWritePermissions map[permission]bool
|
||||
jobLevelWritePermissions map[permission]bool
|
||||
}
|
||||
|
||||
type permissionCbData struct {
|
||||
// map of filename to write permissions used.
|
||||
workflows map[string]permissions
|
||||
}
|
||||
|
||||
// TokenPermissions runs Token-Permissions check.
|
||||
// TokenPermissions will run the Token-Permissions check.
|
||||
func TokenPermissions(c *checker.CheckRequest) checker.CheckResult {
|
||||
// data is shared across all GitHub workflows.
|
||||
data := permissionCbData{
|
||||
workflows: make(map[string]permissions),
|
||||
}
|
||||
|
||||
if err := remediation.Setup(c); err != nil {
|
||||
createResultForLeastPrivilegeTokens(data, err)
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
return checker.CreateRuntimeErrorResult(CheckTokenPermissions, e)
|
||||
}
|
||||
|
||||
err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
|
||||
Pattern: ".github/workflows/*",
|
||||
CaseSensitive: false,
|
||||
}, validateGitHubActionTokenPermissions, c.Dlogger, &data)
|
||||
return createResultForLeastPrivilegeTokens(data, err)
|
||||
}
|
||||
|
||||
// Check file content.
|
||||
var validateGitHubActionTokenPermissions fileparser.DoWhileTrueOnFileContent = func(path string,
|
||||
content []byte,
|
||||
args ...interface{},
|
||||
) (bool, error) {
|
||||
if !fileparser.IsWorkflowFile(path) {
|
||||
return true, nil
|
||||
}
|
||||
// Verify the type of the data.
|
||||
if len(args) != 2 {
|
||||
return false, fmt.Errorf(
|
||||
"validateGitHubActionTokenPermissions requires exactly 2 arguments: %w", errInvalidArgLength)
|
||||
}
|
||||
pdata, ok := args[1].(*permissionCbData)
|
||||
if !ok {
|
||||
return false, fmt.Errorf(
|
||||
"validateGitHubActionTokenPermissions requires arg[0] of type *permissionCbData: %w", errInvalidArgType)
|
||||
}
|
||||
dl, ok := args[0].(checker.DetailLogger)
|
||||
if !ok {
|
||||
return false, fmt.Errorf(
|
||||
"validateGitHubActionTokenPermissions requires arg[1] of type checker.DetailLogger: %w", errInvalidArgType)
|
||||
}
|
||||
|
||||
if !fileparser.CheckFileContainsCommands(content, "#") {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
workflow, errs := actionlint.Parse(content)
|
||||
if len(errs) > 0 && workflow == nil {
|
||||
return false, fileparser.FormatActionlintError(errs)
|
||||
}
|
||||
|
||||
// 1. Top-level permission definitions.
|
||||
//nolint
|
||||
// https://docs.github.com/en/actions/reference/authentication-in-a-workflow#example-1-passing-the-github_token-as-an-input,
|
||||
// https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/,
|
||||
// https://docs.github.com/en/actions/reference/authentication-in-a-workflow#modifying-the-permissions-for-the-github_token.
|
||||
if err := validateTopLevelPermissions(workflow, path, dl, pdata); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 2. Run-level permission definitions,
|
||||
// see https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idpermissions.
|
||||
ignoredPermissions := createIgnoredPermissions(workflow, path, dl)
|
||||
if err := validatejobLevelPermissions(workflow, path, dl, pdata, ignoredPermissions); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// TODO(laurent): 2. Identify github actions that require write and add checks.
|
||||
|
||||
// TODO(laurent): 3. Read a few runs and ensures they have the same permissions.
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func validatePermission(permissionKey permission, permissionValue *actionlint.PermissionScope,
|
||||
permLevel, path string, dl checker.DetailLogger, pPermissions map[permission]bool,
|
||||
ignoredPermissions map[permission]bool,
|
||||
) error {
|
||||
if permissionValue.Value == nil {
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
||||
}
|
||||
val := permissionValue.Value.Value
|
||||
lineNumber := fileparser.GetLineNumber(permissionValue.Value.Pos)
|
||||
if strings.EqualFold(val, "write") {
|
||||
if isPermissionOfInterest(permissionKey, ignoredPermissions) {
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: lineNumber,
|
||||
Text: fmt.Sprintf("%s '%v' permission set to '%v'", permLevel, permissionKey, val),
|
||||
Snippet: val,
|
||||
Remediation: remediation.CreateWorkflowPermissionRemediation(path),
|
||||
})
|
||||
recordPermissionWrite(pPermissions, permissionKey)
|
||||
} else {
|
||||
// Only log for debugging, otherwise
|
||||
// it may confuse users.
|
||||
dl.Debug(&checker.LogMessage{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: lineNumber,
|
||||
Text: fmt.Sprintf("%s '%v' permission set to '%v'", permLevel, permissionKey, val),
|
||||
Snippet: val,
|
||||
Remediation: remediation.CreateWorkflowPermissionRemediation(path),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
dl.Info(&checker.LogMessage{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: lineNumber,
|
||||
Text: fmt.Sprintf("%s '%v' permission set to '%v'", permLevel, permissionKey, val),
|
||||
// TODO: set Snippet.
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateMapPermissions(scopes map[string]*actionlint.PermissionScope, permLevel, path string,
|
||||
dl checker.DetailLogger, pPermissions map[permission]bool,
|
||||
ignoredPermissions map[permission]bool,
|
||||
) error {
|
||||
for key, v := range scopes {
|
||||
if err := validatePermission(permission(key), v, permLevel, path, dl, pPermissions, ignoredPermissions); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func recordPermissionWrite(pPermissions map[permission]bool, perm permission) {
|
||||
pPermissions[perm] = true
|
||||
}
|
||||
|
||||
func getWritePermissionsMap(p *permissionCbData, path, permLevel string) map[permission]bool {
|
||||
if _, exists := p.workflows[path]; !exists {
|
||||
p.workflows[path] = permissions{
|
||||
topLevelWritePermissions: make(map[permission]bool),
|
||||
jobLevelWritePermissions: make(map[permission]bool),
|
||||
}
|
||||
}
|
||||
if permLevel == jobLevelPermission {
|
||||
return p.workflows[path].jobLevelWritePermissions
|
||||
}
|
||||
return p.workflows[path].topLevelWritePermissions
|
||||
}
|
||||
|
||||
func recordAllPermissionsWrite(p *permissionCbData, permLevel, path string) {
|
||||
// Special case: `all` does not correspond
|
||||
// to a GitHub permission.
|
||||
m := getWritePermissionsMap(p, path, permLevel)
|
||||
m[permissionAll] = true
|
||||
}
|
||||
|
||||
func validatePermissions(permissions *actionlint.Permissions, permLevel, path string,
|
||||
dl checker.DetailLogger, pdata *permissionCbData,
|
||||
ignoredPermissions map[permission]bool,
|
||||
) error {
|
||||
allIsSet := permissions != nil && permissions.All != nil && permissions.All.Value != ""
|
||||
scopeIsSet := permissions != nil && len(permissions.Scopes) > 0
|
||||
if permissions == nil || (!allIsSet && !scopeIsSet) {
|
||||
dl.Info(&checker.LogMessage{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: checker.OffsetDefault,
|
||||
Text: fmt.Sprintf("%s permissions set to 'none'", permLevel),
|
||||
})
|
||||
}
|
||||
if allIsSet {
|
||||
val := permissions.All.Value
|
||||
lineNumber := fileparser.GetLineNumber(permissions.All.Pos)
|
||||
if !strings.EqualFold(val, "read-all") && val != "" {
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: lineNumber,
|
||||
Text: fmt.Sprintf("%s permissions set to '%v'", permLevel, val),
|
||||
Snippet: val,
|
||||
Remediation: remediation.CreateWorkflowPermissionRemediation(path),
|
||||
})
|
||||
recordAllPermissionsWrite(pdata, permLevel, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
dl.Info(&checker.LogMessage{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: lineNumber,
|
||||
Text: fmt.Sprintf("%s permissions set to '%v'", permLevel, val),
|
||||
Snippet: val,
|
||||
Remediation: remediation.CreateWorkflowPermissionRemediation(path),
|
||||
})
|
||||
} else /* scopeIsSet == true */ if err := validateMapPermissions(permissions.Scopes,
|
||||
permLevel, path, dl, getWritePermissionsMap(pdata, path, permLevel), ignoredPermissions); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateTopLevelPermissions(workflow *actionlint.Workflow, path string,
|
||||
dl checker.DetailLogger, pdata *permissionCbData,
|
||||
) error {
|
||||
// Check if permissions are set explicitly.
|
||||
if workflow.Permissions == nil {
|
||||
dl.Warn(&checker.LogMessage{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: checker.OffsetDefault,
|
||||
Text: fmt.Sprintf("no %s permission defined", topLevelPermission),
|
||||
Remediation: remediation.CreateWorkflowPermissionRemediation(path),
|
||||
})
|
||||
recordAllPermissionsWrite(pdata, topLevelPermission, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
return validatePermissions(workflow.Permissions, topLevelPermission, path, dl,
|
||||
pdata, map[permission]bool{})
|
||||
}
|
||||
|
||||
func validatejobLevelPermissions(workflow *actionlint.Workflow, path string,
|
||||
dl checker.DetailLogger, pdata *permissionCbData,
|
||||
ignoredPermissions map[permission]bool,
|
||||
) error {
|
||||
for _, job := range workflow.Jobs {
|
||||
// Run-level permissions may be left undefined.
|
||||
// For most workflows, no write permissions are needed,
|
||||
// so only top-level read-only permissions need to be declared.
|
||||
if job.Permissions == nil {
|
||||
dl.Debug(&checker.LogMessage{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: fileparser.GetLineNumber(job.Pos),
|
||||
Text: fmt.Sprintf("no %s permission defined", jobLevelPermission),
|
||||
Remediation: remediation.CreateWorkflowPermissionRemediation(path),
|
||||
})
|
||||
recordAllPermissionsWrite(pdata, jobLevelPermission, path)
|
||||
continue
|
||||
}
|
||||
err := validatePermissions(job.Permissions, jobLevelPermission,
|
||||
path, dl, pdata, ignoredPermissions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isPermissionOfInterest(name permission, ignoredPermissions map[permission]bool) bool {
|
||||
for _, p := range permissionsOfInterest {
|
||||
_, present := ignoredPermissions[p]
|
||||
if strings.EqualFold(string(name), string(p)) && !present {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func permissionIsPresent(perms permissions, name permission) bool {
|
||||
return permissionIsPresentInTopLevel(perms, name) ||
|
||||
permissionIsPresentInRunLevel(perms, name)
|
||||
}
|
||||
|
||||
func permissionIsPresentInTopLevel(perms permissions, name permission) bool {
|
||||
_, ok := perms.topLevelWritePermissions[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func permissionIsPresentInRunLevel(perms permissions, name permission) bool {
|
||||
_, ok := perms.jobLevelWritePermissions[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Calculate the score.
|
||||
func calculateScore(result permissionCbData) int {
|
||||
// See list https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/.
|
||||
// Note: there are legitimate reasons to use some of the permissions like checks, deployments, etc.
|
||||
// in CI/CD systems https://docs.travis-ci.com/user/github-oauth-scopes/.
|
||||
|
||||
// Start with a perfect score.
|
||||
score := float32(checker.MaxResultScore)
|
||||
|
||||
// Retrieve the overall results.
|
||||
for _, perms := range result.workflows {
|
||||
// If no top level permissions are defined, all the permissions
|
||||
// are enabled by default, hence permissionAll. In this case,
|
||||
if permissionIsPresentInTopLevel(perms, permissionAll) {
|
||||
if permissionIsPresentInRunLevel(perms, permissionAll) {
|
||||
// ... give lowest score if no run level permissions are defined either.
|
||||
return checker.MinResultScore
|
||||
}
|
||||
// ... reduce score if run level permissions are defined.
|
||||
score -= 0.5
|
||||
}
|
||||
|
||||
// status: https://docs.github.com/en/rest/reference/repos#statuses.
|
||||
// May allow an attacker to change the result of pre-submit and get a PR merged.
|
||||
// Low risk: -0.5.
|
||||
if permissionIsPresent(perms, permissionStatuses) {
|
||||
score -= 0.5
|
||||
}
|
||||
|
||||
// checks.
|
||||
// May allow an attacker to edit checks to remove pre-submit and introduce a bug.
|
||||
// Low risk: -0.5.
|
||||
if permissionIsPresent(perms, permissionChecks) {
|
||||
score -= 0.5
|
||||
}
|
||||
|
||||
// secEvents.
|
||||
// May allow attacker to read vuln reports before patch available.
|
||||
// Low risk: -1
|
||||
if permissionIsPresent(perms, permissionSecurityEvents) {
|
||||
score--
|
||||
}
|
||||
|
||||
// deployments: https://docs.github.com/en/rest/reference/repos#deployments.
|
||||
// May allow attacker to charge repo owner by triggering VM runs,
|
||||
// and tiny chance an attacker can trigger a remote
|
||||
// service with code they own if server accepts code/location var unsanitized.
|
||||
// Low risk: -1
|
||||
if permissionIsPresent(perms, permissionDeployments) {
|
||||
score--
|
||||
}
|
||||
|
||||
// contents.
|
||||
// Allows attacker to commit unreviewed code.
|
||||
// High risk: -10
|
||||
if permissionIsPresent(perms, permissionContents) {
|
||||
score -= checker.MaxResultScore
|
||||
}
|
||||
|
||||
// packages: https://docs.github.com/en/packages/learn-github-packages/about-permissions-for-github-packages.
|
||||
// Allows attacker to publish packages.
|
||||
// High risk: -10
|
||||
if permissionIsPresent(perms, permissionPackages) {
|
||||
score -= checker.MaxResultScore
|
||||
}
|
||||
|
||||
// actions.
|
||||
// May allow an attacker to steal GitHub secrets by approving to run an action that needs approval.
|
||||
// High risk: -10
|
||||
if permissionIsPresent(perms, permissionActions) {
|
||||
score -= checker.MaxResultScore
|
||||
}
|
||||
|
||||
if score < checker.MinResultScore {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// We're done, calculate the final score.
|
||||
if score < checker.MinResultScore {
|
||||
return checker.MinResultScore
|
||||
}
|
||||
|
||||
return int(score)
|
||||
}
|
||||
|
||||
// Create the result.
|
||||
func createResultForLeastPrivilegeTokens(result permissionCbData, err error) checker.CheckResult {
|
||||
rawData, err := raw.TokenPermissions(c)
|
||||
if err != nil {
|
||||
return checker.CreateRuntimeErrorResult(CheckTokenPermissions, err)
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
return checker.CreateRuntimeErrorResult(CheckTokenPermissions, e)
|
||||
}
|
||||
|
||||
score := calculateScore(result)
|
||||
|
||||
if score != checker.MaxResultScore {
|
||||
return checker.CreateResultWithScore(CheckTokenPermissions,
|
||||
"non read-only tokens detected in GitHub workflows", score)
|
||||
// Return raw results.
|
||||
if c.RawResults != nil {
|
||||
c.RawResults.TokenPermissionsResults = rawData
|
||||
}
|
||||
|
||||
return checker.CreateMaxScoreResult(CheckTokenPermissions,
|
||||
"tokens are read-only in GitHub workflows")
|
||||
}
|
||||
|
||||
func createIgnoredPermissions(workflow *actionlint.Workflow, fp string, dl checker.DetailLogger) map[permission]bool {
|
||||
ignoredPermissions := make(map[permission]bool)
|
||||
if requiresPackagesPermissions(workflow, fp, dl) {
|
||||
ignoredPermissions[permissionPackages] = true
|
||||
}
|
||||
if requiresContentsPermissions(workflow, fp, dl) {
|
||||
ignoredPermissions[permissionContents] = true
|
||||
}
|
||||
if isSARIFUploadWorkflow(workflow, fp, dl) {
|
||||
ignoredPermissions[permissionSecurityEvents] = true
|
||||
}
|
||||
|
||||
return ignoredPermissions
|
||||
}
|
||||
|
||||
// Scanning tool run externally and SARIF file uploaded.
|
||||
func isSARIFUploadWorkflow(workflow *actionlint.Workflow, fp string, dl checker.DetailLogger) bool {
|
||||
//nolint
|
||||
// CodeQl analysis workflow automatically sends sarif file to GitHub.
|
||||
// https://docs.github.com/en/code-security/secure-coding/integrating-with-code-scanning/uploading-a-sarif-file-to-github#about-sarif-file-uploads-for-code-scanning.
|
||||
// `The CodeQL action uploads the SARIF file automatically when it completes analysis`.
|
||||
if isCodeQlAnalysisWorkflow(workflow, fp, dl) {
|
||||
return true
|
||||
}
|
||||
|
||||
//nolint
|
||||
// Third-party scanning tools use the SARIF-upload action from code-ql.
|
||||
// https://docs.github.com/en/code-security/secure-coding/integrating-with-code-scanning/uploading-a-sarif-file-to-github#uploading-a-code-scanning-analysis-with-github-actions
|
||||
// We only support CodeQl today.
|
||||
if isSARIFUploadAction(workflow, fp, dl) {
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO: some third party tools may upload directly thru their actions.
|
||||
// Very unlikely.
|
||||
// See https://github.com/marketplace for tools.
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// CodeQl run externally and SARIF file uploaded.
|
||||
func isSARIFUploadAction(workflow *actionlint.Workflow, fp string, dl checker.DetailLogger) bool {
|
||||
for _, job := range workflow.Jobs {
|
||||
for _, step := range job.Steps {
|
||||
uses := fileparser.GetUses(step)
|
||||
if uses == nil {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(uses.Value, "github/codeql-action/upload-sarif@") {
|
||||
dl.Debug(&checker.LogMessage{
|
||||
Path: fp,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: fileparser.GetLineNumber(uses.Pos),
|
||||
Text: "codeql SARIF upload workflow detected",
|
||||
// TODO: set Snippet.
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
dl.Debug(&checker.LogMessage{
|
||||
Path: fp,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: checker.OffsetDefault,
|
||||
Text: "not a codeql upload SARIF workflow",
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
//nolint
|
||||
// CodeQl run within GitHub worklow automatically bubbled up to
|
||||
// security events, see
|
||||
// https://docs.github.com/en/code-security/secure-coding/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning.
|
||||
func isCodeQlAnalysisWorkflow(workflow *actionlint.Workflow, fp string, dl checker.DetailLogger) bool {
|
||||
for _, job := range workflow.Jobs {
|
||||
for _, step := range job.Steps {
|
||||
uses := fileparser.GetUses(step)
|
||||
if uses == nil {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(uses.Value, "github/codeql-action/analyze@") {
|
||||
dl.Debug(&checker.LogMessage{
|
||||
Path: fp,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: fileparser.GetLineNumber(uses.Pos),
|
||||
Text: "codeql workflow detected",
|
||||
// TODO: set Snippet.
|
||||
})
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
dl.Debug(&checker.LogMessage{
|
||||
Path: fp,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: checker.OffsetDefault,
|
||||
Text: "not a codeql workflow",
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
// A packaging workflow using GitHub's supported packages:
|
||||
// https://docs.github.com/en/packages.
|
||||
func requiresPackagesPermissions(workflow *actionlint.Workflow, fp string, dl checker.DetailLogger) bool {
|
||||
// TODO: add support for GitHub registries.
|
||||
// Example: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry.
|
||||
// This feature requires parsing actions properly.
|
||||
// For now, we just re-use the Packaging check to verify that the
|
||||
// workflow is a packaging workflow.
|
||||
return isPackagingWorkflow(workflow, fp, dl)
|
||||
}
|
||||
|
||||
// requiresContentsPermissions returns true if the workflow requires the `contents: write` permission.
|
||||
func requiresContentsPermissions(workflow *actionlint.Workflow, fp string, dl checker.DetailLogger) bool {
|
||||
return isReleasingWorkflow(workflow, fp, dl) || isGitHubPagesDeploymentWorkflow(workflow, fp, dl)
|
||||
}
|
||||
|
||||
// isGitHubPagesDeploymentWorkflow returns true if the workflow involves pushing static pages to GitHub pages.
|
||||
func isGitHubPagesDeploymentWorkflow(workflow *actionlint.Workflow, fp string, dl checker.DetailLogger) bool {
|
||||
jobMatchers := []fileparser.JobMatcher{
|
||||
{
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "peaceiris/actions-gh-pages",
|
||||
},
|
||||
},
|
||||
LogText: "candidate GitHub page deployment workflow using peaceiris/actions-gh-pages",
|
||||
},
|
||||
}
|
||||
return fileparser.AnyJobsMatch(workflow, jobMatchers, fp, dl, "not a GitHub Pages deployment workflow")
|
||||
}
|
||||
|
||||
// isReleasingWorkflow returns true if the workflow involves creating a release on GitHub.
|
||||
func isReleasingWorkflow(workflow *actionlint.Workflow, fp string, dl checker.DetailLogger) bool {
|
||||
jobMatchers := []fileparser.JobMatcher{
|
||||
{
|
||||
// Python packages.
|
||||
// This is a custom Python packaging/releasing workflow based on semantic versioning.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "relekang/python-semantic-release",
|
||||
},
|
||||
},
|
||||
LogText: "candidate python publishing workflow using python-semantic-release",
|
||||
},
|
||||
{
|
||||
// Go packages.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-go",
|
||||
},
|
||||
{
|
||||
Uses: "goreleaser/goreleaser-action",
|
||||
},
|
||||
},
|
||||
LogText: "candidate golang publishing workflow",
|
||||
},
|
||||
}
|
||||
|
||||
return fileparser.AnyJobsMatch(workflow, jobMatchers, fp, dl, "not a releasing workflow")
|
||||
}
|
||||
|
||||
// TODO: remove when migrated to raw results.
|
||||
// Should be using the definition in raw/packaging.go.
|
||||
func isPackagingWorkflow(workflow *actionlint.Workflow, fp string, dl checker.DetailLogger) bool {
|
||||
jobMatchers := []fileparser.JobMatcher{
|
||||
{
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-node",
|
||||
With: map[string]string{"registry-url": "https://registry.npmjs.org"},
|
||||
},
|
||||
{
|
||||
Run: "npm.*publish",
|
||||
},
|
||||
},
|
||||
LogText: "candidate node publishing workflow using npm",
|
||||
},
|
||||
{
|
||||
// Java packages with maven.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-java",
|
||||
},
|
||||
{
|
||||
Run: "mvn.*deploy",
|
||||
},
|
||||
},
|
||||
LogText: "candidate java publishing workflow using maven",
|
||||
},
|
||||
{
|
||||
// Java packages with gradle.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-java",
|
||||
},
|
||||
{
|
||||
Run: "gradle.*publish",
|
||||
},
|
||||
},
|
||||
LogText: "candidate java publishing workflow using gradle",
|
||||
},
|
||||
{
|
||||
// Ruby packages.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Run: "gem.*push",
|
||||
},
|
||||
},
|
||||
LogText: "candidate ruby publishing workflow using gem",
|
||||
},
|
||||
{
|
||||
// NuGet packages.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Run: "nuget.*push",
|
||||
},
|
||||
},
|
||||
LogText: "candidate nuget publishing workflow",
|
||||
},
|
||||
{
|
||||
// Docker packages.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Run: "docker.*push",
|
||||
},
|
||||
},
|
||||
LogText: "candidate docker publishing workflow",
|
||||
},
|
||||
{
|
||||
// Docker packages.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "docker/build-push-action",
|
||||
},
|
||||
},
|
||||
LogText: "candidate docker publishing workflow",
|
||||
},
|
||||
{
|
||||
// Python packages.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-python",
|
||||
},
|
||||
{
|
||||
Uses: "pypa/gh-action-pypi-publish",
|
||||
},
|
||||
},
|
||||
LogText: "candidate python publishing workflow using pypi",
|
||||
},
|
||||
{
|
||||
// Python packages.
|
||||
// This is a custom Python packaging workflow based on semantic versioning.
|
||||
// TODO(#1642): accept custom workflows through a separate configuration.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "relekang/python-semantic-release",
|
||||
},
|
||||
},
|
||||
LogText: "candidate python publishing workflow using python-semantic-release",
|
||||
},
|
||||
{
|
||||
// Go packages.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-go",
|
||||
},
|
||||
{
|
||||
Uses: "goreleaser/goreleaser-action",
|
||||
},
|
||||
},
|
||||
LogText: "candidate golang publishing workflow",
|
||||
},
|
||||
{
|
||||
// Rust packages. https://doc.rust-lang.org/cargo/reference/publishing.html
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Run: "cargo.*publish",
|
||||
},
|
||||
},
|
||||
LogText: "candidate rust publishing workflow using cargo",
|
||||
},
|
||||
}
|
||||
|
||||
return fileparser.AnyJobsMatch(workflow, jobMatchers, fp, dl, "not a publishing workflow")
|
||||
// Return the score evaluation.
|
||||
return evaluation.TokenPermissions(CheckTokenPermissions, c.Dlogger, &rawData)
|
||||
}
|
||||
|
@ -20,33 +20,15 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/clients"
|
||||
mockrepo "github.com/ossf/scorecard/v4/clients/mockclients"
|
||||
scut "github.com/ossf/scorecard/v4/utests"
|
||||
)
|
||||
|
||||
type file struct {
|
||||
pathfn string
|
||||
content []byte
|
||||
}
|
||||
|
||||
func testValidateGitHubActionTokenPermissions(files []file,
|
||||
dl checker.DetailLogger,
|
||||
) checker.CheckResult {
|
||||
data := permissionCbData{
|
||||
workflows: make(map[string]permissions),
|
||||
}
|
||||
var err error
|
||||
for _, f := range files {
|
||||
_, err = validateGitHubActionTokenPermissions(f.pathfn, f.content, dl, &data)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return createResultForLeastPrivilegeTokens(data, err)
|
||||
}
|
||||
|
||||
//nolint
|
||||
// nolint
|
||||
func TestGithubTokenPermissions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -95,8 +77,8 @@ func TestGithubTokenPermissions(t *testing.T) {
|
||||
Error: nil,
|
||||
Score: checker.MaxResultScore,
|
||||
NumberOfWarn: 0,
|
||||
NumberOfInfo: 2,
|
||||
NumberOfDebug: 5,
|
||||
NumberOfInfo: 1,
|
||||
NumberOfDebug: 6,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -271,8 +253,8 @@ func TestGithubTokenPermissions(t *testing.T) {
|
||||
Error: nil,
|
||||
Score: checker.MinResultScore,
|
||||
NumberOfWarn: 1,
|
||||
NumberOfInfo: 2,
|
||||
NumberOfDebug: 4,
|
||||
NumberOfInfo: 1,
|
||||
NumberOfDebug: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -282,8 +264,8 @@ func TestGithubTokenPermissions(t *testing.T) {
|
||||
Error: nil,
|
||||
Score: checker.MaxResultScore,
|
||||
NumberOfWarn: 0,
|
||||
NumberOfInfo: 3,
|
||||
NumberOfDebug: 3,
|
||||
NumberOfInfo: 1,
|
||||
NumberOfDebug: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -293,8 +275,8 @@ func TestGithubTokenPermissions(t *testing.T) {
|
||||
Error: nil,
|
||||
Score: checker.MaxResultScore,
|
||||
NumberOfWarn: 0,
|
||||
NumberOfInfo: 2,
|
||||
NumberOfDebug: 5,
|
||||
NumberOfInfo: 1,
|
||||
NumberOfDebug: 6,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -370,8 +352,8 @@ func TestGithubTokenPermissions(t *testing.T) {
|
||||
Error: nil,
|
||||
Score: checker.MaxResultScore,
|
||||
NumberOfWarn: 0,
|
||||
NumberOfInfo: 2,
|
||||
NumberOfDebug: 5,
|
||||
NumberOfInfo: 1,
|
||||
NumberOfDebug: 6,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -379,22 +361,37 @@ func TestGithubTokenPermissions(t *testing.T) {
|
||||
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()
|
||||
var files []file
|
||||
var content []byte
|
||||
var err error
|
||||
for _, fn := range tt.filenames {
|
||||
content, err = os.ReadFile(fn)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot read file: %w", err))
|
||||
}
|
||||
|
||||
files = append(files, file{pathfn: strings.Replace(fn, "./testdata/", "", 1), content: content})
|
||||
ctrl := gomock.NewController(t)
|
||||
mockRepo := mockrepo.NewMockRepoClient(ctrl)
|
||||
|
||||
main := "main"
|
||||
mockRepo.EXPECT().URI().Return("github.com/ossf/scorecard").AnyTimes()
|
||||
mockRepo.EXPECT().GetDefaultBranch().Return(&clients.BranchRef{Name: &main}, nil).AnyTimes()
|
||||
mockRepo.EXPECT().ListFiles(gomock.Any()).DoAndReturn(func(predicate func(string) (bool, error)) ([]string, error) {
|
||||
files := []string{}
|
||||
for _, fn := range tt.filenames {
|
||||
files = append(files, strings.TrimPrefix(fn, "./testdata/"))
|
||||
}
|
||||
return files, nil
|
||||
}).AnyTimes()
|
||||
mockRepo.EXPECT().GetFileContent(gomock.Any()).DoAndReturn(func(fn string) ([]byte, error) {
|
||||
content, err := os.ReadFile("./testdata/" + fn)
|
||||
if err != nil {
|
||||
return content, fmt.Errorf("%w", err)
|
||||
}
|
||||
return content, nil
|
||||
}).AnyTimes()
|
||||
dl := scut.TestDetailLogger{}
|
||||
c := checker.CheckRequest{
|
||||
RepoClient: mockRepo,
|
||||
Dlogger: &dl,
|
||||
}
|
||||
|
||||
dl := scut.TestDetailLogger{}
|
||||
r := testValidateGitHubActionTokenPermissions(files, &dl)
|
||||
if !scut.ValidateTestReturn(t, tt.name, &tt.expected, &r, &dl) {
|
||||
t.Fail()
|
||||
res := TokenPermissions(&c)
|
||||
|
||||
if !scut.ValidateTestReturn(t, tt.name, &tt.expected, &res, &dl) {
|
||||
t.Errorf("test failed: log message not present: %+v\n%+v", tt.expected, dl)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -440,11 +437,28 @@ func TestGithubTokenPermissionsLineNumber(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("cannot read file: %v", err)
|
||||
}
|
||||
dl := scut.TestDetailLogger{}
|
||||
p := strings.Replace(tt.filename, "./testdata/", "", 1)
|
||||
files := []file{{pathfn: p, content: content}}
|
||||
|
||||
testValidateGitHubActionTokenPermissions(files, &dl)
|
||||
p := strings.Replace(tt.filename, "./testdata/", "", 1)
|
||||
ctrl := gomock.NewController(t)
|
||||
mockRepo := mockrepo.NewMockRepoClient(ctrl)
|
||||
|
||||
main := "main"
|
||||
mockRepo.EXPECT().URI().Return("github.com/ossf/scorecard").AnyTimes()
|
||||
mockRepo.EXPECT().GetDefaultBranch().Return(&clients.BranchRef{Name: &main}, nil).AnyTimes()
|
||||
mockRepo.EXPECT().ListFiles(gomock.Any()).DoAndReturn(func(predicate func(string) (bool, error)) ([]string, error) {
|
||||
return []string{p}, nil
|
||||
}).AnyTimes()
|
||||
mockRepo.EXPECT().GetFileContent(gomock.Any()).DoAndReturn(func(fn string) ([]byte, error) {
|
||||
return content, nil
|
||||
}).AnyTimes()
|
||||
dl := scut.TestDetailLogger{}
|
||||
c := checker.CheckRequest{
|
||||
RepoClient: mockRepo,
|
||||
Dlogger: &dl,
|
||||
}
|
||||
|
||||
_ = TokenPermissions(&c)
|
||||
|
||||
for _, expectedLog := range tt.expected {
|
||||
isExpectedLog := func(logMessage checker.LogMessage, logType checker.DetailType) bool {
|
||||
return logMessage.Offset == expectedLog.lineNumber && logMessage.Path == p &&
|
||||
|
@ -15,8 +15,6 @@
|
||||
package checks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/checks/evaluation"
|
||||
"github.com/ossf/scorecard/v4/checks/raw"
|
||||
@ -42,7 +40,7 @@ func init() {
|
||||
// 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))
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
return checker.CreateRuntimeErrorResult(CheckPinnedDependencies, e)
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ func Packaging(c *checker.CheckRequest) (checker.PackagingData, error) {
|
||||
}
|
||||
|
||||
// Check if it's a packaging workflow.
|
||||
match, ok := isPackagingWorkflow(workflow, fp)
|
||||
match, ok := fileparser.IsPackagingWorkflow(workflow, fp)
|
||||
// Always print debug messages.
|
||||
data.Packages = append(data.Packages,
|
||||
checker.Package{
|
||||
@ -117,127 +117,3 @@ func Packaging(c *checker.CheckRequest) (checker.PackagingData, error) {
|
||||
func stringPointer(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
// A packaging workflow.
|
||||
func isPackagingWorkflow(workflow *actionlint.Workflow, fp string) (fileparser.JobMatchResult, bool) {
|
||||
jobMatchers := []fileparser.JobMatcher{
|
||||
{
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-node",
|
||||
With: map[string]string{"registry-url": "https://registry.npmjs.org"},
|
||||
},
|
||||
{
|
||||
Run: "npm.*publish",
|
||||
},
|
||||
},
|
||||
LogText: "candidate node publishing workflow using npm",
|
||||
},
|
||||
{
|
||||
// Java packages with maven.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-java",
|
||||
},
|
||||
{
|
||||
Run: "mvn.*deploy",
|
||||
},
|
||||
},
|
||||
LogText: "candidate java publishing workflow using maven",
|
||||
},
|
||||
{
|
||||
// Java packages with gradle.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-java",
|
||||
},
|
||||
{
|
||||
Run: "gradle.*publish",
|
||||
},
|
||||
},
|
||||
LogText: "candidate java publishing workflow using gradle",
|
||||
},
|
||||
{
|
||||
// Ruby packages.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Run: "gem.*push",
|
||||
},
|
||||
},
|
||||
LogText: "candidate ruby publishing workflow using gem",
|
||||
},
|
||||
{
|
||||
// NuGet packages.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Run: "nuget.*push",
|
||||
},
|
||||
},
|
||||
LogText: "candidate nuget publishing workflow",
|
||||
},
|
||||
{
|
||||
// Docker packages.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Run: "docker.*push",
|
||||
},
|
||||
},
|
||||
LogText: "candidate docker publishing workflow",
|
||||
},
|
||||
{
|
||||
// Docker packages.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "docker/build-push-action",
|
||||
},
|
||||
},
|
||||
LogText: "candidate docker publishing workflow",
|
||||
},
|
||||
{
|
||||
// Python packages.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-python",
|
||||
},
|
||||
{
|
||||
Uses: "pypa/gh-action-pypi-publish",
|
||||
},
|
||||
},
|
||||
LogText: "candidate python publishing workflow using pypi",
|
||||
},
|
||||
{
|
||||
// Python packages.
|
||||
// This is a custom Python packaging workflow based on semantic versioning.
|
||||
// TODO(#1642): accept custom workflows through a separate configuration.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "relekang/python-semantic-release",
|
||||
},
|
||||
},
|
||||
LogText: "candidate python publishing workflow using python-semantic-release",
|
||||
},
|
||||
{
|
||||
// Go packages.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-go",
|
||||
},
|
||||
{
|
||||
Uses: "goreleaser/goreleaser-action",
|
||||
},
|
||||
},
|
||||
LogText: "candidate golang publishing workflow",
|
||||
},
|
||||
{
|
||||
// Rust packages. https://doc.rust-lang.org/cargo/reference/publishing.html
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Run: "cargo.*publish",
|
||||
},
|
||||
},
|
||||
LogText: "candidate rust publishing workflow using cargo",
|
||||
},
|
||||
}
|
||||
|
||||
return fileparser.RawAnyJobsMatch(workflow, jobMatchers, fp, "not a publishing workflow")
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/rhysd/actionlint"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checks/fileparser"
|
||||
)
|
||||
|
||||
func TestIsPackagingWorkflow(t *testing.T) {
|
||||
@ -106,7 +108,7 @@ func TestIsPackagingWorkflow(t *testing.T) {
|
||||
}
|
||||
p := strings.Replace(tt.filename, "./testdata/", "", 1)
|
||||
|
||||
_, ok := isPackagingWorkflow(workflow, p)
|
||||
_, ok := fileparser.IsPackagingWorkflow(workflow, p)
|
||||
if ok != tt.expected {
|
||||
t.Errorf("isPackagingWorkflow() = %v, expected %v", ok, tt.expected)
|
||||
}
|
||||
|
569
checks/raw/permissions.go
Normal file
569
checks/raw/permissions.go
Normal file
@ -0,0 +1,569 @@
|
||||
// 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 raw
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rhysd/actionlint"
|
||||
|
||||
"github.com/ossf/scorecard/v4/checker"
|
||||
"github.com/ossf/scorecard/v4/checks/fileparser"
|
||||
sce "github.com/ossf/scorecard/v4/errors"
|
||||
)
|
||||
|
||||
type permission string
|
||||
|
||||
const (
|
||||
permissionStatuses = permission("statuses")
|
||||
permissionChecks = permission("checks")
|
||||
permissionSecurityEvents = permission("security-events")
|
||||
permissionDeployments = permission("deployments")
|
||||
permissionContents = permission("contents")
|
||||
permissionPackages = permission("packages")
|
||||
permissionActions = permission("actions")
|
||||
)
|
||||
|
||||
var permissionsOfInterest = []permission{
|
||||
permissionStatuses, permissionChecks,
|
||||
permissionSecurityEvents, permissionDeployments,
|
||||
permissionContents, permissionPackages, permissionActions,
|
||||
}
|
||||
|
||||
type permissionCbData struct {
|
||||
results checker.TokenPermissionsData
|
||||
}
|
||||
|
||||
// TokenPermissions runs Token-Permissions check.
|
||||
func TokenPermissions(c *checker.CheckRequest) (checker.TokenPermissionsData, error) {
|
||||
// data is shared across all GitHub workflows.
|
||||
var data permissionCbData
|
||||
|
||||
err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
|
||||
Pattern: ".github/workflows/*",
|
||||
CaseSensitive: false,
|
||||
}, validateGitHubActionTokenPermissions, &data)
|
||||
|
||||
return data.results, err
|
||||
}
|
||||
|
||||
// Check file content.
|
||||
var validateGitHubActionTokenPermissions fileparser.DoWhileTrueOnFileContent = func(path string,
|
||||
content []byte,
|
||||
args ...interface{},
|
||||
) (bool, error) {
|
||||
if !fileparser.IsWorkflowFile(path) {
|
||||
return true, nil
|
||||
}
|
||||
// Verify the type of the data.
|
||||
if len(args) != 1 {
|
||||
return false, fmt.Errorf(
|
||||
"validateGitHubActionTokenPermissions requires exactly 2 arguments: %w", errInvalidArgLength)
|
||||
}
|
||||
pdata, ok := args[0].(*permissionCbData)
|
||||
if !ok {
|
||||
return false, fmt.Errorf(
|
||||
"validateGitHubActionTokenPermissions requires arg[0] of type *permissionCbData: %w", errInvalidArgType)
|
||||
}
|
||||
|
||||
if !fileparser.CheckFileContainsCommands(content, "#") {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
workflow, errs := actionlint.Parse(content)
|
||||
if len(errs) > 0 && workflow == nil {
|
||||
return false, fileparser.FormatActionlintError(errs)
|
||||
}
|
||||
|
||||
// 1. Top-level permission definitions.
|
||||
//nolint
|
||||
// https://docs.github.com/en/actions/reference/authentication-in-a-workflow#example-1-passing-the-github_token-as-an-input,
|
||||
// https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/,
|
||||
// https://docs.github.com/en/actions/reference/authentication-in-a-workflow#modifying-the-permissions-for-the-github_token.
|
||||
if err := validateTopLevelPermissions(workflow, path, pdata); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 2. Run-level permission definitions,
|
||||
// see https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idpermissions.
|
||||
ignoredPermissions := createIgnoredPermissions(workflow, path, pdata)
|
||||
if err := validatejobLevelPermissions(workflow, path, pdata, ignoredPermissions); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// TODO(laurent): 2. Identify github actions that require write and add checks.
|
||||
|
||||
// TODO(laurent): 3. Read a few runs and ensures they have the same permissions.
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func validatePermission(permissionKey permission, permissionValue *actionlint.PermissionScope,
|
||||
permLoc checker.PermissionLocation, path string, p *permissionCbData,
|
||||
ignoredPermissions map[permission]bool,
|
||||
) error {
|
||||
if permissionValue.Value == nil {
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, errInvalidGitHubWorkflow.Error())
|
||||
}
|
||||
key := string(permissionKey)
|
||||
val := permissionValue.Value.Value
|
||||
lineNumber := fileparser.GetLineNumber(permissionValue.Value.Pos)
|
||||
if strings.EqualFold(val, "write") {
|
||||
if isPermissionOfInterest(permissionKey, ignoredPermissions) {
|
||||
p.results.TokenPermissions = append(p.results.TokenPermissions,
|
||||
checker.TokenPermission{
|
||||
File: &checker.File{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: lineNumber,
|
||||
Snippet: val,
|
||||
},
|
||||
LocationType: &permLoc,
|
||||
Name: &key,
|
||||
Value: &val,
|
||||
Type: checker.PermissionLevelWrite,
|
||||
// TODO: Job
|
||||
})
|
||||
} else {
|
||||
// Only log for debugging, otherwise
|
||||
// it may confuse users.
|
||||
p.results.TokenPermissions = append(p.results.TokenPermissions,
|
||||
checker.TokenPermission{
|
||||
File: &checker.File{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: lineNumber,
|
||||
Snippet: val,
|
||||
},
|
||||
LocationType: &permLoc,
|
||||
Name: &key,
|
||||
Value: &val,
|
||||
// It's a write but not considered dangerous.
|
||||
Type: checker.PermissionLevelUnknown,
|
||||
// TODO: Job
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
p.results.TokenPermissions = append(p.results.TokenPermissions,
|
||||
checker.TokenPermission{
|
||||
File: &checker.File{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: lineNumber,
|
||||
// TODO: set Snippet.
|
||||
},
|
||||
LocationType: &permLoc,
|
||||
Name: &key,
|
||||
Value: &val,
|
||||
Type: typeOfPermission(val),
|
||||
// TODO: Job
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func typeOfPermission(val string) checker.PermissionLevel {
|
||||
switch val {
|
||||
case "read", "read-all":
|
||||
return checker.PermissionLevelRead
|
||||
case "none":
|
||||
return checker.PermissionLevelNone
|
||||
}
|
||||
return checker.PermissionLevelUnknown
|
||||
}
|
||||
|
||||
func validateMapPermissions(scopes map[string]*actionlint.PermissionScope, permLoc checker.PermissionLocation,
|
||||
path string, pdata *permissionCbData,
|
||||
ignoredPermissions map[permission]bool,
|
||||
) error {
|
||||
for key, v := range scopes {
|
||||
if err := validatePermission(permission(key), v, permLoc, path, pdata, ignoredPermissions); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validatePermissions(permissions *actionlint.Permissions, permLoc checker.PermissionLocation,
|
||||
path string, pdata *permissionCbData,
|
||||
ignoredPermissions map[permission]bool,
|
||||
) error {
|
||||
allIsSet := permissions != nil && permissions.All != nil && permissions.All.Value != ""
|
||||
scopeIsSet := permissions != nil && len(permissions.Scopes) > 0
|
||||
none := "none"
|
||||
if permissions == nil || (!allIsSet && !scopeIsSet) {
|
||||
pdata.results.TokenPermissions = append(pdata.results.TokenPermissions,
|
||||
checker.TokenPermission{
|
||||
File: &checker.File{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: checker.OffsetDefault,
|
||||
},
|
||||
LocationType: &permLoc,
|
||||
Type: checker.PermissionLevelNone,
|
||||
Value: &none,
|
||||
// TODO: Job, etc.
|
||||
})
|
||||
}
|
||||
if allIsSet {
|
||||
val := permissions.All.Value
|
||||
lineNumber := fileparser.GetLineNumber(permissions.All.Pos)
|
||||
if !strings.EqualFold(val, "read-all") && val != "" {
|
||||
pdata.results.TokenPermissions = append(pdata.results.TokenPermissions,
|
||||
checker.TokenPermission{
|
||||
File: &checker.File{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: lineNumber,
|
||||
Snippet: val,
|
||||
},
|
||||
LocationType: &permLoc,
|
||||
Value: &val,
|
||||
Type: checker.PermissionLevelWrite,
|
||||
// TODO: Job
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
pdata.results.TokenPermissions = append(pdata.results.TokenPermissions,
|
||||
checker.TokenPermission{
|
||||
File: &checker.File{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: lineNumber,
|
||||
Snippet: val,
|
||||
},
|
||||
LocationType: &permLoc,
|
||||
Value: &val,
|
||||
Type: typeOfPermission(val),
|
||||
// TODO: Job
|
||||
})
|
||||
} else /* scopeIsSet == true */ if err := validateMapPermissions(permissions.Scopes,
|
||||
permLoc, path, pdata, ignoredPermissions); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateTopLevelPermissions(workflow *actionlint.Workflow, path string,
|
||||
pdata *permissionCbData,
|
||||
) error {
|
||||
// Check if permissions are set explicitly.
|
||||
if workflow.Permissions == nil {
|
||||
permLoc := checker.PermissionLocationTop
|
||||
pdata.results.TokenPermissions = append(pdata.results.TokenPermissions,
|
||||
checker.TokenPermission{
|
||||
File: &checker.File{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: checker.OffsetDefault,
|
||||
},
|
||||
LocationType: &permLoc,
|
||||
Type: checker.PermissionLevelUndeclared,
|
||||
// TODO: Job
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return validatePermissions(workflow.Permissions, checker.PermissionLocationTop, path,
|
||||
pdata, map[permission]bool{})
|
||||
}
|
||||
|
||||
func validatejobLevelPermissions(workflow *actionlint.Workflow, path string,
|
||||
pdata *permissionCbData,
|
||||
ignoredPermissions map[permission]bool,
|
||||
) error {
|
||||
for _, job := range workflow.Jobs {
|
||||
// Run-level permissions may be left undefined.
|
||||
// For most workflows, no write permissions are needed,
|
||||
// so only top-level read-only permissions need to be declared.
|
||||
if job.Permissions == nil {
|
||||
permLoc := checker.PermissionLocationJob
|
||||
pdata.results.TokenPermissions = append(pdata.results.TokenPermissions,
|
||||
checker.TokenPermission{
|
||||
File: &checker.File{
|
||||
Path: path,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: fileparser.GetLineNumber(job.Pos),
|
||||
},
|
||||
LocationType: &permLoc,
|
||||
Type: checker.PermissionLevelUndeclared,
|
||||
Msg: stringPointer(fmt.Sprintf("no %s permission defined", permLoc)),
|
||||
// TODO: Job
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
err := validatePermissions(job.Permissions, checker.PermissionLocationJob,
|
||||
path, pdata, ignoredPermissions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isPermissionOfInterest(name permission, ignoredPermissions map[permission]bool) bool {
|
||||
for _, p := range permissionsOfInterest {
|
||||
_, present := ignoredPermissions[p]
|
||||
if strings.EqualFold(string(name), string(p)) && !present {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func createIgnoredPermissions(workflow *actionlint.Workflow, fp string,
|
||||
pdata *permissionCbData,
|
||||
) map[permission]bool {
|
||||
ignoredPermissions := make(map[permission]bool)
|
||||
if requiresPackagesPermissions(workflow, fp, pdata) {
|
||||
ignoredPermissions[permissionPackages] = true
|
||||
}
|
||||
if requiresContentsPermissions(workflow, fp, pdata) {
|
||||
ignoredPermissions[permissionContents] = true
|
||||
}
|
||||
if isSARIFUploadWorkflow(workflow, fp, pdata) {
|
||||
ignoredPermissions[permissionSecurityEvents] = true
|
||||
}
|
||||
|
||||
return ignoredPermissions
|
||||
}
|
||||
|
||||
// Scanning tool run externally and SARIF file uploaded.
|
||||
func isSARIFUploadWorkflow(workflow *actionlint.Workflow, fp string, pdata *permissionCbData) bool {
|
||||
//nolint
|
||||
// CodeQl analysis workflow automatically sends sarif file to GitHub.
|
||||
// https://docs.github.com/en/code-security/secure-coding/integrating-with-code-scanning/uploading-a-sarif-file-to-github#about-sarif-file-uploads-for-code-scanning.
|
||||
// `The CodeQL action uploads the SARIF file automatically when it completes analysis`.
|
||||
if isCodeQlAnalysisWorkflow(workflow, fp, pdata) {
|
||||
return true
|
||||
}
|
||||
|
||||
//nolint
|
||||
// Third-party scanning tools use the SARIF-upload action from code-ql.
|
||||
// https://docs.github.com/en/code-security/secure-coding/integrating-with-code-scanning/uploading-a-sarif-file-to-github#uploading-a-code-scanning-analysis-with-github-actions
|
||||
// We only support CodeQl today.
|
||||
if isSARIFUploadAction(workflow, fp, pdata) {
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO: some third party tools may upload directly thru their actions.
|
||||
// Very unlikely.
|
||||
// See https://github.com/marketplace for tools.
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// CodeQl run externally and SARIF file uploaded.
|
||||
func isSARIFUploadAction(workflow *actionlint.Workflow, fp string, pdata *permissionCbData) bool {
|
||||
for _, job := range workflow.Jobs {
|
||||
for _, step := range job.Steps {
|
||||
uses := fileparser.GetUses(step)
|
||||
if uses == nil {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(uses.Value, "github/codeql-action/upload-sarif@") {
|
||||
pdata.results.TokenPermissions = append(pdata.results.TokenPermissions,
|
||||
checker.TokenPermission{
|
||||
File: &checker.File{
|
||||
Path: fp,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: fileparser.GetLineNumber(uses.Pos),
|
||||
// TODO: set Snippet.
|
||||
},
|
||||
Type: checker.PermissionLevelUnknown,
|
||||
Msg: stringPointer("codeql SARIF upload workflow detected"),
|
||||
// TODO: Job
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
pdata.results.TokenPermissions = append(pdata.results.TokenPermissions,
|
||||
checker.TokenPermission{
|
||||
File: &checker.File{
|
||||
Path: fp,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: checker.OffsetDefault,
|
||||
},
|
||||
Type: checker.PermissionLevelUnknown,
|
||||
Msg: stringPointer("not a codeql upload SARIF workflow"),
|
||||
|
||||
// TODO: Job
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// nolint
|
||||
// CodeQl run within GitHub worklow automatically bubbled up to
|
||||
// security events, see
|
||||
// https://docs.github.com/en/code-security/secure-coding/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning.
|
||||
func isCodeQlAnalysisWorkflow(workflow *actionlint.Workflow, fp string, pdata *permissionCbData) bool {
|
||||
for _, job := range workflow.Jobs {
|
||||
for _, step := range job.Steps {
|
||||
uses := fileparser.GetUses(step)
|
||||
if uses == nil {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(uses.Value, "github/codeql-action/analyze@") {
|
||||
pdata.results.TokenPermissions = append(pdata.results.TokenPermissions,
|
||||
checker.TokenPermission{
|
||||
File: &checker.File{
|
||||
Path: fp,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: fileparser.GetLineNumber(uses.Pos),
|
||||
},
|
||||
Type: checker.PermissionLevelUnknown,
|
||||
Msg: stringPointer("codeql workflow detected"),
|
||||
// TODO: Job
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pdata.results.TokenPermissions = append(pdata.results.TokenPermissions,
|
||||
checker.TokenPermission{
|
||||
File: &checker.File{
|
||||
Path: fp,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: checker.OffsetDefault,
|
||||
},
|
||||
Type: checker.PermissionLevelUnknown,
|
||||
Msg: stringPointer("not a codeql workflow"),
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// A packaging workflow using GitHub's supported packages:
|
||||
// https://docs.github.com/en/packages.
|
||||
func requiresPackagesPermissions(workflow *actionlint.Workflow, fp string, pdata *permissionCbData) bool {
|
||||
// TODO: add support for GitHub registries.
|
||||
// Example: https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry.
|
||||
match, ok := fileparser.IsPackagingWorkflow(workflow, fp)
|
||||
// Print debug messages.
|
||||
pdata.results.TokenPermissions = append(pdata.results.TokenPermissions,
|
||||
checker.TokenPermission{
|
||||
File: &checker.File{
|
||||
Path: fp,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: checker.OffsetDefault,
|
||||
},
|
||||
Msg: &match.Msg,
|
||||
Type: checker.PermissionLevelUnknown,
|
||||
})
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// requiresContentsPermissions returns true if the workflow requires the `contents: write` permission.
|
||||
func requiresContentsPermissions(workflow *actionlint.Workflow, fp string, pdata *permissionCbData) bool {
|
||||
return isReleasingWorkflow(workflow, fp, pdata) || isGitHubPagesDeploymentWorkflow(workflow, fp, pdata)
|
||||
}
|
||||
|
||||
// isGitHubPagesDeploymentWorkflow returns true if the workflow involves pushing static pages to GitHub pages.
|
||||
func isGitHubPagesDeploymentWorkflow(workflow *actionlint.Workflow, fp string, pdata *permissionCbData) bool {
|
||||
jobMatchers := []fileparser.JobMatcher{
|
||||
{
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "peaceiris/actions-gh-pages",
|
||||
},
|
||||
},
|
||||
LogText: "candidate GitHub page deployment workflow using peaceiris/actions-gh-pages",
|
||||
},
|
||||
}
|
||||
|
||||
return isWorkflowOf(workflow, fp, jobMatchers,
|
||||
"not a GitHub Pages deployment workflow", pdata)
|
||||
}
|
||||
|
||||
// isReleasingWorkflow returns true if the workflow involves creating a release on GitHub.
|
||||
func isReleasingWorkflow(workflow *actionlint.Workflow, fp string, pdata *permissionCbData) bool {
|
||||
jobMatchers := []fileparser.JobMatcher{
|
||||
{
|
||||
// Python packages.
|
||||
// This is a custom Python packaging/releasing workflow based on semantic versioning.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "relekang/python-semantic-release",
|
||||
},
|
||||
},
|
||||
LogText: "candidate python publishing workflow using python-semantic-release",
|
||||
},
|
||||
{
|
||||
// Go binaries.
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "actions/setup-go",
|
||||
},
|
||||
{
|
||||
Uses: "goreleaser/goreleaser-action",
|
||||
},
|
||||
},
|
||||
LogText: "candidate golang publishing workflow",
|
||||
},
|
||||
{
|
||||
// SLSA Go builder. https://github.com/slsa-framework/slsa-github-generator
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml",
|
||||
},
|
||||
},
|
||||
LogText: "candidate SLSA publishing workflow using slsa-github-generator",
|
||||
},
|
||||
{
|
||||
// SLSA generic generator. https://github.com/slsa-framework/slsa-github-generator
|
||||
Steps: []*fileparser.JobMatcherStep{
|
||||
{
|
||||
Uses: "slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml",
|
||||
},
|
||||
},
|
||||
LogText: "candidate SLSA publishing workflow using slsa-github-generator",
|
||||
},
|
||||
}
|
||||
|
||||
return isWorkflowOf(workflow, fp, jobMatchers, "not a releasing workflow", pdata)
|
||||
}
|
||||
|
||||
func isWorkflowOf(workflow *actionlint.Workflow, fp string,
|
||||
jobMatchers []fileparser.JobMatcher, msg string,
|
||||
pdata *permissionCbData,
|
||||
) bool {
|
||||
match, ok := fileparser.AnyJobsMatch(workflow, jobMatchers, fp, msg)
|
||||
|
||||
// Print debug messages.
|
||||
pdata.results.TokenPermissions = append(pdata.results.TokenPermissions,
|
||||
checker.TokenPermission{
|
||||
File: &checker.File{
|
||||
Path: fp,
|
||||
Type: checker.FileTypeSource,
|
||||
Offset: checker.OffsetDefault,
|
||||
},
|
||||
Msg: &match.Msg,
|
||||
Type: checker.PermissionLevelUnknown,
|
||||
})
|
||||
|
||||
return ok
|
||||
}
|
@ -31,6 +31,7 @@ import (
|
||||
// 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
|
||||
|
@ -4,7 +4,7 @@
|
||||
// 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
|
||||
// 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,
|
||||
@ -57,30 +57,6 @@ var _ = Describe("E2E TEST:"+checks.CheckTokenPermissions, func() {
|
||||
Expect(scut.ValidateTestReturn(nil, "token permissions", &expected, &result, &dl)).Should(BeTrue())
|
||||
Expect(repoClient.Close()).Should(BeNil())
|
||||
})
|
||||
It("Should return token permission works on empty repo", func() {
|
||||
dl := scut.TestDetailLogger{}
|
||||
repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-empty-repo")
|
||||
Expect(err).Should(BeNil())
|
||||
repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger)
|
||||
err = repoClient.InitRepo(repo, clients.HeadSHA)
|
||||
Expect(err).Should(BeNil())
|
||||
req := checker.CheckRequest{
|
||||
Ctx: context.Background(),
|
||||
RepoClient: repoClient,
|
||||
Repo: repo,
|
||||
Dlogger: &dl,
|
||||
}
|
||||
expected := scut.TestReturn{
|
||||
Error: nil,
|
||||
Score: checker.MaxResultScore,
|
||||
NumberOfWarn: 0,
|
||||
NumberOfInfo: 0,
|
||||
NumberOfDebug: 0,
|
||||
}
|
||||
result := checks.TokenPermissions(&req)
|
||||
Expect(scut.ValidateTestReturn(nil, "token permissions", &expected, &result, &dl)).Should(BeTrue())
|
||||
Expect(repoClient.Close()).Should(BeNil())
|
||||
})
|
||||
It("Should return token permission at commit", func() {
|
||||
dl := scut.TestDetailLogger{}
|
||||
repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-check-token-permissions-e2e")
|
||||
|
1
go.mod
1
go.mod
@ -95,6 +95,7 @@ require (
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
|
||||
github.com/ossf/scorecard v1.2.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/prometheus v2.5.0+incompatible // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
|
77
go.sum
77
go.sum
@ -22,6 +22,7 @@ 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=
|
||||
@ -62,6 +63,7 @@ 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=
|
||||
@ -76,6 +78,7 @@ 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.23.1 h1:eVtkabVa+1M5ai67fGU+idws0hVb/KEPXiDmSS17+qc=
|
||||
cloud.google.com/go/pubsub v1.23.1/go.mod h1:ttM6nEGYK/2CnB36ndNySU3ZxPwpBk8cXM6+iOlxH9U=
|
||||
@ -85,6 +88,7 @@ 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.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
|
||||
cloud.google.com/go/storage v1.23.0 h1:wWRIaDURQA8xxHguFCshYepGlrWIrbBnAmc7wfg07qY=
|
||||
@ -97,6 +101,7 @@ 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=
|
||||
@ -109,6 +114,8 @@ 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=
|
||||
@ -119,18 +126,24 @@ 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=
|
||||
@ -145,6 +158,10 @@ 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=
|
||||
@ -154,11 +171,15 @@ 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=
|
||||
@ -175,6 +196,7 @@ 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=
|
||||
@ -186,6 +208,7 @@ 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=
|
||||
@ -268,9 +291,11 @@ 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=
|
||||
@ -333,6 +358,9 @@ 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=
|
||||
@ -537,6 +565,7 @@ 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=
|
||||
@ -728,6 +757,7 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
@ -795,8 +825,10 @@ 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 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/crfs v0.0.0-20191108021818-71d77da419c9/go.mod h1:etGhoOqfwPkooV6aqoX3eBGQOJblqdoc9XvWOeuxpPw=
|
||||
@ -823,6 +855,10 @@ 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 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts=
|
||||
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=
|
||||
@ -831,9 +867,11 @@ 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=
|
||||
@ -856,6 +894,7 @@ 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=
|
||||
@ -919,6 +958,7 @@ 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=
|
||||
@ -1106,6 +1146,7 @@ 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=
|
||||
@ -1181,6 +1222,7 @@ 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=
|
||||
@ -1221,6 +1263,8 @@ 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 h1:1mvisGk8KNWdixG7FZ+MGEGdZFxo2e4u82Ke56RqUos=
|
||||
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=
|
||||
@ -1243,6 +1287,7 @@ 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=
|
||||
@ -1259,6 +1304,7 @@ 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=
|
||||
@ -1300,6 +1346,8 @@ 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=
|
||||
@ -1307,6 +1355,7 @@ github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bA
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
@ -1417,6 +1466,7 @@ 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=
|
||||
@ -1487,6 +1537,7 @@ 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=
|
||||
@ -1633,6 +1684,7 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
@ -1641,15 +1693,19 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
|
||||
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
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 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
||||
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=
|
||||
@ -1662,6 +1718,7 @@ 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=
|
||||
@ -1780,6 +1837,7 @@ 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=
|
||||
@ -1827,6 +1885,7 @@ 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=
|
||||
@ -2072,6 +2131,7 @@ 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=
|
||||
@ -2115,12 +2175,18 @@ 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=
|
||||
@ -2157,6 +2223,7 @@ 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=
|
||||
@ -2170,6 +2237,8 @@ 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=
|
||||
@ -2209,6 +2278,7 @@ 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=
|
||||
@ -2253,11 +2323,15 @@ 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=
|
||||
@ -2341,6 +2415,7 @@ 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=
|
||||
@ -2384,6 +2459,7 @@ 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=
|
||||
@ -2525,6 +2601,7 @@ 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=
|
||||
|
@ -162,7 +162,7 @@ type jsonOssfBestPractices struct {
|
||||
Badge string `json:"badge"`
|
||||
}
|
||||
|
||||
//nolint
|
||||
// nolint
|
||||
type jsonLicense struct {
|
||||
File jsonFile `json:"file"`
|
||||
// TODO: add fields, like type of license, etc.
|
||||
@ -206,10 +206,25 @@ type jsonDependency struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
//nolint
|
||||
type jsonPermissionsData struct {
|
||||
TokenPermissions []jsonTokenPermission `json:"tokens,omitempty"`
|
||||
}
|
||||
|
||||
type jsonTokenPermission struct {
|
||||
Job *jsonWorkflowJob `json:"job,omitempty"`
|
||||
LocationType *string `json:"locationType,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Value *string `json:"value,omitempty"`
|
||||
File *jsonFile `json:"file,omitempty"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// nolint
|
||||
type jsonRawResults struct {
|
||||
// Workflow results.
|
||||
Workflows []jsonWorkflow `json:"workflows"`
|
||||
// Permissions.
|
||||
Permissions jsonPermissionsData `json:"permissions"`
|
||||
// License.
|
||||
Licenses []jsonLicense `json:"licenses"`
|
||||
// List of recent issues.
|
||||
@ -246,6 +261,54 @@ type jsonRawResults struct {
|
||||
DependencyPinning jsonPinningDependenciesData `json:"dependencyPinning"`
|
||||
}
|
||||
|
||||
func asPointer(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func (r *jsonScorecardRawResult) addTokenPermissionsRawResults(tp *checker.TokenPermissionsData) error {
|
||||
r.Results.Permissions = jsonPermissionsData{}
|
||||
|
||||
for _, t := range tp.TokenPermissions {
|
||||
// We ignore debug messages for read/none permissions.
|
||||
if t.Type != checker.PermissionLevelUndeclared &&
|
||||
t.Type != checker.PermissionLevelWrite {
|
||||
continue
|
||||
}
|
||||
|
||||
if t.LocationType == nil {
|
||||
//nolint
|
||||
return errors.New("locationType is nil")
|
||||
}
|
||||
|
||||
p := jsonTokenPermission{
|
||||
LocationType: asPointer(string(*t.LocationType)),
|
||||
Name: t.Name,
|
||||
Value: t.Value,
|
||||
Type: string(t.Type),
|
||||
}
|
||||
|
||||
if t.Job != nil {
|
||||
p.Job = &jsonWorkflowJob{
|
||||
Name: t.Job.Name,
|
||||
ID: t.Job.ID,
|
||||
}
|
||||
}
|
||||
|
||||
if t.File != nil {
|
||||
p.File = &jsonFile{
|
||||
Path: t.File.Path,
|
||||
Offset: t.File.Offset,
|
||||
}
|
||||
if t.File.Snippet != "" {
|
||||
p.File.Snippet = &t.File.Snippet
|
||||
}
|
||||
}
|
||||
|
||||
r.Results.Permissions.TokenPermissions = append(r.Results.Permissions.TokenPermissions, p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *jsonScorecardRawResult) addPackagingRawResults(pk *checker.PackagingData) error {
|
||||
r.Results.Packages = []jsonPackage{}
|
||||
|
||||
@ -264,7 +327,10 @@ func (r *jsonScorecardRawResult) addPackagingRawResults(pk *checker.PackagingDat
|
||||
jpk.File = &jsonFile{
|
||||
Path: p.File.Path,
|
||||
Offset: p.File.Offset,
|
||||
// TODO: Snippet
|
||||
}
|
||||
|
||||
if p.File.Snippet != "" {
|
||||
jpk.File.Snippet = &p.File.Snippet
|
||||
}
|
||||
|
||||
for _, run := range p.Runs {
|
||||
@ -690,6 +756,10 @@ func (r *jsonScorecardRawResult) fillJSONRawResults(raw *checker.RawResults) err
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
}
|
||||
|
||||
if err := r.addTokenPermissionsRawResults(&raw.TokenPermissionsResults); err != nil {
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ func Setup(c *checker.CheckRequest) error {
|
||||
|
||||
if b != nil && b.Name != nil {
|
||||
branch = *b.Name
|
||||
uri := c.Repo.URI()
|
||||
uri := c.RepoClient.URI()
|
||||
parts := strings.Split(uri, "/")
|
||||
if len(parts) != 3 {
|
||||
setupErr = fmt.Errorf("%w: enpty: %s", errInvalidArg, uri)
|
||||
@ -67,7 +67,6 @@ func Setup(c *checker.CheckRequest) error {
|
||||
repo = fmt.Sprintf("%s/%s", parts[1], parts[2])
|
||||
}
|
||||
})
|
||||
|
||||
return setupErr
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user