Add a Vulnerabilities check. (#628)

Uses OSV to check this.

Fixes #52.
This commit is contained in:
Oliver Chang 2021-06-29 13:09:40 +10:00 committed by GitHub
parent 18b53076d6
commit 34621504fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 188 additions and 0 deletions

View File

@ -59,6 +59,7 @@ SAST | Does the project use static code analysis tools, e.g. [Code
Active | Did the project get any commits in the last 90 days?
Branch-Protection | Does the project use [Branch Protection](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/about-protected-branches) ?
Packaging | Does the project build and publish official packages from CI/CD, e.g. [GitHub Publishing](https://docs.github.com/en/free-pro-team@latest/actions/guides/about-packaging-with-github-actions#workflows-for-publishing-packages) ?
Vulnerabilities | Does the project have unfixed vulnerabilities? Uses the [OSV service](https://osv.dev).
To see detailed information about each check and remediation steps, check out
the [checks documentation page](checks/checks.md).

View File

@ -140,3 +140,10 @@ This check tries to determine if a project's GitHub workflows follow the princip
**Remediation steps**
- Set permissions as `read-all` or `contents: read` as described in GitHub's [documentation](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions).
## Vulnerabilities
This check determines whether if there are open, unfixed vulnerabilities in the project using the [OSV](https://osv.dev) service.
**Remediation steps**
- Fix the vulnerabilities. The details of each vulnerability can be found on <https://osv.dev>.

View File

@ -251,3 +251,11 @@ checks:
- >-
For GitHub, check out the steps
[here](https://docs.github.com/en/github/administering-a-repository/managing-a-branch-protection-rule).
Vulnerabilities:
description: >-
This check determines whether if there are open, unfixed vulnerabilities
in the project using the [OSV](https://osv.dev) service.
remediation:
- >-
Fix the vulnerabilities. The details of each vulnerability can be found
on <https://osv.dev>.

108
checks/vulnerabilities.go Normal file
View File

@ -0,0 +1,108 @@
// 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 checks
import (
"bytes"
"encoding/json"
"errors"
"net/http"
"strings"
"github.com/google/go-github/v32/github"
"github.com/ossf/scorecard/checker"
)
const (
// CheckVulnerabilities is the registered name for the OSV check.
CheckVulnerabilities = "Vulnerabilities"
osvQueryEndpoint = "https://api.osv.dev/v1/query"
)
// ErrNoCommits is the error for when there are no commits found.
var ErrNoCommits = errors.New("no commits found")
type osvQuery struct {
Commit string `json:"commit"`
}
type osvResponse struct {
Vulns []struct {
ID string `json:"id"`
} `json:"vulns"`
}
//nolint:gochecknoinits
func init() {
registerCheck(CheckVulnerabilities, HasUnfixedVulnerabilities)
}
func (resp *osvResponse) getVulnerabilities() []string {
ids := make([]string, 0, len(resp.Vulns))
for _, vuln := range resp.Vulns {
ids = append(ids, vuln.ID)
}
return ids
}
func HasUnfixedVulnerabilities(c *checker.CheckRequest) checker.CheckResult {
commits, _, err := c.Client.Repositories.ListCommits(c.Ctx, c.Owner, c.Repo, &github.CommitsListOptions{
ListOptions: github.ListOptions{
PerPage: 1,
},
})
if err != nil {
return checker.MakeRetryResult(CheckVulnerabilities, err)
}
if len(commits) != 1 || commits[0].SHA == nil {
return checker.MakeInconclusiveResult(CheckVulnerabilities, ErrNoCommits)
}
query, err := json.Marshal(&osvQuery{
Commit: *commits[0].SHA,
})
if err != nil {
panic("!! failed to marshal OSV query.")
}
req, err := http.NewRequestWithContext(c.Ctx, http.MethodPost, osvQueryEndpoint, bytes.NewReader(query))
if err != nil {
return checker.MakeRetryResult(CheckVulnerabilities, err)
}
// Use our own http client as the one from CheckRequest adds GitHub tokens to the headers.
httpClient := &http.Client{}
resp, err := httpClient.Do(req)
if err != nil {
return checker.MakeRetryResult(CheckVulnerabilities, err)
}
defer resp.Body.Close()
var osvResp osvResponse
decoder := json.NewDecoder(resp.Body)
if err := decoder.Decode(&osvResp); err != nil {
return checker.MakeRetryResult(CheckVulnerabilities, err)
}
vulnIDs := osvResp.getVulnerabilities()
if len(vulnIDs) > 0 {
c.Logf("HEAD is vulnerable to %s", strings.Join(vulnIDs, ", "))
return checker.MakeFailResult(CheckVulnerabilities, nil)
}
return checker.MakePassResult(CheckVulnerabilities)
}

View File

@ -0,0 +1,64 @@
// 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.
//nolint:dupl // repeating test cases that are slightly different is acceptable
package e2e
import (
"context"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/ossf/scorecard/checker"
"github.com/ossf/scorecard/checks"
)
var _ = Describe("E2E TEST:Vulnerabilities", func() {
Context("E2E TEST:Validating vulnerabilities status", func() {
It("Should return that there are no vulnerabilities", func() {
l := log{}
checkRequest := checker.CheckRequest{
Ctx: context.Background(),
Client: ghClient,
HTTPClient: httpClient,
RepoClient: nil,
Owner: "ossf",
Repo: "scorecard",
GraphClient: graphClient,
Logf: l.Logf,
}
result := checks.HasUnfixedVulnerabilities(&checkRequest)
Expect(result.Error).Should(BeNil())
Expect(result.Pass).Should(BeTrue())
})
It("Should return that there are vulnerabilities", func() {
l := log{}
checkRequest := checker.CheckRequest{
Ctx: context.Background(),
Client: ghClient,
HTTPClient: httpClient,
RepoClient: nil,
Owner: "oliverchang",
Repo: "open62541",
GraphClient: graphClient,
Logf: l.Logf,
}
result := checks.HasUnfixedVulnerabilities(&checkRequest)
Expect(result.Error).Should(BeNil())
Expect(result.Pass).Should(BeFalse())
})
})
})