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 }