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 }