Raw results for Signed-Release check (#1789)

* Raw results for Signed-Releases

* updates

* linter
This commit is contained in:
laurentsimon 2022-04-01 16:13:58 -07:00 committed by GitHub
parent e8c633a41b
commit 27dbf9c7e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 223 additions and 72 deletions

View File

@ -18,6 +18,7 @@ import "time"
// RawResults contains results before a policy
// is applied.
//nolint
type RawResults struct {
VulnerabilitiesResults VulnerabilitiesData
BinaryArtifactResults BinaryArtifactData
@ -27,6 +28,7 @@ type RawResults struct {
CodeReviewResults CodeReviewData
WebhookResults WebhooksData
MaintainedResults MaintainedData
SignedReleasesResults SignedReleasesData
}
// MaintainedData contains the raw results
@ -63,6 +65,12 @@ type BinaryArtifactData struct {
Files []File
}
// SignedReleasesData contains the raw results
// for the Signed-Releases check.
type SignedReleasesData struct {
Releases []Release
}
// DependencyUpdateToolData contains the raw results
// for the Dependency-Update-Tool check.
type DependencyUpdateToolData struct {
@ -227,3 +235,17 @@ type Vulnerability struct {
ID string
// TODO(vuln): Add additional fields, if needed.
}
// Release represents a project release.
type Release struct {
Tag string
URL string
Assets []ReleaseAsset
// TODO: add needed fields, e.g. Path.
}
// ReleaseAsset represents a release asset.
type ReleaseAsset struct {
Name string
URL string
}

View File

@ -0,0 +1,91 @@
// Copyright 2021 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package evaluation
import (
"fmt"
"strings"
"github.com/ossf/scorecard/v4/checker"
sce "github.com/ossf/scorecard/v4/errors"
)
var artifactExtensions = []string{".asc", ".minisig", ".sig", ".sign"}
const releaseLookBack = 5
// SignedReleases applies the score policy for the Signed-Releases check.
func SignedReleases(name string, dl checker.DetailLogger, r *checker.SignedReleasesData) checker.CheckResult {
if r == nil {
e := sce.WithMessage(sce.ErrScorecardInternal, "empty raw data")
return checker.CreateRuntimeErrorResult(name, e)
}
totalReleases := 0
totalSigned := 0
for _, release := range r.Releases {
if len(release.Assets) == 0 {
continue
}
dl.Debug(&checker.LogMessage{
Text: fmt.Sprintf("GitHub release found: %s", release.Tag),
})
totalReleases++
signed := false
for _, asset := range release.Assets {
for _, suffix := range artifactExtensions {
if strings.HasSuffix(asset.Name, suffix) {
dl.Info(&checker.LogMessage{
Path: asset.URL,
Type: checker.FileTypeURL,
Text: fmt.Sprintf("signed release artifact: %s", asset.Name),
})
signed = true
break
}
}
if signed {
totalSigned++
break
}
}
if !signed {
dl.Warn(&checker.LogMessage{
Path: release.URL,
Type: checker.FileTypeURL,
Text: fmt.Sprintf("release artifact %s not signed", release.Tag),
})
}
if totalReleases >= releaseLookBack {
break
}
}
if totalReleases == 0 {
dl.Warn(&checker.LogMessage{
Text: "no GitHub releases found",
})
// Generic summary.
return checker.CreateInconclusiveResult(name, "no releases found")
}
reason := fmt.Sprintf("%d out of %d artifacts are signed", totalSigned, totalReleases)
return checker.CreateProportionalScoreResult(name, reason, totalSigned, totalReleases)
}

View File

@ -0,0 +1,49 @@
// Copyright 2020 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package raw
import (
"fmt"
"github.com/ossf/scorecard/v4/checker"
)
// SignedReleases checks for presence of signed release check.
func SignedReleases(c *checker.CheckRequest) (checker.SignedReleasesData, error) {
releases, err := c.RepoClient.ListReleases()
if err != nil {
return checker.SignedReleasesData{}, fmt.Errorf("%w", err)
}
var results checker.SignedReleasesData
for i, r := range releases {
results.Releases = append(results.Releases,
checker.Release{
Tag: r.TagName,
URL: r.URL,
})
for _, asset := range r.Assets {
a := checker.ReleaseAsset{
URL: asset.URL,
Name: asset.Name,
}
results.Releases[i].Assets = append(results.Releases[i].Assets, a)
}
}
// Return raw results.
return results, nil
}

View File

@ -15,28 +15,29 @@
package raw
import (
"errors"
"fmt"
"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/clients"
sce "github.com/ossf/scorecard/v4/errors"
)
var errNoCommitFound = errors.New("no commit found")
// Vulnerabilities retrieves the raw data for the Vulnerabilities check.
func Vulnerabilities(c *checker.CheckRequest) (checker.VulnerabilitiesData, error) {
commits, err := c.RepoClient.ListCommits()
if err != nil {
return checker.VulnerabilitiesData{},
sce.WithMessage(sce.ErrScorecardInternal, "Client.Repositories.ListCommits")
return checker.VulnerabilitiesData{}, fmt.Errorf("repoClient.ListCommits: %w", err)
}
if len(commits) < 1 || commits[0].SHA == "" {
return checker.VulnerabilitiesData{},
sce.WithMessage(sce.ErrScorecardInternal, "no commits found")
return checker.VulnerabilitiesData{}, fmt.Errorf("%w", errNoCommitFound)
}
resp, err := c.VulnerabilitiesClient.HasUnfixedVulnerabilities(c.Ctx, commits[0].SHA)
if err != nil {
return checker.VulnerabilitiesData{},
sce.WithMessage(sce.ErrScorecardInternal, "VulnerabilitiesClient.HasUnfixedVulnerabilities")
return checker.VulnerabilitiesData{}, fmt.Errorf("vulnerabilitiesClient.HasUnfixedVulnerabilities: %w", err)
}
vulnIDs := getVulnerabilities(&resp)

View File

@ -15,20 +15,14 @@
package checks
import (
"fmt"
"strings"
"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/checks/evaluation"
"github.com/ossf/scorecard/v4/checks/raw"
sce "github.com/ossf/scorecard/v4/errors"
)
const (
// CheckSignedReleases is the registered name for SignedReleases.
CheckSignedReleases = "Signed-Releases"
releaseLookBack = 5
)
var artifactExtensions = []string{".asc", ".minisig", ".sig", ".sign"}
// CheckSignedReleases is the registered name for SignedReleases.
const CheckSignedReleases = "Signed-Releases"
//nolint:gochecknoinits
func init() {
@ -40,60 +34,17 @@ func init() {
// SignedReleases runs Signed-Releases check.
func SignedReleases(c *checker.CheckRequest) checker.CheckResult {
releases, err := c.RepoClient.ListReleases()
rawData, err := raw.SignedReleases(c)
if err != nil {
e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("Client.Repositories.ListReleases: %v", err))
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
return checker.CreateRuntimeErrorResult(CheckSignedReleases, e)
}
totalReleases := 0
totalSigned := 0
for _, r := range releases {
if len(r.Assets) == 0 {
continue
}
c.Dlogger.Debug(&checker.LogMessage{
Text: fmt.Sprintf("GitHub release found: %s", r.TagName),
})
totalReleases++
signed := false
for _, asset := range r.Assets {
for _, suffix := range artifactExtensions {
if strings.HasSuffix(asset.Name, suffix) {
c.Dlogger.Info(&checker.LogMessage{
Path: asset.URL,
Type: checker.FileTypeURL,
Text: fmt.Sprintf("signed release artifact: %s", asset.Name),
})
signed = true
break
}
}
if signed {
totalSigned++
break
}
}
if !signed {
c.Dlogger.Warn(&checker.LogMessage{
Path: r.URL,
Type: checker.FileTypeURL,
Text: fmt.Sprintf("release artifact %s not signed", r.TagName),
})
}
if totalReleases >= releaseLookBack {
break
}
// Return raw results.
if c.RawResults != nil {
c.RawResults.SignedReleasesResults = rawData
}
if totalReleases == 0 {
c.Dlogger.Warn(&checker.LogMessage{
Text: "no GitHub releases found",
})
// Generic summary.
return checker.CreateInconclusiveResult(CheckSignedReleases, "no releases found")
}
reason := fmt.Sprintf("%d out of %d artifacts are signed", totalSigned, totalReleases)
return checker.CreateProportionalScoreResult(CheckSignedReleases, reason, totalSigned, totalReleases)
// Return the score evaluation.
return evaluation.SignedReleases(CheckSignedReleases, c.Dlogger, &rawData)
}

View File

@ -119,6 +119,19 @@ type jsonIssue struct {
// TODO: add fields, e.g., state=[opened|closed]
}
type jsonRelease struct {
Tag string `json:"tag"`
URL string `json:"url"`
Assets []jsonReleaseAsset `json:"assets"`
// TODO: add needed fields, e.g. Path.
}
type jsonReleaseAsset struct {
Path string `json:"path"`
URL string `json:"url"`
}
//nolint
type jsonRawResults struct {
// List of recent issues.
RecentIssues []jsonIssue `json:"issues"`
@ -138,14 +151,33 @@ type jsonRawResults struct {
DefaultBranchCommits []jsonDefaultBranchCommit `json:"default-branch-commits"`
// Archived status of the repo.
ArchivedStatus jsonArchivedStatus `json:"archived"`
// Releases.
Releases []jsonRelease `json:"releases"`
}
//nolint:unparam
func (r *jsonScorecardRawResult) addSignedReleasesRawResults(sr *checker.SignedReleasesData) error {
r.Results.Releases = []jsonRelease{}
for i, release := range sr.Releases {
r.Results.Releases = append(r.Results.Releases,
jsonRelease{
Tag: release.Tag,
URL: release.URL,
})
for _, asset := range release.Assets {
r.Results.Releases[i].Assets = append(r.Results.Releases[i].Assets,
jsonReleaseAsset{
Path: asset.Name,
URL: asset.URL,
},
)
}
}
return nil
}
func getRepoAssociation(author *checker.User) *string {
if author == nil {
return nil
}
if author.RepoAssociation == nil {
if author == nil || author.RepoAssociation == nil {
return nil
}
@ -367,6 +399,11 @@ func (r *jsonScorecardRawResult) fillJSONRawResults(raw *checker.RawResults) err
return sce.WithMessage(sce.ErrScorecardInternal, err.Error())
}
// Signed-Releases.
if err := r.addSignedReleasesRawResults(&raw.SignedReleasesResults); err != nil {
return sce.WithMessage(sce.ErrScorecardInternal, err.Error())
}
return nil
}