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", &parametersMetadata)
    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  // }