mirror of
https://github.com/ossf/scorecard.git
synced 2024-09-19 21:18:09 +03:00
bfaa9febc2
* add projectpackageversions to signed releases raw results Signed-off-by: Raghav Kaul <raghavkaul+github@google.com> * finding: add NewNot* helpers, fix error msg Signed-off-by: Raghav Kaul <raghavkaul+github@google.com> * probe: releasesHaveVerifiedProvenance Signed-off-by: Raghav Kaul <raghavkaul+github@google.com> * logging Signed-off-by: Raghav Kaul <raghavkaul+github@google.com> * fix tests and lint Signed-off-by: Raghav Kaul <raghavkaul+github@google.com> * address comments Signed-off-by: Raghav Kaul <raghavkaul+github@google.com> * remove unused Signed-off-by: Raghav Kaul <raghavkaul+github@google.com> * fix merge conflict Signed-off-by: Raghav Kaul <raghavkaul+github@google.com> --------- Signed-off-by: Raghav Kaul <raghavkaul+github@google.com>
298 lines
8.4 KiB
Go
298 lines
8.4 KiB
Go
// Copyright 2023 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 finding
|
|
|
|
import (
|
|
"embed"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// FileType is the type of a file.
|
|
type FileType int
|
|
|
|
const (
|
|
// FileTypeNone must be `0`.
|
|
FileTypeNone FileType = iota
|
|
// FileTypeSource is for source code files.
|
|
FileTypeSource
|
|
// FileTypeBinary is for binary files.
|
|
FileTypeBinary
|
|
// FileTypeText is for text files.
|
|
FileTypeText
|
|
// FileTypeURL for URLs.
|
|
FileTypeURL
|
|
// FileTypeBinaryVerified for verified binary files.
|
|
FileTypeBinaryVerified
|
|
)
|
|
|
|
// Location represents the location of a finding.
|
|
type Location struct {
|
|
LineStart *uint `json:"lineStart,omitempty"`
|
|
LineEnd *uint `json:"lineEnd,omitempty"`
|
|
Snippet *string `json:"snippet,omitempty"`
|
|
Path string `json:"path"`
|
|
Type FileType `json:"type"`
|
|
}
|
|
|
|
// Outcome is the result of a finding.
|
|
type Outcome string
|
|
|
|
// TODO(#2928): re-visit the finding definitions.
|
|
const (
|
|
// OutcomeFalse indicates the answer to the probe's question is "false" or "no".
|
|
OutcomeFalse Outcome = "False"
|
|
// OutcomeNotAvailable indicates an unavailable outcome,
|
|
// typically because an API call did not return an answer.
|
|
OutcomeNotAvailable Outcome = "NotAvailable"
|
|
// OutcomeError indicates an errors while running.
|
|
// The results could not be determined.
|
|
OutcomeError Outcome = "Error"
|
|
// OutcomeTrue indicates the answer to the probe's question is "true" or "yes".
|
|
OutcomeTrue Outcome = "True"
|
|
// OutcomeNotSupported indicates a non-supported outcome.
|
|
OutcomeNotSupported Outcome = "NotSupported"
|
|
// OutcomeNotApplicable indicates if a finding should not
|
|
// be considered in evaluation.
|
|
OutcomeNotApplicable Outcome = "NotApplicable"
|
|
)
|
|
|
|
// Finding represents a finding.
|
|
type Finding struct {
|
|
Location *Location `json:"location,omitempty"`
|
|
Remediation *Remediation `json:"remediation,omitempty"`
|
|
Values map[string]string `json:"values,omitempty"`
|
|
Probe string `json:"probe"`
|
|
Message string `json:"message"`
|
|
Outcome Outcome `json:"outcome"`
|
|
|
|
// Expected bad outcome, used to determine if Remediation should be set
|
|
badOutcome Outcome
|
|
}
|
|
|
|
// AnonymousFinding is a finding without a corresponding probe ID.
|
|
type AnonymousFinding struct {
|
|
Probe string `json:"probe,omitempty"`
|
|
Finding
|
|
}
|
|
|
|
var errInvalid = errors.New("invalid")
|
|
|
|
// FromBytes creates a finding for a probe given its config file's content.
|
|
func FromBytes(content []byte, probeID string) (*Finding, error) {
|
|
p, err := probeFromBytes(content, probeID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
f := &Finding{
|
|
Probe: p.ID,
|
|
Outcome: OutcomeFalse,
|
|
Remediation: p.Remediation,
|
|
badOutcome: p.RemediateOnOutcome,
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
// New creates a new finding.
|
|
func New(loc embed.FS, probeID string) (*Finding, error) {
|
|
p, err := newProbe(loc, probeID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
f := &Finding{
|
|
Probe: p.ID,
|
|
Outcome: OutcomeFalse,
|
|
Remediation: p.Remediation,
|
|
badOutcome: p.RemediateOnOutcome,
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
// NewWith create a finding with the desired location and outcome.
|
|
func NewWith(efs embed.FS, probeID, text string, loc *Location,
|
|
o Outcome,
|
|
) (*Finding, error) {
|
|
f, err := New(efs, probeID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("finding.New: %w", err)
|
|
}
|
|
|
|
f = f.WithMessage(text).WithOutcome(o).WithLocation(loc)
|
|
return f, nil
|
|
}
|
|
|
|
// NewFalse create a false finding with the desired location.
|
|
func NewFalse(efs embed.FS, probeID, text string, loc *Location,
|
|
) (*Finding, error) {
|
|
return NewWith(efs, probeID, text, loc, OutcomeFalse)
|
|
}
|
|
|
|
// NewNotApplicable create a finding with a NotApplicable outcome and the desired location.
|
|
func NewNotApplicable(efs embed.FS, probeID, text string, loc *Location,
|
|
) (*Finding, error) {
|
|
return NewWith(efs, probeID, text, loc, OutcomeNotApplicable)
|
|
}
|
|
|
|
// NewNotAvailable create a finding with a NotAvailable outcome and the desired location.
|
|
func NewNotAvailable(efs embed.FS, probeID, text string, loc *Location,
|
|
) (*Finding, error) {
|
|
return NewWith(efs, probeID, text, loc, OutcomeNotAvailable)
|
|
}
|
|
|
|
// NewNotSupported create a finding with a NotSupported outcome and the desired location.
|
|
func NewNotSupported(efs embed.FS, probeID, text string, loc *Location,
|
|
) (*Finding, error) {
|
|
return NewWith(efs, probeID, text, loc, OutcomeNotSupported)
|
|
}
|
|
|
|
// NewTrue create a true finding with the desired location.
|
|
func NewTrue(efs embed.FS, probeID, text string, loc *Location,
|
|
) (*Finding, error) {
|
|
return NewWith(efs, probeID, text, loc, OutcomeTrue)
|
|
}
|
|
|
|
// Anonymize removes the probe ID and outcome
|
|
// from the finding. It is a temporary solution
|
|
// to integrate the code in the details without exposing
|
|
// too much information.
|
|
func (f *Finding) Anonymize() *AnonymousFinding {
|
|
return &AnonymousFinding{Finding: *f}
|
|
}
|
|
|
|
// WithMessage adds a message to an existing finding.
|
|
// No copy is made.
|
|
func (f *Finding) WithMessage(text string) *Finding {
|
|
f.Message = text
|
|
return f
|
|
}
|
|
|
|
// UniqueProbesEqual checks the probe names present in a list of findings
|
|
// and compare them against an expected list.
|
|
func UniqueProbesEqual(findings []Finding, probes []string) bool {
|
|
// Collect unique probes from findings.
|
|
fm := make(map[string]bool)
|
|
for i := range findings {
|
|
f := &findings[i]
|
|
fm[f.Probe] = true
|
|
}
|
|
// Collect probes from list.
|
|
pm := make(map[string]bool)
|
|
for i := range probes {
|
|
p := &probes[i]
|
|
pm[*p] = true
|
|
}
|
|
return reflect.DeepEqual(pm, fm)
|
|
}
|
|
|
|
// WithLocation adds a location to an existing finding.
|
|
// No copy is made.
|
|
func (f *Finding) WithLocation(loc *Location) *Finding {
|
|
f.Location = loc
|
|
if f.Remediation != nil && f.Location != nil {
|
|
// Replace location data.
|
|
f.Remediation.Text = strings.ReplaceAll(f.Remediation.Text,
|
|
"${{ finding.location.path }}", f.Location.Path)
|
|
f.Remediation.Markdown = strings.ReplaceAll(f.Remediation.Markdown,
|
|
"${{ finding.location.path }}", f.Location.Path)
|
|
}
|
|
return f
|
|
}
|
|
|
|
// WithValues sets the values to an existing finding.
|
|
// No copy is made.
|
|
func (f *Finding) WithValues(values map[string]string) *Finding {
|
|
f.Values = values
|
|
return f
|
|
}
|
|
|
|
// WithPatch adds a patch to an existing finding.
|
|
// No copy is made.
|
|
func (f *Finding) WithPatch(patch *string) *Finding {
|
|
f.Remediation.Patch = patch
|
|
// NOTE: we will update the remediation section
|
|
// using patch information, e.g. ${{ patch.content }}.
|
|
return f
|
|
}
|
|
|
|
// WithOutcome adds an outcome to an existing finding.
|
|
// No copy is made.
|
|
func (f *Finding) WithOutcome(o Outcome) *Finding {
|
|
f.Outcome = o
|
|
// only bad outcomes need remediation, clear if unneeded
|
|
if o != f.badOutcome {
|
|
f.Remediation = nil
|
|
}
|
|
|
|
return f
|
|
}
|
|
|
|
// WithRemediationMetadata adds remediation metadata to an existing finding.
|
|
// No copy is made.
|
|
func (f *Finding) WithRemediationMetadata(values map[string]string) *Finding {
|
|
if f.Remediation != nil {
|
|
// Replace all dynamic values.
|
|
for k, v := range values {
|
|
// Replace metadata.
|
|
f.Remediation.Text = strings.ReplaceAll(f.Remediation.Text,
|
|
fmt.Sprintf("${{ metadata.%s }}", k), v)
|
|
f.Remediation.Markdown = strings.ReplaceAll(f.Remediation.Markdown,
|
|
fmt.Sprintf("${{ metadata.%s }}", k), v)
|
|
}
|
|
}
|
|
return f
|
|
}
|
|
|
|
// WithValue adds a value to f.Values.
|
|
// No copy is made.
|
|
func (f *Finding) WithValue(k, v string) *Finding {
|
|
if f.Values == nil {
|
|
f.Values = make(map[string]string)
|
|
}
|
|
f.Values[k] = v
|
|
return f
|
|
}
|
|
|
|
// UnmarshalYAML is a custom unmarshalling function
|
|
// to transform the string into an enum.
|
|
func (o *Outcome) UnmarshalYAML(n *yaml.Node) error {
|
|
var str string
|
|
if err := n.Decode(&str); err != nil {
|
|
return fmt.Errorf("decode: %w", err)
|
|
}
|
|
|
|
switch n.Value {
|
|
case "False":
|
|
*o = OutcomeFalse
|
|
case "True":
|
|
*o = OutcomeTrue
|
|
case "NotAvailable":
|
|
*o = OutcomeNotAvailable
|
|
case "NotSupported":
|
|
*o = OutcomeNotSupported
|
|
case "NotApplicable":
|
|
*o = OutcomeNotApplicable
|
|
case "Error":
|
|
*o = OutcomeError
|
|
default:
|
|
return fmt.Errorf("%w: %q", errInvalid, str)
|
|
}
|
|
return nil
|
|
}
|