cfn-check 0.3.2__py3-none-any.whl → 0.8.1__py3-none-any.whl
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.
Potentially problematic release.
This version of cfn-check might be problematic. Click here for more details.
- cfn_check/cli/config.py +10 -0
- cfn_check/cli/render.py +142 -0
- cfn_check/cli/root.py +3 -1
- cfn_check/cli/utils/attributes.py +1 -1
- cfn_check/cli/utils/files.py +46 -21
- cfn_check/cli/utils/stdout.py +18 -0
- cfn_check/cli/validate.py +35 -26
- cfn_check/collection/collection.py +58 -1
- cfn_check/evaluation/evaluator.py +31 -3
- cfn_check/evaluation/parsing/token.py +4 -1
- cfn_check/evaluation/validate.py +33 -2
- cfn_check/rendering/__init__.py +1 -0
- cfn_check/rendering/cidr_solver.py +66 -0
- cfn_check/rendering/renderer.py +1316 -0
- cfn_check/rendering/utils.py +13 -0
- cfn_check/rules/rule.py +3 -0
- cfn_check/validation/validator.py +11 -1
- {cfn_check-0.3.2.dist-info → cfn_check-0.8.1.dist-info}/METADATA +106 -5
- cfn_check-0.8.1.dist-info/RECORD +42 -0
- example/multitag.py +21 -0
- example/pydantic_rules.py +114 -0
- example/renderer_test.py +42 -0
- cfn_check/loader/__init__.py +0 -0
- cfn_check/loader/loader.py +0 -21
- cfn_check-0.3.2.dist-info/RECORD +0 -34
- {cfn_check-0.3.2.dist-info → cfn_check-0.8.1.dist-info}/WHEEL +0 -0
- {cfn_check-0.3.2.dist-info → cfn_check-0.8.1.dist-info}/entry_points.txt +0 -0
- {cfn_check-0.3.2.dist-info → cfn_check-0.8.1.dist-info}/licenses/LICENSE +0 -0
- {cfn_check-0.3.2.dist-info → cfn_check-0.8.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from ruamel.yaml.comments import CommentedMap, CommentedSeq
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def assign(parent: CommentedMap | CommentedSeq | None, key_or_index: Any, value: Any):
|
|
6
|
+
if parent is None:
|
|
7
|
+
return # root already set
|
|
8
|
+
if isinstance(parent, CommentedMap):
|
|
9
|
+
parent[key_or_index] = value
|
|
10
|
+
else:
|
|
11
|
+
# key_or_index is an int for sequences
|
|
12
|
+
# Ensure sequence large enough (iterative approach assigns in order, so append is fine)
|
|
13
|
+
parent.append(value)
|
cfn_check/rules/rule.py
CHANGED
|
@@ -13,13 +13,16 @@ class Rule:
|
|
|
13
13
|
self,
|
|
14
14
|
query: str,
|
|
15
15
|
name: str,
|
|
16
|
+
filters: list[Callable[[JsonValue], JsonValue]] | None = None
|
|
16
17
|
):
|
|
17
18
|
self.query = query
|
|
18
19
|
self.name = name
|
|
20
|
+
self.filters = filters
|
|
19
21
|
|
|
20
22
|
def __call__(self, func: Callable[[T], None]):
|
|
21
23
|
return Validator[T](
|
|
22
24
|
func,
|
|
23
25
|
self.query,
|
|
24
26
|
self.name,
|
|
27
|
+
filters=self.filters,
|
|
25
28
|
)
|
|
@@ -14,10 +14,12 @@ class Validator(Generic[T]):
|
|
|
14
14
|
func: Callable[[T], None],
|
|
15
15
|
query: str,
|
|
16
16
|
name: str,
|
|
17
|
+
filters: list[Callable[[Data], Data]] | None = None
|
|
17
18
|
):
|
|
18
19
|
self.func = func
|
|
19
20
|
self.query = query
|
|
20
21
|
self.name = name
|
|
22
|
+
self.filters = filters
|
|
21
23
|
|
|
22
24
|
self.model: BaseModel | None = None
|
|
23
25
|
|
|
@@ -31,8 +33,16 @@ class Validator(Generic[T]):
|
|
|
31
33
|
|
|
32
34
|
try:
|
|
33
35
|
path, item = arg
|
|
36
|
+
|
|
37
|
+
if self.filters:
|
|
38
|
+
for filter in self.filters:
|
|
39
|
+
item = filter(item)
|
|
40
|
+
|
|
41
|
+
if item is None:
|
|
42
|
+
return
|
|
43
|
+
|
|
34
44
|
if self.model and isinstance(item, dict):
|
|
35
|
-
|
|
45
|
+
item = self.model(**item)
|
|
36
46
|
|
|
37
47
|
return self.func(item)
|
|
38
48
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cfn-check
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: Validate Cloud Formation
|
|
5
5
|
Author-email: Ada Lundhe <adalundhe@lundhe.audio>
|
|
6
6
|
License: MIT License
|
|
@@ -27,14 +27,15 @@ 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
|
|
37
|
-
Requires-Dist:
|
|
38
|
+
Requires-Dist: ruamel.yaml
|
|
38
39
|
Requires-Dist: hyperlight-cocoa
|
|
39
40
|
Requires-Dist: async-logging
|
|
40
41
|
Dynamic: license-file
|
|
@@ -50,7 +51,7 @@ Dynamic: license-file
|
|
|
50
51
|
|
|
51
52
|
| Package | cfn-check |
|
|
52
53
|
| ----------- | ----------- |
|
|
53
|
-
| Version | 0.
|
|
54
|
+
| Version | 0.8.0 |
|
|
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 |
|
|
@@ -70,15 +71,20 @@ problems inherint to `cfn-lint` more than `cfn-guard`, primarily:
|
|
|
70
71
|
- Inability to parse non-resource wildcards
|
|
71
72
|
- Inability to validate non-resource template data
|
|
72
73
|
- Inabillity to use structured models to validate input
|
|
74
|
+
- Poor ability to parse and render CloudFormation Refs/Functions
|
|
73
75
|
|
|
74
76
|
In comparison to `cfn-guard`, `cfn-check` is pure Python, thus
|
|
75
77
|
avoiding YADSL (Yet Another DSL) headaches. It also proves
|
|
76
78
|
significantly more configurable/modular/hackable as a result.
|
|
79
|
+
`cfn-check` can resolve _some_ (not all) CloudFormation Intrinsic
|
|
80
|
+
Functions and Refs.
|
|
77
81
|
|
|
78
82
|
CFN-Check uses a combination of simple depth-first-search tree
|
|
79
83
|
parsing, friendly `cfn-lint` like query syntax, `Pydantic` models,
|
|
80
84
|
and `pytest`-like assert-driven checks to make validating your
|
|
81
85
|
Cloud Formation easy while offering both CLI and Python API interfaces.
|
|
86
|
+
CFN-Check also uses a lightning-fast AST-parser to render your templates,
|
|
87
|
+
allowing you to validate policy, not just a YAML document.
|
|
82
88
|
|
|
83
89
|
<br/>
|
|
84
90
|
|
|
@@ -447,7 +453,7 @@ Resources::*::Type
|
|
|
447
453
|
Selects all `Resource` objects. If we convert the Wildcard Token in the query to a Wildcard Range Token:
|
|
448
454
|
|
|
449
455
|
```
|
|
450
|
-
Resources
|
|
456
|
+
Resources::[*]::Type
|
|
451
457
|
```
|
|
452
458
|
|
|
453
459
|
The Rule will fail as below:
|
|
@@ -537,5 +543,100 @@ With Nested Ranges, this can be shortened to:
|
|
|
537
543
|
|
|
538
544
|
```
|
|
539
545
|
Resources::AppendItemToListFunction::Properties::Code::ZipFile::[[]]
|
|
546
|
+
```
|
|
540
547
|
|
|
541
548
|
Which is both more concise *and* more representitave of our intention to select only the array.
|
|
549
|
+
|
|
550
|
+
<br/>
|
|
551
|
+
|
|
552
|
+
# Using Pydantic Models
|
|
553
|
+
|
|
554
|
+
In addition to traditional `pytest`-like assert statements, `cfn-lint` can validate results returned by queries via `Pydantic` models.
|
|
555
|
+
|
|
556
|
+
For example, consider again the initial example where we validate the `Type` field of `Resource` objects.
|
|
557
|
+
|
|
558
|
+
```python
|
|
559
|
+
from cfn_check import Collection, Rule
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
class ValidateResourceType(Collection):
|
|
563
|
+
|
|
564
|
+
@Rule(
|
|
565
|
+
"Resources::*::Type",
|
|
566
|
+
"It checks Resource::Type is correctly definined",
|
|
567
|
+
)
|
|
568
|
+
def validate_test(self, value: str):
|
|
569
|
+
assert value is not None, '❌ Resource Type not defined'
|
|
570
|
+
assert isinstance(value, str), '❌ Resource Type not a string'
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
Rather than explicitly querying for the type field and writing assertions, we can instead define a `Pydantic` schema, then pass all `Resource` objects to that schema by specifying it as a Python type hint in our `Rule` method's signature.
|
|
574
|
+
|
|
575
|
+
```python
|
|
576
|
+
from cfn_check import Collection, Rule
|
|
577
|
+
from pydantic import BaseModel, StrictStr
|
|
578
|
+
|
|
579
|
+
class Resource(BaseModel):
|
|
580
|
+
Type: StrictStr
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
class ValidateResourceType(Collection):
|
|
584
|
+
|
|
585
|
+
@Rule(
|
|
586
|
+
"Resources::*",
|
|
587
|
+
"It checks Resource::Type is correctly definined",
|
|
588
|
+
)
|
|
589
|
+
def validate_test(self, value: Resource):
|
|
590
|
+
assert value is not None
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
By deferring type and existence assertions to `Pydantic` models, you can focus your actual assertion logic on business/security policy checks.
|
|
594
|
+
|
|
595
|
+
<br/>
|
|
596
|
+
|
|
597
|
+
# The Rendering Engine
|
|
598
|
+
|
|
599
|
+
### Overview
|
|
600
|
+
|
|
601
|
+
In Version 0.6.X, CFN-Check introduced a rendering engine, which allows it
|
|
602
|
+
to parse and execute Refs and all CloudFormation intrinsic functions via
|
|
603
|
+
either the CloudFormation document or user-supplied values. This additional
|
|
604
|
+
also resulted in the:
|
|
605
|
+
|
|
606
|
+
```bash
|
|
607
|
+
cfn-check render <TEMPLATE_PATH >
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
command being added, allowing you to effectively "dry run" render your
|
|
611
|
+
CloudFormation templates akin to the `helm template` command for Helm.
|
|
612
|
+
|
|
613
|
+
By default, `cfn-check render` outputs to stdout, however you can easily
|
|
614
|
+
save rendered output to a file via the `-o/--output-file` flag. For example:
|
|
615
|
+
|
|
616
|
+
```bash
|
|
617
|
+
cfn-check render template.yml -o rendered.yml
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
The `cfn-check render` command also offers the following options:
|
|
621
|
+
|
|
622
|
+
- `-a/--attributes`: A list of <key>=<value> input `!GetAtt` attributes to use
|
|
623
|
+
- `-m/--mappings`: A list of <key>=<value> input `Mappings` to use
|
|
624
|
+
- `-p/--parameters`: A list of <key>=<value> input `Parameters` to use
|
|
625
|
+
- `-l/--log-level`: The log level to use
|
|
626
|
+
|
|
627
|
+
### The Rendering Engine during Checks
|
|
628
|
+
|
|
629
|
+
By default rendering is enabled when running `cfn-check` validation. You can
|
|
630
|
+
disable it by supplying `no-render` to the `-F/--flags` option as below:
|
|
631
|
+
|
|
632
|
+
```bash
|
|
633
|
+
cfn-check validate -F no-render -r rules.py template.yaml
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Disabling rendering means CFN-Check will validate your template as-is, with
|
|
637
|
+
no additional pre-processing and no application of user input values.
|
|
638
|
+
|
|
639
|
+
> [!WARNING]
|
|
640
|
+
> CloudFormation documents are <b>not</b> "plain yaml" and disabling
|
|
641
|
+
> rendering means any dynamically determined values will likely fail
|
|
642
|
+
> to pass validation, resulting in false positives for failures!
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
cfn_check/__init__.py,sha256=ccUo2YxBmuEmak1M5o-8J0ECLXNkDDUsLJ4mkm31GvU,96
|
|
2
|
+
cfn_check/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
cfn_check/cli/config.py,sha256=khoxOcLBu-tPXA6U3QyySfLq41l9Z9wcghZ4tO5IAaM,416
|
|
4
|
+
cfn_check/cli/render.py,sha256=vsAk--RSIzRu4ebRlq4vfQSD-EqWp93GN029ck6-GO8,4551
|
|
5
|
+
cfn_check/cli/root.py,sha256=Fi-G3nP-HQMY4iPenF2xnkQF798x5cNWDqJZs9TH66A,1727
|
|
6
|
+
cfn_check/cli/validate.py,sha256=X8tlk3QIiUC4YiqEU8PQGgxbfh3EL51GaVhj2Ap-Mas,2376
|
|
7
|
+
cfn_check/cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
cfn_check/cli/utils/attributes.py,sha256=hEMWJfNcTOKqWrleS8idWlZP81wAq2J06yV-JQm_WNw,340
|
|
9
|
+
cfn_check/cli/utils/files.py,sha256=87F72INUuA61k3pQ1NNbg0vUwBYOY7-wn1rPqRWbrao,3357
|
|
10
|
+
cfn_check/cli/utils/stdout.py,sha256=dztgy5cBF03oGHRr5ITvMVVf5qdopPbAQm6Rp0cHZq4,423
|
|
11
|
+
cfn_check/collection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
cfn_check/collection/collection.py,sha256=Fl5ONtvosLrksJklRoxER9j-YN5RUdPN45yS02Yw5jU,1492
|
|
13
|
+
cfn_check/evaluation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
cfn_check/evaluation/errors.py,sha256=yPJdtRYo67le4yMC9sYqcboCnkqKsJ3KPbSPFY2-Pi8,773
|
|
15
|
+
cfn_check/evaluation/evaluator.py,sha256=xKbqVES_mmPXVspagPibF-7J58OL-O1vcKwNEuXccuo,3224
|
|
16
|
+
cfn_check/evaluation/validate.py,sha256=hM4pkA1FVv6OPkxxkgOPvjxXMwiMhDLeU6l3YrHd4mo,2639
|
|
17
|
+
cfn_check/evaluation/parsing/__init__.py,sha256=s5TxU4mzsbNIpbMynbwibGR8ac0dTcf_2qUfGkAEDvQ,52
|
|
18
|
+
cfn_check/evaluation/parsing/query_parser.py,sha256=4J3CJQKAyb11gugfx6OZT-mfSdNDB5Al8Jiy9DbJZMw,3459
|
|
19
|
+
cfn_check/evaluation/parsing/token.py,sha256=nrg7Tca182WY0VhRqfsZ1UgpxsUX73vdLToSeK50DZE,7055
|
|
20
|
+
cfn_check/evaluation/parsing/token_type.py,sha256=E5AVBerinBszMLjjc7ejwSSWEc0p0Ju_CNFhpoZi63c,325
|
|
21
|
+
cfn_check/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
cfn_check/logging/models.py,sha256=-tBaK6p8mJ0cO8h2keEJ-VmtFX_VW4XzwAw2PtqbkF0,490
|
|
23
|
+
cfn_check/rendering/__init__.py,sha256=atcbddYun4YHyY7bVGA9CgEYzzXpYzvkx9_Kg-gnD5w,42
|
|
24
|
+
cfn_check/rendering/cidr_solver.py,sha256=aCUH3q9PvQ7-hkJd79VmUc175Ks-HifShPIMVnD8Ws8,1528
|
|
25
|
+
cfn_check/rendering/renderer.py,sha256=p4LV8qFav0wv4UVu467CGTLziRcE218BJHyFzTOwuyg,44162
|
|
26
|
+
cfn_check/rendering/utils.py,sha256=MNaKePylbJ9Bs4kjuoV0PpCmPJYttPXXvKQILemCrUI,489
|
|
27
|
+
cfn_check/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
+
cfn_check/rules/rule.py,sha256=_cKNQ5ciJgPj-exmtBUz31cU2lxWYxw2n2NWIlhYc3s,635
|
|
29
|
+
cfn_check/shared/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
+
cfn_check/shared/types.py,sha256=-om3DyZsjK_tJd-I8SITkoE55W0nB2WA3LOc87Cs7xI,414
|
|
31
|
+
cfn_check/validation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
|
+
cfn_check/validation/validator.py,sha256=Z6S6T_4yQW1IUa5Kv3ohR9U8NDrhTvBadW2FEM8TRL8,1478
|
|
33
|
+
cfn_check-0.8.1.dist-info/licenses/LICENSE,sha256=EbCpGNzOkyQ53ig7J2Iwgmy4Og0dgHe8COo3WylhIKk,1069
|
|
34
|
+
example/multitag.py,sha256=QQfcRERGEDgTUCGqWRqRbXHrLwSX4jEOFq8ED4NJnz8,636
|
|
35
|
+
example/pydantic_rules.py,sha256=6NFtDiaqmnYWt6oZIWB7AO_v5LJoZVOGXrmEe2_J_rI,4162
|
|
36
|
+
example/renderer_test.py,sha256=XG5PVTSHztYXHrBw4bpwVuuYt1JNZdtLGJ-DZ9wPjFM,741
|
|
37
|
+
example/rules.py,sha256=mWHB0DK283lb0CeSHgnyO5qiVTJJpybuwWXb4Yoa3zQ,3148
|
|
38
|
+
cfn_check-0.8.1.dist-info/METADATA,sha256=pCUvnxDUaPRqCx2Oy4kMNneY_zCuNQsY2EIbgpOgL-A,22448
|
|
39
|
+
cfn_check-0.8.1.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
40
|
+
cfn_check-0.8.1.dist-info/entry_points.txt,sha256=B4lCHoDHmwisABxKgRLShwqqFv7QwwDAFXoAChOnkwg,53
|
|
41
|
+
cfn_check-0.8.1.dist-info/top_level.txt,sha256=hUn9Ya50yY1fpgWxEhG5iMgfMDDVX7qWQnM1xrgZnhM,18
|
|
42
|
+
cfn_check-0.8.1.dist-info/RECORD,,
|
example/multitag.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import ruamel.yaml
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
class MultiTaggedObject:
|
|
5
|
+
def __init__(self, value, tags):
|
|
6
|
+
self.value = value
|
|
7
|
+
self.tags = tags
|
|
8
|
+
|
|
9
|
+
def represent_multi_tagged_object(dumper, data):
|
|
10
|
+
return dumper.represent_mapping('!MultiTagged', {'value': data.value, 'tags': data.tags})
|
|
11
|
+
|
|
12
|
+
def construct_multi_tagged_object(constructor, node):
|
|
13
|
+
mapping = constructor.construct_mapping(node)
|
|
14
|
+
return MultiTaggedObject(mapping['value'], mapping['tags'])
|
|
15
|
+
|
|
16
|
+
yaml = ruamel.yaml.YAML()
|
|
17
|
+
yaml.register_class(MultiTaggedObject)
|
|
18
|
+
|
|
19
|
+
# Example usage:
|
|
20
|
+
data = MultiTaggedObject("some_value", ["tag1", "tag2"])
|
|
21
|
+
yaml.dump({'item': data}, sys.stdout)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from cfn_check import Collection, Rule
|
|
2
|
+
from pydantic import BaseModel, StrictStr, StrictInt, Field
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
class RDSDBProperties(BaseModel):
|
|
6
|
+
AvailabilityZone: StrictStr
|
|
7
|
+
BackupRetentionPeriod: StrictInt
|
|
8
|
+
DBInstanceClass: StrictStr = Field(pattern=r'^((db)\.(c6g|c6gd|c6gn|c6i|c6id|c7g|g5g|im4gn|is4gen|m6g|m6gd|r6g|r6gd|t4g|x2gd)\.(10xlarge|112xlarge|12xlarge|16xlarge|18xlarge|24xlarge|2xlarge|32xlarge|3xlarge|48xlarge|4xlarge|56xlarge|6xlarge|8xlarge|9xlarge|large|medium|metal|micro|nano|small|xlarge))')
|
|
9
|
+
StorageType: Literal['sc1', 'st1', 'gp3']
|
|
10
|
+
|
|
11
|
+
class RDSDBInstance(BaseModel):
|
|
12
|
+
Type: Literal["AWS::RDS::DBInstance"]
|
|
13
|
+
|
|
14
|
+
class EC2EbsDevice(BaseModel):
|
|
15
|
+
VolumeType: StrictStr
|
|
16
|
+
DeleteOnTermination: Literal[True]
|
|
17
|
+
|
|
18
|
+
class EC2BlockDeviceMappings(BaseModel):
|
|
19
|
+
Ebs: EC2EbsDevice
|
|
20
|
+
|
|
21
|
+
class EC2VolumeProperties(BaseModel):
|
|
22
|
+
VolumeType: Literal["sc1", "st1", "gp3"]
|
|
23
|
+
BlockDeviceMappings: EC2BlockDeviceMappings
|
|
24
|
+
|
|
25
|
+
class EC2Volume(BaseModel):
|
|
26
|
+
Type: Literal["AWS::EC2::Volume"]
|
|
27
|
+
Properties: EC2VolumeProperties
|
|
28
|
+
|
|
29
|
+
class EC2InstanceProperties(BaseModel):
|
|
30
|
+
InstanceType: StrictStr = Field(pattern=r'^((c6g|c6gd|c6gn|c6i|c6id|c7g|g5g|im4gn|is4gen|m6g|m6gd|r6g|r6gd|t4g|x2gd)\.(10xlarge|112xlarge|12xlarge|16xlarge|18xlarge|24xlarge|2xlarge|32xlarge|3xlarge|48xlarge|4xlarge|56xlarge|6xlarge|8xlarge|9xlarge|large|medium|metal|micro|nano|small|xlarge))')
|
|
31
|
+
|
|
32
|
+
class EC2Instance(BaseModel):
|
|
33
|
+
Type: Literal["AWS::EC2::Instance"]
|
|
34
|
+
Properties: EC2InstanceProperties
|
|
35
|
+
|
|
36
|
+
class LoggingGroupProperties(BaseModel):
|
|
37
|
+
LogGroupClass: Literal["Infrequent Access"]
|
|
38
|
+
RetentionInDays: StrictInt
|
|
39
|
+
|
|
40
|
+
class LoggingGroup(BaseModel):
|
|
41
|
+
Type: Literal["AWS::Logs::LogGroup"]
|
|
42
|
+
Properties: LoggingGroupProperties
|
|
43
|
+
|
|
44
|
+
class LambdaLoggingConfig(BaseModel):
|
|
45
|
+
LogGroup: StrictStr
|
|
46
|
+
|
|
47
|
+
class LambdaProperties(BaseModel):
|
|
48
|
+
LoggingConfig: LambdaLoggingConfig
|
|
49
|
+
|
|
50
|
+
class Lambda(BaseModel):
|
|
51
|
+
Type: Literal["AWS::Serverless::Function", "AWS::Lambda::Function"]
|
|
52
|
+
Properties: LambdaProperties
|
|
53
|
+
|
|
54
|
+
class Resource(BaseModel):
|
|
55
|
+
Type: StrictStr
|
|
56
|
+
|
|
57
|
+
class ValidateResourceType(Collection):
|
|
58
|
+
|
|
59
|
+
@Rule(
|
|
60
|
+
"Resources::*",
|
|
61
|
+
"It checks Resource::Type is correctly definined",
|
|
62
|
+
)
|
|
63
|
+
def validate_test(self, value: Resource):
|
|
64
|
+
assert isinstance(value, Resource), "Not a valid CloudFormation Resource"
|
|
65
|
+
|
|
66
|
+
@Rule(
|
|
67
|
+
"Resources::*",
|
|
68
|
+
"It validates a lambda is configured correctly",
|
|
69
|
+
filters=[
|
|
70
|
+
lambda data: data if data.get("Type") in ["AWS::Serverless::Function", "AWS::Lambda::Function"] else None,
|
|
71
|
+
]
|
|
72
|
+
)
|
|
73
|
+
def validate_lambda(self, lambda_resource: Lambda):
|
|
74
|
+
assert isinstance(lambda_resource, Lambda), "Not a valid Lambda"
|
|
75
|
+
|
|
76
|
+
resources = self.query("Resources")
|
|
77
|
+
document = {}
|
|
78
|
+
|
|
79
|
+
for resource in resources:
|
|
80
|
+
document.update(resource)
|
|
81
|
+
|
|
82
|
+
lambda_logging_group = document.get(lambda_resource.Properties.LoggingConfig.LogGroup)
|
|
83
|
+
assert lambda_logging_group is not None, "No matching logging group found in Resources for Lambda"
|
|
84
|
+
LoggingGroup(**lambda_logging_group)
|
|
85
|
+
|
|
86
|
+
@Rule(
|
|
87
|
+
"Resources::*",
|
|
88
|
+
"It validates a logging group is configured correctly",
|
|
89
|
+
filters=[
|
|
90
|
+
lambda data: data if data.get("Type") == 'AWS::Logs::LogGroup' else None,
|
|
91
|
+
]
|
|
92
|
+
)
|
|
93
|
+
def validate_logging_group(self, logging_group: LoggingGroup):
|
|
94
|
+
assert isinstance(logging_group, LoggingGroup), "Not a valid logging group"
|
|
95
|
+
|
|
96
|
+
@Rule(
|
|
97
|
+
"Resources::*",
|
|
98
|
+
"It validates an EC2 instance is configured correctly",
|
|
99
|
+
filters=[
|
|
100
|
+
lambda data: data if data.get("Type") == 'AWS::EC2::Instance' else None,
|
|
101
|
+
]
|
|
102
|
+
)
|
|
103
|
+
def validate_ec2_instances(self, ec2_instance: EC2Instance):
|
|
104
|
+
assert isinstance(ec2_instance, EC2Instance)
|
|
105
|
+
|
|
106
|
+
@Rule(
|
|
107
|
+
"Resources::*",
|
|
108
|
+
"It validates an EC2 Volume is configured correctly",
|
|
109
|
+
filters=[
|
|
110
|
+
lambda data: data if data.get("Type") == 'AWS::EC2::Volume' else None,
|
|
111
|
+
]
|
|
112
|
+
)
|
|
113
|
+
def validate_ec2_volumes(self, ec2_volume: EC2Volume):
|
|
114
|
+
assert isinstance(ec2_volume, EC2Volume), "Not a valid EC2 Volume"
|
example/renderer_test.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
from cfn_check.rendering import Renderer
|
|
3
|
+
from cfn_check.cli.utils.files import open_template, Loader, create_tag
|
|
4
|
+
def test():
|
|
5
|
+
|
|
6
|
+
renderer = Renderer()
|
|
7
|
+
data = {}
|
|
8
|
+
|
|
9
|
+
tags = [
|
|
10
|
+
'Ref',
|
|
11
|
+
'Sub',
|
|
12
|
+
'Join',
|
|
13
|
+
'Select',
|
|
14
|
+
'Split',
|
|
15
|
+
'GetAtt',
|
|
16
|
+
'GetAZs',
|
|
17
|
+
'ImportValue',
|
|
18
|
+
'Equals',
|
|
19
|
+
'If',
|
|
20
|
+
'Not',
|
|
21
|
+
'And',
|
|
22
|
+
'Or',
|
|
23
|
+
'Condition',
|
|
24
|
+
'FindInMap',
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
for tag in tags:
|
|
28
|
+
new_tag = create_tag(tag)
|
|
29
|
+
Loader.add_constructor(f'!{tag}', new_tag)
|
|
30
|
+
|
|
31
|
+
_, template = open_template('template.yaml')
|
|
32
|
+
|
|
33
|
+
data = renderer.render(template)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
with open('rendered.yaml', 'w') as yml:
|
|
37
|
+
yaml.safe_dump(data, yml)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
test()
|
cfn_check/loader/__init__.py
DELETED
|
File without changes
|
cfn_check/loader/loader.py
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import yaml
|
|
2
|
-
import pathlib
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class Loader(yaml.SafeLoader):
|
|
6
|
-
pass
|
|
7
|
-
|
|
8
|
-
def create_tag(tag):
|
|
9
|
-
def constructor(loader: Loader, node):
|
|
10
|
-
if isinstance(node, yaml.ScalarNode):
|
|
11
|
-
return node.value
|
|
12
|
-
elif isinstance(node, yaml.SequenceNode):
|
|
13
|
-
return loader.construct_sequence(node)
|
|
14
|
-
elif isinstance(node, yaml.MappingNode):
|
|
15
|
-
return loader.construct_mapping(node)
|
|
16
|
-
return constructor
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def find_templates(path, file_pattern):
|
|
20
|
-
return list(pathlib.Path(path).rglob(file_pattern))
|
|
21
|
-
|
cfn_check-0.3.2.dist-info/RECORD
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
cfn_check/__init__.py,sha256=ccUo2YxBmuEmak1M5o-8J0ECLXNkDDUsLJ4mkm31GvU,96
|
|
2
|
-
cfn_check/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
cfn_check/cli/root.py,sha256=dHq9zzXyj-Wrj-fzirtFjptShzWbBGsO3n9tspm-pec,1688
|
|
4
|
-
cfn_check/cli/validate.py,sha256=EY6-YgORApOxCgFAmPdwmfDg1UA80GLsRzQv44zSuY4,1954
|
|
5
|
-
cfn_check/cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
cfn_check/cli/utils/attributes.py,sha256=iIUIgl6cT5XEUOW7D54-xxmMpTis84ySQY1b9osB47E,339
|
|
7
|
-
cfn_check/cli/utils/files.py,sha256=QMYYR7C7mXdDx_6jj1Ye9w7ol1twAzUEZ9hxSa-O4-k,2563
|
|
8
|
-
cfn_check/collection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
cfn_check/collection/collection.py,sha256=wNxahoOqQge3C56blz5VtOq6lX5MZ9F2JjQIyZ3_SxU,27
|
|
10
|
-
cfn_check/evaluation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
cfn_check/evaluation/errors.py,sha256=yPJdtRYo67le4yMC9sYqcboCnkqKsJ3KPbSPFY2-Pi8,773
|
|
12
|
-
cfn_check/evaluation/evaluator.py,sha256=weFBAYKVc9TjTB2zNMIVX77Bi9kIaWp2KRJa1ISSLNs,2240
|
|
13
|
-
cfn_check/evaluation/validate.py,sha256=yy8byYAoHxFqkS2HfewHup22B3bYtrUH2PhPuNAc--A,1547
|
|
14
|
-
cfn_check/evaluation/parsing/__init__.py,sha256=s5TxU4mzsbNIpbMynbwibGR8ac0dTcf_2qUfGkAEDvQ,52
|
|
15
|
-
cfn_check/evaluation/parsing/query_parser.py,sha256=4J3CJQKAyb11gugfx6OZT-mfSdNDB5Al8Jiy9DbJZMw,3459
|
|
16
|
-
cfn_check/evaluation/parsing/token.py,sha256=_mf1hMnnu5TFXvnyfxAy-GjdVW9kvhgv1NLaSh4GF4k,6993
|
|
17
|
-
cfn_check/evaluation/parsing/token_type.py,sha256=E5AVBerinBszMLjjc7ejwSSWEc0p0Ju_CNFhpoZi63c,325
|
|
18
|
-
cfn_check/loader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
-
cfn_check/loader/loader.py,sha256=7yiDLLW_vNp_8O47erLXjQQtAB47fU3nimb91N5N_R8,532
|
|
20
|
-
cfn_check/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
-
cfn_check/logging/models.py,sha256=-tBaK6p8mJ0cO8h2keEJ-VmtFX_VW4XzwAw2PtqbkF0,490
|
|
22
|
-
cfn_check/rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
-
cfn_check/rules/rule.py,sha256=r6-eKCUdPaNIena2NDv3J_QlcWes1KLObI9ukRZZz1o,500
|
|
24
|
-
cfn_check/shared/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
-
cfn_check/shared/types.py,sha256=-om3DyZsjK_tJd-I8SITkoE55W0nB2WA3LOc87Cs7xI,414
|
|
26
|
-
cfn_check/validation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
-
cfn_check/validation/validator.py,sha256=FGPeb8Uc8lvX3Y5rs-fxeJKIOqzUXwXh_gCFcy6d3b0,1182
|
|
28
|
-
cfn_check-0.3.2.dist-info/licenses/LICENSE,sha256=EbCpGNzOkyQ53ig7J2Iwgmy4Og0dgHe8COo3WylhIKk,1069
|
|
29
|
-
example/rules.py,sha256=mWHB0DK283lb0CeSHgnyO5qiVTJJpybuwWXb4Yoa3zQ,3148
|
|
30
|
-
cfn_check-0.3.2.dist-info/METADATA,sha256=aAqR3dS-h4J_kHL4K6vCtl2DSFgYvOCHps_PvoraX5Q,19047
|
|
31
|
-
cfn_check-0.3.2.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
32
|
-
cfn_check-0.3.2.dist-info/entry_points.txt,sha256=B4lCHoDHmwisABxKgRLShwqqFv7QwwDAFXoAChOnkwg,53
|
|
33
|
-
cfn_check-0.3.2.dist-info/top_level.txt,sha256=hUn9Ya50yY1fpgWxEhG5iMgfMDDVX7qWQnM1xrgZnhM,18
|
|
34
|
-
cfn_check-0.3.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|