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

     1  package parser
     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/debug"
    12  	"github.com/khulnasoft-lab/defsec/pkg/scanners/azure"
    13  	"github.com/khulnasoft-lab/defsec/pkg/scanners/azure/resolver"
    14  
    15  	"github.com/khulnasoft-lab/defsec/pkg/scanners/options"
    16  
    17  	"github.com/khulnasoft-lab/defsec/pkg/types"
    18  
    19  	"github.com/khulnasoft-lab/defsec/pkg/scanners/azure/arm/parser/armjson"
    20  )
    21  
    22  type Parser struct {
    23  	targetFS     fs.FS
    24  	skipRequired bool
    25  	debug        debug.Logger
    26  }
    27  
    28  func (p *Parser) SetDebugWriter(writer io.Writer) {
    29  	p.debug = debug.New(writer, "azure", "arm")
    30  }
    31  
    32  func (p *Parser) SetSkipRequiredCheck(b bool) {
    33  	p.skipRequired = b
    34  }
    35  
    36  func New(targetFS fs.FS, opts ...options.ParserOption) *Parser {
    37  	p := &Parser{
    38  		targetFS: targetFS,
    39  	}
    40  	for _, opt := range opts {
    41  		opt(p)
    42  	}
    43  	return p
    44  }
    45  
    46  func (p *Parser) ParseFS(ctx context.Context, dir string) ([]azure.Deployment, error) {
    47  
    48  	var deployments []azure.Deployment
    49  
    50  	if err := fs.WalkDir(p.targetFS, dir, func(path string, entry fs.DirEntry, err error) error {
    51  		if err != nil {
    52  			return err
    53  		}
    54  		select {
    55  		case <-ctx.Done():
    56  			return ctx.Err()
    57  		default:
    58  		}
    59  		if entry.IsDir() {
    60  			return nil
    61  		}
    62  		if !p.Required(path) {
    63  			return nil
    64  		}
    65  		f, err := p.targetFS.Open(path)
    66  		if err != nil {
    67  			return err
    68  		}
    69  		defer f.Close()
    70  		deployment, err := p.parseFile(f, path)
    71  		if err != nil {
    72  			return err
    73  		}
    74  		deployments = append(deployments, *deployment)
    75  		return nil
    76  	}); err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	return deployments, nil
    81  }
    82  
    83  func (p *Parser) Required(path string) bool {
    84  	if p.skipRequired {
    85  		return true
    86  	}
    87  	if !strings.HasSuffix(path, ".json") {
    88  		return false
    89  	}
    90  	data, err := fs.ReadFile(p.targetFS, path)
    91  	if err != nil {
    92  		return false
    93  	}
    94  	var template Template
    95  	root := types.NewMetadata(
    96  		types.NewRange(filepath.Base(path), 0, 0, "", p.targetFS),
    97  		"",
    98  	)
    99  	if err := armjson.Unmarshal(data, &template, &root); err != nil {
   100  		p.debug.Log("Error scanning %s: %s", path, err)
   101  		return false
   102  	}
   103  
   104  	if template.Schema.Kind != azure.KindString {
   105  		return false
   106  	}
   107  
   108  	return strings.HasPrefix(template.Schema.AsString(), "https://schema.management.azure.com")
   109  }
   110  
   111  func (p *Parser) parseFile(r io.Reader, filename string) (*azure.Deployment, error) {
   112  	var template Template
   113  	data, err := io.ReadAll(r)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	root := types.NewMetadata(
   118  		types.NewRange(filename, 0, 0, "", p.targetFS),
   119  		"",
   120  	).WithInternal(resolver.NewResolver())
   121  
   122  	if err := armjson.Unmarshal(data, &template, &root); err != nil {
   123  		return nil, fmt.Errorf("failed to parse template: %w", err)
   124  	}
   125  	return p.convertTemplate(template), nil
   126  }
   127  
   128  func (p *Parser) convertTemplate(template Template) *azure.Deployment {
   129  
   130  	deployment := azure.Deployment{
   131  		Metadata:    template.Metadata,
   132  		TargetScope: azure.ScopeResourceGroup, // TODO: override from --resource-group?
   133  		Parameters:  nil,
   134  		Variables:   nil,
   135  		Resources:   nil,
   136  		Outputs:     nil,
   137  	}
   138  
   139  	if r, ok := template.Metadata.Internal().(resolver.Resolver); ok {
   140  		r.SetDeployment(&deployment)
   141  	}
   142  
   143  	// TODO: the references passed here should probably not be the name - maybe params.NAME.DefaultValue?
   144  	for name, param := range template.Parameters {
   145  		deployment.Parameters = append(deployment.Parameters, azure.Parameter{
   146  			Variable: azure.Variable{
   147  				Name:  name,
   148  				Value: param.DefaultValue,
   149  			},
   150  			Default:    param.DefaultValue,
   151  			Decorators: nil,
   152  		})
   153  	}
   154  
   155  	for name, variable := range template.Variables {
   156  		deployment.Variables = append(deployment.Variables, azure.Variable{
   157  			Name:  name,
   158  			Value: variable,
   159  		})
   160  	}
   161  
   162  	for name, output := range template.Outputs {
   163  		deployment.Outputs = append(deployment.Outputs, azure.Output{
   164  			Name:  name,
   165  			Value: output,
   166  		})
   167  	}
   168  
   169  	for _, resource := range template.Resources {
   170  		deployment.Resources = append(deployment.Resources, p.convertResource(resource))
   171  	}
   172  
   173  	return &deployment
   174  }
   175  
   176  func (p *Parser) convertResource(input Resource) azure.Resource {
   177  
   178  	var children []azure.Resource
   179  
   180  	for _, child := range input.Resources {
   181  		children = append(children, p.convertResource(child))
   182  	}
   183  
   184  	resource := azure.Resource{
   185  		Metadata:   input.Metadata,
   186  		APIVersion: input.APIVersion,
   187  		Type:       input.Type,
   188  		Kind:       input.Kind,
   189  		Name:       input.Name,
   190  		Location:   input.Location,
   191  		Properties: input.Properties,
   192  		Resources:  children,
   193  	}
   194  
   195  	return resource
   196  }