github.com/aquasecurity/trivy-iac@v0.8.1-0.20240127024015-3d8e412cf0ab/pkg/scanners/azure/arm/parser/parser_test.go (about) 1 package parser 2 3 import ( 4 "context" 5 "io/fs" 6 "os" 7 "testing" 8 9 "github.com/aquasecurity/defsec/pkg/types" 10 "github.com/liamg/memoryfs" 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 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/resolver" 17 ) 18 19 func createMetadata(targetFS fs.FS, filename string, start, end int, ref string, parent *types.Metadata) types.Metadata { 20 child := types.NewMetadata(types.NewRange(filename, start, end, "", targetFS), ref) 21 if parent != nil { 22 child.SetParentPtr(parent) 23 } 24 return child 25 } 26 27 func TestParser_Parse(t *testing.T) { 28 29 filename := "example.json" 30 31 targetFS := memoryfs.New() 32 33 tests := []struct { 34 name string 35 input string 36 want func() azure.Deployment 37 wantDeployment bool 38 }{ 39 { 40 name: "invalid code", 41 input: `blah`, 42 wantDeployment: false, 43 }, 44 { 45 name: "basic param", 46 input: `{ 47 "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", // another one 48 "contentVersion": "1.0.0.0", 49 "parameters": { 50 "storagePrefix": { 51 "type": "string", 52 "defaultValue": "x", 53 "maxLength": 11, 54 "minLength": 3 55 } 56 }, 57 "resources": [] 58 }`, 59 want: func() azure.Deployment { 60 61 root := createMetadata(targetFS, filename, 0, 0, "", nil).WithInternal(resolver.NewResolver()) 62 metadata := createMetadata(targetFS, filename, 1, 13, "", &root) 63 parametersMetadata := createMetadata(targetFS, filename, 4, 11, "parameters", &metadata) 64 storageMetadata := createMetadata(targetFS, filename, 5, 10, "parameters.storagePrefix", ¶metersMetadata) 65 66 return azure.Deployment{ 67 Metadata: metadata, 68 TargetScope: azure.ScopeResourceGroup, 69 Parameters: []azure.Parameter{ 70 { 71 Variable: azure.Variable{ 72 Name: "storagePrefix", 73 Value: azure.NewValue("x", createMetadata(targetFS, filename, 7, 7, "parameters.storagePrefix.defaultValue", &storageMetadata)), 74 }, 75 Default: azure.NewValue("x", createMetadata(targetFS, filename, 7, 7, "parameters.storagePrefix.defaultValue", &storageMetadata)), 76 Decorators: nil, 77 }, 78 }, 79 } 80 }, 81 wantDeployment: true, 82 }, 83 { 84 name: "storageAccount", 85 input: `{ 86 "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", // another one 87 "contentVersion": "1.0.0.0", 88 "parameters": {}, 89 "resources": [ 90 { 91 "type": "Microsoft.Storage/storageAccounts", 92 "apiVersion": "2022-05-01", 93 "name": "myResource", 94 "location": "string", 95 "tags": { 96 "tagName1": "tagValue1", 97 "tagName2": "tagValue2" 98 }, 99 "sku": { 100 "name": "string" 101 }, 102 "kind": "string", 103 "extendedLocation": { 104 "name": "string", 105 "type": "EdgeZone" 106 }, 107 "identity": { 108 "type": "string", 109 "userAssignedIdentities": {} 110 }, 111 "properties": { 112 "allowSharedKeyAccess":false, 113 "customDomain": { 114 "name": "string", 115 "useSubDomainName":false, 116 "number": 123 117 }, 118 "networkAcls": [ 119 { 120 "bypass": "AzureServices1" 121 }, 122 { 123 "bypass": "AzureServices2" 124 } 125 ] 126 } 127 } 128 ] 129 }`, 130 want: func() azure.Deployment { 131 132 rootMetadata := createMetadata(targetFS, filename, 0, 0, "", nil).WithInternal(resolver.NewResolver()) 133 fileMetadata := createMetadata(targetFS, filename, 1, 45, "", &rootMetadata) 134 resourcesMetadata := createMetadata(targetFS, filename, 5, 44, "resources", &fileMetadata) 135 136 resourceMetadata := createMetadata(targetFS, filename, 6, 43, "resources[0]", &resourcesMetadata) 137 138 propertiesMetadata := createMetadata(targetFS, filename, 27, 42, "resources[0].properties", &resourceMetadata) 139 140 customDomainMetadata := createMetadata(targetFS, filename, 29, 33, "resources[0].properties.customDomain", &propertiesMetadata) 141 networkACLListMetadata := createMetadata(targetFS, filename, 34, 41, "resources[0].properties.networkAcls", &propertiesMetadata) 142 143 networkACL0Metadata := createMetadata(targetFS, filename, 35, 37, "resources[0].properties.networkAcls[0]", &networkACLListMetadata) 144 networkACL1Metadata := createMetadata(targetFS, filename, 38, 40, "resources[0].properties.networkAcls[1]", &networkACLListMetadata) 145 146 return azure.Deployment{ 147 Metadata: fileMetadata, 148 TargetScope: azure.ScopeResourceGroup, 149 Resources: []azure.Resource{ 150 { 151 Metadata: resourceMetadata, 152 APIVersion: azure.NewValue( 153 "2022-05-01", 154 createMetadata(targetFS, filename, 8, 8, "resources[0].apiVersion", &resourceMetadata), 155 ), 156 Type: azure.NewValue( 157 "Microsoft.Storage/storageAccounts", 158 createMetadata(targetFS, filename, 7, 7, "resources[0].type", &resourceMetadata), 159 ), 160 Kind: azure.NewValue( 161 "string", 162 createMetadata(targetFS, filename, 18, 18, "resources[0].kind", &resourceMetadata), 163 ), 164 Name: azure.NewValue( 165 "myResource", 166 createMetadata(targetFS, filename, 9, 9, "resources[0].name", &resourceMetadata), 167 ), 168 Location: azure.NewValue( 169 "string", 170 createMetadata(targetFS, filename, 10, 10, "resources[0].location", &resourceMetadata), 171 ), 172 Properties: azure.NewValue( 173 map[string]azure.Value{ 174 "allowSharedKeyAccess": azure.NewValue(false, createMetadata(targetFS, filename, 28, 28, "resources[0].properties.allowSharedKeyAccess", &propertiesMetadata)), 175 "customDomain": azure.NewValue( 176 map[string]azure.Value{ 177 "name": azure.NewValue("string", createMetadata(targetFS, filename, 30, 30, "resources[0].properties.customDomain.name", &customDomainMetadata)), 178 "useSubDomainName": azure.NewValue(false, createMetadata(targetFS, filename, 31, 31, "resources[0].properties.customDomain.useSubDomainName", &customDomainMetadata)), 179 "number": azure.NewValue(int64(123), createMetadata(targetFS, filename, 32, 32, "resources[0].properties.customDomain.number", &customDomainMetadata)), 180 }, customDomainMetadata), 181 "networkAcls": azure.NewValue( 182 []azure.Value{ 183 azure.NewValue( 184 map[string]azure.Value{ 185 "bypass": azure.NewValue("AzureServices1", createMetadata(targetFS, filename, 36, 36, "resources[0].properties.networkAcls[0].bypass", &networkACL0Metadata)), 186 }, 187 networkACL0Metadata, 188 ), 189 azure.NewValue( 190 map[string]azure.Value{ 191 "bypass": azure.NewValue("AzureServices2", createMetadata(targetFS, filename, 39, 39, "resources[0].properties.networkAcls[1].bypass", &networkACL1Metadata)), 192 }, 193 networkACL1Metadata, 194 ), 195 }, networkACLListMetadata), 196 }, 197 propertiesMetadata, 198 ), 199 }, 200 }, 201 } 202 }, 203 204 wantDeployment: true, 205 }, 206 } 207 208 for _, tt := range tests { 209 t.Run(tt.name, func(t *testing.T) { 210 211 require.NoError(t, targetFS.WriteFile(filename, []byte(tt.input), 0644)) 212 213 p := New(targetFS, options.ParserWithDebug(os.Stderr)) 214 got, err := p.ParseFS(context.Background(), ".") 215 require.NoError(t, err) 216 217 if !tt.wantDeployment { 218 assert.Len(t, got, 0) 219 return 220 } 221 222 require.Len(t, got, 1) 223 want := tt.want() 224 g := got[0] 225 226 require.Equal(t, want, g) 227 }) 228 } 229 } 230 231 func Test_NestedResourceParsing(t *testing.T) { 232 233 input := ` 234 { 235 "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 236 "contentVersion": "1.0.0.0", 237 "parameters": { 238 "environment": { 239 "type": "string", 240 "allowedValues": [ 241 "dev", 242 "test", 243 "prod" 244 ] 245 }, 246 "location": { 247 "type": "string", 248 "defaultValue": "[resourceGroup().location]", 249 "metadata": { 250 "description": "Location for all resources." 251 } 252 }, 253 "storageAccountSkuName": { 254 "type": "string", 255 "defaultValue": "Standard_LRS" 256 }, 257 "storageAccountSkuTier": { 258 "type": "string", 259 "defaultValue": "Standard" 260 } 261 }, 262 "variables": { 263 "uniquePart": "[take(uniqueString(resourceGroup().id), 4)]", 264 "storageAccountName": "[concat('mystorageaccount', variables('uniquePart'), parameters('environment'))]", 265 "queueName": "myqueue" 266 }, 267 "resources": [ 268 { 269 "type": "Microsoft.Storage/storageAccounts", 270 "name": "[variables('storageAccountName')]", 271 "location": "[parameters('location')]", 272 "apiVersion": "2019-06-01", 273 "sku": { 274 "name": "[parameters('storageAccountSkuName')]", 275 "tier": "[parameters('storageAccountSkuTier')]" 276 }, 277 "kind": "StorageV2", 278 "properties": {}, 279 "resources": [ 280 { 281 "name": "[concat('default/', variables('queueName'))]", 282 "type": "queueServices/queues", 283 "apiVersion": "2019-06-01", 284 "dependsOn": [ 285 "[variables('storageAccountName')]" 286 ], 287 "properties": { 288 "metadata": {} 289 } 290 } 291 ] 292 } 293 ] 294 } 295 ` 296 297 targetFS := memoryfs.New() 298 299 require.NoError(t, targetFS.WriteFile("nested.json", []byte(input), 0644)) 300 301 p := New(targetFS, options.ParserWithDebug(os.Stderr)) 302 got, err := p.ParseFS(context.Background(), ".") 303 require.NoError(t, err) 304 require.Len(t, got, 1) 305 306 deployment := got[0] 307 308 require.Len(t, deployment.Resources, 1) 309 310 storageAccountResource := deployment.Resources[0] 311 312 require.Len(t, storageAccountResource.Resources, 1) 313 314 queue := storageAccountResource.Resources[0] 315 316 assert.Equal(t, "queueServices/queues", queue.Type.AsString()) 317 } 318 319 // 320 // func Test_JsonFile(t *testing.T) { 321 // 322 // input, err := os.ReadFile("testdata/postgres.json") 323 // require.NoError(t, err) 324 // 325 // targetFS := memoryfs.New() 326 // 327 // require.NoError(t, targetFS.WriteFile("postgres.json", input, 0644)) 328 // 329 // p := New(targetFS, options.ParserWithDebug(os.Stderr)) 330 // got, err := p.ParseFS(context.Background(), ".") 331 // require.NoError(t, err) 332 // 333 // got[0].Resources[3].Name.Resolve() 334 // 335 // name := got[0].Resources[3].Name.AsString() 336 // assert.Equal(t, "myserver", name) 337 // 338 // }