From cd0bc8f6c7253e7b888b311e354d7f2828534c16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 May 2023 09:40:33 -0500 Subject: [PATCH 01/10] :seedling: Bump github.com/onsi/gomega from 1.27.6 to 1.27.7 (#3040) Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.6 to 1.27.7. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.27.6...v1.27.7) --- updated-dependencies: - dependency-name: github.com/onsi/gomega dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f9201b8b6e5..fcca8727799 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( github.com/jszwec/csvutil v1.8.0 github.com/moby/buildkit v0.11.6 github.com/olekukonko/tablewriter v0.0.5 - github.com/onsi/gomega v1.27.6 + github.com/onsi/gomega v1.27.7 github.com/shurcooL/githubv4 v0.0.0-20201206200315-234843c633fa github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a github.com/sirupsen/logrus v1.9.2 diff --git a/go.sum b/go.sum index 63461674b72..0c626b2190e 100644 --- a/go.sum +++ b/go.sum @@ -1683,8 +1683,8 @@ github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeR github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= -github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= +github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= From f4e3363f0b5dcc175b0767217faf834619b654aa Mon Sep 17 00:00:00 2001 From: Ashish Kurmi <100655670+ashishkurmi@users.noreply.github.com> Date: Fri, 19 May 2023 13:23:28 -0700 Subject: [PATCH 02/10] :book: Make all StepSecurity app endpoint references consistent (#3042) Signed-off-by: Ashish Kurmi --- docs/checks.md | 4 ++-- docs/checks/internal/checks.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/checks.md b/docs/checks.md index a27dc624a77..9e569b9ec6a 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -499,7 +499,7 @@ dependencies using the [GitHub dependency graph](https://docs.github.com/en/code - If your project is producing an application, declare all your dependencies with specific versions in your package format file (e.g. `package.json` for npm, `requirements.txt` for python, `packages.config` for nuget). For C/C++, check in the code from a trusted source and add a `README` on the specific version used (and the archive SHA hashes). - If your project is producing an application and the package manager supports lock files (e.g. `package-lock.json` for npm), make sure to check these in the source code as well. These files maintain signatures for the entire dependency tree and saves from future exploitation in case the package is compromised. - For Dockerfiles used in building and releasing your project, pin dependencies by hash. See [Dockerfile](https://github.com/ossf/scorecard/blob/main/cron/internal/worker/Dockerfile) for example. If you are using a manifest list to support builds across multiple architectures, you can pin to the manifest list hash instead of a single image hash. You can use a tool like [crane](https://github.com/google/go-containerregistry/blob/main/cmd/crane/README.md) to obtain the hash of the manifest list like in this [example](https://github.com/ossf/scorecard/issues/1773#issuecomment-1076699039). -- For GitHub workflows used in building and releasing your project, pin dependencies by hash. See [main.yaml](https://github.com/ossf/scorecard/blob/f55b86d6627cc3717e3a0395e03305e81b9a09be/.github/workflows/main.yml#L27) for example. To determine the permissions needed for your workflows, you may use [StepSecurity's online tool](https://app.stepsecurity.io/) by ticking the "Pin actions to a full length commit SHA". You may also tick the "Restrict permissions for GITHUB_TOKEN" to fix issues found by the Token-Permissions check. +- For GitHub workflows used in building and releasing your project, pin dependencies by hash. See [main.yaml](https://github.com/ossf/scorecard/blob/f55b86d6627cc3717e3a0395e03305e81b9a09be/.github/workflows/main.yml#L27) for example. To determine the permissions needed for your workflows, you may use [StepSecurity's online tool](https://app.stepsecurity.io/secureworkflow/) by ticking the "Pin actions to a full length commit SHA". You may also tick the "Restrict permissions for GITHUB_TOKEN" to fix issues found by the Token-Permissions check. - To help update your dependencies after pinning them, use tools such as those listed for the dependency update tool check. ## SAST @@ -639,7 +639,7 @@ Additionally, points are reduced if certain write permissions are defined for a **Remediation steps** - Set permissions as `read-all` or `contents: read` as described in GitHub's [documentation](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions). -- To help determine the permissions needed for your workflows, you may use [StepSecurity's online tool](https://app.stepsecurity.io/) by ticking the "Restrict permissions for GITHUB_TOKEN". You may also tick the "Pin actions to a full length commit SHA" to fix issues found by the Pinned-dependencies check. +- To help determine the permissions needed for your workflows, you may use [StepSecurity's online tool](https://app.stepsecurity.io/secureworkflow/) by ticking the "Restrict permissions for GITHUB_TOKEN". You may also tick the "Pin actions to a full length commit SHA" to fix issues found by the Pinned-dependencies check. ## Vulnerabilities diff --git a/docs/checks/internal/checks.yaml b/docs/checks/internal/checks.yaml index 3e1c54b2ea7..65fdac3de74 100644 --- a/docs/checks/internal/checks.yaml +++ b/docs/checks/internal/checks.yaml @@ -511,7 +511,7 @@ checks: to obtain the hash of the manifest list like in this [example](https://github.com/ossf/scorecard/issues/1773#issuecomment-1076699039). - >- For GitHub workflows used in building and releasing your project, pin dependencies by hash. See [main.yaml](https://github.com/ossf/scorecard/blob/f55b86d6627cc3717e3a0395e03305e81b9a09be/.github/workflows/main.yml#L27) for example. - To determine the permissions needed for your workflows, you may use [StepSecurity's online tool](https://app.stepsecurity.io/) by ticking + To determine the permissions needed for your workflows, you may use [StepSecurity's online tool](https://app.stepsecurity.io/secureworkflow/) by ticking the "Pin actions to a full length commit SHA". You may also tick the "Restrict permissions for GITHUB_TOKEN" to fix issues found by the Token-Permissions check. - >- @@ -681,7 +681,7 @@ checks: Set permissions as `read-all` or `contents: read` as described in GitHub's [documentation](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions). - >- - To help determine the permissions needed for your workflows, you may use [StepSecurity's online tool](https://app.stepsecurity.io/) by ticking + To help determine the permissions needed for your workflows, you may use [StepSecurity's online tool](https://app.stepsecurity.io/secureworkflow/) by ticking the "Restrict permissions for GITHUB_TOKEN". You may also tick the "Pin actions to a full length commit SHA" to fix issues found by the Pinned-dependencies check. Vulnerabilities: From 028fa93e924d3facde890a113f7edf1225a87ea2 Mon Sep 17 00:00:00 2001 From: Joyce Date: Fri, 19 May 2023 18:34:38 -0300 Subject: [PATCH 03/10] =?UTF-8?q?=F0=9F=93=96=20Update=20checks.md=20to=20?= =?UTF-8?q?show=20the=20benefit=20of=20>=3D2=20reviewers=20(#3013)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update checks.yaml instead of cehcks.md Signed-off-by: Joyce * feat: generate checks.md Signed-off-by: Joyce Brum --------- Signed-off-by: Joyce Signed-off-by: Joyce Brum --- docs/checks.md | 11 ++++++++--- docs/checks/internal/checks.yaml | 7 ++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/checks.md b/docs/checks.md index 9e569b9ec6a..339e26e8d88 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -73,11 +73,16 @@ result to meet most user needs. Different types of branch protection protect against different risks: - - Require code review: requires at least one reviewer, which greatly + - Require code review: + - requires at least one reviewer, which greatly reduces the risk that a compromised contributor can inject malicious code. Review also increases the likelihood that an unintentional vulnerability in a contribution will be detected and fixed before the change is accepted. + - requiring two or more reviewers protects even more from the insider risk + whereby a compromised contributor can be used by an attacker to LGTM + the attacker PR and inject a malicious code as if it was legitm. + - Prevent force push: prevents use of the `--force` command on public branches, which overwrites code irrevocably. This protection prevents the rewriting of public history without external notice. @@ -182,8 +187,8 @@ However, note that in those overlapping cases, Scorecard can only report what it Risk: `High` (unintentional vulnerabilities or possible injection of malicious code) -This check determines whether the project requires human code review before pull -requests (merge requests) are merged. +This check determines whether the project requires human code review +before pull requests (merge requests) are merged. Reviews detect various unintentional problems, including vulnerabilities that can be fixed immediately before they are merged, which improves the quality of diff --git a/docs/checks/internal/checks.yaml b/docs/checks/internal/checks.yaml index 65fdac3de74..e5db1e2dc10 100644 --- a/docs/checks/internal/checks.yaml +++ b/docs/checks/internal/checks.yaml @@ -162,11 +162,16 @@ checks: Different types of branch protection protect against different risks: - - Require code review: requires at least one reviewer, which greatly + - Require code review: + - requires at least one reviewer, which greatly reduces the risk that a compromised contributor can inject malicious code. Review also increases the likelihood that an unintentional vulnerability in a contribution will be detected and fixed before the change is accepted. + - requiring two or more reviewers protects even more from the insider risk + whereby a compromised contributor can be used by an attacker to LGTM + the attacker PR and inject a malicious code as if it was legitm. + - Prevent force push: prevents use of the `--force` command on public branches, which overwrites code irrevocably. This protection prevents the rewriting of public history without external notice. From fe7a8441ad5ab26356d9cdd1f5fd3219b1aceae0 Mon Sep 17 00:00:00 2001 From: Naveen <172697+naveensrinivasan@users.noreply.github.com> Date: Mon, 22 May 2023 11:55:45 -0500 Subject: [PATCH 04/10] :seedling: Improve workflow pinning remediation tests (#3021) - Add 3 tests for workflow pinning remediation [remediation/remediations_test.go] - Add 3 tests for workflow pinning remediation Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com> --- remediation/remediations_test.go | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/remediation/remediations_test.go b/remediation/remediations_test.go index 666537d238c..dce119881d5 100644 --- a/remediation/remediations_test.go +++ b/remediation/remediations_test.go @@ -140,3 +140,55 @@ func TestCreateDockerfilePinningRemediation(t *testing.T) { }) } } + +func TestCreateWorkflowPinningRemediation(t *testing.T) { + t.Parallel() + + tests := []struct { //nolint:govet + name string + branch string + repo string + filepath string + expected *rule.Remediation + }{ + { + name: "valid input", + branch: "main", + repo: "ossf/scorecard", + filepath: ".github/workflows/scorecard.yml", + expected: &rule.Remediation{ + Text: fmt.Sprintf(workflowText, "ossf/scorecard", "scorecard.yml", "main", "pin"), + Markdown: fmt.Sprintf(workflowMarkdown, "ossf/scorecard", "scorecard.yml", "main", "pin"), + }, + }, + { + name: "empty branch", + branch: "", + repo: "ossf/scorecard", + filepath: ".github/workflows/", + expected: nil, + }, + { + name: "empty repo", + branch: "main", + repo: "", + filepath: ".github/workflows/", + expected: nil, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + r := RemediationMetadata{ + Branch: tt.branch, + Repo: tt.repo, + } + got := r.CreateWorkflowPinningRemediation(tt.filepath) + if !cmp.Equal(got, tt.expected) { + t.Errorf(cmp.Diff(got, tt.expected)) + } + }) + } +} From b5142abe4dc9983fe11fb4fed2a132cda2ceabbb Mon Sep 17 00:00:00 2001 From: Naveen <172697+naveensrinivasan@users.noreply.github.com> Date: Mon, 22 May 2023 12:15:35 -0500 Subject: [PATCH 05/10] :seedling: E2E tests for clients/githubrepo/languages_e2e_test.go (#3000) * :seedling: E2E tests for clients/githubrepo/languages_e2e_test.go - Included e2e tests for clients/githubrepo/languages_e2e_test.go Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com> * Fixed the token type check. Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com> --------- Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com> Signed-off-by: Naveen <172697+naveensrinivasan@users.noreply.github.com> --- clients/githubrepo/languages_e2e_test.go | 54 ++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 clients/githubrepo/languages_e2e_test.go diff --git a/clients/githubrepo/languages_e2e_test.go b/clients/githubrepo/languages_e2e_test.go new file mode 100644 index 00000000000..3186d5ee126 --- /dev/null +++ b/clients/githubrepo/languages_e2e_test.go @@ -0,0 +1,54 @@ +// Copyright 2023 OpenSSF Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package githubrepo + +import ( + "context" + "net/http" + + "github.com/google/go-github/v38/github" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/ossf/scorecard/v4/clients/githubrepo/roundtripper" + "github.com/ossf/scorecard/v4/log" +) + +var _ = Describe("E2E TEST: githubrepo.languagesHandler", func() { + var langHandler *languagesHandler + BeforeEach(func() { + ctx := context.Background() + rt := roundtripper.NewTransport(context.Background(), &log.Logger{}) + httpClient := &http.Client{ + Transport: rt, + } + langHandler = &languagesHandler{ + ghclient: github.NewClient(httpClient), + ctx: ctx, + } + }) + Context("listProgrammingLanguages()", func() { + It("returns a list of programming languages for a valid repository", func() { + repoURL := repoURL{ + owner: "ossf", + repo: "scorecard", + } + langHandler.init(context.Background(), &repoURL) + languages, err := langHandler.listProgrammingLanguages() + Expect(err).Should(BeNil()) + Expect(languages).ShouldNot(BeEmpty()) + }) + }) +}) From f8d33f8b3ef2ef6502330ac1d4bf51f592fd6c95 Mon Sep 17 00:00:00 2001 From: Naveen <172697+naveensrinivasan@users.noreply.github.com> Date: Mon, 22 May 2023 12:37:12 -0500 Subject: [PATCH 06/10] :seedling: Unit tests for pkg/json_raw_results (#3044) * :seedling: Unit tests for pkg/json_raw_results.go - Unit tests for pkg/json_raw_results.go Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com> * Additional tests Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com> --------- Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com> --- pkg/json_raw_results.go | 2 +- pkg/json_raw_results_test.go | 1403 ++++++++++++++++++++++++++++++++++ 2 files changed, 1404 insertions(+), 1 deletion(-) create mode 100644 pkg/json_raw_results_test.go diff --git a/pkg/json_raw_results.go b/pkg/json_raw_results.go index fcbe461c2aa..4be011aabd7 100644 --- a/pkg/json_raw_results.go +++ b/pkg/json_raw_results.go @@ -352,7 +352,7 @@ func (r *jsonScorecardRawResult) addPackagingRawResults(pk *checker.PackagingDat } if p.File == nil { //nolint - return errors.New("File field is nil") + return errors.New("file field is nil") } jpk.File = &jsonFile{ diff --git a/pkg/json_raw_results_test.go b/pkg/json_raw_results_test.go new file mode 100644 index 00000000000..82adcc8b116 --- /dev/null +++ b/pkg/json_raw_results_test.go @@ -0,0 +1,1403 @@ +// Copyright 2023 OpenSSF Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package pkg + +import ( + "bytes" + "reflect" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/ossf/scorecard/v4/checker" + "github.com/ossf/scorecard/v4/clients" +) + +func TestAsPointer(t *testing.T) { + t.Parallel() + + tests := []struct { //nolint:govet + name string + input string + expected *string + }{ + { + name: "test_empty_string", + input: "", + expected: asPointer(""), + }, + { + name: "test_non_empty_string", + input: "test", + expected: asPointer("test"), + }, + { + name: "test_number_string", + input: "123", + expected: asPointer("123"), + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + result := asPointer(tt.input) + if *result != *tt.expected { + t.Errorf("asPointer() = %v, want %v", result, tt.expected) + } + }) + } +} + +func TestJsonScorecardRawResult_AddPackagingRawResults(t *testing.T) { + t.Parallel() + + tests := []struct { //nolint:govet + name string + input *checker.PackagingData + wantError bool + }{ + { + name: "test_with_nil_file_field", + input: &checker.PackagingData{ + Packages: []checker.Package{ + {File: nil}, + }, + }, + wantError: true, + }, + { + name: "test_with_empty_package_data", + input: &checker.PackagingData{ + Packages: []checker.Package{}, + }, + wantError: false, + }, + { + name: "test_with_valid_package_data", + input: &checker.PackagingData{ + Packages: []checker.Package{ + { + File: &checker.File{ + Path: "testPath", + Offset: 0, + Snippet: "testSnippet", + }, + Runs: []checker.Run{ + {URL: "testUrl"}, + }, + }, + }, + }, + wantError: false, + }, + { + name: "test_with_package_with_msg", + input: &checker.PackagingData{ + Packages: []checker.Package{ + { + Msg: asPointer("testMsg"), + }, + }, + }, + }, + { + name: "test_with_package_with_file_nil", + input: &checker.PackagingData{ + Packages: []checker.Package{ + { + File: nil, + }, + }, + }, + wantError: true, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + r := &jsonScorecardRawResult{} + err := r.addPackagingRawResults(test.input) + if (err != nil) != test.wantError { + t.Errorf("addPackagingRawResults() error = %v, wantError %v", err, test.wantError) + } + }) + } +} + +func TestJsonScorecardRawResult_AddTokenPermissionsRawResults(t *testing.T) { + t.Parallel() + loc := checker.PermissionLocation("testLocationType") + tests := []struct { //nolint:govet + name string + input *checker.TokenPermissionsData + wantError bool + }{ + { + name: "test_with_nil_location_type", + input: &checker.TokenPermissionsData{ + TokenPermissions: []checker.TokenPermission{ + { + LocationType: nil, + Type: checker.PermissionLevelUndeclared, + }, + }, + }, + wantError: true, + }, + { + name: "test_with_debug_message", + input: &checker.TokenPermissionsData{ + TokenPermissions: []checker.TokenPermission{ + { + LocationType: &loc, + Type: checker.PermissionLevelRead, + }, + }, + }, + }, + { + name: "test_with_nil_job_and_file", + input: &checker.TokenPermissionsData{ + TokenPermissions: []checker.TokenPermission{ + { + LocationType: &loc, + Type: checker.PermissionLevelUndeclared, + }, + }, + }, + wantError: false, + }, + { + name: "test_with_valid_data", + input: &checker.TokenPermissionsData{ + TokenPermissions: []checker.TokenPermission{ + { + LocationType: &loc, + Type: checker.PermissionLevelUndeclared, + Job: &checker.WorkflowJob{ + Name: asPointer("testJobName"), + ID: asPointer("testJobID"), + }, + File: &checker.File{ + Path: "testPath", + Offset: 0, + Snippet: "testSnippet", + }, + }, + }, + }, + wantError: false, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + r := &jsonScorecardRawResult{} + err := r.addTokenPermissionsRawResults(test.input) + if (err != nil) != test.wantError { + t.Errorf("addTokenPermissionsRawResults() error = %v, wantError %v", err, test.wantError) + } + }) + } +} + +func TestJsonScorecardRawResult_AddDependencyPinningRawResults(t *testing.T) { + t.Parallel() + + tests := []struct { //nolint:govet + name string + input *checker.PinningDependenciesData + wantError bool + }{ + { + name: "test_with_nil_location", + input: &checker.PinningDependenciesData{ + Dependencies: []checker.Dependency{ + {Location: nil}, + }, + }, + wantError: false, + }, + { + name: "test_with_valid_data", + input: &checker.PinningDependenciesData{ + Dependencies: []checker.Dependency{ + { + Location: &checker.File{ + Path: "testPath", + Offset: 0, + EndOffset: 5, + Snippet: "testSnippet", + }, + Name: asPointer("testDependency"), + PinnedAt: asPointer("testPinnedAt"), + Type: checker.DependencyUseTypeGHAction, + }, + }, + }, + wantError: false, + }, + { + name: "test_with_nil_location", + input: &checker.PinningDependenciesData{ + Dependencies: []checker.Dependency{ + { + Location: nil, + Name: asPointer("testDependency"), + PinnedAt: asPointer("testPinnedAt"), + Type: checker.DependencyUseTypeGHAction, + }, + }, + }, + wantError: false, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + r := &jsonScorecardRawResult{} + err := r.addDependencyPinningRawResults(test.input) + if (err != nil) != test.wantError { + t.Errorf("addDependencyPinningRawResults() error = %v, wantError %v", err, test.wantError) + } + }) + } +} + +func TestJsonScorecardRawResult_AddDangerousWorkflowRawResults(t *testing.T) { + t.Parallel() + + tests := []struct { //nolint:govet + name string + input *checker.DangerousWorkflowData + wantError bool + }{ + { + name: "test_with_valid_data", + input: &checker.DangerousWorkflowData{ + Workflows: []checker.DangerousWorkflow{ + { + File: checker.File{ + Path: "testPath", + Offset: 0, + EndOffset: 5, + Snippet: "testSnippet", + }, + Type: checker.DangerousWorkflowScriptInjection, + Job: &checker.WorkflowJob{ + Name: asPointer("testJob"), + ID: asPointer("testID"), + }, + }, + }, + }, + wantError: false, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + r := &jsonScorecardRawResult{} + err := r.addDangerousWorkflowRawResults(test.input) + if (err != nil) != test.wantError { + t.Errorf("addDangerousWorkflowRawResults() error = %v, wantError %v", err, test.wantError) + } + }) + } +} + +func TestJsonScorecardRawResult_AddContributorsRawResults(t *testing.T) { + t.Parallel() + + tests := []struct { //nolint:govet + name string + input *checker.ContributorsData + wantError bool + }{ + { + name: "test_with_valid_data", + input: &checker.ContributorsData{ + Users: []clients.User{ + { + Login: "testLogin", + NumContributions: 5, + Organizations: []clients.User{ + {Login: "testOrg"}, + }, + Companies: []string{"testCompany"}, + }, + }, + }, + wantError: false, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + r := &jsonScorecardRawResult{} + err := r.addContributorsRawResults(test.input) + if (err != nil) != test.wantError { + t.Errorf("addContributorsRawResults() error = %v, wantError %v", err, test.wantError) + } + }) + } +} + +func TestJsonScorecardRawResult_AddSignedReleasesRawResults(t *testing.T) { + t.Parallel() + + tests := []struct { //nolint:govet + name string + input *checker.SignedReleasesData + wantError bool + }{ + { + name: "test_with_valid_data", + input: &checker.SignedReleasesData{ + Releases: []clients.Release{ + { + TagName: "v1.0", + URL: "https://example.com/v1.0", + Assets: []clients.ReleaseAsset{ + { + Name: "asset1", + URL: "https://example.com/v1.0/asset1", + }, + }, + }, + }, + }, + wantError: false, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + r := &jsonScorecardRawResult{} + err := r.addSignedReleasesRawResults(test.input) + if (err != nil) != test.wantError { + t.Errorf("addSignedReleasesRawResults() error = %v, wantError %v", err, test.wantError) + } + }) + } +} + +func TestJsonScorecardRawResult_AddMaintainedRawResults(t *testing.T) { + t.Parallel() + c := clients.RepoAssociationNone + tests := []struct { //nolint:govet + name string + input *checker.MaintainedData + wantError bool + }{ + { + name: "test_with_nil_archived_status", + input: &checker.MaintainedData{ + CreatedAt: time.Now(), + Issues: []clients.Issue{}, + }, + wantError: false, + }, + { + name: "test_with_valid_archived_status", + input: &checker.MaintainedData{ + CreatedAt: time.Now(), + Issues: []clients.Issue{ + { + URI: asPointer("testUrl"), + Author: &clients.User{ + Login: "testLogin", + }, + AuthorAssociation: &c, + Comments: []clients.IssueComment{ + { + Author: &clients.User{ + Login: "testLogin", + }, + AuthorAssociation: &c, + }, + }, + }, + }, + }, + wantError: false, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + r := &jsonScorecardRawResult{} + err := r.addMaintainedRawResults(test.input) + if (err != nil) != test.wantError { + t.Errorf("addMaintainedRawResults() error = %v, wantError %v", err, test.wantError) + } + }) + } +} + +func TestSetDefaultCommitData(t *testing.T) { + // Define some test data. + changesets := []checker.Changeset{ + { + ReviewPlatform: "GitHub", + RevisionID: "abc123", + Commits: []clients.Commit{ + { + CommittedDate: time.Now(), + Message: "Initial commit", + SHA: "def456", + Committer: clients.User{ + Login: "johndoe", + }, + }, + }, + Reviews: []clients.Review{ + { + State: "approved", + Author: &clients.User{ + Login: "janedoe", + IsBot: false, + }, + }, + }, + Author: clients.User{ + Login: "johndoe", + }, + }, + } + + // Create a new jsonScorecardRawResult. + r := &jsonScorecardRawResult{} + + // Call setDefaultCommitData with the test data. + err := r.setDefaultCommitData(changesets) + if err != nil { + t.Fatalf("setDefaultCommitData() returned an error: %v", err) + } + + // Define the expected results. + expected := []jsonDefaultBranchChangeset{ + { + RevisionID: "abc123", + ReviewPlatform: "GitHub", + Commits: []jsonCommit{ + { + Committer: jsonUser{ + Login: "johndoe", + }, + Message: "Initial commit", + SHA: "def456", + }, + }, + Reviews: []jsonReview{ + { + State: "approved", + Reviewer: jsonUser{ + Login: "janedoe", + IsBot: false, + }, + }, + }, + Authors: []jsonUser{ + { + Login: "johndoe", + }, + }, + }, + } + + // Compare the actual results with the expected results. + if diff := cmp.Diff(r.Results.DefaultBranchChangesets, expected); diff != "" { + t.Errorf("setDefaultCommitData() mismatch (-want +got):\n%s", diff) + } +} + +func TestJsonScorecardRawResult_AddOssfBestPracticesRawResults(t *testing.T) { + t.Parallel() + + tests := []struct { //nolint:govet + name string + input *checker.CIIBestPracticesData + wantError bool + }{ + { + name: "test_with_valid_badge", + input: &checker.CIIBestPracticesData{ + Badge: clients.Gold, + }, + wantError: false, + }, + { + name: "test_with_nil_badge", + input: &checker.CIIBestPracticesData{ + Badge: clients.Silver, + }, + wantError: false, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + r := &jsonScorecardRawResult{} + err := r.addOssfBestPracticesRawResults(test.input) + if (err != nil) != test.wantError { + t.Errorf("addOssfBestPracticesRawResults() error = %v, wantError %v", err, test.wantError) + } + if r.Results.OssfBestPractices.Badge != test.input.Badge.String() { + t.Errorf("addOssfBestPracticesRawResults() badge = %v, want %v", r.Results.OssfBestPractices.Badge, test.input.Badge.String()) //nolint:lll + } + }) + } +} + +func TestJsonScorecardRawResult_AddCodeReviewRawResults(t *testing.T) { + t.Parallel() + + tests := []struct { //nolint:govet + name string + input *checker.CodeReviewData + wantError bool + }{ + { + name: "test_with_valid_changesets", + input: &checker.CodeReviewData{ + DefaultBranchChangesets: []checker.Changeset{ + { + ReviewPlatform: "GitHub", + RevisionID: "123", + Commits: []clients.Commit{ + { + CommittedDate: time.Now(), + Message: "test commit", + SHA: "abc123", + Committer: clients.User{ + Login: "testuser", + }, + }, + }, + Reviews: []clients.Review{ + { + State: "approved", + Author: &clients.User{ + Login: "testuser", + }, + }, + }, + Author: clients.User{ + Login: "testuser", + }, + }, + }, + }, + wantError: false, + }, + { + name: "test_with_nil_changesets", + input: &checker.CodeReviewData{ + DefaultBranchChangesets: nil, + }, + wantError: false, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + r := &jsonScorecardRawResult{} + err := r.addCodeReviewRawResults(test.input) + if (err != nil) != test.wantError { + t.Errorf("addCodeReviewRawResults() error = %v, wantError %v", err, test.wantError) + } + if len(r.Results.DefaultBranchChangesets) != len(test.input.DefaultBranchChangesets) { + t.Errorf("addCodeReviewRawResults() changesets length = %v, want %v", len(r.Results.DefaultBranchChangesets), len(test.input.DefaultBranchChangesets)) //nolint:lll + } + }) + } +} + +func TestAddCodeReviewRawResults(t *testing.T) { + r := &jsonScorecardRawResult{} + cr := &checker.CodeReviewData{ + DefaultBranchChangesets: []checker.Changeset{ + { + RevisionID: "abc123", + ReviewPlatform: "github", + Commits: []clients.Commit{ + { + Committer: clients.User{ + Login: "johndoe", + }, + Message: "Fix bug", + SHA: "def456", + }, + }, + Reviews: []clients.Review{ + { + State: "approved", + Author: &clients.User{ + Login: "janedoe", + IsBot: false, + }, + }, + }, + Author: clients.User{ + Login: "johndoe", + }, + }, + }, + } + + err := r.addCodeReviewRawResults(cr) + if err != nil { + t.Errorf("addCodeReviewRawResults returned an error: %v", err) + } + + expected := []jsonDefaultBranchChangeset{ + { + RevisionID: "abc123", + ReviewPlatform: "github", + Commits: []jsonCommit{ + { + Committer: jsonUser{ + Login: "johndoe", + }, + Message: "Fix bug", + SHA: "def456", + }, + }, + Reviews: []jsonReview{ + { + State: "approved", + Reviewer: jsonUser{ + Login: "janedoe", + IsBot: false, + }, + }, + }, + Authors: []jsonUser{ + { + Login: "johndoe", + }, + }, + }, + } + + if !reflect.DeepEqual(r.Results.DefaultBranchChangesets, expected) { + t.Errorf("addCodeReviewRawResults did not produce the expected output. Got: %v, Expected: %v", r.Results.DefaultBranchChangesets, expected) //nolint:lll + } +} + +func TestAddLicenseRawResults(t *testing.T) { + // Create a new jsonScorecardRawResult instance + r := &jsonScorecardRawResult{} + + // Create a new LicenseData instance + ld := &checker.LicenseData{ + LicenseFiles: []checker.LicenseFile{ + { + File: checker.File{ + Path: "LICENSE", + }, + LicenseInformation: checker.License{ + Name: "MIT License", + SpdxID: "MIT", + Attribution: checker.LicenseAttributionTypeOther, + Approved: true, + }, + }, + }, + } + + // Call the addLicenseRawResults function + err := r.addLicenseRawResults(ld) + // Check if there was an error + if err != nil { + t.Errorf("addLicenseRawResults returned an error: %v", err) + } + + // Check if the Licenses field was populated correctly + expected := []jsonLicense{ + { + License: jsonLicenseInfo{ + File: "LICENSE", + Name: "MIT License", + SpdxID: "MIT", + Attribution: "other", + Approved: "true", + }, + }, + } + + if len(r.Results.Licenses) != len(expected) { + t.Errorf("addLicenseRawResults did not populate the Licenses field correctly") + } + + for i, license := range r.Results.Licenses { + if license.License.File != expected[i].License.File { + t.Errorf("addLicenseRawResults did not populate the Licenses field correctly") + } + if license.License.Name != expected[i].License.Name { + t.Errorf("addLicenseRawResults did not populate the Licenses field correctly") + } + if license.License.SpdxID != expected[i].License.SpdxID { + t.Errorf("addLicenseRawResults did not populate the Licenses field correctly") + } + if license.License.Attribution != expected[i].License.Attribution { + t.Errorf("addLicenseRawResults did not populate the Licenses field correctly") + } + if license.License.Approved != expected[i].License.Approved { + t.Errorf("addLicenseRawResults did not populate the Licenses field correctly") + } + } +} + +func TestAddBinaryArtifactRawResults(t *testing.T) { + r := &jsonScorecardRawResult{} + ba := &checker.BinaryArtifactData{ + Files: []checker.File{ + { + Path: "path/to/file1", + }, + { + Path: "path/to/file2", + }, + }, + } + + err := r.addBinaryArtifactRawResults(ba) + if err != nil { + t.Errorf("addBinaryArtifactRawResults returned an error: %v", err) + } + + expected := []jsonFile{ + { + Path: "path/to/file1", + }, + { + Path: "path/to/file2", + }, + } + + if len(r.Results.Binaries) != len(expected) { + t.Errorf("addBinaryArtifactRawResults did not add the correct number of files. Expected %d, got %d", len(expected), len(r.Results.Binaries)) //nolint:lll + } + + for i, file := range r.Results.Binaries { + if file.Path != expected[i].Path { + t.Errorf("addBinaryArtifactRawResults did not add the correct file. Expected %s, got %s", expected[i].Path, file.Path) //nolint:lll + } + } +} + +func TestAddSecurityPolicyRawResults(t *testing.T) { + r := &jsonScorecardRawResult{} + sp := &checker.SecurityPolicyData{ + PolicyFiles: []checker.SecurityPolicyFile{ + { + File: checker.File{ + Path: "path/to/policy1", + FileSize: 100, + }, + Information: []checker.SecurityPolicyInformation{ + { + InformationType: checker.SecurityPolicyInformationType("type1"), + }, + { + InformationType: checker.SecurityPolicyInformationType("type2"), + }, + }, + }, + { + File: checker.File{ + Path: "path/to/policy2", + FileSize: 200, + }, + Information: []checker.SecurityPolicyInformation{ + { + InformationType: checker.SecurityPolicyInformationType("type3"), + }, + }, + }, + }, + } + + err := r.addSecurityPolicyRawResults(sp) + if err != nil { + t.Errorf("addSecurityPolicyRawResults returned an error: %v", err) + } + + expected := []jsonSecurityFile{ + { + Path: "path/to/policy1", + ContentLength: 100, + Hits: []jsonSecurityPolicyHits{ + { + Type: "type1", + }, + { + Type: "type2", + }, + }, + }, + { + Path: "path/to/policy2", + ContentLength: 200, + Hits: []jsonSecurityPolicyHits{ + { + Type: "type3", + }, + }, + }, + } + + if len(r.Results.SecurityPolicies) != len(expected) { + t.Errorf("addSecurityPolicyRawResults did not add the correct number of policies. Expected %d, got %d", len(expected), len(r.Results.SecurityPolicies)) //nolint:lll + } + + for i, policy := range r.Results.SecurityPolicies { + if policy.Path != expected[i].Path { + t.Errorf("addSecurityPolicyRawResults did not add the correct policy. Expected %s, got %s", expected[i].Path, policy.Path) //nolint:lll + } + + if policy.ContentLength != expected[i].ContentLength { + t.Errorf("addSecurityPolicyRawResults did not add the correct content length. Expected %d, got %d", expected[i].ContentLength, policy.ContentLength) //nolint:lll + } + + if len(policy.Hits) != len(expected[i].Hits) { + t.Errorf("addSecurityPolicyRawResults did not add the correct number of hits. Expected %d, got %d", len(expected[i].Hits), len(policy.Hits)) //nolint:lll + } + + for j, hit := range policy.Hits { + if hit.Type != expected[i].Hits[j].Type { + t.Errorf("addSecurityPolicyRawResults did not add the correct hit type. Expected %s, got %s", expected[i].Hits[j].Type, hit.Type) //nolint:lll + } + } + } +} + +func TestAddVulnerabilitiesRawResults(t *testing.T) { + r := &jsonScorecardRawResult{} + vd := &checker.VulnerabilitiesData{ + Vulnerabilities: []clients.Vulnerability{ + { + ID: "CVE-2021-1234", + }, + { + ID: "CVE-2021-5678", + }, + }, + } + + err := r.addVulnerbilitiesRawResults(vd) + if err != nil { + t.Errorf("addVulnerbilitiesRawResults returned an error: %v", err) + } + + expected := []jsonDatabaseVulnerability{ + { + ID: "CVE-2021-1234", + }, + { + ID: "CVE-2021-5678", + }, + } + + if len(r.Results.DatabaseVulnerabilities) != len(expected) { + t.Errorf("addVulnerbilitiesRawResults did not add the correct number of vulnerabilities. Expected %d, got %d", len(expected), len(r.Results.DatabaseVulnerabilities)) //nolint:lll + } + + for i, vuln := range r.Results.DatabaseVulnerabilities { + if vuln.ID != expected[i].ID { + t.Errorf("addVulnerbilitiesRawResults did not add the correct vulnerability. Expected %s, got %s", expected[i].ID, vuln.ID) //nolint:lll + } + } +} + +func TestAddFuzzingRawResults(t *testing.T) { + r := &jsonScorecardRawResult{} + fd := &checker.FuzzingData{ + Fuzzers: []checker.Tool{ + { + Name: "fuzzer1", + URL: asPointer("https://example.com/fuzzer1"), + Desc: asPointer("Fuzzer 1 description"), + Files: []checker.File{ + { + Path: "path/to/fuzzer1/file1", + }, + { + Path: "path/to/fuzzer1/file2", + }, + }, + }, + { + Name: "fuzzer2", + URL: asPointer("https://example.com/fuzzer2"), + Desc: asPointer("Fuzzer 2 description"), + Files: []checker.File{ + { + Path: "path/to/fuzzer2/file1", + }, + }, + }, + }, + } + + err := r.addFuzzingRawResults(fd) + if err != nil { + t.Errorf("addFuzzingRawResults returned an error: %v", err) + } + + expectedFuzzers := []jsonTool{ + { + Name: "fuzzer1", + URL: asPointer("https://example.com/fuzzer1"), + Desc: asPointer("Fuzzer 1 description"), + Files: []jsonFile{ + { + Path: "path/to/fuzzer1/file1", + }, + { + Path: "path/to/fuzzer1/file2", + }, + }, + }, + { + Name: "fuzzer2", + URL: asPointer("https://example.com/fuzzer2"), + Desc: asPointer("Fuzzer 2 description"), + Files: []jsonFile{ + { + Path: "path/to/fuzzer2/file1", + }, + }, + }, + } + + if len(r.Results.Fuzzers) != len(expectedFuzzers) { + t.Errorf("addFuzzingRawResults did not add the correct number of fuzzers. Expected %d, got %d", len(expectedFuzzers), len(r.Results.Fuzzers)) //nolint:lll + } + for i, fuzzer := range r.Results.Fuzzers { + if fuzzer.Name != expectedFuzzers[i].Name { + t.Errorf("addFuzzingRawResults did not add the correct fuzzer name. Expected %s, got %s", expectedFuzzers[i].Name, fuzzer.Name) //nolint:lll + } + if *fuzzer.URL != *expectedFuzzers[i].URL { + t.Errorf("addFuzzingRawResults did not add the correct fuzzer URL. Expected %s, got %s", *expectedFuzzers[i].URL, *fuzzer.URL) //nolint:lll + } + if *fuzzer.Desc != *expectedFuzzers[i].Desc { + t.Errorf("addFuzzingRawResults did not add the correct fuzzer description. Expected %s, got %s", *expectedFuzzers[i].Desc, *fuzzer.Desc) //nolint:lll + } + if len(fuzzer.Files) != len(expectedFuzzers[i].Files) { + t.Errorf("addFuzzingRawResults did not add the correct number of files for fuzzer %s. Expected %d, got %d", fuzzer.Name, len(expectedFuzzers[i].Files), len(fuzzer.Files)) //nolint:lll + } + for j, file := range fuzzer.Files { + if file.Path != expectedFuzzers[i].Files[j].Path { + t.Errorf("addFuzzingRawResults did not add the correct file path for fuzzer %s. Expected %s, got %s", fuzzer.Name, expectedFuzzers[i].Files[j].Path, file.Path) //nolint:lll + } + } + } +} + +func TestJsonScorecardRawResult(t *testing.T) { + // create a new instance of jsonScorecardRawResult + r := &jsonScorecardRawResult{} + + // create some test data for each of the add*RawResults functions + vd := &checker.VulnerabilitiesData{ + Vulnerabilities: []clients.Vulnerability{ + {ID: "CVE-2021-1234"}, + {ID: "CVE-2021-5678"}, + }, + } + ba := &checker.BinaryArtifactData{ + Files: []checker.File{ + {Path: "binaries/foo"}, + {Path: "binaries/bar"}, + }, + } + sp := &checker.SecurityPolicyData{ + PolicyFiles: []checker.SecurityPolicyFile{ + { + File: checker.File{ + Path: "policies/baz", + FileSize: 1024, + }, + Information: []checker.SecurityPolicyInformation{ + { + InformationType: checker.SecurityPolicyInformationTypeEmail, + InformationValue: checker.SecurityPolicyValueType{ + Match: "match", + LineNumber: 42, + Offset: 0, + }, + }, + }, + }, + }, + } + fd := &checker.FuzzingData{ + Fuzzers: []checker.Tool{ + { + Name: "fuzzer1", + URL: asPointer("https://example.com/fuzzer1"), + Desc: asPointer("fuzzer1 description"), + Files: []checker.File{ + {Path: "fuzzers/fuzzer1/foo"}, + {Path: "fuzzers/fuzzer1/bar"}, + }, + }, + { + Name: "fuzzer2", + URL: asPointer("https://example.com/fuzzer2"), + Desc: asPointer("fuzzer2 description"), + Files: []checker.File{ + {Path: "fuzzers/fuzzer2/foo"}, + {Path: "fuzzers/fuzzer2/bar"}, + }, + }, + }, + } + bp := &checker.BranchProtectionsData{ + Branches: []clients.BranchRef{ + { + Name: stringPtr("main"), + Protected: boolPtr(true), + BranchProtectionRule: clients.BranchProtectionRule{ + AllowDeletions: boolPtr(true), + AllowForcePushes: boolPtr(false), + RequiredPullRequestReviews: clients.PullRequestReviewRule{ + RequireCodeOwnerReviews: boolPtr(true), + DismissStaleReviews: boolPtr(true), + RequiredApprovingReviewCount: intPtr(2), + }, + RequireLinearHistory: boolPtr(true), + EnforceAdmins: boolPtr(true), + CheckRules: clients.StatusChecksRule{ + RequiresStatusChecks: boolPtr(true), + Contexts: []string{"ci"}, + UpToDateBeforeMerge: boolPtr(true), + }, + }, + }, + { + Name: stringPtr("dev"), + Protected: boolPtr(false), + }, + }, + } + + // test addVulnerbilitiesRawResults + err := r.addVulnerbilitiesRawResults(vd) + if err != nil { + t.Errorf("addVulnerbilitiesRawResults returned an error: %v", err) + } + expectedVulnerabilities := []jsonDatabaseVulnerability{ + {ID: "CVE-2021-1234"}, + {ID: "CVE-2021-5678"}, + } + if cmp.Diff(r.Results.DatabaseVulnerabilities, expectedVulnerabilities) != "" { + t.Errorf("addVulnerbilitiesRawResults did not produce the expected results %v", cmp.Diff(r.Results.DatabaseVulnerabilities, expectedVulnerabilities)) //nolint:lll + } + + // test addBinaryArtifactRawResults + err = r.addBinaryArtifactRawResults(ba) + if err != nil { + t.Errorf("addBinaryArtifactRawResults returned an error: %v", err) + } + expectedBinaries := []jsonFile{ + {Path: "binaries/foo"}, + {Path: "binaries/bar"}, + } + if cmp.Diff(expectedBinaries, r.Results.Binaries) != "" { + t.Errorf("addBinaryArtifactRawResults did not produce the expected results") + } + + // test addSecurityPolicyRawResults + err = r.addSecurityPolicyRawResults(sp) + if err != nil { + t.Errorf("addSecurityPolicyRawResults returned an error: %v", err) + } + expectedSecurityPolicies := []jsonSecurityFile{ + { + Path: "policies/baz", + ContentLength: 1024, + Hits: []jsonSecurityPolicyHits{ + { + Type: "emailAddress", + Match: "match", + LineNumber: 42, + Offset: 0, + }, + }, + }, + } + if cmp.Diff(expectedSecurityPolicies, r.Results.SecurityPolicies) != "" { + t.Errorf("addSecurityPolicyRawResults did not produce the expected results %v", cmp.Diff(expectedSecurityPolicies, r.Results.SecurityPolicies)) //nolint:lll + } + + // test addFuzzingRawResults + err = r.addFuzzingRawResults(fd) + if err != nil { + t.Errorf("addFuzzingRawResults returned an error: %v", err) + } + expectedFuzzers := []jsonTool{ + { + Name: "fuzzer1", + URL: asPointer("https://example.com/fuzzer1"), + Desc: asPointer("fuzzer1 description"), + Files: []jsonFile{ + {Path: "fuzzers/fuzzer1/foo"}, + {Path: "fuzzers/fuzzer1/bar"}, + }, + }, + { + Name: "fuzzer2", + URL: asPointer("https://example.com/fuzzer1"), + Desc: asPointer("fuzzer1 description"), + Files: []jsonFile{ + {Path: "fuzzers/fuzzer2/foo"}, + {Path: "fuzzers/fuzzer2/bar"}, + }, + }, + } + if cmp.Diff(expectedFuzzers, r.Results.Fuzzers, cmpopts.IgnoreFields(jsonTool{}, "URL", "Desc")) != "" { + t.Errorf("addFuzzingRawResults did not produce the expected results %v", cmp.Diff(expectedFuzzers, r.Results.Fuzzers)) //nolint:lll + } + + // test addBranchProtectionRawResults + err = r.addBranchProtectionRawResults(bp) + if err != nil { + t.Errorf("addBranchProtectionRawResults returned an error: %v", err) + } +} + +func stringPtr(s string) *string { + return &s +} + +func boolPtr(b bool) *bool { + return &b +} + +func intPtr(i int32) *int32 { + return &i +} + +//nolint:lll +func TestScorecardResult_AsRawJSON(t *testing.T) { + type fields struct { + Repo RepoInfo + Date time.Time + Scorecard ScorecardInfo + Checks []checker.CheckResult + RawResults checker.RawResults + Metadata []string + } + tests := []struct { //nolint:govet + name string + fields fields + wantWriter string + wantErr bool + }{ + { + name: "happy path", + fields: fields{ + Repo: RepoInfo{ + Name: "bar", + CommitSHA: "1234567890123456789012345678901234567890", + }, + }, + wantWriter: `{"date":"0001-01-01","repo":{"name":"bar","commit":"1234567890123456789012345678901234567890"},"scorecard":{"version":"","commit":""},"metadata":null,"results":{"workflows":[],"permissions":{},"licenses":[],"issues":null,"openssfBestPracticesBadge":{"badge":"Unknown"},"databaseVulnerabilities":[],"binaries":[],"securityPolicies":[],"dependencyUpdateTools":[],"branchProtections":{"branches":[],"codeownersFiles":null},"Contributors":{"users":null},"defaultBranchChangesets":[],"archived":{"status":false},"createdAt":{"timestamp":"0001-01-01T00:00:00Z"},"fuzzers":[],"releases":[],"packages":[],"dependencyPinning":{"dependencies":null}}} +`, //nolint:lll + }, + } + for _, tt := range tests { + tt := tt // capture range variable + t.Run(tt.name, func(t *testing.T) { + r := &ScorecardResult{ + Repo: tt.fields.Repo, + Date: tt.fields.Date, + Scorecard: tt.fields.Scorecard, + Checks: tt.fields.Checks, + RawResults: tt.fields.RawResults, + Metadata: tt.fields.Metadata, + } + writer := &bytes.Buffer{} + err := r.AsRawJSON(writer) + if (err != nil) != tt.wantErr { + t.Errorf("AsRawJSON() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotWriter := writer.String(); gotWriter != tt.wantWriter { + t.Errorf(cmp.Diff(gotWriter, tt.wantWriter)) + } + }) + } +} + +func TestAddBranchProtectionRawResults(t *testing.T) { + t.Parallel() + testCases := []struct { //nolint:govet + name string + input *checker.BranchProtectionsData + expected *jsonScorecardRawResult + }{ + { + name: "no branch protections", + input: &checker.BranchProtectionsData{ + Branches: nil, + }, + expected: &jsonScorecardRawResult{ + Results: jsonRawResults{ + BranchProtections: jsonBranchProtectionMetadata{ + Branches: []jsonBranchProtection{}, + }, + }, + }, + }, + { + name: "one protected branch", + input: &checker.BranchProtectionsData{ + Branches: []clients.BranchRef{ + { + Name: stringPtr("main"), + Protected: boolPtr(true), + BranchProtectionRule: clients.BranchProtectionRule{ + AllowDeletions: boolPtr(false), + }, + }, + }, + }, + expected: &jsonScorecardRawResult{ + Results: jsonRawResults{ + BranchProtections: jsonBranchProtectionMetadata{ + Branches: []jsonBranchProtection{ + { + Name: "main", + Protection: &jsonBranchProtectionSettings{ + AllowsDeletions: boolPtr(false), + }, + }, + }, + }, + }, + }, + }, + } + + for _, tc := range testCases { + tc := tc // capture range variable + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + result := &jsonScorecardRawResult{} + err := result.addBranchProtectionRawResults(tc.input) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expected, result); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestFillJSONRawResults(t *testing.T) { + raw := checker.RawResults{ + LicenseResults: checker.LicenseData{ + LicenseFiles: []checker.LicenseFile{ + {LicenseInformation: checker.License{Name: "MIT"}}, + }, + }, + VulnerabilitiesResults: checker.VulnerabilitiesData{ + Vulnerabilities: []clients.Vulnerability{ + {ID: "CVE-2020-1234"}, + }, + }, + BinaryArtifactResults: checker.BinaryArtifactData{ + Files: []checker.File{ + {Path: "bin/app"}, + }, + }, + SecurityPolicyResults: checker.SecurityPolicyData{ + PolicyFiles: []checker.SecurityPolicyFile{ + {File: checker.File{Path: "SECURITY.md"}}, + }, + }, + DependencyUpdateToolResults: checker.DependencyUpdateToolData{ + Tools: []checker.Tool{ + {Name: "Dependabot"}, + }, + }, + BranchProtectionResults: checker.BranchProtectionsData{ + Branches: []clients.BranchRef{ + {Name: stringPtr("main"), Protected: boolPtr(true)}, + }, + }, + CodeReviewResults: checker.CodeReviewData{}, + MaintainedResults: checker.MaintainedData{}, + SignedReleasesResults: checker.SignedReleasesData{ + Releases: []clients.Release{ + {TagName: "v1.0.0"}, + }, + }, + ContributorsResults: checker.ContributorsData{}, + PinningDependenciesResults: checker.PinningDependenciesData{ + Dependencies: []checker.Dependency{ + {Name: stringPtr("requirements.txt")}, + }, + }, + CIIBestPracticesResults: checker.CIIBestPracticesData{}, + DangerousWorkflowResults: checker.DangerousWorkflowData{}, + FuzzingResults: checker.FuzzingData{}, + PackagingResults: checker.PackagingData{}, + TokenPermissionsResults: checker.TokenPermissionsData{}, + } + + r := jsonScorecardRawResult{} + err := r.fillJSONRawResults(&raw) + if err != nil { + t.Fatal(err) + } +} From 1a336d80870ceccc8e7c36243ecffef6ce190f17 Mon Sep 17 00:00:00 2001 From: laurentsimon <64505099+laurentsimon@users.noreply.github.com> Date: Mon, 22 May 2023 18:13:24 -0700 Subject: [PATCH 07/10] =?UTF-8?q?=E2=9C=A8=20=20[experimental]=20Add=20pro?= =?UTF-8?q?be=20code=20and=20support=20for=20Tool-Update-Dependency=20(#29?= =?UTF-8?q?44)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon * update Signed-off-by: laurentsimon --------- Signed-off-by: laurentsimon --- checker/check_result.go | 23 +++ checker/raw_result.go | 22 ++- checks/dependency_update_tool.go | 12 +- checks/dependency_update_tool_test.go | 18 ++- checks/evaluation/dependency_update_tool.go | 49 ++---- .../evaluation/dependency_update_tool_test.go | 152 ++++++------------ checks/raw/dependency_update_tool.go | 8 +- checks/raw/dependency_update_tool_test.go | 119 ++++++-------- checks/run_probes.go | 41 +++++ e2e/dependency_update_tool_test.go | 8 +- probes/entries.go | 59 +++++++ probes/toolDependabotInstalled/def.yml | 32 ++++ probes/toolDependabotInstalled/impl.go | 53 ++++++ probes/toolPyUpInstalled/def.yml | 32 ++++ probes/toolPyUpInstalled/impl.go | 53 ++++++ probes/toolRenovateInstalled/def.yml | 32 ++++ probes/toolRenovateInstalled/impl.go | 53 ++++++ probes/toolSonatypeLiftInstalled/def.yml | 32 ++++ probes/toolSonatypeLiftInstalled/impl.go | 53 ++++++ probes/utils/tools.go | 69 ++++++++ probes/zrunner/runner.go | 51 ++++++ 21 files changed, 742 insertions(+), 229 deletions(-) create mode 100644 checks/run_probes.go create mode 100644 probes/entries.go create mode 100644 probes/toolDependabotInstalled/def.yml create mode 100644 probes/toolDependabotInstalled/impl.go create mode 100644 probes/toolPyUpInstalled/def.yml create mode 100644 probes/toolPyUpInstalled/impl.go create mode 100644 probes/toolRenovateInstalled/def.yml create mode 100644 probes/toolRenovateInstalled/impl.go create mode 100644 probes/toolSonatypeLiftInstalled/def.yml create mode 100644 probes/toolSonatypeLiftInstalled/impl.go create mode 100644 probes/utils/tools.go create mode 100644 probes/zrunner/runner.go diff --git a/checker/check_result.go b/checker/check_result.go index 8491bdd394b..5e0dd21946f 100644 --- a/checker/check_result.go +++ b/checker/check_result.go @@ -193,3 +193,26 @@ func CreateRuntimeErrorResult(name string, e error) CheckResult { Reason: e.Error(), // Note: message already accessible by caller thru `Error`. } } + +// LogFindings logs the list of findings. +func LogFindings(findings []finding.Finding, dl DetailLogger) error { + for i := range findings { + f := &findings[i] + switch f.Outcome { + case finding.OutcomeNegative: + dl.Warn(&LogMessage{ + Finding: f, + }) + case finding.OutcomePositive: + dl.Info(&LogMessage{ + Finding: f, + }) + default: + dl.Debug(&LogMessage{ + Finding: f, + }) + } + } + + return nil +} diff --git a/checker/raw_result.go b/checker/raw_result.go index 8b991d18407..37dfcbd3e4f 100644 --- a/checker/raw_result.go +++ b/checker/raw_result.go @@ -242,7 +242,6 @@ type SignedReleasesData struct { // for the Dependency-Update-Tool check. type DependencyUpdateToolData struct { // Tools contains a list of tools. - // Note: we only populate one entry at most. Tools []Tool } @@ -375,3 +374,24 @@ type TokenPermission struct { Msg *string Type PermissionLevel } + +// Location generates location from a file. +func (f *File) Location() *finding.Location { + // TODO(2626): merge location and path. + if f == nil { + return nil + } + loc := &finding.Location{ + Type: f.Type, + Path: f.Path, + LineStart: &f.Offset, + } + if f.EndOffset != 0 { + loc.LineEnd = &f.EndOffset + } + if f.Snippet != "" { + loc.Snippet = &f.Snippet + } + + return loc +} diff --git a/checks/dependency_update_tool.go b/checks/dependency_update_tool.go index fee37c1973e..54f1954f9b1 100644 --- a/checks/dependency_update_tool.go +++ b/checks/dependency_update_tool.go @@ -19,12 +19,13 @@ import ( "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/probes" ) // CheckDependencyUpdateTool is the exported name for Automatic-Depdendency-Update. const CheckDependencyUpdateTool = "Dependency-Update-Tool" -//nolint +// nolint func init() { supportedRequestTypes := []checker.RequestType{ checker.FileBased, @@ -48,6 +49,13 @@ func DependencyUpdateTool(c *checker.CheckRequest) checker.CheckResult { c.RawResults.DependencyUpdateToolResults = rawData } + // Evaluate the probes. + findings, err := evaluateProbes(c, CheckDependencyUpdateTool, probes.DependencyToolUpdates) + if err != nil { + e := sce.WithMessage(sce.ErrScorecardInternal, err.Error()) + return checker.CreateRuntimeErrorResult(CheckDependencyUpdateTool, e) + } + // Return the score evaluation. - return evaluation.DependencyUpdateTool(CheckDependencyUpdateTool, c.Dlogger, &rawData) + return evaluation.DependencyUpdateTool(CheckDependencyUpdateTool, findings) } diff --git a/checks/dependency_update_tool_test.go b/checks/dependency_update_tool_test.go index 2b21e6f99c9..173f4e5198c 100644 --- a/checks/dependency_update_tool_test.go +++ b/checks/dependency_update_tool_test.go @@ -51,6 +51,7 @@ func TestDependencyUpdateTool(t *testing.T) { CallSearchCommits: 0, expected: scut.TestReturn{ NumberOfInfo: 1, + NumberOfWarn: 3, Score: 10, }, }, @@ -63,6 +64,7 @@ func TestDependencyUpdateTool(t *testing.T) { CallSearchCommits: 0, expected: scut.TestReturn{ NumberOfInfo: 1, + NumberOfWarn: 3, Score: 10, }, }, @@ -75,7 +77,7 @@ func TestDependencyUpdateTool(t *testing.T) { SearchCommits: []clients.Commit{{Committer: clients.User{ID: 111111111}}}, CallSearchCommits: 1, expected: scut.TestReturn{ - NumberOfWarn: 1, + NumberOfWarn: 4, }, }, { @@ -87,7 +89,7 @@ func TestDependencyUpdateTool(t *testing.T) { SearchCommits: []clients.Commit{}, CallSearchCommits: 1, expected: scut.TestReturn{ - NumberOfWarn: 1, + NumberOfWarn: 4, }, }, @@ -101,6 +103,7 @@ func TestDependencyUpdateTool(t *testing.T) { CallSearchCommits: 1, expected: scut.TestReturn{ NumberOfInfo: 1, + NumberOfWarn: 3, Score: 10, }, }, @@ -108,13 +111,14 @@ func TestDependencyUpdateTool(t *testing.T) { name: "found in commits 2", wantErr: false, files: []string{}, - SearchCommits: []clients.Commit{{Committer: clients.User{ID: 111111111}}, + SearchCommits: []clients.Commit{ + {Committer: clients.User{ID: 111111111}}, {Committer: clients.User{ID: dependabotID}}, }, - CallSearchCommits: 1, expected: scut.TestReturn{ NumberOfInfo: 1, + NumberOfWarn: 3, Score: 10, }, }, @@ -125,12 +129,14 @@ func TestDependencyUpdateTool(t *testing.T) { files: []string{ ".github/foobar.yml", }, - SearchCommits: []clients.Commit{{Committer: clients.User{ID: 111111111}}, + SearchCommits: []clients.Commit{ + {Committer: clients.User{ID: 111111111}}, {Committer: clients.User{ID: dependabotID}}, }, CallSearchCommits: 1, expected: scut.TestReturn{ NumberOfInfo: 1, + NumberOfWarn: 3, Score: 10, }, }, @@ -144,9 +150,11 @@ func TestDependencyUpdateTool(t *testing.T) { mockRepo.EXPECT().ListFiles(gomock.Any()).Return(tt.files, nil) mockRepo.EXPECT().SearchCommits(gomock.Any()).Return(tt.SearchCommits, nil).Times(tt.CallSearchCommits) dl := scut.TestDetailLogger{} + raw := checker.RawResults{} c := &checker.CheckRequest{ RepoClient: mockRepo, Dlogger: &dl, + RawResults: &raw, } res := DependencyUpdateTool(c) diff --git a/checks/evaluation/dependency_update_tool.go b/checks/evaluation/dependency_update_tool.go index c527b82b8e3..239167a4e3b 100644 --- a/checks/evaluation/dependency_update_tool.go +++ b/checks/evaluation/dependency_update_tool.go @@ -15,51 +15,20 @@ package evaluation import ( - "fmt" - "github.com/ossf/scorecard/v4/checker" - sce "github.com/ossf/scorecard/v4/errors" + "github.com/ossf/scorecard/v4/finding" ) // DependencyUpdateTool applies the score policy for the Dependency-Update-Tool check. -func DependencyUpdateTool(name string, dl checker.DetailLogger, - r *checker.DependencyUpdateToolData, +func DependencyUpdateTool(name string, + findings []finding.Finding, ) checker.CheckResult { - if r == nil { - e := sce.WithMessage(sce.ErrScorecardInternal, "empty raw data") - return checker.CreateRuntimeErrorResult(name, e) - } - - // Apply the policy evaluation. - if r.Tools == nil || len(r.Tools) == 0 { - dl.Warn(&checker.LogMessage{ - Text: `Config file not detected in source location for dependabot, renovatebot, Sonatype Lift, or - PyUp (Python). We recommend setting this configuration in code so it can be easily verified by others.`, - }) - return checker.CreateMinScoreResult(name, "no update tool detected") - } - - // Validate the input. - if len(r.Tools) != 1 { - e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("found %d tools, expected 1", len(r.Tools))) - return checker.CreateRuntimeErrorResult(name, e) - } - - if r.Tools[0].Files == nil { - e := sce.WithMessage(sce.ErrScorecardInternal, "Files are nil") - return checker.CreateRuntimeErrorResult(name, e) - } - - // Iterate over all the files, since a Tool can contain multiple files. - for _, file := range r.Tools[0].Files { - dl.Info(&checker.LogMessage{ - Path: file.Path, - Type: file.Type, - Offset: file.Offset, - Text: fmt.Sprintf("%s detected", r.Tools[0].Name), - }) + for i := range findings { + f := &findings[i] + if f.Outcome == finding.OutcomePositive { + return checker.CreateMaxScoreResult(name, "update tool detected") + } } - // High score result. - return checker.CreateMaxScoreResult(name, "update tool detected") + return checker.CreateMinScoreResult(name, "no update tool detected") } diff --git a/checks/evaluation/dependency_update_tool_test.go b/checks/evaluation/dependency_update_tool_test.go index 68d50f610d8..62667d6c3ca 100644 --- a/checks/evaluation/dependency_update_tool_test.go +++ b/checks/evaluation/dependency_update_tool_test.go @@ -18,7 +18,6 @@ import ( "testing" "github.com/ossf/scorecard/v4/checker" - sce "github.com/ossf/scorecard/v4/errors" "github.com/ossf/scorecard/v4/finding" scut "github.com/ossf/scorecard/v4/utests" ) @@ -26,135 +25,91 @@ import ( func TestDependencyUpdateTool(t *testing.T) { t.Parallel() //nolint - type args struct { - name string - dl checker.DetailLogger - r *checker.DependencyUpdateToolData - } - //nolint tests := []struct { name string - args args - want checker.CheckResult + findings []finding.Finding err bool + want checker.CheckResult expected scut.TestReturn }{ { - name: "DependencyUpdateTool", - args: args{ - name: "DependencyUpdateTool", - dl: &scut.TestDetailLogger{}, - r: &checker.DependencyUpdateToolData{ - Tools: []checker.Tool{ - { - Name: "DependencyUpdateTool", - }, - }, + name: "dependabot", + findings: []finding.Finding{ + { + Probe: "toolDependabotInstalled", + Outcome: finding.OutcomePositive, }, }, want: checker.CheckResult{ - Score: -1, - }, - err: false, - expected: scut.TestReturn{ - Error: sce.ErrScorecardInternal, - Score: -1, + Score: 10, }, }, { - name: "empty tool list", - args: args{ - name: "DependencyUpdateTool", - dl: &scut.TestDetailLogger{}, - r: &checker.DependencyUpdateToolData{ - Tools: []checker.Tool{}, + name: "renovate", + findings: []finding.Finding{ + { + Probe: "toolRenovateInstalled", + Outcome: finding.OutcomePositive, }, }, want: checker.CheckResult{ - Score: 0, - Error: nil, - }, - err: false, - expected: scut.TestReturn{ - Score: 0, - NumberOfWarn: 1, + Score: 10, }, }, { - name: "Valid tool", - args: args{ - name: "DependencyUpdateTool", - dl: &scut.TestDetailLogger{}, - r: &checker.DependencyUpdateToolData{ - Tools: []checker.Tool{ - { - Name: "DependencyUpdateTool", - Files: []checker.File{ - { - Path: "/etc/dependency-update-tool.conf", - Snippet: ` - [dependency-update-tool] - enabled = true - `, - Type: finding.FileTypeSource, - }, - }, - }, - }, + name: "pyup", + findings: []finding.Finding{ + { + Probe: "toolPyUpInstalled", + Outcome: finding.OutcomePositive, }, }, want: checker.CheckResult{ Score: 10, - Error: nil, }, - expected: scut.TestReturn{ - Error: nil, - Score: 10, - NumberOfInfo: 1, - }, - err: false, }, { - name: "more than one tool in the list", - args: args{ - name: "DependencyUpdateTool", - dl: &scut.TestDetailLogger{}, - r: &checker.DependencyUpdateToolData{ - Tools: []checker.Tool{ - { - Name: "DependencyUpdateTool", - }, - { - Name: "DependencyUpdateTool", - }, - }, + name: "sonatype", + findings: []finding.Finding{ + { + Probe: "toolSonatypeInstalled", + Outcome: finding.OutcomePositive, }, }, want: checker.CheckResult{ - Score: -1, - Error: nil, - }, - expected: scut.TestReturn{ - Error: sce.ErrScorecardInternal, - Score: -1, + Score: 10, }, - err: false, }, { - name: "Nil r", - args: args{ - name: "nil r", - dl: &scut.TestDetailLogger{}, + name: "none", + findings: []finding.Finding{ + { + Probe: "toolDependabotInstalled", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "toolRenovateInstalled", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "toolPyUpInstalled", + Outcome: finding.OutcomeNegative, + }, + { + Probe: "toolSonatypeInstalled", + Outcome: finding.OutcomeNegative, + }, }, want: checker.CheckResult{ - Score: -1, - Error: nil, + Score: 0, }, - expected: scut.TestReturn{ - Error: sce.ErrScorecardInternal, - Score: -1, + }, + { + name: "empty tool list", + want: checker.CheckResult{ + Score: 0, + Error: nil, }, - err: false, }, } for _, tt := range tests { @@ -162,8 +117,7 @@ func TestDependencyUpdateTool(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - dl := scut.TestDetailLogger{} - got := DependencyUpdateTool(tt.args.name, &dl, tt.args.r) + got := DependencyUpdateTool(tt.name, tt.findings) if tt.want.Score != got.Score { t.Errorf("DependencyUpdateTool() got Score = %v, want %v for %v", got.Score, tt.want.Score, tt.name) } @@ -171,10 +125,6 @@ func TestDependencyUpdateTool(t *testing.T) { t.Errorf("DependencyUpdateTool() error = %v, want %v for %v", got.Error, tt.want.Error, tt.name) return } - - if !scut.ValidateTestReturn(t, tt.name, &tt.expected, &got, &dl) { - t.Fatalf(tt.name) - } }) } } diff --git a/checks/raw/dependency_update_tool.go b/checks/raw/dependency_update_tool.go index 68fc4a1eac3..3af013b4088 100644 --- a/checks/raw/dependency_update_tool.go +++ b/checks/raw/dependency_update_tool.go @@ -126,13 +126,11 @@ var checkDependencyFileExists fileparser.DoWhileTrueOnFilename = func(name strin }, }, }) - default: - // Continue iterating. - return true, nil } - // We found a file, no need to continue iterating. - return false, nil + // Continue iterating, even if we have found a tool. + // It's needed for all probes results to be populated. + return true, nil } func asPointer(s string) *string { diff --git a/checks/raw/dependency_update_tool_test.go b/checks/raw/dependency_update_tool_test.go index 87fb21a7055..02a3128690b 100644 --- a/checks/raw/dependency_update_tool_test.go +++ b/checks/raw/dependency_update_tool_test.go @@ -26,115 +26,84 @@ import ( func Test_checkDependencyFileExists(t *testing.T) { t.Parallel() - //nolint - type args struct { - name string - data *[]checker.Tool - } + //nolint tests := []struct { name string - args args + path string want bool wantErr bool }{ { - name: "check dependency file exists", - args: args{ - name: ".github/dependabot.yml", - data: &[]checker.Tool{}, - }, - want: false, + name: ".github/dependabot.yml", + path: ".github/dependabot.yml", + want: true, wantErr: false, }, { - name: ".other", - args: args{ - name: ".other", - data: &[]checker.Tool{}, - }, + name: ".github/dependabot.yaml", + path: ".github/dependabot.yaml", want: true, wantErr: false, }, { - name: ".github/renovate.json", - args: args{ - name: ".github/renovate.json", - data: &[]checker.Tool{}, - }, + name: ".other", + path: ".other", want: false, wantErr: false, }, { - name: ".github/renovate.json5", - args: args{ - name: ".github/renovate.json5", - data: &[]checker.Tool{}, - }, - want: false, + name: ".github/renovate.json", + path: ".github/renovate.json", + want: true, wantErr: false, }, { - name: ".renovaterc.json", - args: args{ - name: ".renovaterc.json", - data: &[]checker.Tool{}, - }, - want: false, + name: ".github/renovate.json5", + path: ".github/renovate.json5", + want: true, wantErr: false, }, { - name: "renovate.json", - args: args{ - name: "renovate.json", - data: &[]checker.Tool{}, - }, - want: false, + name: ".renovaterc.json", + path: ".renovaterc.json", + want: true, wantErr: false, }, { - name: "renovate.json5", - args: args{ - name: "renovate.json5", - data: &[]checker.Tool{}, - }, - want: false, + name: "renovate.json", + path: "renovate.json", + want: true, wantErr: false, }, { - name: ".renovaterc", - args: args{ - name: ".renovaterc", - data: &[]checker.Tool{}, - }, - want: false, + name: "renovate.json5", + path: "renovate.json5", + want: true, wantErr: false, }, { - name: ".pyup.yml", - args: args{ - name: ".pyup.yml", - data: &[]checker.Tool{}, - }, - want: false, + name: ".renovaterc", + path: ".renovaterc", + want: true, wantErr: false, }, { - name: ".lift.toml", - args: args{ - name: ".lift.toml", - data: &[]checker.Tool{}, - }, - want: false, + name: ".pyup.yml", + path: ".pyup.yml", + want: true, wantErr: false, }, { - name: ".lift/config.toml", - args: args{ - name: ".lift/config.toml", - data: &[]checker.Tool{}, - }, - want: false, + name: ".lift.toml", + path: ".lift.toml", + want: true, + wantErr: false, + }, + { + name: ".lift/config.toml", + path: ".lift/config.toml", + want: true, wantErr: false, }, } @@ -142,13 +111,17 @@ func Test_checkDependencyFileExists(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - got, err := checkDependencyFileExists(tt.args.name, tt.args.data) + results := []checker.Tool{} + cont, err := checkDependencyFileExists(tt.path, &results) if (err != nil) != tt.wantErr { t.Errorf("checkDependencyFileExists() error = %v, wantErr %v", err, tt.wantErr) return } - if got != tt.want { - t.Errorf("checkDependencyFileExists() = %v, want %v for test %v", got, tt.want, tt.name) + if !cont { + t.Errorf("continue is false for %v", tt.name) + } + if tt.want != (len(results) == 1) { + t.Errorf("checkDependencyFileExists() = %v, want %v for test %v", len(results), tt.want, tt.name) } }) } diff --git a/checks/run_probes.go b/checks/run_probes.go new file mode 100644 index 00000000000..5af28ac79ed --- /dev/null +++ b/checks/run_probes.go @@ -0,0 +1,41 @@ +// Copyright 2023 OpenSSF Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package checks + +import ( + "fmt" + + "github.com/ossf/scorecard/v4/checker" + "github.com/ossf/scorecard/v4/finding" + "github.com/ossf/scorecard/v4/probes" + "github.com/ossf/scorecard/v4/probes/zrunner" +) + +// evaluateProbes runs the probes in probesToRun and logs its findings. +func evaluateProbes(c *checker.CheckRequest, checkName string, + probesToRun []probes.ProbeImpl, +) ([]finding.Finding, error) { + // Run the probes. + findings, err := zrunner.Run(c.RawResults, probesToRun) + if err != nil { + return nil, fmt.Errorf("zrunner.Run: %w", err) + } + + // Log the findings. + if err := checker.LogFindings(findings, c.Dlogger); err != nil { + return nil, fmt.Errorf("LogFindings: %w", err) + } + return findings, nil +} diff --git a/e2e/dependency_update_tool_test.go b/e2e/dependency_update_tool_test.go index a580a6bb933..244c849f82f 100644 --- a/e2e/dependency_update_tool_test.go +++ b/e2e/dependency_update_tool_test.go @@ -39,16 +39,18 @@ var _ = Describe("E2E TEST:"+checks.CheckDependencyUpdateTool, func() { err = repoClient.InitRepo(repo, clients.HeadSHA, 0) Expect(err).Should(BeNil()) + raw := checker.RawResults{} req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, Repo: repo, Dlogger: &dl, + RawResults: &raw, } expected := scut.TestReturn{ Error: nil, Score: checker.MaxResultScore, - NumberOfWarn: 0, + NumberOfWarn: 3, NumberOfInfo: 1, NumberOfDebug: 0, } @@ -66,16 +68,18 @@ var _ = Describe("E2E TEST:"+checks.CheckDependencyUpdateTool, func() { err = repoClient.InitRepo(repo, clients.HeadSHA, 0) Expect(err).Should(BeNil()) + raw := checker.RawResults{} req := checker.CheckRequest{ Ctx: context.Background(), RepoClient: repoClient, Repo: repo, Dlogger: &dl, + RawResults: &raw, } expected := scut.TestReturn{ Error: nil, Score: checker.MaxResultScore, - NumberOfWarn: 0, + NumberOfWarn: 3, NumberOfInfo: 1, NumberOfDebug: 0, } diff --git a/probes/entries.go b/probes/entries.go new file mode 100644 index 00000000000..84be2c51fee --- /dev/null +++ b/probes/entries.go @@ -0,0 +1,59 @@ +// Copyright 2023 OpenSSF Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package probes + +import ( + "github.com/ossf/scorecard/v4/checker" + "github.com/ossf/scorecard/v4/finding" + "github.com/ossf/scorecard/v4/probes/toolDependabotInstalled" + "github.com/ossf/scorecard/v4/probes/toolPyUpInstalled" + "github.com/ossf/scorecard/v4/probes/toolRenovateInstalled" + "github.com/ossf/scorecard/v4/probes/toolSonatypeLiftInstalled" +) + +// ProbeImpl is the implementation of a probe. +type ProbeImpl func(*checker.RawResults) ([]finding.Finding, string, error) + +var ( + // All represents all the probes. + All []ProbeImpl + // DependencyToolUpdates is all the probes for the + // DpendencyUpdateTool check. + DependencyToolUpdates = []ProbeImpl{ + toolRenovateInstalled.Run, + toolDependabotInstalled.Run, + toolPyUpInstalled.Run, + toolSonatypeLiftInstalled.Run, + } +) + +//nolint:gochecknoinits +func init() { + All = concatMultipleProbes([][]ProbeImpl{ + DependencyToolUpdates, + }) +} + +func concatMultipleProbes(slices [][]ProbeImpl) []ProbeImpl { + var totalLen int + for _, s := range slices { + totalLen += len(s) + } + tmp := make([]ProbeImpl, 0, totalLen) + for _, s := range slices { + tmp = append(tmp, s...) + } + return tmp +} diff --git a/probes/toolDependabotInstalled/def.yml b/probes/toolDependabotInstalled/def.yml new file mode 100644 index 00000000000..e58d6e14194 --- /dev/null +++ b/probes/toolDependabotInstalled/def.yml @@ -0,0 +1,32 @@ +# Copyright 2023 OpenSSF Scorecard Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +id: toolDependabotInstalled +short: Check that Dependabot is enabled +motivation: > + Out-of-date dependencies make a project vulnerable to known flaws and prone to attacks. + Dependabot automates the process of updating dependencies by scanning for outdated or insecure requirements, and opening a pull request to update them if found. +implementation: > + The implemtation looks for the presence of files named ".github/dependabot.yml" or ".github/dependabot.yaml". If none of these files are found, + the implementation checks whether commits are authored by Dependabot. If none of these succeed, Dependabot is not installed. + NOTE: if the configuration files are found, the probe does not ensure that the Dependabot is run or that the Dependabot's pull requests are merged. +outcome: + - If dependendabot is installed, the probe returns OutcomePositive (1) + - If dependendabot is not installed, the probe returns OutcomeNegative (0) +remediation: + effort: Low + text: + - Follow the instructions from https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates. + markdown: + - Follow the instructions from [the official documentation](https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates). \ No newline at end of file diff --git a/probes/toolDependabotInstalled/impl.go b/probes/toolDependabotInstalled/impl.go new file mode 100644 index 00000000000..1ca92087e10 --- /dev/null +++ b/probes/toolDependabotInstalled/impl.go @@ -0,0 +1,53 @@ +// Copyright 2022 OpenSSF Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// nolint:stylecheck +package toolDependabotInstalled + +import ( + "embed" + + "github.com/ossf/scorecard/v4/checker" + "github.com/ossf/scorecard/v4/finding" + "github.com/ossf/scorecard/v4/probes/utils" +) + +//go:embed *.yml +var fs embed.FS + +const probe = "toolDependabotInstalled" + +type dependabot struct{} + +func (t dependabot) Name() string { + return "Dependabot" +} + +func (t dependabot) Matches(tool *checker.Tool) bool { + return t.Name() == tool.Name +} + +func Run(raw *checker.RawResults) ([]finding.Finding, string, error) { + tools := raw.DependencyUpdateToolResults.Tools + var matcher dependabot + // Check whether Dependabot tool is installed on the repo, + // and create the corresponding findings. + //nolint:wrapcheck + return utils.ToolsRun(tools, fs, probe, + // Tool found will generate a positive result. + finding.OutcomePositive, + // Tool not found will generate a negative result. + finding.OutcomeNegative, + matcher) +} diff --git a/probes/toolPyUpInstalled/def.yml b/probes/toolPyUpInstalled/def.yml new file mode 100644 index 00000000000..9529194cea3 --- /dev/null +++ b/probes/toolPyUpInstalled/def.yml @@ -0,0 +1,32 @@ +# Copyright 2023 OpenSSF Scorecard Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +id: toolPyUpInstalled +short: Check that PyUp is installed. +motivation: > + Out-of-date dependencies make a project vulnerable to known flaws and prone to attacks. + PyUp automates the process of updating dependencies by scanning for outdated or insecure requirements, and opening a pull request to update them if found. +implementation: > + The implementation looks for the presence of a file named ".pyup.yml". + If the file is not found, PyUp is not installed. + NOTE: the implementation does not ensure that PyUp is run or that PyUp's pull requests are merged. +outcome: + - If PyUp is installed, the probe returns OutcomePositive (1) + - If PyUp is not installed, the probe returns OutcomeNegative (0) +remediation: + effort: Low + text: + - Follow the instructions from https://docs.pyup.io/docs. + markdown: + - Follow the instructions from [the official documentation](https://docs.pyup.io/docs). \ No newline at end of file diff --git a/probes/toolPyUpInstalled/impl.go b/probes/toolPyUpInstalled/impl.go new file mode 100644 index 00000000000..41c58db88a1 --- /dev/null +++ b/probes/toolPyUpInstalled/impl.go @@ -0,0 +1,53 @@ +// Copyright 2022 OpenSSF Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// nolint:stylecheck +package toolPyUpInstalled + +import ( + "embed" + + "github.com/ossf/scorecard/v4/checker" + "github.com/ossf/scorecard/v4/finding" + "github.com/ossf/scorecard/v4/probes/utils" +) + +//go:embed *.yml +var fs embed.FS + +const probe = "toolPyUpInstalled" + +type pyup struct{} + +func (t pyup) Name() string { + return "PyUp" +} + +func (t pyup) Matches(tool *checker.Tool) bool { + return t.Name() == tool.Name +} + +func Run(raw *checker.RawResults) ([]finding.Finding, string, error) { + tools := raw.DependencyUpdateToolResults.Tools + var matcher pyup + // Check whether PyUp tool is installed on the repo, + // and create the corresponding findings. + //nolint:wrapcheck + return utils.ToolsRun(tools, fs, probe, + // Tool found will generate a positive result. + finding.OutcomePositive, + // Tool not found will generate a negative result. + finding.OutcomeNegative, + matcher) +} diff --git a/probes/toolRenovateInstalled/def.yml b/probes/toolRenovateInstalled/def.yml new file mode 100644 index 00000000000..72a9f106f25 --- /dev/null +++ b/probes/toolRenovateInstalled/def.yml @@ -0,0 +1,32 @@ +# Copyright 2023 OpenSSF Scorecard Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +id: toolRenovateInstalled +short: Check that Renovate bot is installed. +motivation: > + Out-of-date dependencies make a project vulnerable to known flaws and prone to attacks. + Renovate automates the process of updating dependencies by scanning for outdated or insecure requirements, and opening a pull request to update them if found. +implementation: > + The implementation looks for the presence of files named ".github/renovate.json", ".github/renovate.json5", ".renovaterc.json" or. "renovate.json". + If none of these files are found, Renovate is not installed. + NOTE: the implementation does not ensure that Renovate is run or that Renovate's pull requests are merged. +outcome: + - If Renovate is installed, the probe returns OutcomePositive (1) + - If Renovate is not installed, the probe returns OutcomeNegative (0) +remediation: + effort: Low + text: + - Follow the instructions from https://docs.renovatebot.com/configuration-options/. + markdown: + - Follow the instructions from [the official documentation](https://docs.renovatebot.com/configuration-options/). \ No newline at end of file diff --git a/probes/toolRenovateInstalled/impl.go b/probes/toolRenovateInstalled/impl.go new file mode 100644 index 00000000000..a35f91662cc --- /dev/null +++ b/probes/toolRenovateInstalled/impl.go @@ -0,0 +1,53 @@ +// Copyright 2022 OpenSSF Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// nolint:stylecheck +package toolRenovateInstalled + +import ( + "embed" + + "github.com/ossf/scorecard/v4/checker" + "github.com/ossf/scorecard/v4/finding" + "github.com/ossf/scorecard/v4/probes/utils" +) + +//go:embed *.yml +var fs embed.FS + +const probe = "toolRenovateInstalled" + +type renovate struct{} + +func (t renovate) Name() string { + return "RenovateBot" +} + +func (t renovate) Matches(tool *checker.Tool) bool { + return t.Name() == tool.Name +} + +func Run(raw *checker.RawResults) ([]finding.Finding, string, error) { + tools := raw.DependencyUpdateToolResults.Tools + var matcher renovate + // Check whether Renovate tool is installed on the repo, + // and create the corresponding findings. + //nolint:wrapcheck + return utils.ToolsRun(tools, fs, probe, + // Tool found will generate a positive result. + finding.OutcomePositive, + // Tool not found will generate a negative result. + finding.OutcomeNegative, + matcher) +} diff --git a/probes/toolSonatypeLiftInstalled/def.yml b/probes/toolSonatypeLiftInstalled/def.yml new file mode 100644 index 00000000000..e2d38e1c967 --- /dev/null +++ b/probes/toolSonatypeLiftInstalled/def.yml @@ -0,0 +1,32 @@ +# Copyright 2023 OpenSSF Scorecard Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +id: toolSonatypeLiftInstalled +short: Check that Sonatype Lyft is installed. +motivation: > + Out-of-date dependencies make a project vulnerable to known flaws and prone to attacks. + Sonatype Lyft automates the process of updating dependencies by scanning for outdated or insecure requirements, and opening a pull request to update them if found. +implementation: > + The implementation looks for the presence of files named ".lift.toml" or ".lift/config.toml". + If none of these files are found, Sonatype Lyft is not installed. + NOTE: the implementation does not ensure that Sonatype Lyft is run or that Sonatype Lyft's pull requests are merged. +outcome: + - If Sonatype Lyft is installed, the probe returns OutcomePositive (1) + - If Sonatype Lyft is not installed, the probe returns OutcomeNegative (0) +remediation: + effort: Low + text: + - Follow the instructions from https://help.sonatype.com/lift/getting-started. + markdown: + - Follow the instructions from [the official documentation](https://help.sonatype.com/lift/getting-started). \ No newline at end of file diff --git a/probes/toolSonatypeLiftInstalled/impl.go b/probes/toolSonatypeLiftInstalled/impl.go new file mode 100644 index 00000000000..a7c258fb9e8 --- /dev/null +++ b/probes/toolSonatypeLiftInstalled/impl.go @@ -0,0 +1,53 @@ +// Copyright 2022 OpenSSF Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// nolint:stylecheck +package toolSonatypeLiftInstalled + +import ( + "embed" + + "github.com/ossf/scorecard/v4/checker" + "github.com/ossf/scorecard/v4/finding" + "github.com/ossf/scorecard/v4/probes/utils" +) + +//go:embed *.yml +var fs embed.FS + +const probe = "toolSonatypeLiftInstalled" + +type sonatypeLyft struct{} + +func (t sonatypeLyft) Name() string { + return "Sonatype Lift" +} + +func (t sonatypeLyft) Matches(tool *checker.Tool) bool { + return t.Name() == tool.Name +} + +func Run(raw *checker.RawResults) ([]finding.Finding, string, error) { + tools := raw.DependencyUpdateToolResults.Tools + var matcher sonatypeLyft + // Check whether Sona Lyft tool is installed on the repo, + // and create the corresponding findings. + //nolint:wrapcheck + return utils.ToolsRun(tools, fs, probe, + // Tool found will generate a positive result. + finding.OutcomePositive, + // Tool not found will generate a negative result. + finding.OutcomeNegative, + matcher) +} diff --git a/probes/utils/tools.go b/probes/utils/tools.go new file mode 100644 index 00000000000..c802126fe16 --- /dev/null +++ b/probes/utils/tools.go @@ -0,0 +1,69 @@ +// Copyright 2023 OpenSSF Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "embed" + "fmt" + + "github.com/ossf/scorecard/v4/checker" + "github.com/ossf/scorecard/v4/finding" +) + +type toolMatcher interface { + Name() string + Matches(*checker.Tool) bool +} + +// ToolsRun runs the probe for a tool. +// The function iterates thru the raw results and searches for a tool of interest that is used on a repository. +// The function uses 'matcher' to identify the tool of interest. +// If a tool is used in the repository, it creates a finding with the 'foundOutcome'. +// If not, it returns a finding with outcome 'notFoundOutcome'. +func ToolsRun(tools []checker.Tool, fs embed.FS, probeID string, + foundOutcome, notFoundOutcome finding.Outcome, matcher toolMatcher, +) ([]finding.Finding, string, error) { + var findings []finding.Finding + for i := range tools { + tool := &tools[i] + if !matcher.Matches(tool) { + continue + } + + var loc *finding.Location + if len(tool.Files) > 0 { + loc = tool.Files[0].Location() + } + + f, err := finding.NewWith(fs, probeID, fmt.Sprintf("tool '%s' is used", tool.Name), + loc, foundOutcome) + if err != nil { + return nil, probeID, fmt.Errorf("create finding: %w", err) + } + findings = append(findings, *f) + } + + // No tools found. + if len(findings) == 0 { + f, err := finding.NewWith(fs, probeID, fmt.Sprintf("tool '%s' is not used", matcher.Name()), + nil, notFoundOutcome) + if err != nil { + return nil, probeID, fmt.Errorf("create finding: %w", err) + } + findings = append(findings, *f) + } + + return findings, probeID, nil +} diff --git a/probes/zrunner/runner.go b/probes/zrunner/runner.go new file mode 100644 index 00000000000..e8c837bbcd4 --- /dev/null +++ b/probes/zrunner/runner.go @@ -0,0 +1,51 @@ +// Copyright 2023 OpenSSF Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zrunner + +import ( + "errors" + "fmt" + + "github.com/ossf/scorecard/v4/checker" + serrors "github.com/ossf/scorecard/v4/errors" + "github.com/ossf/scorecard/v4/finding" + "github.com/ossf/scorecard/v4/probes" +) + +var errProbeRun = errors.New("probe run failure") + +// Run runs the probes in probesToRun. +func Run(raw *checker.RawResults, probesToRun []probes.ProbeImpl) ([]finding.Finding, error) { + var results []finding.Finding + var errs []error + for _, probeFunc := range probesToRun { + findings, probeID, err := probeFunc(raw) + if err != nil { + errs = append(errs, err) + results = append(results, + finding.Finding{ + Probe: probeID, + Outcome: finding.OutcomeError, + Message: serrors.WithMessage(serrors.ErrScorecardInternal, err.Error()).Error(), + }) + continue + } + results = append(results, findings...) + } + if len(errs) > 0 { + return results, fmt.Errorf("%w: %v", errProbeRun, errs) + } + return results, nil +} From 0888bad6499b8e9c56c13018a45c74ab19dba6e3 Mon Sep 17 00:00:00 2001 From: Amanda L Martin Date: Tue, 23 May 2023 11:19:27 -0400 Subject: [PATCH 08/10] add zoom link and agenda link (#3050) Signed-off-by: Amanda L Martin --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 28304015908..49c8665fe01 100644 --- a/README.md +++ b/README.md @@ -559,6 +559,17 @@ __Maintainers__ are listed in the [CODEOWNERS file](.github/CODEOWNERS). To report a security issue, please follow instructions [here](SECURITY.md). +### Join the Scorecards Project Meeting + +#### Zoom + +We meet every other Thursday - 4p ET on this [zoom link](https://zoom.us/j/98835923979?pwd=RG5JZ3czZEtmRDlGdms0ZktmMFQvUT09). + +#### Agenda + +You can see the [agenda and meeting notes here](https://docs.google.com/document/d/1dB2U7_qZpNW96vtuoG7ShmgKXzIg6R5XT5Tc-0yz6kE/edit#). + + ## Stargazers over time [![Stargazers over time](https://starchart.cc/ossf/scorecard.svg)](https://starchart.cc/ossf/scorecard) From c631ebd7fb4014e1dd921a839f6d3b0966324002 Mon Sep 17 00:00:00 2001 From: Naveen <172697+naveensrinivasan@users.noreply.github.com> Date: Tue, 23 May 2023 12:53:57 -0500 Subject: [PATCH 09/10] :seedling: Run E2E PAT test for push to main (#3046) - Add E2E PAT tests for push to main. Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com> --- .github/workflows/integration.yml | 3 --- .github/workflows/main.yml | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 3b993c37bc7..b51ac6d013a 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -16,9 +16,6 @@ name: Integration tests on: - push: - branches: - - main # The e2e coverage is required to be run on main branch to get the coverage report pull_request: branches: - main diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9f7d00f5b2b..285c8eb046b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -70,6 +70,20 @@ jobs: with: files: ./unit-coverage.out verbose: true + - name: Run PAT Token E2E #using retry because the GitHub token is being throttled. + uses: nick-invision/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd + env: + GITHUB_AUTH_TOKEN: ${{ secrets.GH_AUTH_TOKEN }} + with: + max_attempts: 3 + retry_on: error + timeout_minutes: 30 + command: make e2e-pat + - name: codecov + uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # 2.1.0 + with: + files: "*e2e-coverage.out" + verbose: true generate-mocks: name: generate-mocks runs-on: ubuntu-latest From e0a6d1544b95775d2b3b96f3b0b2119beadfbac2 Mon Sep 17 00:00:00 2001 From: Naveen <172697+naveensrinivasan@users.noreply.github.com> Date: Tue, 23 May 2023 13:36:26 -0500 Subject: [PATCH 10/10] Update main.yml (#3054) -Fixed the YAML indenting issue. Signed-off-by: Naveen <172697+naveensrinivasan@users.noreply.github.com> --- .github/workflows/main.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 285c8eb046b..60c65de0569 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -71,17 +71,17 @@ jobs: files: ./unit-coverage.out verbose: true - name: Run PAT Token E2E #using retry because the GitHub token is being throttled. - uses: nick-invision/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd - env: + uses: nick-invision/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd + env: GITHUB_AUTH_TOKEN: ${{ secrets.GH_AUTH_TOKEN }} - with: + with: max_attempts: 3 retry_on: error timeout_minutes: 30 command: make e2e-pat - name: codecov - uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # 2.1.0 - with: + uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # 2.1.0 + with: files: "*e2e-coverage.out" verbose: true generate-mocks: