diff --git a/dependencydiff/dependencydiff.go b/dependencydiff/dependencydiff.go index c4012035d76..5d77f341c7c 100644 --- a/dependencydiff/dependencydiff.go +++ b/dependencydiff/dependencydiff.go @@ -79,6 +79,11 @@ func GetDependencyDiffResults( if err != nil { return nil, fmt.Errorf("error in fetchRawDependencyDiffData: %w", err) } + // Map the ecosystem naming convention from GitHub to OSV. + err = mapDependencyEcosystemNaming(dCtx.dependencydiffs) + if err != nil { + return nil, fmt.Errorf("error in mapDependencyEcosystemNaming: %w", err) + } err = getScorecardCheckResults(&dCtx) if err != nil { return nil, fmt.Errorf("error getting scorecard check results: %w", err) @@ -86,6 +91,22 @@ func GetDependencyDiffResults( return dCtx.results, nil } +func mapDependencyEcosystemNaming(deps []dependency) error { + for i := range deps { + if deps[i].Ecosystem == nil { + continue + } + mappedEcosys, err := toEcosystem(*deps[i].Ecosystem) + if err != nil { + wrappedErr := fmt.Errorf("error mapping dependency ecosystem: %w", err) + return wrappedErr + } + deps[i].Ecosystem = asPointer(string(mappedEcosys)) + + } + return nil +} + func initRepoAndClientByChecks(dCtx *dependencydiffContext, dSrcRepo string) error { repo, repoClient, ossFuzzClient, ciiClient, vulnsClient, err := checker.GetClients( dCtx.ctx, dSrcRepo, "", dCtx.logger, @@ -171,3 +192,7 @@ func getScorecardCheckResults(dCtx *dependencydiffContext) error { } return nil } + +func asPointer(s string) *string { + return &s +} diff --git a/dependencydiff/dependencydiff_test.go b/dependencydiff/dependencydiff_test.go index 4767ff2174f..d8f4694351b 100644 --- a/dependencydiff/dependencydiff_test.go +++ b/dependencydiff/dependencydiff_test.go @@ -16,6 +16,7 @@ package dependencydiff import ( "context" + "errors" "path" "testing" @@ -158,3 +159,66 @@ func Test_getScorecardCheckResults(t *testing.T) { }) } } + +func Test_mapDependencyEcosystemNaming(t *testing.T) { + t.Parallel() + //nolint + tests := []struct { + name string + deps []dependency + errWanted error + }{ + { + name: "error invalid github ecosystem", + deps: []dependency{ + { + Name: "dependency_1", + Ecosystem: asPointer("not_supported"), + }, + { + Name: "dependency_2", + Ecosystem: asPointer("gomod"), + }, + }, + errWanted: errInvalid, + }, + { + name: "error cannot find mapping", + deps: []dependency{ + { + Name: "dependency_3", + Ecosystem: asPointer("actions"), + }, + }, + errWanted: errInvalid, + }, + { + name: "correct mapping", + deps: []dependency{ + { + Name: "dependency_4", + Ecosystem: asPointer("gomod"), + }, + { + Name: "dependency_5", + Ecosystem: asPointer("pip"), + }, + { + Name: "dependency_6", + Ecosystem: asPointer("cargo"), + }, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + err := mapDependencyEcosystemNaming(tt.deps) + if tt.errWanted != nil && errors.Is(tt.errWanted, err) { + t.Errorf("not a wanted error, want:%v, got:%v", tt.errWanted, err) + return + } + }) + } +} diff --git a/dependencydiff/errors.go b/dependencydiff/errors.go index 0bad31b0db5..6541cc53964 100644 --- a/dependencydiff/errors.go +++ b/dependencydiff/errors.go @@ -18,5 +18,6 @@ import "errors" // static Errors for mapping var ( - errInvalid = errors.New("invalid") + errMappingNotFound = errors.New("ecosystem mapping not found") + errInvalid = errors.New("invalid") ) diff --git a/dependencydiff/mapping.go b/dependencydiff/mapping.go new file mode 100644 index 00000000000..445676b3b6b --- /dev/null +++ b/dependencydiff/mapping.go @@ -0,0 +1,89 @@ +// 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 dependencydiff + +import ( + "fmt" +) + +// Ecosystem is a package ecosystem supported by OSV, GitHub, etc. +type ecosystem string + +// OSV ecosystem naming data source: https://ossf.github.io/osv-schema/#affectedpackage-field +// nolint +const ( + // The Go ecosystem. + ecosystemGo ecosystem = "Go" + + // The NPM ecosystem. + ecosystemNpm ecosystem = "npm" + + // The Android ecosystem + ecosystemAndroid ecosystem = "Android" // nolint:unused + + // The crates.io ecosystem for RUST. + ecosystemCrates ecosystem = "crates.io" + + // For reports from the OSS-Fuzz project that have no more appropriate ecosystem. + ecosystemOssFuzz ecosystem = "OSS-Fuzz" // nolint:unused + + // The Python PyPI ecosystem. PyPI is the main package source of pip. + ecosystemPyPI ecosystem = "PyPI" + + // The RubyGems ecosystem. + ecosystemRubyGems ecosystem = "RubyGems" + + // The PHP package manager ecosystem. Packagist is the main Composer repository. + ecosystemPackagist ecosystem = "Packagist" + + // The Maven Java package ecosystem. + ecosystemMaven ecosystem = "Maven" + + // The NuGet package ecosystem. + ecosystemNuGet ecosystem = "Nuget" + + // The Linux kernel. + ecosystemLinux ecosystem = "Linux" // nolint:unused + + // The Debian package ecosystem. + ecosystemDebian ecosystem = "Debian" // nolint:unused + + // Hex is the package manager of Erlang. + // TODO: GitHub doesn't support hex as the ecosystem for Erlang yet. Add this to the map in the future. + ecosystemHex ecosystem = "Hex" // nolint:unused +) + +var ( + //gitHubToOSV defines the ecosystem naming mapping relationship between GitHub and others. + gitHubToOSV = map[string]ecosystem{ + // GitHub ecosystem naming data source: https://docs.github.com/en/code-security/supply-chain-security/ + // understanding-your-software-supply-chain/about-the-dependency-graph#supported-package-ecosystems + "gomod": ecosystemGo, /* go.mod and go.sum */ + "cargo": ecosystemCrates, + "pip": ecosystemPyPI, /* pip and poetry */ + "npm": ecosystemNpm, /* npm and yarn */ + "maven": ecosystemMaven, + "composer": ecosystemPackagist, + "rubygems": ecosystemRubyGems, + "nuget": ecosystemNuGet, + } +) + +func toEcosystem(e string) (ecosystem, error) { + if ecosystemOSV, found := gitHubToOSV[e]; found { + return ecosystemOSV, nil + } + return "", fmt.Errorf("%w for github entry %s", errMappingNotFound, e) +} diff --git a/dependencydiff/raw_dependencies.go b/dependencydiff/raw_dependencies.go index de313375e82..39194535d51 100644 --- a/dependencydiff/raw_dependencies.go +++ b/dependencydiff/raw_dependencies.go @@ -68,5 +68,10 @@ func fetchRawDependencyDiffData(dCtx *dependencydiffContext) error { if err != nil { return fmt.Errorf("error parsing the dependency-diff reponse: %w", err) } + for _, d := range dCtx.dependencydiffs { + if !d.ChangeType.IsValid() { + return fmt.Errorf("%w: change type", errInvalid) + } + } return nil } diff --git a/pkg/dependencydiff_result.go b/pkg/dependencydiff_result.go index 74b5b834b93..c8608ef74b7 100644 --- a/pkg/dependencydiff_result.go +++ b/pkg/dependencydiff_result.go @@ -35,8 +35,8 @@ const ( ) // IsValid determines if a ChangeType is valid. -func (ct *ChangeType) IsValid() bool { - switch *ct { +func (ct ChangeType) IsValid() bool { + switch ct { case Added, Updated, Removed: return true default: @@ -45,7 +45,7 @@ func (ct *ChangeType) IsValid() bool { } // ScorecardResultWithError is used for the dependency-diff module to record the scorecard result -// and a potential error field if the Scorecard run fails. +// and a error field to record potential errors when the Scorecard run fails. type ScorecardResultWithError struct { // ScorecardResult is the scorecard result for the dependency repo. ScorecardResult *ScorecardResult @@ -74,7 +74,7 @@ type DependencyCheckResult struct { // Version is the package version of the dependency. Version *string - // ScorecardResultWithError is the scorecard checking results of the dependency. + // ScorecardResultWithError is the scorecard checking result of the dependency. ScorecardResultWithError ScorecardResultWithError // Name is the name of the dependency.