cfn-check 0.7.1__tar.gz → 0.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cfn_check-0.7.1 → cfn_check-0.9.0}/PKG-INFO +104 -28
- {cfn_check-0.7.1 → cfn_check-0.9.0}/README.md +101 -26
- cfn_check-0.9.0/cfn_check/cli/config.py +10 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/cli/render.py +65 -8
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/cli/utils/files.py +1 -1
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/cli/utils/stdout.py +2 -2
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/cli/validate.py +17 -4
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/collection/collection.py +7 -7
- cfn_check-0.9.0/cfn_check/evaluation/evaluator.py +128 -0
- cfn_check-0.9.0/cfn_check/evaluation/parsing/operators/__init__.py +1 -0
- cfn_check-0.9.0/cfn_check/evaluation/parsing/operators/value_operator.py +118 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/evaluation/parsing/query_parser.py +26 -2
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/evaluation/parsing/token.py +124 -70
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/evaluation/parsing/token_type.py +2 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/evaluation/validate.py +29 -3
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/rendering/renderer.py +62 -11
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/rendering/utils.py +1 -1
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/rules/rule.py +3 -3
- cfn_check-0.9.0/cfn_check/shared/__init__.py +0 -0
- cfn_check-0.9.0/cfn_check/validation/__init__.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/validation/validator.py +5 -5
- cfn_check-0.9.0/cfn_check/yaml/__init__.py +5 -0
- cfn_check-0.9.0/cfn_check/yaml/anchor.py +18 -0
- cfn_check-0.9.0/cfn_check/yaml/comments.py +1201 -0
- cfn_check-0.9.0/cfn_check/yaml/compat.py +235 -0
- cfn_check-0.9.0/cfn_check/yaml/composer.py +229 -0
- cfn_check-0.9.0/cfn_check/yaml/configobjwalker.py +15 -0
- cfn_check-0.9.0/cfn_check/yaml/constructor.py +1722 -0
- cfn_check-0.9.0/cfn_check/yaml/cyaml.py +196 -0
- cfn_check-0.9.0/cfn_check/yaml/docinfo.py +115 -0
- cfn_check-0.9.0/cfn_check/yaml/dumper.py +216 -0
- cfn_check-0.9.0/cfn_check/yaml/emitter.py +1813 -0
- cfn_check-0.9.0/cfn_check/yaml/error.py +314 -0
- cfn_check-0.9.0/cfn_check/yaml/events.py +265 -0
- cfn_check-0.9.0/cfn_check/yaml/loader.py +91 -0
- cfn_check-0.9.0/cfn_check/yaml/main.py +1521 -0
- cfn_check-0.9.0/cfn_check/yaml/mergevalue.py +37 -0
- cfn_check-0.9.0/cfn_check/yaml/nodes.py +148 -0
- cfn_check-0.9.0/cfn_check/yaml/parser.py +909 -0
- cfn_check-0.9.0/cfn_check/yaml/reader.py +274 -0
- cfn_check-0.9.0/cfn_check/yaml/representer.py +1146 -0
- cfn_check-0.9.0/cfn_check/yaml/resolver.py +390 -0
- cfn_check-0.9.0/cfn_check/yaml/scalarbool.py +41 -0
- cfn_check-0.9.0/cfn_check/yaml/scalarfloat.py +103 -0
- cfn_check-0.9.0/cfn_check/yaml/scalarint.py +122 -0
- cfn_check-0.9.0/cfn_check/yaml/scalarstring.py +140 -0
- cfn_check-0.9.0/cfn_check/yaml/scanner.py +2390 -0
- cfn_check-0.9.0/cfn_check/yaml/serializer.py +231 -0
- cfn_check-0.9.0/cfn_check/yaml/tag.py +124 -0
- cfn_check-0.9.0/cfn_check/yaml/timestamp.py +61 -0
- cfn_check-0.9.0/cfn_check/yaml/tokens.py +382 -0
- cfn_check-0.9.0/cfn_check/yaml/util.py +262 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check.egg-info/PKG-INFO +104 -28
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check.egg-info/SOURCES.txt +39 -2
- cfn_check-0.9.0/example/pydantic_rules.py +35 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/example/rules.py +3 -3
- cfn_check-0.9.0/example/test_models/__init__.py +0 -0
- cfn_check-0.9.0/example/test_models/models.py +54 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/pyproject.toml +4 -3
- cfn_check-0.7.1/cfn_check/evaluation/evaluator.py +0 -98
- cfn_check-0.7.1/example/multitag.py +0 -21
- cfn_check-0.7.1/example/pydantic_rules.py +0 -114
- {cfn_check-0.7.1 → cfn_check-0.9.0}/LICENSE +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/__init__.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/cli/__init__.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/cli/root.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/cli/utils/__init__.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/cli/utils/attributes.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/collection/__init__.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/evaluation/__init__.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/evaluation/errors.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/evaluation/parsing/__init__.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/logging/__init__.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/logging/models.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/rendering/__init__.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/rendering/cidr_solver.py +0 -0
- {cfn_check-0.7.1/cfn_check/rules → cfn_check-0.9.0/cfn_check/rendering/parsing}/__init__.py +0 -0
- /cfn_check-0.7.1/cfn_check/shared/__init__.py → /cfn_check-0.9.0/cfn_check/rendering/parsing/cloudformation_loader.py +0 -0
- {cfn_check-0.7.1/cfn_check/validation → cfn_check-0.9.0/cfn_check/rules}/__init__.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check/shared/types.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check.egg-info/dependency_links.txt +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check.egg-info/entry_points.txt +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check.egg-info/requires.txt +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/cfn_check.egg-info/top_level.txt +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/example/renderer_test.py +0 -0
- {cfn_check-0.7.1 → cfn_check-0.9.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cfn-check
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Validate Cloud Formation
|
|
5
5
|
Author-email: Ada Lundhe <adalundhe@lundhe.audio>
|
|
6
6
|
License: MIT License
|
|
@@ -27,10 +27,11 @@ License: MIT License
|
|
|
27
27
|
|
|
28
28
|
Project-URL: Homepage, https://github.com/adalundhe/cfn-check
|
|
29
29
|
Keywords: cloud-formation,testing,aws,cli
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
30
31
|
Classifier: Programming Language :: Python :: 3.12
|
|
31
32
|
Classifier: Programming Language :: Python :: 3.13
|
|
32
33
|
Classifier: Operating System :: OS Independent
|
|
33
|
-
Requires-Python: >=3.
|
|
34
|
+
Requires-Python: >=3.11
|
|
34
35
|
Description-Content-Type: text/markdown
|
|
35
36
|
License-File: LICENSE
|
|
36
37
|
Requires-Dist: pydantic
|
|
@@ -50,7 +51,7 @@ Dynamic: license-file
|
|
|
50
51
|
|
|
51
52
|
| Package | cfn-check |
|
|
52
53
|
| ----------- | ----------- |
|
|
53
|
-
| Version | 0.
|
|
54
|
+
| Version | 0.8.1 |
|
|
54
55
|
| Download | https://pypi.org/project/cfn-check/ |
|
|
55
56
|
| Source | https://github.com/adalundhe/cfn-check |
|
|
56
57
|
| Keywords | cloud-formation, testing, aws, cli |
|
|
@@ -255,19 +256,20 @@ Congrats! You've just made the cloud a bit better place!
|
|
|
255
256
|
|
|
256
257
|
# Queries, Tokens, and Syntax
|
|
257
258
|
|
|
258
|
-
A `cfn-check` Query is a string made up of
|
|
259
|
+
A `cfn-check` Query is a string made up of period (`.`) delimited "Tokens" centered around four primary types:
|
|
259
260
|
|
|
260
261
|
- <b>`Keys`</b> - `<KEY>`: String name key Tokens that perform exact matching on keys of key/value pairs in a CloudFormation document.
|
|
261
|
-
- <b>`
|
|
262
|
+
- <b>`Values`</b> - `(<KEY> = <VALUE>)`: Parenthesis-enclosed `K==V` pairs that perform matching on values of key/value pairs in a CloudFormation document.
|
|
263
|
+
- <b>`Patterns`</b> - `<\d+>`: Arrow-enclosed regex pattern Tokens that perform pattern-based matching on keys of key/value pairs in a CloudFormation document.
|
|
262
264
|
- <b>`Ranges`</b> - `[]`: Brackets enclosed Tokens that perform array selection and filtering in a CloudFormation document.
|
|
263
265
|
|
|
264
266
|
|
|
265
|
-
In addition to `Key`, `Pattern`, and `Range` selection, you can also incorporate:
|
|
267
|
+
In addition to `Key`, `Value`, `Pattern`, and `Range` selection, you can also incorporate:
|
|
266
268
|
|
|
267
269
|
- <b>`Bounded Ranges`</b> - `[<A>-<B>]`: Exact matches from the starting position (if specified) to the end position (if specified) of an array
|
|
268
270
|
- <b>`Indicies`</b> - `[<A>]`: Exact matches the specified indicies of an array
|
|
269
271
|
- <b>`Key Ranges`</b> - `[<KEY>]`: Exact matches keys of objects within an array
|
|
270
|
-
- <b>`Pattern Ranges`</b> (`[
|
|
272
|
+
- <b>`Pattern Ranges`</b> (`[<\d+>]`): Matches they keys of objects within an array based on the specified pattern
|
|
271
273
|
- <b>`Wildcards`</b> (`*`): Selects all values for a given object or array or returns the non-object/array value at the specified path
|
|
272
274
|
- <b>`Wildcard Ranges`</b> (`[*]`): Selects all values for a given array and ensures that *only* the values of a valid array type are returned (any other type will be treated as a mismatch).
|
|
273
275
|
|
|
@@ -281,6 +283,27 @@ Resources
|
|
|
281
283
|
|
|
282
284
|
as your query, you'll select all items within the CloudFormation document under the `Resources` key.
|
|
283
285
|
|
|
286
|
+
|
|
287
|
+
### Working with Values
|
|
288
|
+
|
|
289
|
+
In addition to searching by keys, filtering by the values associated with those keys is the most common way you'll traverse and validate your CloudFormation template. To filter `Resource` key matches by the value of their `Type` value to only return EC2 Instances, we would specify a query like:
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
Resources.*.(Type == AWS::EC2::Instance)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
You can also match multiple values by utilizing the `in`/`IN` operator:
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
Resources.*.(Type in AWS::Lambda::Function,AWS::Serverless::Function)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
and providing a comma-delimited list of values.
|
|
302
|
+
|
|
303
|
+
> [!NOTE]
|
|
304
|
+
> Value queries do not support Nested Ranges or nested queries, but *do* support
|
|
305
|
+
> Wildcards and Patterns. See below for more info!
|
|
306
|
+
|
|
284
307
|
### Working with Patterns
|
|
285
308
|
|
|
286
309
|
If an object within a CloudFormation document contains multiple similar keys you want to select, `Pattern` Tokens are your go-to solution. Consider this segment of CloudFormation:
|
|
@@ -307,7 +330,7 @@ Resources:
|
|
|
307
330
|
We want to select <i>both</i> `SecurityGroupIngress` and `SecurityGroupEgress` to perform the same rule evaluations. Since the keys for both blocks start with `SecurityGroup`, we could write a Query using a Pattern Token like:
|
|
308
331
|
|
|
309
332
|
```
|
|
310
|
-
Resources
|
|
333
|
+
Resources.SecurityGroup.Properties.<SecurityGroup>
|
|
311
334
|
```
|
|
312
335
|
|
|
313
336
|
which would allow us to use a single rule to evaluate both:
|
|
@@ -316,7 +339,7 @@ which would allow us to use a single rule to evaluate both:
|
|
|
316
339
|
class ValidateSecurityGroups(Collection):
|
|
317
340
|
|
|
318
341
|
@Rule(
|
|
319
|
-
"Resources
|
|
342
|
+
"Resources.SecurityGroup.Properties.<SecurityGroup>",
|
|
320
343
|
"It checks Security Groups are correctly definined",
|
|
321
344
|
)
|
|
322
345
|
def validate_security_groups(self, value: list[dict]):
|
|
@@ -347,7 +370,7 @@ Wildcard Tokens allow you to select all matching objects, array entries, or valu
|
|
|
347
370
|
In fact, you've already used one! In the first example, we use a Wildcard Token in the below query:
|
|
348
371
|
|
|
349
372
|
```
|
|
350
|
-
Resources
|
|
373
|
+
Resources.*.Type
|
|
351
374
|
```
|
|
352
375
|
|
|
353
376
|
To select all `Resource` objects, then further extract the `Type` field from each object. This helps us avoid copy-paste rules at the potential cost of deferring more work to individual `Rule` methods if we aren't careful and select too much!
|
|
@@ -366,7 +389,7 @@ Ranges allow you to perform sophisticated selection of objects or data within a
|
|
|
366
389
|
Unbounded ranges allow you to select and return an array in its entirety. For example:
|
|
367
390
|
|
|
368
391
|
```
|
|
369
|
-
Resources
|
|
392
|
+
Resources.SecurityGroup.Properties.SecurityGroupIngress.[]
|
|
370
393
|
```
|
|
371
394
|
|
|
372
395
|
Would return all SecurityGroupIngress objects in the CloudFormation document as a list, allowing you to check that the array of ingresses has been both defined *and* populated.
|
|
@@ -377,7 +400,7 @@ Would return all SecurityGroupIngress objects in the CloudFormation document as
|
|
|
377
400
|
Indexes allow you to select specific positions within an array. For example:
|
|
378
401
|
|
|
379
402
|
```
|
|
380
|
-
Resources
|
|
403
|
+
Resources.SecurityGroup.Properties.SecurityGroupIngress.[0]
|
|
381
404
|
```
|
|
382
405
|
|
|
383
406
|
Would return the first SecurityGroupIngress objects in the document.
|
|
@@ -391,7 +414,7 @@ Bounded Ranges allow you to select subsets of indicies within an array (much lik
|
|
|
391
414
|
As an example:
|
|
392
415
|
|
|
393
416
|
```
|
|
394
|
-
Resources
|
|
417
|
+
Resources.SecurityGroup.Properties.SecurityGroupIngress.[1-3]
|
|
395
418
|
```
|
|
396
419
|
|
|
397
420
|
Would select the second and third SecurityGroupIngress objects in the document.
|
|
@@ -399,13 +422,13 @@ Would select the second and third SecurityGroupIngress objects in the document.
|
|
|
399
422
|
Start or end positions are optional for Bounded Ranges. If a starting position is not defined, `cfn-check` will default to `0`. Likewise, if an end position is not defined, `cfn-check` will default to the end of given list. For example:
|
|
400
423
|
|
|
401
424
|
```
|
|
402
|
-
Resources
|
|
425
|
+
Resources.SecurityGroup.Properties.SecurityGroupIngress.[-3]
|
|
403
426
|
```
|
|
404
427
|
|
|
405
428
|
selects the first through third SecurityGroupIngress objects in the document while:
|
|
406
429
|
|
|
407
430
|
```
|
|
408
|
-
Resources
|
|
431
|
+
Resources.SecurityGroup.Properties.SecurityGroupIngress.[3-]
|
|
409
432
|
```
|
|
410
433
|
|
|
411
434
|
selects the remaining SecurityGroupIngress objects starting from the third.
|
|
@@ -422,7 +445,7 @@ Often times it's easier to match based upon an array's contents than by exact in
|
|
|
422
445
|
For example:
|
|
423
446
|
|
|
424
447
|
```
|
|
425
|
-
Resources
|
|
448
|
+
Resources.MyEC2Instance.Properties.ImageId.[AWSRegionArch2AMI]
|
|
426
449
|
```
|
|
427
450
|
|
|
428
451
|
returns only the EC2 ImageIds where the ImageId exactly matches `AWSRegionArch2AMI`.
|
|
@@ -433,7 +456,7 @@ returns only the EC2 ImageIds where the ImageId exactly matches `AWSRegionArch2A
|
|
|
433
456
|
Pattern Ranges function much like Key Ranges, but utilize regex-based pattern matching for comparison. Adapting the above example:
|
|
434
457
|
|
|
435
458
|
```
|
|
436
|
-
Resources
|
|
459
|
+
Resources.MyEC2Instance.Properties.ImageId.[<^AWSRegion>]
|
|
437
460
|
```
|
|
438
461
|
|
|
439
462
|
returns only the EC2 ImageIds where the ImageId begins with `AWSRegion`. This can be helpful in checking for and enforcing naming standards, etc.
|
|
@@ -446,13 +469,13 @@ Wildcard Ranges extend the powerful functionality of Wildcard Tokens with the ad
|
|
|
446
469
|
For example we know:
|
|
447
470
|
|
|
448
471
|
```
|
|
449
|
-
Resources
|
|
472
|
+
Resources.*.Type
|
|
450
473
|
```
|
|
451
474
|
|
|
452
475
|
Selects all `Resource` objects. If we convert the Wildcard Token in the query to a Wildcard Range Token:
|
|
453
476
|
|
|
454
477
|
```
|
|
455
|
-
Resources
|
|
478
|
+
Resources.[*].Type
|
|
456
479
|
```
|
|
457
480
|
|
|
458
481
|
The Rule will fail as below:
|
|
@@ -486,7 +509,7 @@ Note that the array we want is nested within another array, and we need to make
|
|
|
486
509
|
We can accomplish this by using a Wildcard Range Token in our Query as below:
|
|
487
510
|
|
|
488
511
|
```
|
|
489
|
-
Resources
|
|
512
|
+
Resources.AppendItemToListFunction.Properties.Code.ZipFile.[*].[]
|
|
490
513
|
```
|
|
491
514
|
|
|
492
515
|
Which allows us to then evaluate the Unbounded Range token against each array item, returning only the array we want.
|
|
@@ -503,7 +526,7 @@ You can use multiple Tokens within a Range Token by seperating each token with a
|
|
|
503
526
|
For example:
|
|
504
527
|
|
|
505
528
|
```
|
|
506
|
-
Resources
|
|
529
|
+
Resources.SecurityGroup.Properties.SecurityGroupIngress.[0, -2]
|
|
507
530
|
```
|
|
508
531
|
|
|
509
532
|
Would select all except the last element of an array.
|
|
@@ -511,7 +534,7 @@ Would select all except the last element of an array.
|
|
|
511
534
|
This also applies to Bounded Ranges, Key Ranges, Pattern Ranges, and Wildcard Ranges! For example:
|
|
512
535
|
|
|
513
536
|
```
|
|
514
|
-
Resources
|
|
537
|
+
Resources.MyEC2Instance.Properties.ImageId.[(^AWSRegion),(^),(^Custom)]
|
|
515
538
|
```
|
|
516
539
|
|
|
517
540
|
will select any EC2 ImageIds that start with either `AWSRegion` or `Custom`.
|
|
@@ -535,17 +558,42 @@ ZipFile: !Join
|
|
|
535
558
|
from our previous examples, we used the below query to select the nested array:
|
|
536
559
|
|
|
537
560
|
```
|
|
538
|
-
Resources
|
|
561
|
+
Resources.AppendItemToListFunction.Properties.Code.ZipFile.[*].[]
|
|
539
562
|
```
|
|
540
563
|
|
|
541
564
|
With Nested Ranges, this can be shortened to:
|
|
542
565
|
|
|
543
566
|
```
|
|
544
|
-
Resources
|
|
567
|
+
Resources.AppendItemToListFunction.Properties.Code.ZipFile.[[]]
|
|
545
568
|
```
|
|
546
569
|
|
|
547
570
|
Which is both more concise *and* more representitave of our intention to select only the array.
|
|
548
571
|
|
|
572
|
+
# Grouping Queries
|
|
573
|
+
|
|
574
|
+
CFN-Check grouping allows you significant freedom of expression in how you write queries while *also* allowing you to more easily restrict and filter results by multiple criterion. Queries support both logical "or" and "and" statements via the `|` and `&` operators respectively. For example, consider the previous values query where we used an `in` operator:
|
|
575
|
+
|
|
576
|
+
```
|
|
577
|
+
Resources.*.(Type in AWS::Lambda::Function,AWS::Serverless::Function)
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
This could be rewritten as:
|
|
581
|
+
```
|
|
582
|
+
Resources.*(Type == AWS::Lambda::Function | Type == AWS::Serverless::Function)
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
A more likely scenario might be finding specifically NodeJS Lambda functions. For example:
|
|
586
|
+
|
|
587
|
+
```
|
|
588
|
+
Resources.*.(Type == AWS::Lambda::Function,AWS::Serverless::Function & Properties.Runtime == <nodejs20>)
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
Queries are "split" by `|` operator and then "grouped" by `&` operator. That means if we want a query to match one set of criterion <i>or</i> another we could write:
|
|
592
|
+
|
|
593
|
+
```
|
|
594
|
+
Resources.*.(Type == AWS::Lambda::Function & Properties.Runtime == <nodejs20> | Type == AWS::Serverless::Function & Properties.Runtime == <nodejs20>)
|
|
595
|
+
```
|
|
596
|
+
|
|
549
597
|
<br/>
|
|
550
598
|
|
|
551
599
|
# Using Pydantic Models
|
|
@@ -561,8 +609,8 @@ from cfn_check import Collection, Rule
|
|
|
561
609
|
class ValidateResourceType(Collection):
|
|
562
610
|
|
|
563
611
|
@Rule(
|
|
564
|
-
"Resources
|
|
565
|
-
"It checks Resource
|
|
612
|
+
"Resources.*.Type",
|
|
613
|
+
"It checks Resource.Type is correctly definined",
|
|
566
614
|
)
|
|
567
615
|
def validate_test(self, value: str):
|
|
568
616
|
assert value is not None, '❌ Resource Type not defined'
|
|
@@ -582,8 +630,8 @@ class Resource(BaseModel):
|
|
|
582
630
|
class ValidateResourceType(Collection):
|
|
583
631
|
|
|
584
632
|
@Rule(
|
|
585
|
-
"Resources
|
|
586
|
-
"It checks Resource
|
|
633
|
+
"Resources.*",
|
|
634
|
+
"It checks Resource.Type is correctly definined",
|
|
587
635
|
)
|
|
588
636
|
def validate_test(self, value: Resource):
|
|
589
637
|
assert value is not None
|
|
@@ -593,6 +641,34 @@ By deferring type and existence assertions to `Pydantic` models, you can focus y
|
|
|
593
641
|
|
|
594
642
|
<br/>
|
|
595
643
|
|
|
644
|
+
# Using .query()
|
|
645
|
+
|
|
646
|
+
Some of the most challenging validations to write in CFN-Guard or CFN-Lint are those requring validation of other template information against an existing selection. For example, validating that a Lambda has a LoggingGroup attached and specified within the same template.
|
|
647
|
+
|
|
648
|
+
CFN-Check makes performing these complex assertions intuitive and painless by allowing you to execute additional querieis within a rule via the `.query()` method. For example, to perform the LoggingGroup validation above you might write:
|
|
649
|
+
|
|
650
|
+
```python
|
|
651
|
+
@Rule("Resources.*.(Type in AWS::Lambda::Function,AWS::Serverless::Function)", "It validates a lambda is configured correctly")
|
|
652
|
+
def validate_lambda(self, lambda_resource: Lambda):
|
|
653
|
+
assert isinstance(lambda_resource, Lambda), "Not a valid Lambda"
|
|
654
|
+
|
|
655
|
+
log_groups = self.query(
|
|
656
|
+
f"Resources.{lambda_resource.Properties.LoggingConfig.LogGroup}",
|
|
657
|
+
transforms=[
|
|
658
|
+
lambda data: LoggingGroup(**data)
|
|
659
|
+
]
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
assert log_groups is not None, f"No resources found for LoggingGroup {lambda_resource.Properties.LoggingConfig.LogGroup}"
|
|
663
|
+
assert len(log_groups) > 0, "No matching logging group found in Resources for Lambda"
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
The `query()` method accepts the following parameters:
|
|
667
|
+
|
|
668
|
+
- `query - (str, required)`: A string CFN-Check query as used when defining Rules.
|
|
669
|
+
- `document - (dict/list/any, optional)`: The filepath to a CloudFormation template document. This document must have been loaded by and specified to CFN-Check either via the default CLI path param, the `-i/--import-values` optional CLI arg, or under the `input_values` config key. This will cause the query specified in the call to execute against the template specified.
|
|
670
|
+
- `transforms - (list[Function], optional)`: A list of functions that can be used to modify and filter returned results. In the example above, we use a single transform function to convert matches returned to a `LoggingGroup` Pydantic model instance.
|
|
671
|
+
|
|
596
672
|
# The Rendering Engine
|
|
597
673
|
|
|
598
674
|
### Overview
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
| Package | cfn-check |
|
|
11
11
|
| ----------- | ----------- |
|
|
12
|
-
| Version | 0.
|
|
12
|
+
| Version | 0.8.1 |
|
|
13
13
|
| Download | https://pypi.org/project/cfn-check/ |
|
|
14
14
|
| Source | https://github.com/adalundhe/cfn-check |
|
|
15
15
|
| Keywords | cloud-formation, testing, aws, cli |
|
|
@@ -214,19 +214,20 @@ Congrats! You've just made the cloud a bit better place!
|
|
|
214
214
|
|
|
215
215
|
# Queries, Tokens, and Syntax
|
|
216
216
|
|
|
217
|
-
A `cfn-check` Query is a string made up of
|
|
217
|
+
A `cfn-check` Query is a string made up of period (`.`) delimited "Tokens" centered around four primary types:
|
|
218
218
|
|
|
219
219
|
- <b>`Keys`</b> - `<KEY>`: String name key Tokens that perform exact matching on keys of key/value pairs in a CloudFormation document.
|
|
220
|
-
- <b>`
|
|
220
|
+
- <b>`Values`</b> - `(<KEY> = <VALUE>)`: Parenthesis-enclosed `K==V` pairs that perform matching on values of key/value pairs in a CloudFormation document.
|
|
221
|
+
- <b>`Patterns`</b> - `<\d+>`: Arrow-enclosed regex pattern Tokens that perform pattern-based matching on keys of key/value pairs in a CloudFormation document.
|
|
221
222
|
- <b>`Ranges`</b> - `[]`: Brackets enclosed Tokens that perform array selection and filtering in a CloudFormation document.
|
|
222
223
|
|
|
223
224
|
|
|
224
|
-
In addition to `Key`, `Pattern`, and `Range` selection, you can also incorporate:
|
|
225
|
+
In addition to `Key`, `Value`, `Pattern`, and `Range` selection, you can also incorporate:
|
|
225
226
|
|
|
226
227
|
- <b>`Bounded Ranges`</b> - `[<A>-<B>]`: Exact matches from the starting position (if specified) to the end position (if specified) of an array
|
|
227
228
|
- <b>`Indicies`</b> - `[<A>]`: Exact matches the specified indicies of an array
|
|
228
229
|
- <b>`Key Ranges`</b> - `[<KEY>]`: Exact matches keys of objects within an array
|
|
229
|
-
- <b>`Pattern Ranges`</b> (`[
|
|
230
|
+
- <b>`Pattern Ranges`</b> (`[<\d+>]`): Matches they keys of objects within an array based on the specified pattern
|
|
230
231
|
- <b>`Wildcards`</b> (`*`): Selects all values for a given object or array or returns the non-object/array value at the specified path
|
|
231
232
|
- <b>`Wildcard Ranges`</b> (`[*]`): Selects all values for a given array and ensures that *only* the values of a valid array type are returned (any other type will be treated as a mismatch).
|
|
232
233
|
|
|
@@ -240,6 +241,27 @@ Resources
|
|
|
240
241
|
|
|
241
242
|
as your query, you'll select all items within the CloudFormation document under the `Resources` key.
|
|
242
243
|
|
|
244
|
+
|
|
245
|
+
### Working with Values
|
|
246
|
+
|
|
247
|
+
In addition to searching by keys, filtering by the values associated with those keys is the most common way you'll traverse and validate your CloudFormation template. To filter `Resource` key matches by the value of their `Type` value to only return EC2 Instances, we would specify a query like:
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
Resources.*.(Type == AWS::EC2::Instance)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
You can also match multiple values by utilizing the `in`/`IN` operator:
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
Resources.*.(Type in AWS::Lambda::Function,AWS::Serverless::Function)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
and providing a comma-delimited list of values.
|
|
260
|
+
|
|
261
|
+
> [!NOTE]
|
|
262
|
+
> Value queries do not support Nested Ranges or nested queries, but *do* support
|
|
263
|
+
> Wildcards and Patterns. See below for more info!
|
|
264
|
+
|
|
243
265
|
### Working with Patterns
|
|
244
266
|
|
|
245
267
|
If an object within a CloudFormation document contains multiple similar keys you want to select, `Pattern` Tokens are your go-to solution. Consider this segment of CloudFormation:
|
|
@@ -266,7 +288,7 @@ Resources:
|
|
|
266
288
|
We want to select <i>both</i> `SecurityGroupIngress` and `SecurityGroupEgress` to perform the same rule evaluations. Since the keys for both blocks start with `SecurityGroup`, we could write a Query using a Pattern Token like:
|
|
267
289
|
|
|
268
290
|
```
|
|
269
|
-
Resources
|
|
291
|
+
Resources.SecurityGroup.Properties.<SecurityGroup>
|
|
270
292
|
```
|
|
271
293
|
|
|
272
294
|
which would allow us to use a single rule to evaluate both:
|
|
@@ -275,7 +297,7 @@ which would allow us to use a single rule to evaluate both:
|
|
|
275
297
|
class ValidateSecurityGroups(Collection):
|
|
276
298
|
|
|
277
299
|
@Rule(
|
|
278
|
-
"Resources
|
|
300
|
+
"Resources.SecurityGroup.Properties.<SecurityGroup>",
|
|
279
301
|
"It checks Security Groups are correctly definined",
|
|
280
302
|
)
|
|
281
303
|
def validate_security_groups(self, value: list[dict]):
|
|
@@ -306,7 +328,7 @@ Wildcard Tokens allow you to select all matching objects, array entries, or valu
|
|
|
306
328
|
In fact, you've already used one! In the first example, we use a Wildcard Token in the below query:
|
|
307
329
|
|
|
308
330
|
```
|
|
309
|
-
Resources
|
|
331
|
+
Resources.*.Type
|
|
310
332
|
```
|
|
311
333
|
|
|
312
334
|
To select all `Resource` objects, then further extract the `Type` field from each object. This helps us avoid copy-paste rules at the potential cost of deferring more work to individual `Rule` methods if we aren't careful and select too much!
|
|
@@ -325,7 +347,7 @@ Ranges allow you to perform sophisticated selection of objects or data within a
|
|
|
325
347
|
Unbounded ranges allow you to select and return an array in its entirety. For example:
|
|
326
348
|
|
|
327
349
|
```
|
|
328
|
-
Resources
|
|
350
|
+
Resources.SecurityGroup.Properties.SecurityGroupIngress.[]
|
|
329
351
|
```
|
|
330
352
|
|
|
331
353
|
Would return all SecurityGroupIngress objects in the CloudFormation document as a list, allowing you to check that the array of ingresses has been both defined *and* populated.
|
|
@@ -336,7 +358,7 @@ Would return all SecurityGroupIngress objects in the CloudFormation document as
|
|
|
336
358
|
Indexes allow you to select specific positions within an array. For example:
|
|
337
359
|
|
|
338
360
|
```
|
|
339
|
-
Resources
|
|
361
|
+
Resources.SecurityGroup.Properties.SecurityGroupIngress.[0]
|
|
340
362
|
```
|
|
341
363
|
|
|
342
364
|
Would return the first SecurityGroupIngress objects in the document.
|
|
@@ -350,7 +372,7 @@ Bounded Ranges allow you to select subsets of indicies within an array (much lik
|
|
|
350
372
|
As an example:
|
|
351
373
|
|
|
352
374
|
```
|
|
353
|
-
Resources
|
|
375
|
+
Resources.SecurityGroup.Properties.SecurityGroupIngress.[1-3]
|
|
354
376
|
```
|
|
355
377
|
|
|
356
378
|
Would select the second and third SecurityGroupIngress objects in the document.
|
|
@@ -358,13 +380,13 @@ Would select the second and third SecurityGroupIngress objects in the document.
|
|
|
358
380
|
Start or end positions are optional for Bounded Ranges. If a starting position is not defined, `cfn-check` will default to `0`. Likewise, if an end position is not defined, `cfn-check` will default to the end of given list. For example:
|
|
359
381
|
|
|
360
382
|
```
|
|
361
|
-
Resources
|
|
383
|
+
Resources.SecurityGroup.Properties.SecurityGroupIngress.[-3]
|
|
362
384
|
```
|
|
363
385
|
|
|
364
386
|
selects the first through third SecurityGroupIngress objects in the document while:
|
|
365
387
|
|
|
366
388
|
```
|
|
367
|
-
Resources
|
|
389
|
+
Resources.SecurityGroup.Properties.SecurityGroupIngress.[3-]
|
|
368
390
|
```
|
|
369
391
|
|
|
370
392
|
selects the remaining SecurityGroupIngress objects starting from the third.
|
|
@@ -381,7 +403,7 @@ Often times it's easier to match based upon an array's contents than by exact in
|
|
|
381
403
|
For example:
|
|
382
404
|
|
|
383
405
|
```
|
|
384
|
-
Resources
|
|
406
|
+
Resources.MyEC2Instance.Properties.ImageId.[AWSRegionArch2AMI]
|
|
385
407
|
```
|
|
386
408
|
|
|
387
409
|
returns only the EC2 ImageIds where the ImageId exactly matches `AWSRegionArch2AMI`.
|
|
@@ -392,7 +414,7 @@ returns only the EC2 ImageIds where the ImageId exactly matches `AWSRegionArch2A
|
|
|
392
414
|
Pattern Ranges function much like Key Ranges, but utilize regex-based pattern matching for comparison. Adapting the above example:
|
|
393
415
|
|
|
394
416
|
```
|
|
395
|
-
Resources
|
|
417
|
+
Resources.MyEC2Instance.Properties.ImageId.[<^AWSRegion>]
|
|
396
418
|
```
|
|
397
419
|
|
|
398
420
|
returns only the EC2 ImageIds where the ImageId begins with `AWSRegion`. This can be helpful in checking for and enforcing naming standards, etc.
|
|
@@ -405,13 +427,13 @@ Wildcard Ranges extend the powerful functionality of Wildcard Tokens with the ad
|
|
|
405
427
|
For example we know:
|
|
406
428
|
|
|
407
429
|
```
|
|
408
|
-
Resources
|
|
430
|
+
Resources.*.Type
|
|
409
431
|
```
|
|
410
432
|
|
|
411
433
|
Selects all `Resource` objects. If we convert the Wildcard Token in the query to a Wildcard Range Token:
|
|
412
434
|
|
|
413
435
|
```
|
|
414
|
-
Resources
|
|
436
|
+
Resources.[*].Type
|
|
415
437
|
```
|
|
416
438
|
|
|
417
439
|
The Rule will fail as below:
|
|
@@ -445,7 +467,7 @@ Note that the array we want is nested within another array, and we need to make
|
|
|
445
467
|
We can accomplish this by using a Wildcard Range Token in our Query as below:
|
|
446
468
|
|
|
447
469
|
```
|
|
448
|
-
Resources
|
|
470
|
+
Resources.AppendItemToListFunction.Properties.Code.ZipFile.[*].[]
|
|
449
471
|
```
|
|
450
472
|
|
|
451
473
|
Which allows us to then evaluate the Unbounded Range token against each array item, returning only the array we want.
|
|
@@ -462,7 +484,7 @@ You can use multiple Tokens within a Range Token by seperating each token with a
|
|
|
462
484
|
For example:
|
|
463
485
|
|
|
464
486
|
```
|
|
465
|
-
Resources
|
|
487
|
+
Resources.SecurityGroup.Properties.SecurityGroupIngress.[0, -2]
|
|
466
488
|
```
|
|
467
489
|
|
|
468
490
|
Would select all except the last element of an array.
|
|
@@ -470,7 +492,7 @@ Would select all except the last element of an array.
|
|
|
470
492
|
This also applies to Bounded Ranges, Key Ranges, Pattern Ranges, and Wildcard Ranges! For example:
|
|
471
493
|
|
|
472
494
|
```
|
|
473
|
-
Resources
|
|
495
|
+
Resources.MyEC2Instance.Properties.ImageId.[(^AWSRegion),(^),(^Custom)]
|
|
474
496
|
```
|
|
475
497
|
|
|
476
498
|
will select any EC2 ImageIds that start with either `AWSRegion` or `Custom`.
|
|
@@ -494,17 +516,42 @@ ZipFile: !Join
|
|
|
494
516
|
from our previous examples, we used the below query to select the nested array:
|
|
495
517
|
|
|
496
518
|
```
|
|
497
|
-
Resources
|
|
519
|
+
Resources.AppendItemToListFunction.Properties.Code.ZipFile.[*].[]
|
|
498
520
|
```
|
|
499
521
|
|
|
500
522
|
With Nested Ranges, this can be shortened to:
|
|
501
523
|
|
|
502
524
|
```
|
|
503
|
-
Resources
|
|
525
|
+
Resources.AppendItemToListFunction.Properties.Code.ZipFile.[[]]
|
|
504
526
|
```
|
|
505
527
|
|
|
506
528
|
Which is both more concise *and* more representitave of our intention to select only the array.
|
|
507
529
|
|
|
530
|
+
# Grouping Queries
|
|
531
|
+
|
|
532
|
+
CFN-Check grouping allows you significant freedom of expression in how you write queries while *also* allowing you to more easily restrict and filter results by multiple criterion. Queries support both logical "or" and "and" statements via the `|` and `&` operators respectively. For example, consider the previous values query where we used an `in` operator:
|
|
533
|
+
|
|
534
|
+
```
|
|
535
|
+
Resources.*.(Type in AWS::Lambda::Function,AWS::Serverless::Function)
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
This could be rewritten as:
|
|
539
|
+
```
|
|
540
|
+
Resources.*(Type == AWS::Lambda::Function | Type == AWS::Serverless::Function)
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
A more likely scenario might be finding specifically NodeJS Lambda functions. For example:
|
|
544
|
+
|
|
545
|
+
```
|
|
546
|
+
Resources.*.(Type == AWS::Lambda::Function,AWS::Serverless::Function & Properties.Runtime == <nodejs20>)
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
Queries are "split" by `|` operator and then "grouped" by `&` operator. That means if we want a query to match one set of criterion <i>or</i> another we could write:
|
|
550
|
+
|
|
551
|
+
```
|
|
552
|
+
Resources.*.(Type == AWS::Lambda::Function & Properties.Runtime == <nodejs20> | Type == AWS::Serverless::Function & Properties.Runtime == <nodejs20>)
|
|
553
|
+
```
|
|
554
|
+
|
|
508
555
|
<br/>
|
|
509
556
|
|
|
510
557
|
# Using Pydantic Models
|
|
@@ -520,8 +567,8 @@ from cfn_check import Collection, Rule
|
|
|
520
567
|
class ValidateResourceType(Collection):
|
|
521
568
|
|
|
522
569
|
@Rule(
|
|
523
|
-
"Resources
|
|
524
|
-
"It checks Resource
|
|
570
|
+
"Resources.*.Type",
|
|
571
|
+
"It checks Resource.Type is correctly definined",
|
|
525
572
|
)
|
|
526
573
|
def validate_test(self, value: str):
|
|
527
574
|
assert value is not None, '❌ Resource Type not defined'
|
|
@@ -541,8 +588,8 @@ class Resource(BaseModel):
|
|
|
541
588
|
class ValidateResourceType(Collection):
|
|
542
589
|
|
|
543
590
|
@Rule(
|
|
544
|
-
"Resources
|
|
545
|
-
"It checks Resource
|
|
591
|
+
"Resources.*",
|
|
592
|
+
"It checks Resource.Type is correctly definined",
|
|
546
593
|
)
|
|
547
594
|
def validate_test(self, value: Resource):
|
|
548
595
|
assert value is not None
|
|
@@ -552,6 +599,34 @@ By deferring type and existence assertions to `Pydantic` models, you can focus y
|
|
|
552
599
|
|
|
553
600
|
<br/>
|
|
554
601
|
|
|
602
|
+
# Using .query()
|
|
603
|
+
|
|
604
|
+
Some of the most challenging validations to write in CFN-Guard or CFN-Lint are those requring validation of other template information against an existing selection. For example, validating that a Lambda has a LoggingGroup attached and specified within the same template.
|
|
605
|
+
|
|
606
|
+
CFN-Check makes performing these complex assertions intuitive and painless by allowing you to execute additional querieis within a rule via the `.query()` method. For example, to perform the LoggingGroup validation above you might write:
|
|
607
|
+
|
|
608
|
+
```python
|
|
609
|
+
@Rule("Resources.*.(Type in AWS::Lambda::Function,AWS::Serverless::Function)", "It validates a lambda is configured correctly")
|
|
610
|
+
def validate_lambda(self, lambda_resource: Lambda):
|
|
611
|
+
assert isinstance(lambda_resource, Lambda), "Not a valid Lambda"
|
|
612
|
+
|
|
613
|
+
log_groups = self.query(
|
|
614
|
+
f"Resources.{lambda_resource.Properties.LoggingConfig.LogGroup}",
|
|
615
|
+
transforms=[
|
|
616
|
+
lambda data: LoggingGroup(**data)
|
|
617
|
+
]
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
assert log_groups is not None, f"No resources found for LoggingGroup {lambda_resource.Properties.LoggingConfig.LogGroup}"
|
|
621
|
+
assert len(log_groups) > 0, "No matching logging group found in Resources for Lambda"
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
The `query()` method accepts the following parameters:
|
|
625
|
+
|
|
626
|
+
- `query - (str, required)`: A string CFN-Check query as used when defining Rules.
|
|
627
|
+
- `document - (dict/list/any, optional)`: The filepath to a CloudFormation template document. This document must have been loaded by and specified to CFN-Check either via the default CLI path param, the `-i/--import-values` optional CLI arg, or under the `input_values` config key. This will cause the query specified in the call to execute against the template specified.
|
|
628
|
+
- `transforms - (list[Function], optional)`: A list of functions that can be used to modify and filter returned results. In the example above, we use a single transform function to convert matches returned to a `LoggingGroup` Pydantic model instance.
|
|
629
|
+
|
|
555
630
|
# The Rendering Engine
|
|
556
631
|
|
|
557
632
|
### Overview
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from pydantic import BaseModel, StrictStr, Field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Config(BaseModel):
|
|
5
|
+
attributes: dict[StrictStr, StrictStr] | None = None
|
|
6
|
+
availability_zones: list[StrictStr] | None = None
|
|
7
|
+
import_values: dict[StrictStr, StrictStr] | None = None
|
|
8
|
+
mappings: dict[StrictStr, StrictStr] | None = None
|
|
9
|
+
parameters: dict[StrictStr, StrictStr] | None = None
|
|
10
|
+
references: dict[StrictStr, StrictStr] | None = None
|