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