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 }