github.com/aquasecurity/trivy-iac@v0.8.1-0.20240127024015-3d8e412cf0ab/pkg/scanners/helm/scanner.go (about)

     1  package helm
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"io/fs"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/aquasecurity/defsec/pkg/debug"
    12  	"github.com/aquasecurity/defsec/pkg/framework"
    13  	"github.com/aquasecurity/defsec/pkg/scan"
    14  	"github.com/aquasecurity/defsec/pkg/types"
    15  	"github.com/liamg/memoryfs"
    16  
    17  	"github.com/aquasecurity/defsec/pkg/rego"
    18  	"github.com/aquasecurity/defsec/pkg/scanners/options"
    19  	"github.com/aquasecurity/trivy-iac/pkg/detection"
    20  	"github.com/aquasecurity/trivy-iac/pkg/scanners"
    21  	"github.com/aquasecurity/trivy-iac/pkg/scanners/helm/parser"
    22  	kparser "github.com/aquasecurity/trivy-iac/pkg/scanners/kubernetes/parser"
    23  )
    24  
    25  var _ scanners.FSScanner = (*Scanner)(nil)
    26  var _ options.ConfigurableScanner = (*Scanner)(nil)
    27  
    28  type Scanner struct {
    29  	policyDirs            []string
    30  	dataDirs              []string
    31  	debug                 debug.Logger
    32  	options               []options.ScannerOption
    33  	parserOptions         []options.ParserOption
    34  	policyReaders         []io.Reader
    35  	loadEmbeddedLibraries bool
    36  	loadEmbeddedPolicies  bool
    37  	policyFS              fs.FS
    38  	skipRequired          bool
    39  	frameworks            []framework.Framework
    40  	spec                  string
    41  }
    42  
    43  func (s *Scanner) SetSpec(spec string) {
    44  	s.spec = spec
    45  }
    46  
    47  func (s *Scanner) SetRegoOnly(bool) {
    48  }
    49  
    50  func (s *Scanner) SetFrameworks(frameworks []framework.Framework) {
    51  	s.frameworks = frameworks
    52  }
    53  
    54  // New creates a new Scanner
    55  func New(options ...options.ScannerOption) *Scanner {
    56  	s := &Scanner{
    57  		options: options,
    58  	}
    59  
    60  	for _, option := range options {
    61  		option(s)
    62  	}
    63  	return s
    64  }
    65  
    66  func (s *Scanner) AddParserOptions(options ...options.ParserOption) {
    67  	s.parserOptions = append(s.parserOptions, options...)
    68  }
    69  
    70  func (s *Scanner) SetUseEmbeddedPolicies(b bool) {
    71  	s.loadEmbeddedPolicies = b
    72  }
    73  
    74  func (s *Scanner) SetUseEmbeddedLibraries(b bool) {
    75  	s.loadEmbeddedLibraries = b
    76  }
    77  
    78  func (s *Scanner) Name() string {
    79  	return "Helm"
    80  }
    81  
    82  func (s *Scanner) SetPolicyReaders(readers []io.Reader) {
    83  	s.policyReaders = readers
    84  }
    85  
    86  func (s *Scanner) SetSkipRequiredCheck(skip bool) {
    87  	s.skipRequired = skip
    88  }
    89  
    90  func (s *Scanner) SetDebugWriter(writer io.Writer) {
    91  	s.debug = debug.New(writer, "helm", "scanner")
    92  }
    93  
    94  func (s *Scanner) SetTraceWriter(_ io.Writer) {
    95  	// handled by rego later - nothing to do for now...
    96  }
    97  
    98  func (s *Scanner) SetPerResultTracingEnabled(_ bool) {
    99  	// handled by rego later - nothing to do for now...
   100  }
   101  
   102  func (s *Scanner) SetPolicyDirs(dirs ...string) {
   103  	s.policyDirs = dirs
   104  }
   105  
   106  func (s *Scanner) SetDataDirs(dirs ...string) {
   107  	s.dataDirs = dirs
   108  }
   109  
   110  func (s *Scanner) SetPolicyNamespaces(namespaces ...string) {
   111  	// handled by rego later - nothing to do for now...
   112  }
   113  
   114  func (s *Scanner) SetPolicyFilesystem(policyFS fs.FS) {
   115  	s.policyFS = policyFS
   116  }
   117  
   118  func (s *Scanner) SetDataFilesystem(_ fs.FS) {}
   119  func (s *Scanner) SetRegoErrorLimit(_ int)   {}
   120  
   121  func (s *Scanner) ScanFS(ctx context.Context, target fs.FS, path string) (scan.Results, error) {
   122  
   123  	var results []scan.Result
   124  	if err := fs.WalkDir(target, path, func(path string, d fs.DirEntry, err error) error {
   125  		select {
   126  		case <-ctx.Done():
   127  			return ctx.Err()
   128  		default:
   129  		}
   130  
   131  		if err != nil {
   132  			return err
   133  		}
   134  
   135  		if d.IsDir() {
   136  			return nil
   137  		}
   138  
   139  		if detection.IsArchive(path) {
   140  			if scanResults, err := s.getScanResults(path, ctx, target); err != nil {
   141  				return err
   142  			} else {
   143  				results = append(results, scanResults...)
   144  			}
   145  		}
   146  
   147  		if strings.HasSuffix(path, "Chart.yaml") {
   148  			if scanResults, err := s.getScanResults(filepath.Dir(path), ctx, target); err != nil {
   149  				return err
   150  			} else {
   151  				results = append(results, scanResults...)
   152  			}
   153  		}
   154  
   155  		return nil
   156  	}); err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	return results, nil
   161  
   162  }
   163  
   164  func (s *Scanner) getScanResults(path string, ctx context.Context, target fs.FS) (results []scan.Result, err error) {
   165  	helmParser := parser.New(path, s.parserOptions...)
   166  
   167  	if err := helmParser.ParseFS(ctx, target, path); err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	chartFiles, err := helmParser.RenderedChartFiles()
   172  	if err != nil { // not valid helm, maybe some other yaml etc., abort
   173  		s.debug.Log("Failed to render Chart files: %s", err)
   174  		return nil, nil
   175  	}
   176  
   177  	regoScanner := rego.NewScanner(types.SourceKubernetes, s.options...)
   178  	policyFS := target
   179  	if s.policyFS != nil {
   180  		policyFS = s.policyFS
   181  	}
   182  	if err := regoScanner.LoadPolicies(s.loadEmbeddedLibraries, s.loadEmbeddedPolicies, policyFS, s.policyDirs, s.policyReaders); err != nil {
   183  		return nil, fmt.Errorf("policies load: %w", err)
   184  	}
   185  	for _, file := range chartFiles {
   186  		file := file
   187  		s.debug.Log("Processing rendered chart file: %s", file.TemplateFilePath)
   188  
   189  		manifests, err := kparser.New().Parse(strings.NewReader(file.ManifestContent), file.TemplateFilePath)
   190  		if err != nil {
   191  			return nil, fmt.Errorf("unmarshal yaml: %w", err)
   192  		}
   193  		for _, manifest := range manifests {
   194  			fileResults, err := regoScanner.ScanInput(ctx, rego.Input{
   195  				Path:     file.TemplateFilePath,
   196  				Contents: manifest,
   197  				FS:       target,
   198  			})
   199  			if err != nil {
   200  				return nil, fmt.Errorf("scanning error: %w", err)
   201  			}
   202  
   203  			if len(fileResults) > 0 {
   204  				renderedFS := memoryfs.New()
   205  				if err := renderedFS.MkdirAll(filepath.Dir(file.TemplateFilePath), fs.ModePerm); err != nil {
   206  					return nil, err
   207  				}
   208  				if err := renderedFS.WriteLazyFile(file.TemplateFilePath, func() (io.Reader, error) {
   209  					return strings.NewReader(file.ManifestContent), nil
   210  				}, fs.ModePerm); err != nil {
   211  					return nil, err
   212  				}
   213  				fileResults.SetSourceAndFilesystem(helmParser.ChartSource, renderedFS, detection.IsArchive(helmParser.ChartSource))
   214  			}
   215  
   216  			results = append(results, fileResults...)
   217  		}
   218  
   219  	}
   220  	return results, nil
   221  }