github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/pkg/scanners/cloudformation/parser/parser.go (about)

     1  package parser
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"io/fs"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/khulnasoft-lab/defsec/pkg/debug"
    13  
    14  	"github.com/khulnasoft-lab/defsec/pkg/detection"
    15  	"github.com/khulnasoft-lab/defsec/pkg/scanners/options"
    16  
    17  	"github.com/liamg/jfather"
    18  	"gopkg.in/yaml.v3"
    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, "cloudformation", "parser")
    30  }
    31  
    32  func (p *Parser) SetSkipRequiredCheck(b bool) {
    33  	p.skipRequired = b
    34  }
    35  
    36  func New(options ...options.ParserOption) *Parser {
    37  	p := &Parser{}
    38  	for _, option := range options {
    39  		option(p)
    40  	}
    41  	return p
    42  }
    43  
    44  func (p *Parser) ParseFS(ctx context.Context, target fs.FS, dir string) (FileContexts, error) {
    45  	var contexts FileContexts
    46  	if err := fs.WalkDir(target, filepath.ToSlash(dir), func(path string, entry fs.DirEntry, err error) error {
    47  		select {
    48  		case <-ctx.Done():
    49  			return ctx.Err()
    50  		default:
    51  		}
    52  		if err != nil {
    53  			return err
    54  		}
    55  		if entry.IsDir() {
    56  			return nil
    57  		}
    58  
    59  		if !p.Required(target, path) {
    60  			p.debug.Log("not a CloudFormation file, skipping %s", path)
    61  			return nil
    62  		}
    63  
    64  		c, err := p.ParseFile(ctx, target, path)
    65  		if err != nil {
    66  			p.debug.Log("Error parsing file '%s': %s", path, err)
    67  			return nil
    68  		}
    69  		contexts = append(contexts, c)
    70  		return nil
    71  	}); err != nil {
    72  		return nil, err
    73  	}
    74  	return contexts, nil
    75  }
    76  
    77  func (p *Parser) Required(fs fs.FS, path string) bool {
    78  	if p.skipRequired {
    79  		return true
    80  	}
    81  
    82  	f, err := fs.Open(filepath.ToSlash(path))
    83  	if err != nil {
    84  		return false
    85  	}
    86  	defer func() { _ = f.Close() }()
    87  	if data, err := io.ReadAll(f); err == nil {
    88  		return detection.IsType(path, bytes.NewReader(data), detection.FileTypeCloudFormation)
    89  	}
    90  	return false
    91  
    92  }
    93  
    94  func (p *Parser) ParseFile(ctx context.Context, fs fs.FS, path string) (context *FileContext, err error) {
    95  
    96  	defer func() {
    97  		if e := recover(); e != nil {
    98  			err = fmt.Errorf("panic during parse: %s", e)
    99  		}
   100  	}()
   101  
   102  	select {
   103  	case <-ctx.Done():
   104  		return nil, ctx.Err()
   105  	default:
   106  	}
   107  
   108  	sourceFmt := YamlSourceFormat
   109  	if strings.HasSuffix(strings.ToLower(path), ".json") {
   110  		sourceFmt = JsonSourceFormat
   111  	}
   112  
   113  	f, err := fs.Open(filepath.ToSlash(path))
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	defer func() { _ = f.Close() }()
   118  
   119  	content, err := io.ReadAll(f)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	lines := strings.Split(string(content), "\n")
   125  
   126  	context = &FileContext{
   127  		filepath:     path,
   128  		lines:        lines,
   129  		SourceFormat: sourceFmt,
   130  	}
   131  
   132  	if strings.HasSuffix(strings.ToLower(path), ".json") {
   133  		if err := jfather.Unmarshal(content, context); err != nil {
   134  			return nil, NewErrInvalidContent(path, err)
   135  		}
   136  	} else {
   137  		if err := yaml.Unmarshal(content, context); err != nil {
   138  			return nil, NewErrInvalidContent(path, err)
   139  		}
   140  	}
   141  
   142  	context.lines = lines
   143  	context.SourceFormat = sourceFmt
   144  	context.filepath = path
   145  
   146  	p.debug.Log("Context loaded from source %s", path)
   147  
   148  	// the context must be set to conditions before resources
   149  	for _, c := range context.Conditions {
   150  		c.setContext(context)
   151  	}
   152  
   153  	for name, r := range context.Resources {
   154  		r.ConfigureResource(name, fs, path, context)
   155  	}
   156  
   157  	return context, nil
   158  }