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

     1  package commands
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  
     7  	"github.com/spf13/viper"
     8  	"golang.org/x/xerrors"
     9  
    10  	k8sArtifacts "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
    11  	"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
    12  	cmd "github.com/devseccon/trivy/pkg/commands/artifact"
    13  	"github.com/devseccon/trivy/pkg/commands/operation"
    14  	cr "github.com/devseccon/trivy/pkg/compliance/report"
    15  	"github.com/devseccon/trivy/pkg/flag"
    16  	k8sRep "github.com/devseccon/trivy/pkg/k8s"
    17  	"github.com/devseccon/trivy/pkg/k8s/report"
    18  	"github.com/devseccon/trivy/pkg/k8s/scanner"
    19  	"github.com/devseccon/trivy/pkg/log"
    20  	"github.com/devseccon/trivy/pkg/types"
    21  )
    22  
    23  const (
    24  	clusterArtifact = "cluster"
    25  	allArtifact     = "all"
    26  )
    27  
    28  // Run runs a k8s scan
    29  func Run(ctx context.Context, args []string, opts flag.Options) error {
    30  	cluster, err := k8s.GetCluster(
    31  		k8s.WithContext(opts.K8sOptions.ClusterContext),
    32  		k8s.WithKubeConfig(opts.K8sOptions.KubeConfig),
    33  	)
    34  	if err != nil {
    35  		return xerrors.Errorf("failed getting k8s cluster: %w", err)
    36  	}
    37  	ctx, cancel := context.WithTimeout(ctx, opts.Timeout)
    38  	defer cancel()
    39  
    40  	defer func() {
    41  		if errors.Is(err, context.DeadlineExceeded) {
    42  			log.Logger.Warn("Increase --timeout value")
    43  		}
    44  	}()
    45  	opts.K8sVersion = cluster.GetClusterVersion()
    46  	switch args[0] {
    47  	case clusterArtifact:
    48  		return clusterRun(ctx, opts, cluster)
    49  	case allArtifact:
    50  		if opts.Format == types.FormatCycloneDX {
    51  			return xerrors.Errorf("KBOM with CycloneDX format is not supported for all namespace scans")
    52  		}
    53  		return namespaceRun(ctx, opts, cluster)
    54  	default: // resourceArtifact
    55  		if opts.Format == types.FormatCycloneDX {
    56  			return xerrors.Errorf("KBOM with CycloneDX format is not supported for resource scans")
    57  		}
    58  		return resourceRun(ctx, args, opts, cluster)
    59  	}
    60  }
    61  
    62  type runner struct {
    63  	flagOpts flag.Options
    64  	cluster  string
    65  }
    66  
    67  func newRunner(flagOpts flag.Options, cluster string) *runner {
    68  	return &runner{
    69  		flagOpts,
    70  		cluster,
    71  	}
    72  }
    73  
    74  func (r *runner) run(ctx context.Context, artifacts []*k8sArtifacts.Artifact) error {
    75  	runner, err := cmd.NewRunner(ctx, r.flagOpts)
    76  	if err != nil {
    77  		if errors.Is(err, cmd.SkipScan) {
    78  			return nil
    79  		}
    80  		return xerrors.Errorf("init error: %w", err)
    81  	}
    82  	defer func() {
    83  		if err := runner.Close(ctx); err != nil {
    84  			log.Logger.Errorf("failed to close runner: %s", err)
    85  		}
    86  	}()
    87  
    88  	s := scanner.NewScanner(r.cluster, runner, r.flagOpts)
    89  
    90  	// set scanners types by spec
    91  	if r.flagOpts.Compliance.Spec.ID != "" {
    92  		scanners, err := r.flagOpts.Compliance.Scanners()
    93  		if err != nil {
    94  			return xerrors.Errorf("scanner error: %w", err)
    95  		}
    96  		r.flagOpts.ScanOptions.Scanners = scanners
    97  	}
    98  	var rpt report.Report
    99  	rpt, err = s.Scan(ctx, artifacts)
   100  	if err != nil {
   101  		return xerrors.Errorf("k8s scan error: %w", err)
   102  	}
   103  
   104  	output, cleanup, err := r.flagOpts.OutputWriter()
   105  	if err != nil {
   106  		return xerrors.Errorf("failed to create output file: %w", err)
   107  	}
   108  	defer cleanup()
   109  
   110  	if r.flagOpts.Compliance.Spec.ID != "" {
   111  		var scanResults []types.Results
   112  		for _, rss := range rpt.Resources {
   113  			scanResults = append(scanResults, rss.Results)
   114  		}
   115  		complianceReport, err := cr.BuildComplianceReport(scanResults, r.flagOpts.Compliance)
   116  		if err != nil {
   117  			return xerrors.Errorf("compliance report build error: %w", err)
   118  		}
   119  		return cr.Write(complianceReport, cr.Option{
   120  			Format: r.flagOpts.Format,
   121  			Report: r.flagOpts.ReportFormat,
   122  			Output: output,
   123  		})
   124  	}
   125  
   126  	if err := k8sRep.Write(rpt, report.Option{
   127  		Format:     r.flagOpts.Format,
   128  		Report:     r.flagOpts.ReportFormat,
   129  		Output:     output,
   130  		Severities: r.flagOpts.Severities,
   131  		Components: r.flagOpts.Components,
   132  		Scanners:   r.flagOpts.ScanOptions.Scanners,
   133  		APIVersion: r.flagOpts.AppVersion,
   134  	}); err != nil {
   135  		return xerrors.Errorf("unable to write results: %w", err)
   136  	}
   137  
   138  	operation.Exit(r.flagOpts, rpt.Failed())
   139  
   140  	return nil
   141  }
   142  
   143  // Full-cluster scanning with '--format table' without explicit '--report all' is not allowed so that it won't mess up user's terminal.
   144  // To show all the results, user needs to specify "--report all" explicitly
   145  // even though the default value of "--report" is "all".
   146  //
   147  // e.g.
   148  // $ trivy k8s --report all cluster
   149  // $ trivy k8s --report all all
   150  //
   151  // Or they can use "--format json" with implicit "--report all".
   152  //
   153  // e.g. $ trivy k8s --format json cluster // All the results are shown in JSON
   154  //
   155  // Single resource scanning is allowed with implicit "--report all".
   156  //
   157  // e.g. $ trivy k8s pod myapp
   158  func validateReportArguments(opts flag.Options) error {
   159  	if opts.ReportFormat == "all" &&
   160  		!viper.IsSet("report") &&
   161  		opts.Format == "table" {
   162  
   163  		m := "All the results in the table format can mess up your terminal. Use \"--report all\" to tell Trivy to output it to your terminal anyway, or consider \"--report summary\" to show the summary output."
   164  
   165  		return xerrors.New(m)
   166  	}
   167  
   168  	return nil
   169  }