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

     1  package report
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"os"
     7  	"sort"
     8  	"time"
     9  
    10  	"golang.org/x/xerrors"
    11  
    12  	"github.com/aquasecurity/defsec/pkg/scan"
    13  	"github.com/aquasecurity/tml"
    14  	"github.com/devseccon/trivy/pkg/clock"
    15  	cr "github.com/devseccon/trivy/pkg/compliance/report"
    16  	ftypes "github.com/devseccon/trivy/pkg/fanal/types"
    17  	"github.com/devseccon/trivy/pkg/flag"
    18  	pkgReport "github.com/devseccon/trivy/pkg/report"
    19  	"github.com/devseccon/trivy/pkg/result"
    20  	"github.com/devseccon/trivy/pkg/types"
    21  )
    22  
    23  const (
    24  	tableFormat = "table"
    25  )
    26  
    27  // Report represents an AWS scan report
    28  type Report struct {
    29  	Provider        string
    30  	AccountID       string
    31  	Region          string
    32  	Results         map[string]ResultsAtTime
    33  	ServicesInScope []string
    34  }
    35  
    36  type ResultsAtTime struct {
    37  	Results      types.Results
    38  	CreationTime time.Time
    39  }
    40  
    41  func New(provider, accountID, region string, defsecResults scan.Results, scopedServices []string) *Report {
    42  	return &Report{
    43  		Provider:        provider,
    44  		AccountID:       accountID,
    45  		Results:         ConvertResults(defsecResults, provider, scopedServices),
    46  		ServicesInScope: scopedServices,
    47  		Region:          region,
    48  	}
    49  }
    50  
    51  // Failed returns whether the aws report includes any "failed" results
    52  func (r *Report) Failed() bool {
    53  	for _, set := range r.Results {
    54  		if set.Results.Failed() {
    55  			return true
    56  		}
    57  	}
    58  	return false
    59  }
    60  
    61  // Write writes the results in the give format
    62  func Write(rep *Report, opt flag.Options, fromCache bool) error {
    63  	output, cleanup, err := opt.OutputWriter()
    64  	if err != nil {
    65  		return xerrors.Errorf("failed to create output file: %w", err)
    66  	}
    67  	defer cleanup()
    68  
    69  	if opt.Compliance.Spec.ID != "" {
    70  		return writeCompliance(rep, opt, output)
    71  	}
    72  
    73  	var filtered []types.Result
    74  
    75  	ctx := context.Background()
    76  
    77  	// filter results
    78  	for _, resultsAtTime := range rep.Results {
    79  		for _, res := range resultsAtTime.Results {
    80  			resCopy := res
    81  			if err := result.FilterResult(ctx, &resCopy, result.IgnoreConfig{}, result.FilterOption{
    82  				Severities:         opt.Severities,
    83  				IncludeNonFailures: opt.IncludeNonFailures,
    84  			}); err != nil {
    85  				return err
    86  			}
    87  			sort.Slice(resCopy.Misconfigurations, func(i, j int) bool {
    88  				return resCopy.Misconfigurations[i].CauseMetadata.Resource < resCopy.Misconfigurations[j].CauseMetadata.Resource
    89  			})
    90  			filtered = append(filtered, resCopy)
    91  		}
    92  	}
    93  	sort.Slice(filtered, func(i, j int) bool {
    94  		return filtered[i].Target < filtered[j].Target
    95  	})
    96  
    97  	base := types.Report{
    98  		CreatedAt:    clock.Now(),
    99  		ArtifactName: rep.AccountID,
   100  		ArtifactType: ftypes.ArtifactAWSAccount,
   101  		Results:      filtered,
   102  	}
   103  
   104  	switch opt.Format {
   105  	case tableFormat:
   106  
   107  		// ensure color/formatting is disabled for pipes/non-pty
   108  		var useANSI bool
   109  		if output == os.Stdout {
   110  			if o, err := os.Stdout.Stat(); err == nil {
   111  				useANSI = (o.Mode() & os.ModeCharDevice) == os.ModeCharDevice
   112  			}
   113  		}
   114  		if !useANSI {
   115  			tml.DisableFormatting()
   116  		}
   117  
   118  		switch {
   119  		case len(opt.Services) == 1 && opt.ARN == "":
   120  			if err := writeResourceTable(rep, filtered, output, opt.Services[0]); err != nil {
   121  				return err
   122  			}
   123  		case len(opt.Services) == 1 && opt.ARN != "":
   124  			if err := writeResultsForARN(rep, filtered, output, opt.Services[0], opt.ARN, opt.Severities); err != nil {
   125  				return err
   126  			}
   127  		default:
   128  			if err := writeServiceTable(rep, filtered, output); err != nil {
   129  				return err
   130  			}
   131  		}
   132  
   133  		// render cache info
   134  		if fromCache {
   135  			_ = tml.Fprintf(output, "\n<blue>This scan report was loaded from cached results. If you'd like to run a fresh scan, use --update-cache.</blue>\n")
   136  		}
   137  
   138  		return nil
   139  	default:
   140  		return pkgReport.Write(base, opt)
   141  	}
   142  }
   143  
   144  func writeCompliance(rep *Report, opt flag.Options, output io.Writer) error {
   145  	var crr []types.Results
   146  	for _, r := range rep.Results {
   147  		crr = append(crr, r.Results)
   148  	}
   149  
   150  	complianceReport, err := cr.BuildComplianceReport(crr, opt.Compliance)
   151  	if err != nil {
   152  		return xerrors.Errorf("compliance report build error: %w", err)
   153  	}
   154  
   155  	return cr.Write(complianceReport, cr.Option{
   156  		Format: opt.Format,
   157  		Report: opt.ReportFormat,
   158  		Output: output,
   159  	})
   160  }