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