github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/pkg/scanners/terraformplan/parser/parser.go (about) 1 package parser 2 3 import ( 4 "crypto/md5" //#nosec 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 "strings" 10 11 "github.com/khulnasoft-lab/defsec/pkg/terraform" 12 "github.com/liamg/memoryfs" 13 ) 14 15 type Parser struct { 16 debugWriter io.Writer 17 stopOnHCLError bool 18 } 19 20 func New(options ...Option) *Parser { 21 parser := &Parser{} 22 23 for _, o := range options { 24 o(parser) 25 } 26 return parser 27 } 28 29 func (p *Parser) SetDebugWriter(writer io.Writer) { 30 p.debugWriter = writer 31 } 32 33 func (p *Parser) SetStopOnHCLError(b bool) { 34 p.stopOnHCLError = b 35 } 36 37 func (p *Parser) ParseFile(filepath string) (*PlanFile, error) { 38 39 if _, err := os.Stat(filepath); err != nil { 40 return nil, err 41 } 42 43 reader, err := os.Open(filepath) 44 if err != nil { 45 return nil, err 46 } 47 defer reader.Close() 48 return p.Parse(reader) 49 } 50 51 func (p *Parser) Parse(reader io.Reader) (*PlanFile, error) { 52 53 var planFile PlanFile 54 55 if err := json.NewDecoder(reader).Decode(&planFile); err != nil { 56 return nil, err 57 } 58 59 return &planFile, nil 60 61 } 62 63 func (p *PlanFile) ToFS() (*memoryfs.FS, error) { 64 65 rootFS := memoryfs.New() 66 67 var fileResources []string 68 69 resources, err := getResources(p.PlannedValues.RootModule, p.ResourceChanges, p.Configuration) 70 if err != nil { 71 return nil, err 72 } 73 74 for _, r := range resources { 75 fileResources = append(fileResources, r.ToHCL()) 76 } 77 78 fileContent := strings.Join(fileResources, "\n\n") 79 if err := rootFS.WriteFile("main.tf", []byte(fileContent), os.ModePerm); err != nil { 80 return nil, err 81 } 82 return rootFS, nil 83 84 } 85 86 func getResources(module Module, resourceChanges []ResourceChange, configuration Configuration) ([]terraform.PlanBlock, error) { 87 var resources []terraform.PlanBlock 88 for _, r := range module.Resources { 89 resourceName := r.Name 90 if strings.HasPrefix(r.Address, "module.") { 91 hashable := strings.TrimSuffix(strings.Split(r.Address, fmt.Sprintf(".%s.", r.Type))[0], ".data") 92 /* #nosec */ 93 hash := fmt.Sprintf("%x", md5.Sum([]byte(hashable))) 94 resourceName = fmt.Sprintf("%s_%s", r.Name, hash) 95 } 96 97 res := terraform.NewPlanBlock(r.Mode, r.Type, resourceName) 98 99 changes := getValues(r.Address, resourceChanges) 100 // process the changes to get the after state 101 for k, v := range changes.After { 102 switch t := v.(type) { 103 case []interface{}: 104 if len(t) == 0 { 105 continue 106 } 107 val := t[0] 108 switch v := val.(type) { 109 // is it a HCL block? 110 case map[string]interface{}: 111 res.Blocks[k] = v 112 // just a normal attribute then 113 default: 114 res.Attributes[k] = v 115 } 116 default: 117 res.Attributes[k] = v 118 } 119 } 120 121 resourceConfig := getConfiguration(r.Address, configuration.RootModule) 122 if resourceConfig != nil { 123 124 for attr, val := range resourceConfig.Expressions { 125 if value, shouldReplace := unpackConfigurationValue(val, r); shouldReplace || !res.HasAttribute(attr) { 126 res.Attributes[attr] = value 127 } 128 } 129 } 130 resources = append(resources, *res) 131 } 132 133 for _, m := range module.ChildModules { 134 cr, err := getResources(m.Module, resourceChanges, configuration) 135 if err != nil { 136 return nil, err 137 } 138 resources = append(resources, cr...) 139 } 140 141 return resources, nil 142 } 143 144 func unpackConfigurationValue(val interface{}, r Resource) (interface{}, bool) { 145 switch t := val.(type) { 146 case map[string]interface{}: 147 for k, v := range t { 148 switch k { 149 case "references": 150 reference := v.([]interface{})[0].(string) 151 if strings.HasPrefix(r.Address, "module.") { 152 hashable := strings.TrimSuffix(strings.Split(r.Address, fmt.Sprintf(".%s.", r.Type))[0], ".data") 153 /* #nosec */ 154 hash := fmt.Sprintf("%x", md5.Sum([]byte(hashable))) 155 156 parts := strings.Split(reference, ".") 157 var rejoin []string 158 159 name := parts[1] 160 remainder := parts[2:] 161 if parts[0] == "data" { 162 rejoin = append(rejoin, parts[:2]...) 163 name = parts[2] 164 remainder = parts[3:] 165 } else { 166 rejoin = append(rejoin, parts[:1]...) 167 } 168 169 rejoin = append(rejoin, fmt.Sprintf("%s_%s", name, hash)) 170 rejoin = append(rejoin, remainder...) 171 172 reference = strings.Join(rejoin, ".") 173 } 174 175 shouldReplace := false 176 return terraform.PlanReference{Value: reference}, shouldReplace 177 case "constant_value": 178 return v, false 179 } 180 } 181 } 182 183 return nil, false 184 } 185 186 func getConfiguration(address string, configuration ConfigurationModule) *ConfigurationResource { 187 188 workingAddress := address 189 var moduleParts []string 190 for strings.HasPrefix(workingAddress, "module.") { 191 workingAddressParts := strings.Split(workingAddress, ".") 192 moduleParts = append(moduleParts, workingAddressParts[1]) 193 workingAddress = strings.Join(workingAddressParts[2:], ".") 194 } 195 196 workingModule := configuration 197 for _, moduleName := range moduleParts { 198 if module, ok := workingModule.ModuleCalls[moduleName]; ok { 199 workingModule = module.Module 200 } 201 } 202 203 for _, resource := range workingModule.Resources { 204 if resource.Address == workingAddress { 205 return &resource 206 } 207 } 208 209 return nil 210 } 211 212 func getValues(address string, resourceChange []ResourceChange) *ResourceChange { 213 for _, r := range resourceChange { 214 if r.Address == address { 215 return &r 216 } 217 } 218 return nil 219 }