Skip to content

Commit

Permalink
✨ Raw results for Packaging check (#1913)
Browse files Browse the repository at this point in the history
* update

* update

* update

* update

* update

* update

* update

* updates

* update

* update

* update

* update

* update

* update

* comments
  • Loading branch information
laurentsimon authored Jun 1, 2022
1 parent 1d9cd05 commit 608da94
Show file tree
Hide file tree
Showing 22 changed files with 678 additions and 232 deletions.
21 changes: 21 additions & 0 deletions checker/raw_result.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
// is applied.
//nolint
type RawResults struct {
PackagingResults PackagingData
CIIBestPracticesResults CIIBestPracticesData
DangerousWorkflowResults DangerousWorkflowData
VulnerabilitiesResults VulnerabilitiesData
Expand All @@ -43,6 +44,26 @@ type FuzzingData struct {
Fuzzers []Tool
}

// TODO: Add Msg to all results.

// PackagingData contains results for the Packaging check.
type PackagingData struct {
Packages []Package
}

// Package represents a package.
// nolint
type Package struct {
// TODO: not supported yet. This needs to be unique across
// ecosystems: purl, OSV, CPE, etc.
Name *string
Job *WorkflowJob
File *File
// Note: Msg is populated only for debug messages.
Msg *string
Runs []Run
}

// MaintainedData contains the raw results
// for the Maintained check.
type MaintainedData struct {
Expand Down
89 changes: 89 additions & 0 deletions checks/evaluation/packaging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2021 Security Scorecard Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package evaluation

import (
"fmt"

"github.com/ossf/scorecard/v4/checker"
sce "github.com/ossf/scorecard/v4/errors"
)

// Packaging applies the score policy for the Packaging check.
func Packaging(name string, dl checker.DetailLogger, r *checker.PackagingData) checker.CheckResult {
if r == nil {
e := sce.WithMessage(sce.ErrScorecardInternal, "empty raw data")
return checker.CreateRuntimeErrorResult(name, e)
}

pass := false
for _, p := range r.Packages {
if p.Msg != nil {
// This is a debug message. Let's just replay the message.
dl.Debug(&checker.LogMessage{
Text: *p.Msg,
})
continue
}

// Presence of a single non-debug message means the
// check passes.
pass = true

msg, err := createLogMessage(p)
if err != nil {
return checker.CreateRuntimeErrorResult(name, err)
}
dl.Info(&msg)
}

if pass {
return checker.CreateMaxScoreResult(name,
"publishing workflow detected")
}

dl.Warn(&checker.LogMessage{
Text: "no GitHub publishing workflow detected",
})

return checker.CreateInconclusiveResult(name,
"no published package detected")
}

func createLogMessage(p checker.Package) (checker.LogMessage, error) {
var msg checker.LogMessage

if p.Msg != nil {
return msg, sce.WithMessage(sce.ErrScorecardInternal, "Msg should be nil")
}

if p.File == nil {
return msg, sce.WithMessage(sce.ErrScorecardInternal, "File field is nil")
}

if p.File != nil {
msg.Path = p.File.Path
msg.Type = p.File.Type
msg.Offset = p.File.Offset
}

if len(p.Runs) == 0 {
return msg, sce.WithMessage(sce.ErrScorecardInternal, "no run data")
}

msg.Text = fmt.Sprintf("GitHub publishing workflow used in run %s", p.Runs[0].URL)

return msg, nil
}
38 changes: 38 additions & 0 deletions checks/fileparser/github_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,44 @@ type JobMatcherStep struct {
Run string
}

// JobMatchResult represents the result of a matche.
type JobMatchResult struct {
Msg string
File checker.File
}

// RawAnyJobsMatch returns true if any of the jobs have a match in the given workflow.
// TODO: Rename after migraiton is complete.
func RawAnyJobsMatch(workflow *actionlint.Workflow, jobMatchers []JobMatcher, fp string,
logMsgNoMatch string,
) (JobMatchResult, bool) {
for _, job := range workflow.Jobs {
for _, matcher := range jobMatchers {
if !matcher.matches(job) {
continue
}

return JobMatchResult{
File: checker.File{
Path: fp,
Type: checker.FileTypeSource,
Offset: GetLineNumber(job.Pos),
},
Msg: fmt.Sprintf("%v: %v", matcher.LogText, fp),
}, true
}
}

return JobMatchResult{
File: checker.File{
Path: fp,
Type: checker.FileTypeSource,
Offset: checker.OffsetDefault,
},
Msg: fmt.Sprintf("%v: %v", logMsgNoMatch, fp),
}, false
}

// AnyJobsMatch returns true if any of the jobs have a match in the given workflow.
func AnyJobsMatch(workflow *actionlint.Workflow, jobMatchers []JobMatcher, fp string, dl checker.DetailLogger,
logMsgNoMatch string,
Expand Down
183 changes: 8 additions & 175 deletions checks/packaging.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,9 @@
package checks

import (
"fmt"
"path/filepath"

"github.com/rhysd/actionlint"

"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/checks/fileparser"
"github.com/ossf/scorecard/v4/checks/evaluation"
"github.com/ossf/scorecard/v4/checks/raw"
sce "github.com/ossf/scorecard/v4/errors"
)

Expand All @@ -38,179 +34,16 @@ func init() {

// Packaging runs Packaging check.
func Packaging(c *checker.CheckRequest) checker.CheckResult {
matchedFiles, err := c.RepoClient.ListFiles(fileparser.IsGithubWorkflowFileCb)
rawData, err := raw.Packaging(c)
if err != nil {
e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("RepoClient.ListFiles: %v", err))
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
return checker.CreateRuntimeErrorResult(CheckPackaging, e)
}

for _, fp := range matchedFiles {
fc, err := c.RepoClient.GetFileContent(fp)
if err != nil {
e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("RepoClient.GetFileContent: %v", err))
return checker.CreateRuntimeErrorResult(CheckPackaging, e)
}

workflow, errs := actionlint.Parse(fc)
if len(errs) > 0 && workflow == nil {
e := fileparser.FormatActionlintError(errs)
return checker.CreateRuntimeErrorResult(CheckPackaging, e)
}
if !isPackagingWorkflow(workflow, fp, c.Dlogger) {
continue
}

runs, err := c.RepoClient.ListSuccessfulWorkflowRuns(filepath.Base(fp))
if err != nil {
e := sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("Client.Actions.ListWorkflowRunsByFileName: %v", err))
return checker.CreateRuntimeErrorResult(CheckPackaging, e)
}
if len(runs) > 0 {
c.Dlogger.Info(&checker.LogMessage{
Path: fp,
Type: checker.FileTypeSource,
Offset: checker.OffsetDefault,
Text: fmt.Sprintf("GitHub publishing workflow used in run %s", runs[0].URL),
})
return checker.CreateMaxScoreResult(CheckPackaging,
"publishing workflow detected")
}
c.Dlogger.Debug(&checker.LogMessage{
Path: fp,
Type: checker.FileTypeSource,
Offset: checker.OffsetDefault,
Text: "GitHub publishing workflow not used in runs",
})
}

c.Dlogger.Warn(&checker.LogMessage{
Text: "no GitHub publishing workflow detected",
})

return checker.CreateInconclusiveResult(CheckPackaging,
"no published package detected")
}

// A packaging workflow.
func isPackagingWorkflow(workflow *actionlint.Workflow, fp string, dl checker.DetailLogger) bool {
jobMatchers := []fileparser.JobMatcher{
{
Steps: []*fileparser.JobMatcherStep{
{
Uses: "actions/setup-node",
With: map[string]string{"registry-url": "https://registry.npmjs.org"},
},
{
Run: "npm.*publish",
},
},
LogText: "candidate node publishing workflow using npm",
},
{
// Java packages with maven.
Steps: []*fileparser.JobMatcherStep{
{
Uses: "actions/setup-java",
},
{
Run: "mvn.*deploy",
},
},
LogText: "candidate java publishing workflow using maven",
},
{
// Java packages with gradle.
Steps: []*fileparser.JobMatcherStep{
{
Uses: "actions/setup-java",
},
{
Run: "gradle.*publish",
},
},
LogText: "candidate java publishing workflow using gradle",
},
{
// Ruby packages.
Steps: []*fileparser.JobMatcherStep{
{
Run: "gem.*push",
},
},
LogText: "candidate ruby publishing workflow using gem",
},
{
// NuGet packages.
Steps: []*fileparser.JobMatcherStep{
{
Run: "nuget.*push",
},
},
LogText: "candidate nuget publishing workflow",
},
{
// Docker packages.
Steps: []*fileparser.JobMatcherStep{
{
Run: "docker.*push",
},
},
LogText: "candidate docker publishing workflow",
},
{
// Docker packages.
Steps: []*fileparser.JobMatcherStep{
{
Uses: "docker/build-push-action",
},
},
LogText: "candidate docker publishing workflow",
},
{
// Python packages.
Steps: []*fileparser.JobMatcherStep{
{
Uses: "actions/setup-python",
},
{
Uses: "pypa/gh-action-pypi-publish",
},
},
LogText: "candidate python publishing workflow using pypi",
},
{
// Python packages.
// This is a custom Python packaging workflow based on semantic versioning.
// TODO(#1642): accept custom workflows through a separate configuration.
Steps: []*fileparser.JobMatcherStep{
{
Uses: "relekang/python-semantic-release",
},
},
LogText: "candidate python publishing workflow using python-semantic-release",
},
{
// Go packages.
Steps: []*fileparser.JobMatcherStep{
{
Uses: "actions/setup-go",
},
{
Uses: "goreleaser/goreleaser-action",
},
},
LogText: "candidate golang publishing workflow",
},
{
// Rust packages. https://doc.rust-lang.org/cargo/reference/publishing.html
Steps: []*fileparser.JobMatcherStep{
{
Run: "cargo.*publish",
},
},
LogText: "candidate rust publishing workflow using cargo",
},
// Set the raw results.
if c.RawResults != nil {
c.RawResults.PackagingResults = rawData
}

return fileparser.AnyJobsMatch(workflow, jobMatchers, fp, dl, "not a publishing workflow")
return evaluation.Packaging(CheckPackaging, c.Dlogger, &rawData)
}
Loading

0 comments on commit 608da94

Please sign in to comment.