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 }