github.com/aquasecurity/trivy-iac@v0.8.1-0.20240127024015-3d8e412cf0ab/pkg/scanners/cloudformation/parser/parser_test.go (about)

     1  package parser
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/aquasecurity/trivy-iac/test/testutil"
    14  )
    15  
    16  func parseFile(t *testing.T, source string, name string) (FileContexts, error) {
    17  	tmp, err := os.MkdirTemp(os.TempDir(), "defsec")
    18  	require.NoError(t, err)
    19  	defer func() { _ = os.RemoveAll(tmp) }()
    20  	require.NoError(t, os.WriteFile(filepath.Join(tmp, name), []byte(source), 0600))
    21  	fs := os.DirFS(tmp)
    22  	return New().ParseFS(context.TODO(), fs, ".")
    23  }
    24  
    25  func Test_parse_yaml(t *testing.T) {
    26  
    27  	source := `---
    28  Parameters:
    29    BucketName: 
    30      Type: String
    31      Default: naughty
    32    EncryptBucket:
    33      Type: Boolean
    34      Default: false
    35  Resources:
    36    S3Bucket:
    37      Type: 'AWS::S3::Bucket'
    38      Properties:
    39        BucketName: naughty
    40        BucketEncryption:
    41          ServerSideEncryptionConfiguration:
    42          - BucketKeyEnabled: 
    43              Ref: EncryptBucket`
    44  
    45  	files, err := parseFile(t, source, "cf.yaml")
    46  	require.NoError(t, err)
    47  	assert.Len(t, files, 1)
    48  	file := files[0]
    49  
    50  	assert.Len(t, file.Resources, 1)
    51  	assert.Len(t, file.Parameters, 2)
    52  
    53  	bucket, ok := file.Resources["S3Bucket"]
    54  	require.True(t, ok, "S3Bucket resource should be available")
    55  	assert.Equal(t, "cf.yaml", bucket.Range().GetFilename())
    56  	assert.Equal(t, 10, bucket.Range().GetStartLine())
    57  	assert.Equal(t, 17, bucket.Range().GetEndLine())
    58  }
    59  
    60  func Test_parse_json(t *testing.T) {
    61  	source := `{
    62    "Parameters": {
    63      "BucketName": {
    64        "Type": "String",
    65        "Default": "naughty"
    66      },
    67      "BucketKeyEnabled": {
    68        "Type": "Boolean",
    69        "Default": false
    70      }
    71    },
    72    "Resources": {
    73      "S3Bucket": {
    74        "Type": "AWS::S3::Bucket",
    75        "properties": {
    76          "BucketName": {
    77            "Ref": "BucketName"
    78          },
    79          "BucketEncryption": {
    80            "ServerSideEncryptionConfiguration": [
    81              {
    82                "BucketKeyEnabled": {
    83                    "Ref": "BucketKeyEnabled"
    84                }
    85              }
    86            ]
    87          }
    88        }
    89      }
    90    }
    91  }
    92  `
    93  
    94  	files, err := parseFile(t, source, "cf.json")
    95  	require.NoError(t, err)
    96  	assert.Len(t, files, 1)
    97  	file := files[0]
    98  
    99  	assert.Len(t, file.Resources, 1)
   100  	assert.Len(t, file.Parameters, 2)
   101  }
   102  
   103  func Test_parse_yaml_with_map_ref(t *testing.T) {
   104  
   105  	source := `---
   106  Parameters:
   107    BucketName: 
   108      Type: String
   109      Default: referencedBucket
   110    EncryptBucket:
   111      Type: Boolean
   112      Default: false
   113  Resources:
   114    S3Bucket:
   115      Type: 'AWS::S3::Bucket'
   116      Properties:
   117        BucketName:
   118          Ref: BucketName
   119        BucketEncryption:
   120          ServerSideEncryptionConfiguration:
   121          - BucketKeyEnabled: 
   122              Ref: EncryptBucket`
   123  
   124  	files, err := parseFile(t, source, "cf.yaml")
   125  	require.NoError(t, err)
   126  	assert.Len(t, files, 1)
   127  	file := files[0]
   128  
   129  	assert.Len(t, file.Resources, 1)
   130  	assert.Len(t, file.Parameters, 2)
   131  
   132  	res := file.GetResourceByLogicalID("S3Bucket")
   133  	assert.NotNil(t, res)
   134  
   135  	refProp := res.GetProperty("BucketName")
   136  	assert.False(t, refProp.IsNil())
   137  	assert.Equal(t, "referencedBucket", refProp.AsString())
   138  }
   139  
   140  func Test_parse_yaml_with_intrinsic_functions(t *testing.T) {
   141  
   142  	source := `---
   143  Parameters:
   144    BucketName: 
   145      Type: String
   146      Default: somebucket
   147    EncryptBucket:
   148      Type: Boolean
   149      Default: false
   150  Resources:
   151    S3Bucket:
   152      Type: 'AWS::S3::Bucket'
   153      Properties:
   154        BucketName: !Ref BucketName
   155        BucketEncryption:
   156          ServerSideEncryptionConfiguration:
   157          - BucketKeyEnabled: false
   158  `
   159  
   160  	files, err := parseFile(t, source, "cf.yaml")
   161  	require.NoError(t, err)
   162  	assert.Len(t, files, 1)
   163  	ctx := files[0]
   164  
   165  	assert.Len(t, ctx.Resources, 1)
   166  	assert.Len(t, ctx.Parameters, 2)
   167  
   168  	res := ctx.GetResourceByLogicalID("S3Bucket")
   169  	assert.NotNil(t, res)
   170  
   171  	refProp := res.GetProperty("BucketName")
   172  	assert.False(t, refProp.IsNil())
   173  	assert.Equal(t, "somebucket", refProp.AsString())
   174  }
   175  
   176  func createTestFileContext(t *testing.T, source string) *FileContext {
   177  	contexts, err := parseFile(t, source, "main.yaml")
   178  	require.NoError(t, err)
   179  	require.Len(t, contexts, 1)
   180  	return contexts[0]
   181  }
   182  
   183  func Test_parse_yaml_use_condition_in_resource(t *testing.T) {
   184  	source := `---
   185  AWSTemplateFormatVersion: "2010-09-09"
   186  Description: some description
   187  Parameters:
   188    ServiceName:
   189      Type: String
   190      Description: The service name
   191    EnvName:
   192      Type: String
   193      Description: Optional environment name to prefix all resources with
   194      Default: ""
   195  
   196  Conditions:
   197    SuffixResources: !Not [!Equals [!Ref EnvName, ""]]
   198  
   199  Resources:
   200    ErrorTimedOutMetricFilter:
   201      Type: AWS::Logs::MetricFilter
   202      Properties:
   203        FilterPattern: '?ERROR ?error ?Error ?"timed out"' # If log contains one of these error words or timed out
   204        LogGroupName:
   205          !If [
   206            SuffixResources,
   207            !Sub "/aws/lambda/${ServiceName}-${EnvName}",
   208            !Sub "/aws/lambda/${ServiceName}",
   209          ]
   210        MetricTransformations:
   211          - MetricName: !Sub "${ServiceName}-ErrorLogCount"
   212            MetricNamespace: market-LogMetrics
   213            MetricValue: 1
   214            DefaultValue: 0
   215  `
   216  
   217  	files, err := parseFile(t, source, "cf.yaml")
   218  	require.NoError(t, err)
   219  	assert.Len(t, files, 1)
   220  	ctx := files[0]
   221  
   222  	assert.Len(t, ctx.Parameters, 2)
   223  	assert.Len(t, ctx.Conditions, 1)
   224  	assert.Len(t, ctx.Resources, 1)
   225  
   226  	res := ctx.GetResourceByLogicalID("ErrorTimedOutMetricFilter")
   227  	assert.NotNil(t, res)
   228  
   229  	refProp := res.GetProperty("LogGroupName")
   230  	assert.False(t, refProp.IsNil())
   231  	assert.Equal(t, "/aws/lambda/${ServiceName}", refProp.AsString())
   232  }
   233  
   234  func TestParse_WithParameters(t *testing.T) {
   235  
   236  	fs := testutil.CreateFS(t, map[string]string{
   237  		"main.yaml": `AWSTemplateFormatVersion: 2010-09-09
   238  Parameters:
   239    KmsMasterKeyId:
   240      Type: String
   241  Resources:
   242    TestQueue:
   243      Type: 'AWS::SQS::Queue'
   244      Properties:
   245        QueueName: test-queue
   246        KmsMasterKeyId: !Ref KmsMasterKeyId
   247        `,
   248  	})
   249  
   250  	params := map[string]any{
   251  		"KmsMasterKeyId": "some_id",
   252  	}
   253  	p := New(WithParameters(params))
   254  
   255  	files, err := p.ParseFS(context.TODO(), fs, ".")
   256  	require.NoError(t, err)
   257  	require.Len(t, files, 1)
   258  
   259  	file := files[0]
   260  	res := file.GetResourceByLogicalID("TestQueue")
   261  	assert.NotNil(t, res)
   262  
   263  	kmsProp := res.GetProperty("KmsMasterKeyId")
   264  	assert.False(t, kmsProp.IsNil())
   265  	assert.Equal(t, "some_id", kmsProp.AsString())
   266  }
   267  
   268  func TestParse_WithParameterFiles(t *testing.T) {
   269  	fs := testutil.CreateFS(t, map[string]string{
   270  		"main.yaml": `AWSTemplateFormatVersion: 2010-09-09
   271  Parameters:
   272    KmsMasterKeyId:
   273      Type: String
   274  Resources:
   275    TestQueue:
   276      Type: 'AWS::SQS::Queue'
   277      Properties:
   278        QueueName: test-queue
   279        KmsMasterKeyId: !Ref KmsMasterKeyId
   280  `,
   281  		"params.json": `[
   282     {
   283          "ParameterKey": "KmsMasterKeyId",
   284          "ParameterValue": "some_id"
   285      }
   286  ]
   287        `,
   288  	})
   289  
   290  	p := New(WithParameterFiles("params.json"))
   291  
   292  	files, err := p.ParseFS(context.TODO(), fs, ".")
   293  	require.NoError(t, err)
   294  	require.Len(t, files, 1)
   295  
   296  	file := files[0]
   297  	res := file.GetResourceByLogicalID("TestQueue")
   298  	assert.NotNil(t, res)
   299  
   300  	kmsProp := res.GetProperty("KmsMasterKeyId")
   301  	assert.False(t, kmsProp.IsNil())
   302  	assert.Equal(t, "some_id", kmsProp.AsString())
   303  }
   304  
   305  func TestParse_WithConfigFS(t *testing.T) {
   306  	fs := testutil.CreateFS(t, map[string]string{
   307  		"queue.yaml": `AWSTemplateFormatVersion: 2010-09-09
   308  Parameters:
   309    KmsMasterKeyId:
   310      Type: String
   311  Resources:
   312    TestQueue:
   313      Type: 'AWS::SQS::Queue'
   314      Properties:
   315        QueueName: testqueue
   316        KmsMasterKeyId: !Ref KmsMasterKeyId
   317  `,
   318  		"bucket.yaml": `AWSTemplateFormatVersion: '2010-09-09'
   319  Description: Bucket
   320  Parameters:
   321    BucketName:
   322      Type: String
   323  Resources:
   324    S3Bucket:
   325      Type: AWS::S3::Bucket
   326      Properties:
   327        BucketName: !Ref BucketName
   328  `,
   329  	})
   330  
   331  	configFS := testutil.CreateFS(t, map[string]string{
   332  		"/workdir/parameters/queue.json": `[
   333        {
   334             "ParameterKey": "KmsMasterKeyId",
   335             "ParameterValue": "some_id"
   336         }
   337     ]
   338           `,
   339  		"/workdir/parameters/s3.json": `[
   340        {
   341             "ParameterKey": "BucketName",
   342             "ParameterValue": "testbucket"
   343         }
   344     ]`,
   345  	})
   346  
   347  	p := New(
   348  		WithParameterFiles("/workdir/parameters/queue.json", "/workdir/parameters/s3.json"),
   349  		WithConfigsFS(configFS),
   350  	)
   351  
   352  	files, err := p.ParseFS(context.TODO(), fs, ".")
   353  	require.NoError(t, err)
   354  	require.Len(t, files, 2)
   355  
   356  	for _, file := range files {
   357  		if strings.Contains(file.filepath, "queue") {
   358  			res := file.GetResourceByLogicalID("TestQueue")
   359  			assert.NotNil(t, res)
   360  
   361  			kmsProp := res.GetProperty("KmsMasterKeyId")
   362  			assert.False(t, kmsProp.IsNil())
   363  			assert.Equal(t, "some_id", kmsProp.AsString())
   364  		} else if strings.Contains(file.filepath, "s3") {
   365  			res := file.GetResourceByLogicalID("S3Bucket")
   366  			assert.NotNil(t, res)
   367  
   368  			bucketNameProp := res.GetProperty("BucketName")
   369  			assert.False(t, bucketNameProp.IsNil())
   370  			assert.Equal(t, "testbucket", bucketNameProp.AsString())
   371  		}
   372  	}
   373  
   374  }