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