Skip to content

Commit

Permalink
convert Signed Releases to probes
Browse files Browse the repository at this point in the history
Signed-off-by: AdamKorcz <[email protected]>
  • Loading branch information
AdamKorcz committed Oct 25, 2023
1 parent de022da commit fe2432f
Show file tree
Hide file tree
Showing 10 changed files with 971 additions and 151 deletions.
161 changes: 67 additions & 94 deletions checks/evaluation/signed_releases.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,117 +17,90 @@ package evaluation
import (
"fmt"
"math"
"strings"

"github.com/ossf/scorecard/v4/checker"
sce "github.com/ossf/scorecard/v4/errors"
"github.com/ossf/scorecard/v4/finding"
"github.com/ossf/scorecard/v4/probes/releasesAreSigned"
"github.com/ossf/scorecard/v4/probes/releasesHaveProvenance"
)

var (
signatureExtensions = []string{".asc", ".minisig", ".sig", ".sign"}
provenanceExtensions = []string{".intoto.jsonl"}
)

const releaseLookBack = 5

// SignedReleases applies the score policy for the Signed-Releases check.
// nolint
func SignedReleases(name string, dl checker.DetailLogger, r *checker.SignedReleasesData) checker.CheckResult {
if r == nil {
e := sce.WithMessage(sce.ErrScorecardInternal, "empty raw data")
func SignedReleases(name string,
findings []finding.Finding, dl checker.DetailLogger,
) checker.CheckResult {
expectedProbes := []string{
releasesAreSigned.Probe,
releasesHaveProvenance.Probe,
}

if !finding.UniqueProbesEqual(findings, expectedProbes) {
e := sce.WithMessage(sce.ErrScorecardInternal, "invalid probe results")

Check warning on line 39 in checks/evaluation/signed_releases.go

View check run for this annotation

Codecov / codecov/patch

checks/evaluation/signed_releases.go#L39

Added line #L39 was not covered by tests
return checker.CreateRuntimeErrorResult(name, e)
}

totalReleases := 0
total := 0
score := 0
for _, release := range r.Releases {
if len(release.Assets) == 0 {
continue
// All probes have OutcomeNotApplicable in case the project has no
// releases. Therefore, check for any finding with OutcomeNotApplicable.
for i := range findings {
f := &findings[i]
if f.Outcome == finding.OutcomeNotApplicable {
dl.Warn(&checker.LogMessage{
Text: "no GitHub releases found",
})
// Generic summary.
return checker.CreateInconclusiveResult(name, "no releases found")

Check warning on line 52 in checks/evaluation/signed_releases.go

View check run for this annotation

Codecov / codecov/patch

checks/evaluation/signed_releases.go#L48-L52

Added lines #L48 - L52 were not covered by tests
}
}

dl.Debug(&checker.LogMessage{
Text: fmt.Sprintf("GitHub release found: %s", release.TagName),
})

totalReleases++
signed := false
hasProvenance := false

// Check for provenance.
for _, asset := range release.Assets {
for _, suffix := range provenanceExtensions {
if strings.HasSuffix(asset.Name, suffix) {
dl.Info(&checker.LogMessage{
Path: asset.URL,
Type: finding.FileTypeURL,
Text: fmt.Sprintf("provenance for release artifact: %s", asset.Name),
})
hasProvenance = true
total++
break
score := 0
totalPositive := 0
totalReleases := 0
checker.LogFindings(findings, dl)

for i := range findings {
f := &findings[i]
if f.Outcome == finding.OutcomePositive {
switch f.Probe {
case releasesAreSigned.Probe:
totalPositive++
if !releaseAlsoHasProvenance(f, findings) {
score += 8
}
}
if hasProvenance {
// Assign maximum points.
score += 10
break
}
}

if hasProvenance {
continue
}

dl.Warn(&checker.LogMessage{
Path: release.URL,
Type: finding.FileTypeURL,
Text: fmt.Sprintf("release artifact %s does not have provenance", release.TagName),
})
totalReleases = f.Values["totalReleases"]
case releasesHaveProvenance.Probe:
totalPositive++
score += 10
totalReleases = f.Values["totalReleases"]
}
}
}

if totalPositive == 0 {
return checker.CreateMinScoreResult(name, "Project has not signed or included provenance with any releases.")
}

Check warning on line 81 in checks/evaluation/signed_releases.go

View check run for this annotation

Codecov / codecov/patch

checks/evaluation/signed_releases.go#L80-L81

Added lines #L80 - L81 were not covered by tests

if totalReleases == 0 {
// This should not happen in production, but it is useful to have
// for testing.
return checker.CreateInconclusiveResult(name, "no releases found")
}

Check warning on line 87 in checks/evaluation/signed_releases.go

View check run for this annotation

Codecov / codecov/patch

checks/evaluation/signed_releases.go#L84-L87

Added lines #L84 - L87 were not covered by tests
//fmt.Println("totalReleases: ", totalReleases)
score = int(math.Floor(float64(score) / float64(totalReleases)))
reason := fmt.Sprintf("%d out of %d artifacts are signed or have provenance", totalPositive, totalReleases)
return checker.CreateResultWithScore(name, reason, score)
}

// No provenance. Try signatures.
for _, asset := range release.Assets {
for _, suffix := range signatureExtensions {
if strings.HasSuffix(asset.Name, suffix) {
dl.Info(&checker.LogMessage{
Path: asset.URL,
Type: finding.FileTypeURL,
Text: fmt.Sprintf("signed release artifact: %s", asset.Name),
})
signed = true
total++
break
func releaseAlsoHasProvenance(f1 *finding.Finding, findings []finding.Finding) bool {
for i := range findings {
f2 := &findings[i]
if f2.Probe == releasesHaveProvenance.Probe && f2.Outcome == finding.OutcomePositive {
if f1.Values["releaseIndex"] == f2.Values["releaseIndex"] {
if f1.Values["assetIndex"] == f2.Values["assetIndex"] {
return true
}
}
if signed {
// Assign 8 points.
score += 8
break
}
}

if !signed {
dl.Warn(&checker.LogMessage{
Path: release.URL,
Type: finding.FileTypeURL,
Text: fmt.Sprintf("release artifact %s not signed", release.TagName),
})
}
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")
}

score = int(math.Floor(float64(score) / float64(totalReleases)))
reason := fmt.Sprintf("%d out of %d artifacts are signed or have provenance", total, totalReleases)
return checker.CreateResultWithScore(name, reason, score)
return false
}
Loading

0 comments on commit fe2432f

Please sign in to comment.