github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/report/github/github.go (about)

     1  package github
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"strings"
     9  	"time"
    10  
    11  	"golang.org/x/xerrors"
    12  
    13  	"github.com/devseccon/trivy/pkg/clock"
    14  	ftypes "github.com/devseccon/trivy/pkg/fanal/types"
    15  	"github.com/devseccon/trivy/pkg/purl"
    16  	"github.com/devseccon/trivy/pkg/types"
    17  )
    18  
    19  const (
    20  	DirectRelationship   string = "direct"
    21  	IndirectRelationship string = "indirect"
    22  	RuntimeScope         string = "runtime"
    23  	DevelopmentScope     string = "development"
    24  )
    25  
    26  type Package struct {
    27  	PackageUrl   string   `json:"package_url,omitempty"`
    28  	Relationship string   `json:"relationship,omitempty"`
    29  	Dependencies []string `json:"dependencies,omitempty"`
    30  	Scope        string   `json:"scope,omitempty"`
    31  	Metadata     Metadata `json:"metadata,omitempty"`
    32  }
    33  
    34  type File struct {
    35  	SrcLocation string `json:"source_location,omitempty"`
    36  }
    37  
    38  type Metadata map[string]interface{}
    39  
    40  type Manifest struct {
    41  	Name     string             `json:"name,omitempty"`
    42  	File     *File              `json:"file,omitempty"`
    43  	Metadata Metadata           `json:"metadata,omitempty"`
    44  	Resolved map[string]Package `json:"resolved,omitempty"`
    45  }
    46  
    47  type Job struct {
    48  	Correlator string `json:"correlator,omitempty"`
    49  	Id         string `json:"id,omitempty"`
    50  }
    51  type Detector struct {
    52  	Name    string `json:"name"`
    53  	Version string `json:"version"`
    54  	Url     string `json:"url"`
    55  }
    56  
    57  type DependencySnapshot struct {
    58  	Version   int                 `json:"version"`
    59  	Detector  Detector            `json:"detector"`
    60  	Metadata  Metadata            `json:"metadata,omitempty"`
    61  	Ref       string              `json:"ref,omitempty"`
    62  	Sha       string              `json:"sha,omitempty"`
    63  	Job       *Job                `json:"job,omitempty"`
    64  	Scanned   string              `json:"scanned,omitempty"`
    65  	Manifests map[string]Manifest `json:"manifests,omitempty"`
    66  }
    67  
    68  // Writer generates JSON for GitHub Dependency Snapshots
    69  type Writer struct {
    70  	Output  io.Writer
    71  	Version string
    72  }
    73  
    74  func (w Writer) Write(report types.Report) error {
    75  	snapshot := &DependencySnapshot{}
    76  
    77  	// use now() method that can be overwritten while integration tests run
    78  	snapshot.Scanned = clock.Now().Format(time.RFC3339)
    79  	snapshot.Detector = Detector{
    80  		Name:    "trivy",
    81  		Version: w.Version,
    82  		Url:     "https://github.com/devseccon/trivy",
    83  	}
    84  	snapshot.Version = 0 // The version of the repository snapshot submission.
    85  
    86  	snapshot.Ref = os.Getenv("GITHUB_REF")
    87  	snapshot.Sha = os.Getenv("GITHUB_SHA")
    88  
    89  	snapshot.Job = &Job{
    90  		Correlator: fmt.Sprintf("%s_%s", os.Getenv("GITHUB_WORKFLOW"), os.Getenv("GITHUB_JOB")),
    91  		Id:         os.Getenv("GITHUB_RUN_ID"),
    92  	}
    93  
    94  	snapshot.Metadata = getMetadata(report)
    95  
    96  	manifests := make(map[string]Manifest)
    97  
    98  	for _, result := range report.Results {
    99  		if result.Packages == nil {
   100  			continue
   101  		}
   102  
   103  		manifest := Manifest{}
   104  		manifest.Name = string(result.Type)
   105  		// show path for language-specific packages only
   106  		if result.Class == types.ClassLangPkg {
   107  			manifest.File = &File{
   108  				SrcLocation: result.Target,
   109  			}
   110  		}
   111  
   112  		resolved := make(map[string]Package)
   113  
   114  		for _, pkg := range result.Packages {
   115  			var err error
   116  			githubPkg := Package{}
   117  			githubPkg.Scope = RuntimeScope
   118  			githubPkg.Relationship = getPkgRelationshipType(pkg)
   119  			githubPkg.Dependencies = pkg.DependsOn // TODO: replace with PURL
   120  			githubPkg.PackageUrl, err = buildPurl(result.Type, pkg)
   121  			if err != nil {
   122  				return xerrors.Errorf("unable to build purl for %s: %w", pkg.Name, err)
   123  			}
   124  
   125  			resolved[pkg.Name] = githubPkg
   126  		}
   127  
   128  		manifest.Resolved = resolved
   129  		manifests[result.Target] = manifest
   130  	}
   131  
   132  	snapshot.Manifests = manifests
   133  
   134  	output, err := json.MarshalIndent(snapshot, "", "  ")
   135  	if err != nil {
   136  		return xerrors.Errorf("failed to marshal github dependency snapshots: %w", err)
   137  	}
   138  
   139  	if _, err = fmt.Fprint(w.Output, string(output)); err != nil {
   140  		return xerrors.Errorf("failed to write github dependency snapshots: %w", err)
   141  	}
   142  	return nil
   143  }
   144  
   145  func getMetadata(report types.Report) Metadata {
   146  	metadata := Metadata{}
   147  	if report.Metadata.RepoTags != nil {
   148  		metadata["aquasecurity:trivy:RepoTag"] = strings.Join(report.Metadata.RepoTags, ", ")
   149  	}
   150  	if report.Metadata.RepoDigests != nil {
   151  		metadata["aquasecurity:trivy:RepoDigest"] = strings.Join(report.Metadata.RepoDigests, ", ")
   152  	}
   153  	return metadata
   154  }
   155  
   156  func getPkgRelationshipType(pkg ftypes.Package) string {
   157  	if pkg.Indirect {
   158  		return IndirectRelationship
   159  	}
   160  	return DirectRelationship
   161  }
   162  
   163  func buildPurl(t ftypes.TargetType, pkg ftypes.Package) (string, error) {
   164  	packageUrl, err := purl.NewPackageURL(t, types.Metadata{}, pkg)
   165  	if err != nil {
   166  		return "", xerrors.Errorf("purl error: %w", err)
   167  	}
   168  	if packageUrl == nil {
   169  		return "", nil
   170  	}
   171  	return packageUrl.ToString(), nil
   172  }