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

     1  package parser
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"io/fs"
    10  	"path/filepath"
    11  	"regexp"
    12  	"strings"
    13  
    14  	"gopkg.in/yaml.v3"
    15  
    16  	"github.com/aquasecurity/defsec/pkg/debug"
    17  	"github.com/aquasecurity/defsec/pkg/scanners/options"
    18  	"github.com/aquasecurity/trivy-iac/pkg/detection"
    19  )
    20  
    21  var _ options.ConfigurableParser = (*Parser)(nil)
    22  
    23  type Parser struct {
    24  	debug        debug.Logger
    25  	skipRequired bool
    26  }
    27  
    28  func (p *Parser) SetDebugWriter(writer io.Writer) {
    29  	p.debug = debug.New(writer, "kubernetes", "parser")
    30  }
    31  
    32  func (p *Parser) SetSkipRequiredCheck(b bool) {
    33  	p.skipRequired = b
    34  }
    35  
    36  // New creates a new K8s parser
    37  func New(options ...options.ParserOption) *Parser {
    38  	p := &Parser{}
    39  	for _, option := range options {
    40  		option(p)
    41  	}
    42  	return p
    43  }
    44  
    45  func (p *Parser) ParseFS(ctx context.Context, target fs.FS, path string) (map[string][]interface{}, error) {
    46  	files := make(map[string][]interface{})
    47  	if err := fs.WalkDir(target, filepath.ToSlash(path), func(path string, entry fs.DirEntry, err error) error {
    48  		select {
    49  		case <-ctx.Done():
    50  			return ctx.Err()
    51  		default:
    52  		}
    53  		if err != nil {
    54  			return err
    55  		}
    56  		if entry.IsDir() {
    57  			return nil
    58  		}
    59  		if !p.required(target, path) {
    60  			return nil
    61  		}
    62  		parsed, err := p.ParseFile(ctx, target, path)
    63  		if err != nil {
    64  			p.debug.Log("Parse error in '%s': %s", path, err)
    65  			return nil
    66  		}
    67  		files[path] = parsed
    68  		return nil
    69  	}); err != nil {
    70  		return nil, err
    71  	}
    72  	return files, nil
    73  }
    74  
    75  // ParseFile parses Kubernetes manifest from the provided filesystem path.
    76  func (p *Parser) ParseFile(_ context.Context, fs fs.FS, path string) ([]interface{}, error) {
    77  	f, err := fs.Open(filepath.ToSlash(path))
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	defer func() { _ = f.Close() }()
    82  	return p.Parse(f, path)
    83  }
    84  
    85  func (p *Parser) required(fs fs.FS, path string) bool {
    86  	if p.skipRequired {
    87  		return true
    88  	}
    89  	f, err := fs.Open(filepath.ToSlash(path))
    90  	if err != nil {
    91  		return false
    92  	}
    93  	defer func() { _ = f.Close() }()
    94  	if data, err := io.ReadAll(f); err == nil {
    95  		return detection.IsType(path, bytes.NewReader(data), detection.FileTypeKubernetes)
    96  	}
    97  	return false
    98  }
    99  
   100  func (p *Parser) Parse(r io.Reader, path string) ([]interface{}, error) {
   101  
   102  	contents, err := io.ReadAll(r)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	if len(contents) == 0 {
   108  		return nil, nil
   109  	}
   110  
   111  	if strings.TrimSpace(string(contents))[0] == '{' {
   112  		var target interface{}
   113  		if err := json.Unmarshal(contents, &target); err != nil {
   114  			return nil, err
   115  		}
   116  		return []interface{}{target}, nil
   117  	}
   118  
   119  	var results []interface{}
   120  
   121  	re := regexp.MustCompile(`(?m:^---\r?\n)`)
   122  	pos := 0
   123  	for _, partial := range re.Split(string(contents), -1) {
   124  		var result Manifest
   125  		result.Path = path
   126  		if err := yaml.Unmarshal([]byte(partial), &result); err != nil {
   127  			return nil, fmt.Errorf("unmarshal yaml: %w", err)
   128  		}
   129  		if result.Content != nil {
   130  			result.Content.Offset = pos
   131  			results = append(results, result.ToRego())
   132  		}
   133  		pos += len(strings.Split(partial, "\n"))
   134  	}
   135  
   136  	return results, nil
   137  }