⚠️ Add ProjectPackageVersions to raw data collection (#4104)

* add projectpackageversions to signed releases raw results

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

* add mocks

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

* update

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

* fix tests

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

* rename

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

* Update runScorecard

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

* pass depsdevclient to scdiff

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

* error handling

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

* make Host() return domain only

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

* lint

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

* address cr comments

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>

---------

Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>
This commit is contained in:
Raghav Kaul 2024-05-30 13:00:36 -07:00 committed by GitHub
parent 7e6a09e474
commit 77dce6fbef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 215 additions and 27 deletions

View File

@ -89,6 +89,17 @@ type Package struct {
Runs []Run
}
type PackageProvenance struct {
Commit string
IsVerified bool
}
type ProjectPackage struct {
System string
Name string
Version string
Provenance PackageProvenance
}
// DependencyUseType represents a type of dependency use.
type DependencyUseType string
@ -300,6 +311,7 @@ type BinaryArtifactData struct {
// for the Signed-Releases check.
type SignedReleasesData struct {
Releases []clients.Release
Packages []ProjectPackage
}
// DependencyUpdateToolData contains the raw results

View File

@ -39,10 +39,6 @@ func CodeReview(c clients.RepoClient) (checker.CodeReviewData, error) {
changesets := getChangesets(commits)
if err != nil {
return checker.CodeReviewData{}, fmt.Errorf("%w", err)
}
return checker.CodeReviewData{
DefaultBranchChangesets: changesets,
}, nil

View File

@ -27,7 +27,36 @@ func SignedReleases(c *checker.CheckRequest) (checker.SignedReleasesData, error)
return checker.SignedReleasesData{}, fmt.Errorf("%w", err)
}
pkgs := []checker.ProjectPackage{}
versions, err := c.ProjectClient.GetProjectPackageVersions(c.Ctx, c.Repo.Host(), c.Repo.Path())
if err != nil {
c.Dlogger.Debug(&checker.LogMessage{Text: fmt.Sprintf("GetProjectPackageVersions: %v", err)})
return checker.SignedReleasesData{
Releases: releases,
Packages: pkgs,
}, nil
}
for _, v := range versions.Versions {
prov := checker.PackageProvenance{}
if len(v.SLSAProvenances) > 0 {
prov = checker.PackageProvenance{
Commit: v.SLSAProvenances[0].Commit,
IsVerified: v.SLSAProvenances[0].Verified,
}
}
pkgs = append(pkgs, checker.ProjectPackage{
System: v.VersionKey.System,
Name: v.VersionKey.Name,
Version: v.VersionKey.Version,
Provenance: prov,
})
}
return checker.SignedReleasesData{
Releases: releases,
Packages: pkgs,
}, nil
}

View File

@ -15,6 +15,7 @@
package checks
import (
"context"
"errors"
"fmt"
"testing"
@ -24,6 +25,7 @@ import (
"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/internal/packageclient"
scut "github.com/ossf/scorecard/v5/utests"
)
@ -435,8 +437,8 @@ func TestSignedRelease(t *testing.T) {
ctrl := gomock.NewController(t)
mockRepo := mockrepo.NewMockRepoClient(ctrl)
mockRepo.EXPECT().ListReleases().DoAndReturn(
mockRepoC := mockrepo.NewMockRepoClient(ctrl)
mockRepoC.EXPECT().ListReleases().DoAndReturn(
func() ([]clients.Release, error) {
if tt.err != nil {
return nil, tt.err
@ -445,8 +447,30 @@ func TestSignedRelease(t *testing.T) {
},
).MinTimes(1)
mockRepo := mockrepo.NewMockRepo(ctrl)
mockRepo.EXPECT().Host().DoAndReturn(
func() string {
return ""
},
).AnyTimes()
mockRepo.EXPECT().Path().DoAndReturn(
func() string {
return ""
},
).AnyTimes()
mockPkgC := mockrepo.NewMockProjectPackageClient(ctrl)
mockPkgC.EXPECT().GetProjectPackageVersions(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
func(ctx context.Context, host, project string) (*packageclient.ProjectPackageVersions, error) {
v := packageclient.ProjectPackageVersions{}
return &v, nil
},
).AnyTimes()
req := checker.CheckRequest{
RepoClient: mockRepo,
RepoClient: mockRepoC,
Repo: mockRepo,
ProjectClient: mockPkgC,
}
req.Dlogger = &scut.TestDetailLogger{}
res := SignedReleases(&req)

View File

@ -132,3 +132,8 @@ func MakeGithubRepo(input string) (clients.Repo, error) {
}
return &repo, nil
}
// Path() implements RepoClient.Path.
func (r *repoURL) Path() string {
return fmt.Sprintf("%s/%s", r.owner, r.repo)
}

View File

@ -283,7 +283,8 @@ func CreateGitlabClient(ctx context.Context, host string) (clients.RepoClient, e
}
func CreateGitlabClientWithToken(ctx context.Context, token, host string) (clients.RepoClient, error) {
client, err := gitlab.NewClient(token, gitlab.WithBaseURL(host))
url := "https://" + host
client, err := gitlab.NewClient(token, gitlab.WithBaseURL(url))
if err != nil {
return nil, fmt.Errorf("could not create gitlab client with error: %w", err)
}

View File

@ -45,7 +45,7 @@ func (handler *graphqlHandler) init(ctx context.Context, repourl *repoURL) {
&oauth2.Token{AccessToken: os.Getenv("GITLAB_AUTH_TOKEN")},
)
handler.client = oauth2.NewClient(ctx, src)
handler.graphClient = graphql.NewClient(fmt.Sprintf("%s/api/graphql", repourl.Host()), handler.client)
handler.graphClient = graphql.NewClient(fmt.Sprintf("https://%s/api/graphql", repourl.Host()), handler.client)
}
type graphqlData struct {

View File

@ -109,7 +109,7 @@ func (r *repoURL) URI() string {
}
func (r *repoURL) Host() string {
return fmt.Sprintf("%s://%s", r.scheme, r.host)
return r.host
}
// String implements Repo.String.
@ -134,7 +134,8 @@ func (r *repoURL) IsValid() error {
// intentionally pass empty token
// "When accessed without authentication, only public projects with simple fields are returned."
// https://docs.gitlab.com/ee/api/projects.html#list-all-projects
client, err := gitlab.NewClient("", gitlab.WithBaseURL(r.Host()))
baseURL := fmt.Sprintf("%s://%s", r.scheme, r.host)
client, err := gitlab.NewClient("", gitlab.WithBaseURL(baseURL))
if err != nil {
return sce.WithMessage(err,
fmt.Sprintf("couldn't create gitlab client for %s", r.host),
@ -144,7 +145,7 @@ func (r *repoURL) IsValid() error {
_, resp, err := client.Projects.ListProjects(&gitlab.ListProjectsOptions{})
if resp == nil || resp.StatusCode != http.StatusOK {
return sce.WithMessage(sce.ErrRepoUnreachable,
fmt.Sprintf("couldn't reach gitlab instance at %s", r.host),
fmt.Sprintf("couldn't reach gitlab instance at %s: %v", r.host, err),
)
}
if err != nil {
@ -165,6 +166,11 @@ func (r *repoURL) Metadata() []string {
return r.metadata
}
// Path() implements RepoClient.Path.
func (r *repoURL) Path() string {
return fmt.Sprintf("%s/%s", r.owner, r.project)
}
// MakeGitlabRepo takes input of forms in parse and returns and implementation
// of clients.Repo interface.
func MakeGitlabRepo(input string) (clients.Repo, error) {

View File

@ -46,7 +46,7 @@ func TestRepoURL_IsValid(t *testing.T) {
name: "GitHub project with 'gitlab.' in the title",
expected: repoURL{
scheme: "http",
host: "http://github.com",
host: "github.com",
owner: "foo",
project: "gitlab.test",
},
@ -56,7 +56,7 @@ func TestRepoURL_IsValid(t *testing.T) {
{
name: "valid gitlab project",
expected: repoURL{
host: "https://gitlab.com",
host: "gitlab.com",
owner: "ossf-test",
project: "scorecard-check-binary-artifacts-e2e",
},
@ -66,7 +66,7 @@ func TestRepoURL_IsValid(t *testing.T) {
{
name: "valid gitlab project",
expected: repoURL{
host: "https://gitlab.com",
host: "gitlab.com",
owner: "ossf-test",
project: "scorecard-check-binary-artifacts-e2e",
},
@ -77,7 +77,7 @@ func TestRepoURL_IsValid(t *testing.T) {
name: "valid https address with trailing slash",
expected: repoURL{
scheme: "https",
host: "https://gitlab.haskell.org",
host: "gitlab.haskell.org",
owner: "haskell",
project: "filepath",
},
@ -88,7 +88,7 @@ func TestRepoURL_IsValid(t *testing.T) {
name: "valid hosted gitlab project",
expected: repoURL{
scheme: "https",
host: "https://salsa.debian.org",
host: "salsa.debian.org",
owner: "webmaster-team",
project: "webml",
},
@ -115,6 +115,9 @@ func TestRepoURL_IsValid(t *testing.T) {
if err := r.IsValid(); (err != nil) != tt.wantErr {
t.Errorf("repoURL.IsValid() error = %v, wantErr %v", err, tt.wantErr)
}
if tt.wantErr {
return
}
t.Log(r.URI())
if !tt.wantErr && !cmp.Equal(tt.expected, r, cmpopts.IgnoreUnexported(repoURL{})) {
t.Logf("expected: %s GOT: %s", tt.expected.host, r.host)

View File

@ -120,7 +120,7 @@ func (handler *tarballHandler) setup() error {
}
func (handler *tarballHandler) getTarball() error {
url := fmt.Sprintf("%s/api/v4/projects/%d/repository/archive.tar.gz?sha=%s",
url := fmt.Sprintf("https://%s/api/v4/projects/%d/repository/archive.tar.gz?sha=%s",
handler.repourl.Host(), handler.repo.ID, handler.commitSHA)
// Create a temp file. This automatically appends a random number to the name.
@ -138,7 +138,7 @@ func (handler *tarballHandler) getTarball() error {
return fmt.Errorf("gitlab.apiFunction: %w", err)
}
// Gitlab url for pulling combined ci
url = fmt.Sprintf("%s/api/v4/projects/%d/ci/lint",
url = fmt.Sprintf("https://%s/api/v4/projects/%d/ci/lint",
handler.repourl.Host(), handler.repo.ID)
ciFile, err := os.CreateTemp(tempDir, "gitlabscorecard_lint*.json")
if err != nil {

View File

@ -69,6 +69,11 @@ func (r *repoLocal) AppendMetadata(m ...string) {
r.metadata = append(r.metadata, m...)
}
// Path() implements RepoClient.Path.
func (r *repoLocal) Path() string {
return r.path
}
// MakeLocalDirRepo returns an implementation of clients.Repo interface.
func MakeLocalDirRepo(pathfn string) (clients.Repo, error) {
p := path.Clean(pathfn)

View File

@ -0,0 +1,65 @@
// 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.
//
// Code generated by MockGen. DO NOT EDIT.
// Source: internal/packageclient/depsdev.go
// Package mock_packageclient is a generated GoMock package.
package mockrepo
import (
context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
packageclient "github.com/ossf/scorecard/v5/internal/packageclient"
)
// MockProjectPackageClient is a mock of ProjectPackageClient interface.
type MockProjectPackageClient struct {
ctrl *gomock.Controller
recorder *MockProjectPackageClientMockRecorder
}
// MockProjectPackageClientMockRecorder is the mock recorder for MockProjectPackageClient.
type MockProjectPackageClientMockRecorder struct {
mock *MockProjectPackageClient
}
// NewMockProjectPackageClient creates a new mock instance.
func NewMockProjectPackageClient(ctrl *gomock.Controller) *MockProjectPackageClient {
mock := &MockProjectPackageClient{ctrl: ctrl}
mock.recorder = &MockProjectPackageClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockProjectPackageClient) EXPECT() *MockProjectPackageClientMockRecorder {
return m.recorder
}
// GetProjectPackageVersions mocks base method.
func (m *MockProjectPackageClient) GetProjectPackageVersions(ctx context.Context, host, project string) (*packageclient.ProjectPackageVersions, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetProjectPackageVersions", ctx, host, project)
ret0, _ := ret[0].(*packageclient.ProjectPackageVersions)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetProjectPackageVersions indicates an expected call of GetProjectPackageVersions.
func (mr *MockProjectPackageClientMockRecorder) GetProjectPackageVersions(ctx, host, project interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProjectPackageVersions", reflect.TypeOf((*MockProjectPackageClient)(nil).GetProjectPackageVersions), ctx, host, project)
}

View File

@ -106,6 +106,20 @@ func (mr *MockRepoMockRecorder) Metadata() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Metadata", reflect.TypeOf((*MockRepo)(nil).Metadata))
}
// Path mocks base method.
func (m *MockRepo) Path() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Path")
ret0, _ := ret[0].(string)
return ret0
}
// Path indicates an expected call of Path.
func (mr *MockRepoMockRecorder) Path() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Path", reflect.TypeOf((*MockRepo)(nil).Path))
}
// String mocks base method.
func (m *MockRepo) String() string {
m.ctrl.T.Helper()

View File

@ -16,9 +16,16 @@ package clients
// Repo interface uniquely identifies a repo.
type Repo interface {
// Path returns the specifier of the repository within its forge
Path() string
// URI returns the fully qualified address of the repository
URI() string
// Host returns the web domain of the repository
Host() string
// String returns a string representation of the repository URI
String() string
// IsValid returns whether the repository path on the forge is properly
// -formatted (GitHub), or that the repository exists (GitLab)
IsValid() error
Metadata() []string
AppendMetadata(metadata ...string)

View File

@ -53,7 +53,7 @@ type Runner struct {
func New(enabledChecks []string) Runner {
ctx := context.Background()
logger := log.NewLogger(log.DefaultLevel)
gitlabClient, err := gitlabrepo.CreateGitlabClient(ctx, "https://gitlab.com")
gitlabClient, err := gitlabrepo.CreateGitlabClient(ctx, "gitlab.com")
if err != nil {
logger.Error(err, "creating gitlab client")
}
@ -65,6 +65,7 @@ func New(enabledChecks []string) Runner {
ossFuzz: ossfuzz.CreateOSSFuzzClient(ossfuzz.StatusURL),
cii: clients.DefaultCIIBestPracticesClient(),
vuln: clients.DefaultVulnerabilitiesClient(),
deps: packageclient.CreateDepsDevClient(),
enabledChecks: parseChecks(enabledChecks),
}
}

View File

@ -124,7 +124,7 @@ func newScorecardWorker() (*ScorecardWorker, error) {
sw.logger = log.NewCronLogger(log.InfoLevel)
sw.githubClient = githubrepo.CreateGithubRepoClient(sw.ctx, sw.logger)
// TODO(raghavkaul): Read GitLab auth token from environment
if sw.gitlabClient, err = gitlabrepo.CreateGitlabClient(sw.ctx, "https://gitlab.com"); err != nil {
if sw.gitlabClient, err = gitlabrepo.CreateGitlabClient(sw.ctx, "gitlab.com"); err != nil {
return nil, fmt.Errorf("gitlabrepo.CreateGitlabClient: %w", err)
}
sw.ciiClient = clients.BlobCIIBestPracticesClient(ciiDataBucketURL)
@ -138,6 +138,8 @@ func newScorecardWorker() (*ScorecardWorker, error) {
sw.vulnsClient = clients.DefaultVulnerabilitiesClient()
}
sw.projectClient = packageclient.CreateDepsDevClient()
if sw.exporter, err = startMetricsExporter(); err != nil {
return nil, fmt.Errorf("startMetricsExporter: %w", err)
}

View File

@ -24,6 +24,7 @@ import (
"github.com/ossf/scorecard/v5/checks"
"github.com/ossf/scorecard/v5/clients"
"github.com/ossf/scorecard/v5/clients/githubrepo"
"github.com/ossf/scorecard/v5/internal/packageclient"
scut "github.com/ossf/scorecard/v5/utests"
)
@ -34,13 +35,15 @@ var _ = Describe("E2E TEST:"+checks.CheckSignedReleases, func() {
repo, err := githubrepo.MakeGithubRepo("ossf-tests/scorecard-check-signed-releases-e2e")
Expect(err).Should(BeNil())
repoClient := githubrepo.CreateGithubRepoClient(context.Background(), logger)
projClient := packageclient.CreateDepsDevClient()
err = repoClient.InitRepo(repo, clients.HeadSHA, 0)
Expect(err).Should(BeNil())
req := checker.CheckRequest{
Ctx: context.Background(),
RepoClient: repoClient,
Repo: repo,
Dlogger: &dl,
Ctx: context.Background(),
RepoClient: repoClient,
ProjectClient: projClient,
Repo: repo,
Dlogger: &dl,
}
expected := scut.TestReturn{
Error: nil,

View File

@ -17,6 +17,7 @@ package packageclient
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
@ -57,6 +58,11 @@ func CreateDepsDevClient() ProjectPackageClient {
}
}
var (
ErrDepsDevAPI = errors.New("deps.dev")
ErrProjNotFoundInDepsDev = errors.New("project not found in deps.dev")
)
func (d depsDevClient) GetProjectPackageVersions(
ctx context.Context, host, project string,
) (*ProjectPackageVersions, error) {
@ -74,15 +80,23 @@ func (d depsDevClient) GetProjectPackageVersions(
}
defer resp.Body.Close()
var res ProjectPackageVersions
if resp.StatusCode == http.StatusNotFound {
return nil, ErrProjNotFoundInDepsDev
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%w: %s", ErrDepsDevAPI, resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("resp.Body.Read: %w", err)
}
var res ProjectPackageVersions
err = json.Unmarshal(body, &res)
if err != nil {
return nil, fmt.Errorf("json.Unmarshal from deps.dev: %w", err)
return nil, fmt.Errorf("deps.dev json.Unmarshal: %w", err)
}
return &res, nil

View File

@ -150,6 +150,7 @@ func runScorecard(ctx context.Context,
OssFuzzRepo: ossFuzzRepoClient,
CIIClient: ciiClient,
VulnerabilitiesClient: vulnsClient,
ProjectClient: projectClient,
Repo: repo,
RawResults: &ret.RawResults,
}