mirror of
https://github.com/aquasecurity/trivy.git
synced 2026-01-31 22:03:32 +08:00
195 lines
5.4 KiB
Go
195 lines
5.4 KiB
Go
package github
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/aquasecurity/trivy/pkg/clock"
|
|
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
|
"github.com/aquasecurity/trivy/pkg/purl"
|
|
"github.com/aquasecurity/trivy/pkg/types"
|
|
)
|
|
|
|
const (
|
|
DirectRelationship string = "direct"
|
|
IndirectRelationship string = "indirect"
|
|
RuntimeScope string = "runtime"
|
|
DevelopmentScope string = "development"
|
|
)
|
|
|
|
type Package struct {
|
|
PackageUrl string `json:"package_url,omitempty"`
|
|
Relationship string `json:"relationship,omitempty"`
|
|
Dependencies []string `json:"dependencies,omitempty"`
|
|
Scope string `json:"scope,omitempty"`
|
|
Metadata Metadata `json:"metadata,omitempty"`
|
|
}
|
|
|
|
type File struct {
|
|
SrcLocation string `json:"source_location,omitempty"`
|
|
}
|
|
|
|
type Metadata map[string]any
|
|
|
|
type Manifest struct {
|
|
Name string `json:"name,omitempty"`
|
|
File *File `json:"file,omitempty"`
|
|
Metadata Metadata `json:"metadata,omitempty"`
|
|
Resolved map[string]Package `json:"resolved,omitempty"`
|
|
}
|
|
|
|
type Job struct {
|
|
Correlator string `json:"correlator,omitempty"`
|
|
Id string `json:"id,omitempty"`
|
|
}
|
|
type Detector struct {
|
|
Name string `json:"name"`
|
|
Version string `json:"version"`
|
|
Url string `json:"url"`
|
|
}
|
|
|
|
type DependencySnapshot struct {
|
|
Version int `json:"version"`
|
|
Detector Detector `json:"detector"`
|
|
Metadata Metadata `json:"metadata,omitempty"`
|
|
Ref string `json:"ref,omitempty"`
|
|
Sha string `json:"sha,omitempty"`
|
|
Job *Job `json:"job,omitempty"`
|
|
Scanned string `json:"scanned,omitempty"`
|
|
Manifests map[string]Manifest `json:"manifests,omitempty"`
|
|
}
|
|
|
|
// Writer generates JSON for GitHub Dependency Snapshots
|
|
type Writer struct {
|
|
Output io.Writer
|
|
Version string
|
|
}
|
|
|
|
func (w Writer) Write(ctx context.Context, report types.Report) error {
|
|
snapshot := &DependencySnapshot{}
|
|
|
|
// use now() method that can be overwritten while integration tests run
|
|
snapshot.Scanned = clock.Now(ctx).Format(time.RFC3339)
|
|
snapshot.Detector = Detector{
|
|
Name: "trivy",
|
|
Version: w.Version,
|
|
Url: "https://github.com/aquasecurity/trivy",
|
|
}
|
|
snapshot.Version = 0 // The version of the repository snapshot submission.
|
|
|
|
snapshot.Ref = os.Getenv("GITHUB_REF")
|
|
snapshot.Sha = os.Getenv("GITHUB_SHA")
|
|
|
|
snapshot.Job = &Job{
|
|
Correlator: fmt.Sprintf("%s_%s", os.Getenv("GITHUB_WORKFLOW"), os.Getenv("GITHUB_JOB")),
|
|
Id: os.Getenv("GITHUB_RUN_ID"),
|
|
}
|
|
|
|
snapshot.Metadata = getMetadata(report)
|
|
|
|
manifests := make(map[string]Manifest)
|
|
|
|
for _, result := range report.Results {
|
|
if result.Packages == nil {
|
|
continue
|
|
}
|
|
|
|
manifest := Manifest{}
|
|
manifest.Name = string(result.Type)
|
|
// show path for language-specific packages only
|
|
if result.Class == types.ClassLangPkg {
|
|
if report.ArtifactType == ftypes.TypeContainerImage {
|
|
// `RepoDigests` ~= <registry>/<image_name>@sha256:<image_hash>
|
|
// `RepoTag` ~= <registry>/<image_name>:<image_tag>
|
|
// By concatenating the hash from `RepoDigests` at the end of `RepoTag` we get all the information
|
|
imageReference := strings.Join(report.Metadata.RepoTags, ", ")
|
|
imageWithHash := strings.Join(report.Metadata.RepoDigests, ", ")
|
|
_, imageHash, found := strings.Cut(imageWithHash, "@")
|
|
if found {
|
|
imageReference += "@" + imageHash
|
|
}
|
|
// Replacing `source_location` in manifest by the image name, tag and hash
|
|
manifest.File = &File{
|
|
SrcLocation: imageReference,
|
|
}
|
|
|
|
} else {
|
|
manifest.File = &File{
|
|
SrcLocation: result.Target,
|
|
}
|
|
}
|
|
}
|
|
|
|
resolved := make(map[string]Package)
|
|
|
|
for _, pkg := range result.Packages {
|
|
var err error
|
|
githubPkg := Package{}
|
|
githubPkg.Scope = RuntimeScope
|
|
githubPkg.Relationship = getPkgRelationshipType(pkg)
|
|
githubPkg.Dependencies = pkg.DependsOn // TODO: replace with PURL
|
|
githubPkg.PackageUrl, err = buildPurl(result.Type, report.Metadata, pkg)
|
|
if err != nil {
|
|
return xerrors.Errorf("unable to build purl for %s: %w", pkg.Name, err)
|
|
}
|
|
|
|
if pkg.FilePath != "" {
|
|
githubPkg.Metadata = Metadata{"source_location": pkg.FilePath}
|
|
}
|
|
|
|
resolved[pkg.Name] = githubPkg
|
|
}
|
|
|
|
manifest.Resolved = resolved
|
|
manifests[result.Target] = manifest
|
|
}
|
|
|
|
snapshot.Manifests = manifests
|
|
|
|
output, err := json.MarshalIndent(snapshot, "", " ")
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to marshal github dependency snapshots: %w", err)
|
|
}
|
|
|
|
if _, err = fmt.Fprint(w.Output, string(output)); err != nil {
|
|
return xerrors.Errorf("failed to write github dependency snapshots: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getMetadata(report types.Report) Metadata {
|
|
metadata := Metadata{}
|
|
if report.Metadata.RepoTags != nil {
|
|
metadata["aquasecurity:trivy:RepoTag"] = strings.Join(report.Metadata.RepoTags, ", ")
|
|
}
|
|
if report.Metadata.RepoDigests != nil {
|
|
metadata["aquasecurity:trivy:RepoDigest"] = strings.Join(report.Metadata.RepoDigests, ", ")
|
|
}
|
|
return metadata
|
|
}
|
|
|
|
func getPkgRelationshipType(pkg ftypes.Package) string {
|
|
if pkg.Relationship == ftypes.RelationshipIndirect {
|
|
return IndirectRelationship
|
|
}
|
|
return DirectRelationship
|
|
}
|
|
|
|
func buildPurl(t ftypes.TargetType, metadata types.Metadata, pkg ftypes.Package) (string, error) {
|
|
packageUrl, err := purl.New(t, metadata, pkg)
|
|
if err != nil {
|
|
return "", xerrors.Errorf("purl error: %w", err)
|
|
}
|
|
if packageUrl == nil {
|
|
return "", nil
|
|
}
|
|
return packageUrl.String(), nil
|
|
}
|