mirror of
https://github.com/ossf/scorecard.git
synced 2024-10-05 13:17:08 +03:00
✨ Add experimental check for published SBOM (#3903)
* Sbom check MVP Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com> * PR suggestion fixes Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com> * fix line length Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com> * update gitlab client to check 20 latest pipelines in default branch Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com> * correct issues Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com> * add unit tests for sbom client code Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com> * probe name alignment, updated evaluation tests Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com> * consolidate probes, reuse available data sources Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com> * add autogen doc update Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com> * address PR comments, remove CI/CD check code Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com> * update unit tests Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com> * fix linting errors Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com> * revert unnecessary changes, correct check documentation Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com> * address PR comments Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com> * move release lookback to data collection side Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com> --------- Signed-off-by: Allen Shearin <allen.p.shearin@gmail.com>
This commit is contained in:
parent
956d7c3895
commit
8de90207bc
@ -37,6 +37,7 @@ type RawResults struct {
|
||||
DependencyUpdateToolResults DependencyUpdateToolData
|
||||
FuzzingResults FuzzingData
|
||||
LicenseResults LicenseData
|
||||
SBOMResults SBOMData
|
||||
MaintainedResults MaintainedData
|
||||
Metadata MetadataData
|
||||
PackagingResults PackagingData
|
||||
@ -168,6 +169,18 @@ type LicenseData struct {
|
||||
LicenseFiles []LicenseFile
|
||||
}
|
||||
|
||||
// SBOM details.
|
||||
type SBOM struct {
|
||||
Name string // SBOM Filename
|
||||
File File // SBOM File Object
|
||||
}
|
||||
|
||||
// SBOMData contains the raw results for the SBOM check.
|
||||
// Some repos may have more than one SBOM.
|
||||
type SBOMData struct {
|
||||
SBOMFiles []SBOM
|
||||
}
|
||||
|
||||
// CodeReviewData contains the raw results
|
||||
// for the Code-Review check.
|
||||
type CodeReviewData struct {
|
||||
|
@ -38,6 +38,7 @@ func getAll(overrideExperimental bool) checker.CheckNameToFnMap {
|
||||
if _, experimental := os.LookupEnv("SCORECARD_EXPERIMENTAL"); !experimental {
|
||||
// TODO: remove this check when v6 is released
|
||||
delete(possibleChecks, CheckWebHooks)
|
||||
delete(possibleChecks, CheckSBOM)
|
||||
}
|
||||
|
||||
return possibleChecks
|
||||
|
75
checks/evaluation/sbom.go
Normal file
75
checks/evaluation/sbom.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2024 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 evaluation
|
||||
|
||||
import (
|
||||
"github.com/ossf/scorecard/v5/checker"
|
||||
sce "github.com/ossf/scorecard/v5/errors"
|
||||
"github.com/ossf/scorecard/v5/finding"
|
||||
"github.com/ossf/scorecard/v5/probes/hasReleaseSBOM"
|
||||
"github.com/ossf/scorecard/v5/probes/hasSBOM"
|
||||
)
|
||||
|
||||
// SBOM applies the score policy for the SBOM check.
|
||||
func SBOM(name string,
|
||||
findings []finding.Finding,
|
||||
dl checker.DetailLogger,
|
||||
) checker.CheckResult {
|
||||
// We have 4 unique probes, each should have a finding.
|
||||
expectedProbes := []string{
|
||||
hasSBOM.Probe,
|
||||
hasReleaseSBOM.Probe,
|
||||
}
|
||||
|
||||
if !finding.UniqueProbesEqual(findings, expectedProbes) {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, "invalid probe results")
|
||||
return checker.CreateRuntimeErrorResult(name, e)
|
||||
}
|
||||
|
||||
// Compute the score.
|
||||
score := 0
|
||||
m := make(map[string]bool)
|
||||
var logLevel checker.DetailType
|
||||
for i := range findings {
|
||||
f := &findings[i]
|
||||
switch f.Outcome {
|
||||
case finding.OutcomeTrue:
|
||||
logLevel = checker.DetailInfo
|
||||
switch f.Probe {
|
||||
case hasSBOM.Probe:
|
||||
score += scoreProbeOnce(f.Probe, m, 5)
|
||||
case hasReleaseSBOM.Probe:
|
||||
score += scoreProbeOnce(f.Probe, m, 5)
|
||||
}
|
||||
case finding.OutcomeFalse:
|
||||
logLevel = checker.DetailWarn
|
||||
default:
|
||||
continue // for linting
|
||||
}
|
||||
checker.LogFinding(dl, f, logLevel)
|
||||
}
|
||||
|
||||
_, defined := m[hasSBOM.Probe]
|
||||
if !defined {
|
||||
return checker.CreateMinScoreResult(name, "SBOM file not detected")
|
||||
}
|
||||
|
||||
_, defined = m[hasReleaseSBOM.Probe]
|
||||
if defined {
|
||||
return checker.CreateMaxScoreResult(name, "SBOM file found in release artifacts")
|
||||
}
|
||||
|
||||
return checker.CreateResultWithScore(name, "SBOM file found in project", score)
|
||||
}
|
95
checks/evaluation/sbom_test.go
Normal file
95
checks/evaluation/sbom_test.go
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright 2024 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 evaluation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ossf/scorecard/v5/checker"
|
||||
"github.com/ossf/scorecard/v5/finding"
|
||||
scut "github.com/ossf/scorecard/v5/utests"
|
||||
)
|
||||
|
||||
func TestSBOM(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
findings []finding.Finding
|
||||
result scut.TestReturn
|
||||
}{
|
||||
{
|
||||
name: "No SBOM. Min Score",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "hasSBOM",
|
||||
Outcome: finding.OutcomeFalse,
|
||||
},
|
||||
{
|
||||
Probe: "hasReleaseSBOM",
|
||||
Outcome: finding.OutcomeFalse,
|
||||
},
|
||||
},
|
||||
result: scut.TestReturn{
|
||||
Score: checker.MinResultScore,
|
||||
NumberOfInfo: 0,
|
||||
NumberOfWarn: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Only Source SBOM. Half Points",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "hasSBOM",
|
||||
Outcome: finding.OutcomeTrue,
|
||||
},
|
||||
{
|
||||
Probe: "hasReleaseSBOM",
|
||||
Outcome: finding.OutcomeFalse,
|
||||
},
|
||||
},
|
||||
result: scut.TestReturn{
|
||||
Score: 5,
|
||||
NumberOfInfo: 1,
|
||||
NumberOfWarn: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SBOM in Release Assets. Max score",
|
||||
findings: []finding.Finding{
|
||||
{
|
||||
Probe: "hasSBOM",
|
||||
Outcome: finding.OutcomeTrue,
|
||||
},
|
||||
{
|
||||
Probe: "hasReleaseSBOM",
|
||||
Outcome: finding.OutcomeTrue,
|
||||
},
|
||||
},
|
||||
result: scut.TestReturn{
|
||||
Score: checker.MaxResultScore,
|
||||
NumberOfInfo: 2,
|
||||
NumberOfWarn: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
dl := scut.TestDetailLogger{}
|
||||
got := SBOM(tt.name, tt.findings, &dl)
|
||||
scut.ValidateTestReturn(t, tt.name, &tt.result, &got, &dl)
|
||||
})
|
||||
}
|
||||
}
|
106
checks/raw/sbom.go
Normal file
106
checks/raw/sbom.go
Normal file
@ -0,0 +1,106 @@
|
||||
// Copyright 2024 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 raw
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/ossf/scorecard/v5/checker"
|
||||
"github.com/ossf/scorecard/v5/clients"
|
||||
"github.com/ossf/scorecard/v5/finding"
|
||||
)
|
||||
|
||||
var (
|
||||
reRootFile = regexp.MustCompile(`^[^.]([^//]*)$`)
|
||||
reSBOMFile = regexp.MustCompile(
|
||||
`(?i).+\.(cdx.json|cdx.xml|spdx|spdx.json|spdx.xml|spdx.y[a?]ml|spdx.rdf|spdx.rdf.xml)`,
|
||||
)
|
||||
)
|
||||
|
||||
const releaseLookBack = 5
|
||||
|
||||
// SBOM retrieves the raw data for the SBOM check.
|
||||
func SBOM(c *checker.CheckRequest) (checker.SBOMData, error) {
|
||||
var results checker.SBOMData
|
||||
|
||||
releases, lerr := c.RepoClient.ListReleases()
|
||||
if lerr != nil {
|
||||
return results, fmt.Errorf("RepoClient.ListReleases: %w", lerr)
|
||||
}
|
||||
|
||||
results.SBOMFiles = append(results.SBOMFiles, checkSBOMReleases(releases)...)
|
||||
|
||||
// Look for SBOMs in source
|
||||
repoFiles, err := c.RepoClient.ListFiles(func(file string) (bool, error) {
|
||||
return reSBOMFile.MatchString(file) && reRootFile.MatchString(file), nil
|
||||
})
|
||||
if err != nil {
|
||||
return results, fmt.Errorf("error during ListFiles: %w", err)
|
||||
}
|
||||
|
||||
results.SBOMFiles = append(results.SBOMFiles, checkSBOMSource(repoFiles)...)
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func checkSBOMReleases(releases []clients.Release) []checker.SBOM {
|
||||
var foundSBOMs []checker.SBOM
|
||||
|
||||
for i := range releases {
|
||||
if i >= releaseLookBack {
|
||||
break
|
||||
}
|
||||
|
||||
v := releases[i]
|
||||
|
||||
for _, link := range v.Assets {
|
||||
if !reSBOMFile.MatchString(link.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
foundSBOMs = append(foundSBOMs,
|
||||
checker.SBOM{
|
||||
File: checker.File{
|
||||
Path: link.URL,
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
Name: link.Name,
|
||||
})
|
||||
|
||||
// Only want one sbom from each release
|
||||
break
|
||||
}
|
||||
}
|
||||
return foundSBOMs
|
||||
}
|
||||
|
||||
func checkSBOMSource(fileList []string) []checker.SBOM {
|
||||
var foundSBOMs []checker.SBOM
|
||||
|
||||
for _, file := range fileList {
|
||||
// TODO: parse matching file contents to determine schema & version
|
||||
foundSBOMs = append(foundSBOMs,
|
||||
checker.SBOM{
|
||||
File: checker.File{
|
||||
Path: file,
|
||||
Type: finding.FileTypeSource,
|
||||
},
|
||||
Name: file,
|
||||
})
|
||||
}
|
||||
|
||||
return foundSBOMs
|
||||
}
|
129
checks/raw/sbom_test.go
Normal file
129
checks/raw/sbom_test.go
Normal file
@ -0,0 +1,129 @@
|
||||
// Copyright 2024 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 raw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/ossf/scorecard/v5/checker"
|
||||
"github.com/ossf/scorecard/v5/clients"
|
||||
mockrepo "github.com/ossf/scorecard/v5/clients/mockclients"
|
||||
"github.com/ossf/scorecard/v5/finding"
|
||||
scut "github.com/ossf/scorecard/v5/utests"
|
||||
)
|
||||
|
||||
func TestSbom(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
releases []clients.Release
|
||||
files []string
|
||||
err error
|
||||
expected checker.SBOMData
|
||||
}{
|
||||
{
|
||||
name: "With Sbom in release artifacts",
|
||||
releases: []clients.Release{
|
||||
{
|
||||
Assets: []clients.ReleaseAsset{
|
||||
{
|
||||
Name: "test-sbom.cdx.json",
|
||||
URL: "https://this.url",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
files: []string{},
|
||||
expected: checker.SBOMData{
|
||||
SBOMFiles: []checker.SBOM{
|
||||
{
|
||||
Name: "test-sbom.cdx.json",
|
||||
File: checker.File{
|
||||
Type: finding.FileTypeURL,
|
||||
Path: "https://this.url",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "With Sbom in source",
|
||||
releases: []clients.Release{},
|
||||
files: []string{"test-sbom.spdx.json"},
|
||||
err: nil,
|
||||
expected: checker.SBOMData{
|
||||
SBOMFiles: []checker.SBOM{
|
||||
{
|
||||
Name: "test-sbom.spdx.json",
|
||||
File: checker.File{
|
||||
Type: finding.FileTypeSource,
|
||||
Path: "test-sbom.spdx.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Without SBOM",
|
||||
releases: []clients.Release{},
|
||||
files: []string{},
|
||||
expected: checker.SBOMData{},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt // Re-initializing variable so it is not changed while executing the closure below
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
mockRepo := mockrepo.NewMockRepoClient(ctrl)
|
||||
|
||||
mockRepo.EXPECT().ListReleases().DoAndReturn(
|
||||
func() ([]clients.Release, error) {
|
||||
if tt.err != nil {
|
||||
return nil, tt.err
|
||||
}
|
||||
return tt.releases, tt.err
|
||||
},
|
||||
).MaxTimes(1)
|
||||
|
||||
mockRepo.EXPECT().ListFiles(gomock.Any()).DoAndReturn(func(predicate func(string) (bool, error)) ([]string, error) {
|
||||
return tt.files, nil
|
||||
}).AnyTimes()
|
||||
|
||||
dl := scut.TestDetailLogger{}
|
||||
req := checker.CheckRequest{
|
||||
RepoClient: mockRepo,
|
||||
Ctx: context.Background(),
|
||||
Dlogger: &dl,
|
||||
}
|
||||
res, err := SBOM(&req)
|
||||
if tt.err != nil {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error %v, got nil", tt.err)
|
||||
}
|
||||
}
|
||||
|
||||
if !cmp.Equal(res, tt.expected) {
|
||||
t.Errorf("Expected %v, got %v for %v", tt.expected, res, tt.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
71
checks/sbom.go
Normal file
71
checks/sbom.go
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright 2024 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 (
|
||||
"os"
|
||||
|
||||
"github.com/ossf/scorecard/v5/checker"
|
||||
"github.com/ossf/scorecard/v5/checks/evaluation"
|
||||
"github.com/ossf/scorecard/v5/checks/raw"
|
||||
sce "github.com/ossf/scorecard/v5/errors"
|
||||
"github.com/ossf/scorecard/v5/probes"
|
||||
"github.com/ossf/scorecard/v5/probes/zrunner"
|
||||
)
|
||||
|
||||
// SBOM is the registered name for SBOM.
|
||||
const CheckSBOM = "SBOM"
|
||||
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
if err := registerCheck(CheckSBOM, SBOM, nil); err != nil {
|
||||
// this should never happen
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// SBOM runs SBOM check.
|
||||
func SBOM(c *checker.CheckRequest) checker.CheckResult {
|
||||
_, enabled := os.LookupEnv("SCORECARD_EXPERIMENTAL")
|
||||
if !enabled {
|
||||
c.Dlogger.Warn(&checker.LogMessage{
|
||||
Text: "SCORECARD_EXPERIMENTAL is not set, not running the SBOM check",
|
||||
})
|
||||
|
||||
e := sce.WithMessage(sce.ErrUnsupportedCheck, "SCORECARD_EXPERIMENTAL is not set, not running the SBOM check")
|
||||
return checker.CreateRuntimeErrorResult(CheckSBOM, e)
|
||||
}
|
||||
|
||||
rawData, err := raw.SBOM(c)
|
||||
if err != nil {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
return checker.CreateRuntimeErrorResult(CheckSBOM, e)
|
||||
}
|
||||
|
||||
// Set the raw results.
|
||||
pRawResults := getRawResults(c)
|
||||
pRawResults.SBOMResults = rawData
|
||||
|
||||
// Evaluate the probes.
|
||||
findings, err := zrunner.Run(pRawResults, probes.SBOM)
|
||||
if err != nil {
|
||||
e := sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
return checker.CreateRuntimeErrorResult(CheckSBOM, e)
|
||||
}
|
||||
|
||||
ret := evaluation.SBOM(CheckSBOM, findings, c.Dlogger)
|
||||
ret.Findings = findings
|
||||
return ret
|
||||
}
|
116
checks/sbom_test.go
Normal file
116
checks/sbom_test.go
Normal file
@ -0,0 +1,116 @@
|
||||
// Copyright 2024 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 (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
|
||||
"github.com/ossf/scorecard/v5/checker"
|
||||
"github.com/ossf/scorecard/v5/clients"
|
||||
mockrepo "github.com/ossf/scorecard/v5/clients/mockclients"
|
||||
scut "github.com/ossf/scorecard/v5/utests"
|
||||
)
|
||||
|
||||
func TestSbom(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
releases []clients.Release
|
||||
files []string
|
||||
err error
|
||||
expected scut.TestReturn
|
||||
}{
|
||||
{
|
||||
name: "With Sbom in release artifacts",
|
||||
releases: []clients.Release{
|
||||
{
|
||||
Assets: []clients.ReleaseAsset{
|
||||
{
|
||||
Name: "test-sbom.cdx.json",
|
||||
URL: "https://this.url",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
files: []string{},
|
||||
expected: scut.TestReturn{
|
||||
Score: checker.MaxResultScore,
|
||||
NumberOfInfo: 2,
|
||||
NumberOfWarn: 0,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "With Sbom in source",
|
||||
releases: []clients.Release{},
|
||||
files: []string{"test-sbom.spdx.json"},
|
||||
err: nil,
|
||||
expected: scut.TestReturn{
|
||||
Score: 5,
|
||||
NumberOfInfo: 1,
|
||||
NumberOfWarn: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Without SBOM",
|
||||
releases: []clients.Release{},
|
||||
files: []string{},
|
||||
expected: scut.TestReturn{
|
||||
Score: checker.MinResultScore,
|
||||
NumberOfInfo: 0,
|
||||
NumberOfWarn: 2,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt // Re-initializing variable so it is not changed while executing the closure below
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Setenv("SCORECARD_EXPERIMENTAL", "true")
|
||||
ctrl := gomock.NewController(t)
|
||||
mockRepo := mockrepo.NewMockRepoClient(ctrl)
|
||||
|
||||
mockRepo.EXPECT().ListReleases().DoAndReturn(
|
||||
func() ([]clients.Release, error) {
|
||||
if tt.err != nil {
|
||||
return nil, tt.err
|
||||
}
|
||||
return tt.releases, tt.err
|
||||
},
|
||||
).MaxTimes(1)
|
||||
|
||||
mockRepo.EXPECT().ListFiles(gomock.Any()).DoAndReturn(func(predicate func(string) (bool, error)) ([]string, error) {
|
||||
return tt.files, nil
|
||||
}).AnyTimes()
|
||||
|
||||
dl := scut.TestDetailLogger{}
|
||||
req := checker.CheckRequest{
|
||||
RepoClient: mockRepo,
|
||||
Ctx: context.Background(),
|
||||
Dlogger: &dl,
|
||||
}
|
||||
res := SBOM(&req)
|
||||
if tt.err != nil {
|
||||
if res.Error == nil {
|
||||
t.Fatalf("Expected error %v, got nil", tt.err)
|
||||
}
|
||||
}
|
||||
|
||||
scut.ValidateTestReturn(t, tt.name, &tt.expected, &res, &dl)
|
||||
})
|
||||
}
|
||||
}
|
@ -126,6 +126,7 @@ func (client *Client) InitRepo(inputRepo clients.Repo, commitSHA string, commitD
|
||||
|
||||
// Setup licensesHandler.
|
||||
client.licenses.init(client.ctx, client.repourl)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -541,6 +541,38 @@ is therefore not a definitive indication that the project is at risk.
|
||||
**Remediation steps**
|
||||
- Run CodeQL checks in your CI/CD by following the instructions [here](https://github.com/github/codeql-action#usage).
|
||||
|
||||
## SBOM
|
||||
|
||||
Risk: `Medium` (possible inaccurate reporting of dependencies/vulnerabilities)
|
||||
|
||||
This check tries to determine if the project maintains a Software Bill of Materials (SBOM)
|
||||
either as a file in the source or a release artifact.
|
||||
|
||||
An SBOM can give users information about what dependencies your project has which
|
||||
allows them to identify vulnerabilities in the software supply chain.
|
||||
|
||||
Standards to be used during checks;
|
||||
- OSSF SBOM Everywhere SIG naming and directory conventions:
|
||||
- <https://github.com/ossf/SBOM-everywhere/blob/main/reference/SBOM_naming.md#consistent-naming-conventions>
|
||||
|
||||
This check currently looks for the existence of an SBOM in the
|
||||
source of a project and as a pipeline or release artifact.
|
||||
|
||||
An SBOM Exists (one or more) (5/10 points):
|
||||
- Any SBOM found counts for this test either in source. pipeline or release.
|
||||
- A SBOM stored with your source code is not ideal, but is a good first step.
|
||||
\* It is recommended to publish with your release artifacts.
|
||||
|
||||
An SBOM is published as a release artifact (5/10 points):
|
||||
- This is the preferred way to store an SBOM, and will be awarded full points.
|
||||
- Checks release artifacts for an SBOM file matching established standards
|
||||
|
||||
|
||||
**Remediation steps**
|
||||
- For Gitlab, see more information [here](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html#cyclonedx-software-bill-of-materials).
|
||||
- For GitHub, see more information [here](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-supply-chain-security).
|
||||
- Alternatively, there are other tools available to generate [CycloneDX](https://cyclonedx.org/tool-center/) and [SPDX](https://spdx.dev/use/tools/) SBOMs.
|
||||
|
||||
## Security-Policy
|
||||
|
||||
Risk: `Medium` (possible insecure reporting of vulnerabilities)
|
||||
|
@ -558,6 +558,46 @@ checks:
|
||||
- >-
|
||||
Run CodeQL checks in your CI/CD by following the instructions
|
||||
[here](https://github.com/github/codeql-action#usage).
|
||||
|
||||
SBOM:
|
||||
risk: Medium
|
||||
short: Determines if the project maintains a Software Bill of Materials.
|
||||
repos: GitHub, Gitlab
|
||||
tags: supply-chain, security, vulnerabilities, dependencies, SBOM
|
||||
description: |
|
||||
Risk: `Medium` (possible inaccurate reporting of dependencies/vulnerabilities)
|
||||
|
||||
This check tries to determine if the project maintains a Software Bill of Materials (SBOM)
|
||||
either as a file in the source or a release artifact.
|
||||
|
||||
An SBOM can give users information about what dependencies your project has which
|
||||
allows them to identify vulnerabilities in the software supply chain.
|
||||
|
||||
Standards to be used during checks;
|
||||
- OSSF SBOM Everywhere SIG naming and directory conventions:
|
||||
- <https://github.com/ossf/SBOM-everywhere/blob/main/reference/SBOM_naming.md#consistent-naming-conventions>
|
||||
|
||||
This check currently looks for the existence of an SBOM in the
|
||||
source of a project and as a pipeline or release artifact.
|
||||
|
||||
An SBOM Exists (one or more) (5/10 points):
|
||||
- Any SBOM found counts for this test either in source. pipeline or release.
|
||||
- A SBOM stored with your source code is not ideal, but is a good first step.
|
||||
\* It is recommended to publish with your release artifacts.
|
||||
|
||||
An SBOM is published as a release artifact (5/10 points):
|
||||
- This is the preferred way to store an SBOM, and will be awarded full points.
|
||||
- Checks release artifacts for an SBOM file matching established standards
|
||||
remediation:
|
||||
- >-
|
||||
For Gitlab, see more information
|
||||
[here](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html#cyclonedx-software-bill-of-materials).
|
||||
- >-
|
||||
For GitHub, see more information
|
||||
[here](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-supply-chain-security).
|
||||
- >-
|
||||
Alternatively, there are other tools available to generate [CycloneDX](https://cyclonedx.org/tool-center/) and [SPDX](https://spdx.dev/use/tools/) SBOMs.
|
||||
|
||||
Security-Policy:
|
||||
risk: Medium
|
||||
short: Determines if the project has published a security policy.
|
||||
|
@ -40,6 +40,7 @@ const (
|
||||
Packaging CheckName = "Packaging"
|
||||
PinnedDependencies CheckName = "Pinned-Dependencies"
|
||||
SAST CheckName = "SAST"
|
||||
SBOM CheckName = "SBOM"
|
||||
SecurityPolicy CheckName = "Security-Policy"
|
||||
SignedReleases CheckName = "Signed-Releases"
|
||||
TokenPermissions CheckName = "Token-Permissions"
|
||||
|
@ -345,6 +345,12 @@ func assignRawData(probeCheckName string, request *checker.CheckRequest, ret *Sc
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
}
|
||||
ret.RawResults.SASTResults = rawData
|
||||
case checks.CheckSBOM:
|
||||
rawData, err := raw.SBOM(request)
|
||||
if err != nil {
|
||||
return sce.WithMessage(sce.ErrScorecardInternal, err.Error())
|
||||
}
|
||||
ret.RawResults.SBOMResults = rawData
|
||||
case checks.CheckSecurityPolicy:
|
||||
rawData, err := raw.SecurityPolicy(request)
|
||||
if err != nil {
|
||||
|
@ -39,6 +39,8 @@ import (
|
||||
"github.com/ossf/scorecard/v5/probes/hasOpenSSFBadge"
|
||||
"github.com/ossf/scorecard/v5/probes/hasPermissiveLicense"
|
||||
"github.com/ossf/scorecard/v5/probes/hasRecentCommits"
|
||||
"github.com/ossf/scorecard/v5/probes/hasReleaseSBOM"
|
||||
"github.com/ossf/scorecard/v5/probes/hasSBOM"
|
||||
"github.com/ossf/scorecard/v5/probes/hasUnverifiedBinaryArtifacts"
|
||||
"github.com/ossf/scorecard/v5/probes/issueActivityByProjectMember"
|
||||
"github.com/ossf/scorecard/v5/probes/jobLevelPermissions"
|
||||
@ -130,6 +132,10 @@ var (
|
||||
CITests = []ProbeImpl{
|
||||
testsRunInCI.Run,
|
||||
}
|
||||
SBOM = []ProbeImpl{
|
||||
hasSBOM.Run,
|
||||
hasReleaseSBOM.Run,
|
||||
}
|
||||
SignedReleases = []ProbeImpl{
|
||||
releasesAreSigned.Run,
|
||||
releasesHaveProvenance.Run,
|
||||
|
36
probes/hasReleaseSBOM/def.yml
Normal file
36
probes/hasReleaseSBOM/def.yml
Normal file
@ -0,0 +1,36 @@
|
||||
# Copyright 2024 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: hasReleaseSBOM
|
||||
short: Check that the project publishes an SBOM as part of its release artifacts.
|
||||
motivation: >
|
||||
An SBOM can give users information about how the source code components and dependencies. They help facilitate sotware supplychain security and aid in identifying upstream vulnerabilities in a codebase.
|
||||
implementation: >
|
||||
The implementation checks whether a SBOM artifact is included in release artifacts.
|
||||
outcome:
|
||||
- If SBOM artifacts are found, the probe returns OutcomeTrue for each SBOM artifact up to 5.
|
||||
- If an SBOM artifact is not found, the probe returns a single OutcomeFalse.
|
||||
remediation:
|
||||
onOutcome: False
|
||||
effort: Low
|
||||
text:
|
||||
- For Github projects, start with [this guide](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-supply-chain-security) to determine which steps are needed to generate an adequate SBOM.
|
||||
- For Gitlab projects, see existing [Dependency Scanning](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html#cyclonedx-software-bill-of-materials) and [Container Scanning](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#cyclonedx-software-bill-of-materials) tools.
|
||||
- Alternatively, there are other tools available to generate [CycloneDX](https://cyclonedx.org/tool-center/) and [SPDX](https://spdx.dev/use/tools/) SBOMs.
|
||||
ecosystem:
|
||||
languages:
|
||||
- all
|
||||
clients:
|
||||
- github
|
||||
- gitlab
|
82
probes/hasReleaseSBOM/impl.go
Normal file
82
probes/hasReleaseSBOM/impl.go
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright 2024 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 hasReleaseSBOM
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v5/checker"
|
||||
"github.com/ossf/scorecard/v5/finding"
|
||||
"github.com/ossf/scorecard/v5/internal/probes"
|
||||
"github.com/ossf/scorecard/v5/probes/internal/utils/uerror"
|
||||
)
|
||||
|
||||
func init() {
|
||||
probes.MustRegister(Probe, Run, []probes.CheckName{probes.SBOM})
|
||||
}
|
||||
|
||||
//go:embed *.yml
|
||||
var fs embed.FS
|
||||
|
||||
const (
|
||||
Probe = "hasReleaseSBOM"
|
||||
AssetNameKey = "assetName"
|
||||
AssetURLKey = "assetURL"
|
||||
missingSbom = "Project is not publishing an SBOM file as part of a release or CICD"
|
||||
)
|
||||
|
||||
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
|
||||
if raw == nil {
|
||||
return nil, "", fmt.Errorf("%w: raw", uerror.ErrNil)
|
||||
}
|
||||
|
||||
var findings []finding.Finding
|
||||
var msg string
|
||||
|
||||
SBOMFiles := raw.SBOMResults.SBOMFiles
|
||||
|
||||
for i := range SBOMFiles {
|
||||
SBOMFile := SBOMFiles[i]
|
||||
|
||||
if SBOMFile.File.Type != finding.FileTypeURL {
|
||||
continue
|
||||
}
|
||||
|
||||
loc := SBOMFile.File.Location()
|
||||
msg = "Project publishes an SBOM file as part of a release or CICD"
|
||||
f, err := finding.NewTrue(fs, Probe, msg, loc)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
f.Values = map[string]string{
|
||||
AssetNameKey: SBOMFile.Name,
|
||||
AssetURLKey: SBOMFile.File.Path,
|
||||
}
|
||||
findings = append(findings, *f)
|
||||
}
|
||||
|
||||
if len(findings) == 0 {
|
||||
msg = missingSbom
|
||||
f, err := finding.NewFalse(fs, Probe, msg, nil)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
findings = append(findings, *f)
|
||||
}
|
||||
|
||||
return findings, Probe, nil
|
||||
}
|
122
probes/hasReleaseSBOM/impl_test.go
Normal file
122
probes/hasReleaseSBOM/impl_test.go
Normal file
@ -0,0 +1,122 @@
|
||||
// Copyright 2024 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 hasReleaseSBOM
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
"github.com/ossf/scorecard/v5/checker"
|
||||
"github.com/ossf/scorecard/v5/finding"
|
||||
"github.com/ossf/scorecard/v5/probes/internal/utils/test"
|
||||
"github.com/ossf/scorecard/v5/probes/internal/utils/uerror"
|
||||
)
|
||||
|
||||
func Test_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
//nolint:govet
|
||||
tests := []struct {
|
||||
name string
|
||||
raw *checker.RawResults
|
||||
outcomes []finding.Outcome
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Release SBOM file found and outcome should be positive",
|
||||
raw: &checker.RawResults{
|
||||
SBOMResults: checker.SBOMData{
|
||||
SBOMFiles: []checker.SBOM{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SBOM.cdx.json",
|
||||
Type: finding.FileTypeURL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeTrue,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Release SBOM file not found and outcome should be negative",
|
||||
raw: &checker.RawResults{
|
||||
SBOMResults: checker.SBOMData{
|
||||
SBOMFiles: []checker.SBOM{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SBOM.cdx.json",
|
||||
Type: finding.FileTypeSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeFalse,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SBOM file not found and outcome should be negative",
|
||||
raw: &checker.RawResults{
|
||||
SBOMResults: checker.SBOMData{
|
||||
SBOMFiles: []checker.SBOM{},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeFalse,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil license files and outcome should be negative",
|
||||
raw: &checker.RawResults{
|
||||
SBOMResults: checker.SBOMData{
|
||||
SBOMFiles: nil,
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeFalse,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no raw data",
|
||||
raw: nil,
|
||||
err: uerror.ErrNil,
|
||||
outcomes: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt // Re-initializing variable so it is not changed while executing the closure below
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
findings, s, err := Run(tt.raw)
|
||||
if !cmp.Equal(tt.err, err, cmpopts.EquateErrors()) {
|
||||
t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(tt.err, err, cmpopts.EquateErrors()))
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if diff := cmp.Diff(Probe, s); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
test.AssertOutcomes(t, findings, tt.outcomes)
|
||||
})
|
||||
}
|
||||
}
|
36
probes/hasSBOM/def.yml
Normal file
36
probes/hasSBOM/def.yml
Normal file
@ -0,0 +1,36 @@
|
||||
# Copyright 2024 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: hasSBOM
|
||||
short: Check that the project has an SBOM file
|
||||
motivation: >
|
||||
An SBOM can give users information about how the source code components and dependencies. They help facilitate sotware supplychain security and aid in identifying upstream vulnerabilities in a codebase.
|
||||
implementation: >
|
||||
The implementation checks whether an SBOM file is present in the source code.
|
||||
outcome:
|
||||
- If an SBOM file(s) is found, the probe returns OutcomeTrue for each SBOM artifact up to 5.
|
||||
- If an SBOM file is not found, the probe returns a single OutcomeFalse.
|
||||
remediation:
|
||||
onOutcome: False
|
||||
effort: Low
|
||||
text:
|
||||
- For Github projects, start with [this guide](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-supply-chain-security) to determine which steps are needed to generate an adequate SBOM.
|
||||
- For Gitlab projects, see existing [Dependency Scanning](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html#cyclonedx-software-bill-of-materials) and [Container Scanning](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#cyclonedx-software-bill-of-materials) tools.
|
||||
- Alternatively, there are other tools available to generate [CycloneDX](https://cyclonedx.org/tool-center/) and [SPDX](https://spdx.dev/use/tools/) SBOMs.
|
||||
ecosystem:
|
||||
languages:
|
||||
- all
|
||||
clients:
|
||||
- github
|
||||
- gitlab
|
69
probes/hasSBOM/impl.go
Normal file
69
probes/hasSBOM/impl.go
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright 2024 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 hasSBOM
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/ossf/scorecard/v5/checker"
|
||||
"github.com/ossf/scorecard/v5/finding"
|
||||
"github.com/ossf/scorecard/v5/internal/probes"
|
||||
"github.com/ossf/scorecard/v5/probes/internal/utils/uerror"
|
||||
)
|
||||
|
||||
func init() {
|
||||
probes.MustRegister(Probe, Run, []probes.CheckName{probes.SBOM})
|
||||
}
|
||||
|
||||
//go:embed *.yml
|
||||
var fs embed.FS
|
||||
|
||||
const Probe = "hasSBOM"
|
||||
|
||||
func Run(raw *checker.RawResults) ([]finding.Finding, string, error) {
|
||||
if raw == nil {
|
||||
return nil, "", fmt.Errorf("%w: raw", uerror.ErrNil)
|
||||
}
|
||||
|
||||
var findings []finding.Finding
|
||||
var msg string
|
||||
|
||||
SBOMFiles := raw.SBOMResults.SBOMFiles
|
||||
|
||||
if len(SBOMFiles) == 0 {
|
||||
msg = "Project does not have a SBOM file"
|
||||
f, err := finding.NewFalse(fs, Probe, msg, nil)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
findings = append(findings, *f)
|
||||
return findings, Probe, nil
|
||||
}
|
||||
|
||||
for i := range SBOMFiles {
|
||||
SBOMFile := SBOMFiles[i]
|
||||
loc := SBOMFile.File.Location()
|
||||
msg = "Project has a SBOM file"
|
||||
f, err := finding.NewTrue(fs, Probe, msg, loc)
|
||||
if err != nil {
|
||||
return nil, Probe, fmt.Errorf("create finding: %w", err)
|
||||
}
|
||||
findings = append(findings, *f)
|
||||
}
|
||||
|
||||
return findings, Probe, nil
|
||||
}
|
103
probes/hasSBOM/impl_test.go
Normal file
103
probes/hasSBOM/impl_test.go
Normal file
@ -0,0 +1,103 @@
|
||||
// Copyright 2024 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 hasSBOM
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
"github.com/ossf/scorecard/v5/checker"
|
||||
"github.com/ossf/scorecard/v5/finding"
|
||||
"github.com/ossf/scorecard/v5/probes/internal/utils/test"
|
||||
"github.com/ossf/scorecard/v5/probes/internal/utils/uerror"
|
||||
)
|
||||
|
||||
func Test_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
//nolint:govet
|
||||
tests := []struct {
|
||||
name string
|
||||
raw *checker.RawResults
|
||||
outcomes []finding.Outcome
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "SBOM file found and outcome should be positive",
|
||||
raw: &checker.RawResults{
|
||||
SBOMResults: checker.SBOMData{
|
||||
SBOMFiles: []checker.SBOM{
|
||||
{
|
||||
File: checker.File{
|
||||
Path: "SBOM.cdx.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeTrue,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil SBOM files and outcome should be negative",
|
||||
raw: &checker.RawResults{
|
||||
SBOMResults: checker.SBOMData{
|
||||
SBOMFiles: nil,
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeFalse,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "0 SBOM files and outcome should be negative",
|
||||
raw: &checker.RawResults{
|
||||
SBOMResults: checker.SBOMData{
|
||||
SBOMFiles: []checker.SBOM{},
|
||||
},
|
||||
},
|
||||
outcomes: []finding.Outcome{
|
||||
finding.OutcomeFalse,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no raw data",
|
||||
raw: nil,
|
||||
err: uerror.ErrNil,
|
||||
outcomes: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt // Re-initializing variable so it is not changed while executing the closure below
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
findings, s, err := Run(tt.raw)
|
||||
if !cmp.Equal(tt.err, err, cmpopts.EquateErrors()) {
|
||||
t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(tt.err, err, cmpopts.EquateErrors()))
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if diff := cmp.Diff(Probe, s); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
test.AssertOutcomes(t, findings, tt.outcomes)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user