From ac88460c75c233cfd0df1f63dea8b0d5f15a6d77 Mon Sep 17 00:00:00 2001 From: laurentsimon <64505099+laurentsimon@users.noreply.github.com> Date: Mon, 25 Apr 2022 10:04:21 -0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Raw=20results=20for=20best=20practi?= =?UTF-8?q?ces=20badge=20(#1795)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Raw results for best practices badge * updates * updates * tests * comment --- checker/raw_result.go | 25 ++++++++++ checks/cii_best_practices.go | 49 +++++++------------- checks/cii_best_practices_test.go | 6 +-- checks/evaluation/cii_best_practices.go | 61 +++++++++++++++++++++++++ checks/raw/cii_best_practices.go | 55 ++++++++++++++++++++++ pkg/json_raw_results.go | 21 ++++++++- 6 files changed, 179 insertions(+), 38 deletions(-) create mode 100644 checks/evaluation/cii_best_practices.go create mode 100644 checks/raw/cii_best_practices.go diff --git a/checker/raw_result.go b/checker/raw_result.go index 2c3cbf22..ee16274c 100644 --- a/checker/raw_result.go +++ b/checker/raw_result.go @@ -20,6 +20,7 @@ import "time" // is applied. //nolint type RawResults struct { + CIIBestPracticesResults CIIBestPracticesData DangerousWorkflowResults DangerousWorkflowData VulnerabilitiesResults VulnerabilitiesData BinaryArtifactResults BinaryArtifactData @@ -258,6 +259,30 @@ type ReleaseAsset struct { URL string } +// CIIBadge corresponds to CII-Best-Practices badges. +// https://bestpractices.coreinfrastructure.org/en +type CIIBadge string + +const ( + // CIIBadgeUnknown or non-parsable CII Best Practices badge. + CIIBadgeUnknown CIIBadge = "unknown" + // CIIBadgeNotFound represents when CII Best Practices returns an empty response for a project. + CIIBadgeNotFound CIIBadge = "not_found" + // CIIBadgeInProgress state of CII Best Practices badge. + CIIBadgeInProgress CIIBadge = "in_progress" + // CIIBadgePassing for CII Best Practices badge. + CIIBadgePassing CIIBadge = "passing" + // CIIBadgeSilver for CII Best Practices badge. + CIIBadgeSilver CIIBadge = "silver" + // CIIBadgeGold for CII Best Practices badge. + CIIBadgeGold CIIBadge = "gold" +) + +// CIIBestPracticesData contains data foor CIIBestPractices check. +type CIIBestPracticesData struct { + Badge CIIBadge +} + // DangerousWorkflowData contains raw results // for dangerous workflow check. type DangerousWorkflowData struct { diff --git a/checks/cii_best_practices.go b/checks/cii_best_practices.go index ecc8d5be..b6c13ea1 100644 --- a/checks/cii_best_practices.go +++ b/checks/cii_best_practices.go @@ -15,20 +15,15 @@ package checks import ( - "fmt" - "github.com/ossf/scorecard/v4/checker" - "github.com/ossf/scorecard/v4/clients" + "github.com/ossf/scorecard/v4/checks/evaluation" + "github.com/ossf/scorecard/v4/checks/raw" sce "github.com/ossf/scorecard/v4/errors" ) -const ( - // CheckCIIBestPractices is the registered name for CIIBestPractices. - CheckCIIBestPractices = "CII-Best-Practices" - silverScore = 7 - passingScore = 5 - inProgressScore = 2 -) +// CheckCIIBestPractices is the registered name for CIIBestPractices. +const CheckCIIBestPractices = "CII-Best-Practices" + //nolint:gochecknoinits func init() { @@ -38,31 +33,19 @@ func init() { } } -// CIIBestPractices runs CII-Best-Practices check. +// CIIBestPractices will check if the maintainers have a best practice badge. func CIIBestPractices(c *checker.CheckRequest) checker.CheckResult { - if c.CIIClient == nil { - return checker.CreateInconclusiveResult(CheckCIIBestPractices, "CII client is nil") + rawData, err := raw.CIIBestPractices(c) + if err != nil { + e := sce.WithMessage(sce.ErrScorecardInternal, err.Error()) + return checker.CreateRuntimeErrorResult(CheckCIIBestPractices, e) } - // TODO: not supported for local clients. - badgeLevel, err := c.CIIClient.GetBadgeLevel(c.Ctx, c.Repo.URI()) - if err == nil { - switch badgeLevel { - case clients.NotFound: - return checker.CreateMinScoreResult(CheckCIIBestPractices, "no badge detected") - case clients.InProgress: - return checker.CreateResultWithScore(CheckCIIBestPractices, "badge detected: in_progress", inProgressScore) - case clients.Passing: - return checker.CreateResultWithScore(CheckCIIBestPractices, "badge detected: passing", passingScore) - case clients.Silver: - return checker.CreateResultWithScore(CheckCIIBestPractices, "badge detected: silver", silverScore) - case clients.Gold: - return checker.CreateMaxScoreResult(CheckCIIBestPractices, "badge detected: gold") - case clients.Unknown: - e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("unsupported badge: %v", badgeLevel)) - return checker.CreateRuntimeErrorResult(CheckCIIBestPractices, e) - } + // Return raw results. + if c.RawResults != nil { + c.RawResults.CIIBestPracticesResults = rawData } - e := sce.WithMessage(sce.ErrScorecardInternal, err.Error()) - return checker.CreateRuntimeErrorResult(CheckCIIBestPractices, e) + + // Return the score evaluation. + return evaluation.CIIBestPractices(CheckCIIBestPractices, c.Dlogger, &rawData) } diff --git a/checks/cii_best_practices_test.go b/checks/cii_best_practices_test.go index faf08d34..81fe156a 100644 --- a/checks/cii_best_practices_test.go +++ b/checks/cii_best_practices_test.go @@ -66,21 +66,21 @@ func TestCIIBestPractices(t *testing.T) { name: "InProgressBadge", badgeLevel: clients.InProgress, expected: scut.TestReturn{ - Score: inProgressScore, + Score: 2, }, }, { name: "PassingBadge", badgeLevel: clients.Passing, expected: scut.TestReturn{ - Score: passingScore, + Score: 5, }, }, { name: "SilverBadge", badgeLevel: clients.Silver, expected: scut.TestReturn{ - Score: silverScore, + Score: 7, }, }, { diff --git a/checks/evaluation/cii_best_practices.go b/checks/evaluation/cii_best_practices.go new file mode 100644 index 00000000..ee711c1a --- /dev/null +++ b/checks/evaluation/cii_best_practices.go @@ -0,0 +1,61 @@ +// Copyright Security Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package evaluation + +import ( + "fmt" + + "github.com/ossf/scorecard/v4/checker" + sce "github.com/ossf/scorecard/v4/errors" +) + +// Note: exported for unit tests. +const ( + silverScore = 7 + // Note: if this value is changed, please update the action's threshold score + // https://github.com/ossf/scorecard-action/blob/main/policies/template.yml#L61. + passingScore = 5 + inProgressScore = 2 +) + +// CIIBestPractices applies the score policy for the CIIBestPractices check. +func CIIBestPractices(name string, dl checker.DetailLogger, r *checker.CIIBestPracticesData) checker.CheckResult { + if r == nil { + e := sce.WithMessage(sce.ErrScorecardInternal, "empty raw data") + return checker.CreateRuntimeErrorResult(name, e) + } + + var results checker.CheckResult + switch r.Badge { + case checker.CIIBadgeNotFound: + results = checker.CreateMinScoreResult(name, "no badge detected") + case checker.CIIBadgeInProgress: + msg := fmt.Sprintf("badge detected: %v", r.Badge) + results = checker.CreateResultWithScore(name, msg, inProgressScore) + case checker.CIIBadgePassing: + msg := fmt.Sprintf("badge detected: %v", r.Badge) + results = checker.CreateResultWithScore(name, msg, passingScore) + case checker.CIIBadgeSilver: + msg := fmt.Sprintf("badge detected: %v", r.Badge) + results = checker.CreateResultWithScore(name, msg, silverScore) + case checker.CIIBadgeGold: + msg := fmt.Sprintf("badge detected: %v", r.Badge) + results = checker.CreateMaxScoreResult(name, msg) + case checker.CIIBadgeUnknown: + e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("unsupported badge: %v", r.Badge)) + results = checker.CreateRuntimeErrorResult(name, e) + } + return results +} diff --git a/checks/raw/cii_best_practices.go b/checks/raw/cii_best_practices.go new file mode 100644 index 00000000..fb2aee36 --- /dev/null +++ b/checks/raw/cii_best_practices.go @@ -0,0 +1,55 @@ +// Copyright 2022 Security Scorecard Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package raw + +import ( + "errors" + "fmt" + + "github.com/ossf/scorecard/v4/checker" + "github.com/ossf/scorecard/v4/clients" +) + +var errEmptyClient = errors.New("CII client is nil") + +// CIIBestPractices retrieves the raw data for the CIIBestPractices check. +func CIIBestPractices(c *checker.CheckRequest) (checker.CIIBestPracticesData, error) { + var results checker.CIIBestPracticesData + if c.CIIClient == nil { + return results, fmt.Errorf("%w", errEmptyClient) + } + + badge, err := c.CIIClient.GetBadgeLevel(c.Ctx, c.Repo.URI()) + if err != nil { + return results, fmt.Errorf("%w", err) + } + + switch badge { + case clients.NotFound: + results.Badge = checker.CIIBadgeNotFound + case clients.InProgress: + results.Badge = checker.CIIBadgeInProgress + case clients.Passing: + results.Badge = checker.CIIBadgePassing + case clients.Silver: + results.Badge = checker.CIIBadgeSilver + case clients.Gold: + results.Badge = checker.CIIBadgeGold + case clients.Unknown: + results.Badge = checker.CIIBadgeUnknown + } + + return results, nil +} diff --git a/pkg/json_raw_results.go b/pkg/json_raw_results.go index c4c2f2d3..525258ac 100644 --- a/pkg/json_raw_results.go +++ b/pkg/json_raw_results.go @@ -132,6 +132,10 @@ type jsonReleaseAsset struct { URL string `json:"url"` } +type jsonOssfBestPractices struct { + Badge string `json:"badge"` +} + //nolint type jsonLicense struct { File jsonFile `json:"file"` @@ -172,7 +176,9 @@ type jsonRawResults struct { Licenses []jsonLicense `json:"licenses"` // List of recent issues. RecentIssues []jsonIssue `json:"issues"` - // List of vulnerabilities. + // OSSF best practices badge. + OssfBestPractices jsonOssfBestPractices `json:"openssf-best-practices-badge"` + // Vulnerabilities. DatabaseVulnerabilities []jsonDatabaseVulnerability `json:"database-vulnerabilities"` // List of binaries found in the repo. Binaries []jsonFile `json:"binaries"` @@ -377,6 +383,12 @@ func (r *jsonScorecardRawResult) setDefaultCommitData(commits []checker.DefaultB return nil } +//nolint:unparam +func (r *jsonScorecardRawResult) addOssfBestPracticesRawResults(cbp *checker.CIIBestPracticesData) error { + r.Results.OssfBestPractices.Badge = string(cbp.Badge) + return nil +} + func (r *jsonScorecardRawResult) addCodeReviewRawResults(cr *checker.CodeReviewData) error { return r.setDefaultCommitData(cr.DefaultBranchCommits) } @@ -526,7 +538,12 @@ func (r *jsonScorecardRawResult) fillJSONRawResults(raw *checker.RawResults) err return sce.WithMessage(sce.ErrScorecardInternal, err.Error()) } - // Dangerous workflow. + // CII-Best-Practices. + if err := r.addOssfBestPracticesRawResults(&raw.CIIBestPracticesResults); err != nil { + return sce.WithMessage(sce.ErrScorecardInternal, err.Error()) + } + + // Dangerous workflow. if err := r.addDangerousWorkflowRawResults(&raw.DangerousWorkflowResults); err != nil { return sce.WithMessage(sce.ErrScorecardInternal, err.Error()) }