Skip to content

Commit 6de298b

Browse files
authored
feat: Read-Only Resources SGR check and rules (#85)
* feat: Read-Only Resources SGR check and rules
1 parent 943b818 commit 6de298b

14 files changed

+281
-128
lines changed

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,31 @@ In order to start using Basic Linting you need to run following command:
2626
$ guard-rail --schema file://path-to-schema-1 --schema file://path-to-schema-2 --rule file://path-to-custom-ruleset1 --rule file://path-to-custom-ruleset2
2727
```
2828

29+
#### Read-Only Resource Checks
30+
For read-only resources, you can use the `--is-read-only` flag to run only the essential checks:
31+
```bash
32+
$ guard-rail --schema file://path-to-schema --is-read
33+
```
34+
35+
When `--is-read-only` is specified, only the following checks are performed:
36+
- `ARN001`: arn related property MUST have pattern specified
37+
- `ARN002`: arn related property MUST have pattern specified
38+
- `COM001`: ensure_properties_do_not_support_multitype
39+
- `PID001`: primaryIdentifier MUST exist
40+
- `PID002`: primaryIdentifier MUST contain values
41+
- `PR005`: primaryIdentifier MUST have properties defined in the schema
42+
- `PR007`: readOnlyProperties MUST have properties defined in the schema
43+
- `PER003`: Resource MUST implement read handler
44+
- `PER004`: Resource MUST NOT specify wildcard permissions for read handler
45+
- `PER010`: Resource MUST implement list handler
46+
- `PER011`: Resource MUST NOT specify wildcard permissions for list handler
47+
2948
**[List of Linting Rules](docs/BASIC_LINTING.md)**
3049

3150
#### Breaking Change (Stateful)
3251
Along with basic linting, guard rail supports capability of breaking change evaluation. Provider developer must provider two json objects - previous & current versions of the same resource schema. CloudFormation authored rules will be run and evaluation current version of the schema whether it is compliant or not.
3352

34-
In order to start using Basic Linting you need to run following command:
53+
In order to start using Breaking Change evaluation you need to run following command:
3554
```bash
3655
$ guard-rail --schema file://path-to-schema-1 --schema file://path-to-schema-2 --rule ... --stateful
3756
```

src/cli.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ def main(args_in=None):
5353
compliance_result = None
5454

5555
if not args.stateful:
56-
payload: Stateless = Stateless(schemas=collected_schemas, rules=collected_rules)
56+
payload: Stateless = Stateless(
57+
schemas=collected_schemas,
58+
rules=collected_rules,
59+
is_read_only=args.is_read_only,
60+
)
5761
compliance_result = invoke(payload)
5862
else:
5963
# should be index safe as argument validation should fail prematurely

src/rpdk/guard_rail/core/data_types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ class Stateless:
3333
Args:
3434
schemas (List[Dict[str, Any]]): Collection of Resource Provider Schemas
3535
rules (List[str]): Collection of Custom Compliance Rules
36+
is_read_only (bool): Whether to run only read resource checks
3637
"""
3738

3839
schemas: List[Dict[str, Any]]
3940
rules: List[str] = field(default_factory=list)
41+
is_read_only: bool = field(default=False)
4042

4143

4244
@dataclass

src/rpdk/guard_rail/core/runner.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
Stateless,
2626
)
2727
from rpdk.guard_rail.core.stateful import schema_diff
28-
from rpdk.guard_rail.rule_library import combiners, core, permissions, stateful, tags
28+
from rpdk.guard_rail.rule_library import combiners, core, mutable, stateful, tags
2929
from rpdk.guard_rail.utils.common import is_guard_rule
3030
from rpdk.guard_rail.utils.logger import LOG, logdebug
3131
from rpdk.guard_rail.utils.schema_utils import add_paths_to_schema
@@ -35,22 +35,29 @@
3535

3636

3737
@logdebug
38-
def prepare_ruleset(mode: str = "stateless"):
38+
def prepare_ruleset(mode: str = "stateless", is_read_only: bool = False):
3939
"""Fetches module level schema rules based on mode.
4040
4141
Iterates over provided modules (core, combiners, permissions, tags) or (stateful)
4242
and checks if content is a guard rule-set, ten adds it to the list
4343
`to-run`
4444
45+
Args:
46+
is_read_only: whether the calling resource schema is read only or not
47+
mode: The mode (stateless or stateful)
48+
4549
Returns:
4650
Set[str]: set of rules in a string form
4751
"""
4852
rule_modules = {
49-
"stateless": [core, combiners, permissions, tags],
53+
"stateless": [core, mutable, combiners, tags],
5054
"stateful": [stateful],
5155
}
5256
rule_set = set()
5357
for module in rule_modules[mode]:
58+
module_name = module.__name__.split(".")[-1]
59+
if is_read_only and module_name == "mutable":
60+
continue
5461
for content in pkg_resources.contents(module):
5562
if not is_guard_rule(content):
5663
continue
@@ -148,7 +155,7 @@ def _(payload):
148155
"""
149156

150157
compliance_output = []
151-
ruleset = prepare_ruleset() | set(payload.rules)
158+
ruleset = prepare_ruleset(is_read_only=payload.is_read_only) | set(payload.rules)
152159

153160
def __execute_rules__(schema_exec, ruleset):
154161
output = None
@@ -160,6 +167,7 @@ def __execute_rules__(schema_exec, ruleset):
160167
schema_with_paths = add_paths_to_schema(schema=schema)
161168
schema_to_execute = __exec_rules__(schema=schema_with_paths)
162169
output = __execute_rules__(schema_exec=schema_to_execute, ruleset=ruleset)
170+
163171
compliance_output.append(output)
164172
return compliance_output
165173

@@ -191,6 +199,7 @@ def __execute__(schema_exec, ruleset):
191199

192200
schema_to_execute = __exec_rules__(schema=schema_difference)
193201
output = __execute__(schema_exec=schema_to_execute, ruleset=ruleset)
202+
194203
output.schema_difference = schema_difference
195204
compliance_output.append(output)
196205

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
let wildcard_notation = /^[a-z0-9-]{0,20}:([a-zA-Z0-9]+)?\*$/
2+
3+
4+
5+
rule ensure_resource_read_handler_exists_and_have_permissions {
6+
handlers.read exists
7+
<<
8+
{
9+
"result": "NON_COMPLIANT",
10+
"check_id": "PER003",
11+
"message": "Resource MUST implement read handler"
12+
}
13+
>>
14+
15+
handlers.read.permissions exists
16+
<<
17+
{
18+
"result": "NON_COMPLIANT",
19+
"check_id": "PER004",
20+
"message": "Resource read handler MUST have permissions list specified"
21+
}
22+
>>
23+
24+
when handlers.read.permissions exists {
25+
handlers.read.permissions !empty
26+
<<
27+
{
28+
"result": "NON_COMPLIANT",
29+
"check_id": "PER004",
30+
"message": "Resource read handler MUST have non-empty permissions"
31+
}
32+
>>
33+
}
34+
35+
when handlers.read.permissions !empty {
36+
handlers.read.permissions.* {
37+
this != %wildcard_notation
38+
<<
39+
{
40+
"result": "NON_COMPLIANT",
41+
"check_id": "PER004",
42+
"message": "Resource MUST NOT specify wildcard permissions for read handler"
43+
}
44+
>>
45+
}
46+
}
47+
}
48+
49+
rule ensure_resource_list_handler_exists_and_have_permissions {
50+
handlers.list exists
51+
<<
52+
{
53+
"result": "NON_COMPLIANT",
54+
"check_id": "PER010",
55+
"message": "Resource MUST implement list handler"
56+
}
57+
>>
58+
59+
handlers.list.permissions exists
60+
<<
61+
{
62+
"result": "NON_COMPLIANT",
63+
"check_id": "PER011",
64+
"message": "Resource list handler MUST have permissions list specified"
65+
}
66+
>>
67+
68+
when handlers.list.permissions exists {
69+
handlers.list.permissions !empty
70+
<<
71+
{
72+
"result": "NON_COMPLIANT",
73+
"check_id": "PER011",
74+
"message": "Resource list handler MUST have non-empty permissions"
75+
}
76+
>>
77+
}
78+
79+
when handlers.list.permissions !empty {
80+
handlers.list.permissions.* {
81+
this != %wildcard_notation
82+
<<
83+
{
84+
"result": "NON_COMPLIANT",
85+
"check_id": "PER011",
86+
"message": "Resource MUST NOT specify wildcard permissions for list handler"
87+
}
88+
>>
89+
}
90+
}
91+
}

src/rpdk/guard_rail/rule_library/core/schema-linter-core-rules.guard

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ rule ensure_write_and_read_only_intersection_is_empty
133133
}
134134
}
135135

136-
rule verify_property_notation
136+
rule verify_property_notation_for_immutable_properties
137137
{
138138
let paths = paths
139139
when primaryIdentifier exists {
@@ -149,19 +149,6 @@ rule verify_property_notation
149149
}
150150
}
151151

152-
when createOnlyProperties exists {
153-
createOnlyProperties[*] {
154-
this IN %paths
155-
<<
156-
{
157-
"result": "NON_COMPLIANT",
158-
"check_id": "PR006",
159-
"message": "createOnlyProperties MUST have properties defined in the schema"
160-
}
161-
>>
162-
}
163-
}
164-
165152
when readOnlyProperties exists {
166153
readOnlyProperties[*] {
167154
this IN %paths
@@ -174,19 +161,6 @@ rule verify_property_notation
174161
>>
175162
}
176163
}
177-
178-
when writeOnlyProperties exists {
179-
writeOnlyProperties[*] {
180-
this IN %paths
181-
<<
182-
{
183-
"result": "NON_COMPLIANT",
184-
"check_id": "PR008",
185-
"message": "writeOnlyProperties MUST have properties defined in the schema"
186-
}
187-
>>
188-
}
189-
}
190164
}
191165

192166

src/rpdk/guard_rail/rule_library/permissions/schema-linter-core-permission-rules.guard renamed to src/rpdk/guard_rail/rule_library/mutable/schema-linter-mutable-permission-rules.guard

Lines changed: 0 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -46,50 +46,6 @@ rule ensure_resource_create_handler_exists_and_have_permissions {
4646
}
4747
}
4848

49-
rule ensure_resource_read_handler_exists_and_have_permissions {
50-
handlers.read exists
51-
<<
52-
{
53-
"result": "NON_COMPLIANT",
54-
"check_id": "PER003",
55-
"message": "Resource MUST implement read handler"
56-
}
57-
>>
58-
59-
handlers.read.permissions exists
60-
<<
61-
{
62-
"result": "NON_COMPLIANT",
63-
"check_id": "PER004",
64-
"message": "Resource read handler MUST have permissions list specified"
65-
}
66-
>>
67-
68-
when handlers.read.permissions exists {
69-
handlers.read.permissions !empty
70-
<<
71-
{
72-
"result": "NON_COMPLIANT",
73-
"check_id": "PER004",
74-
"message": "Resource read handler MUST have non-empty permissions"
75-
}
76-
>>
77-
}
78-
79-
when handlers.read.permissions !empty {
80-
handlers.read.permissions.* {
81-
this != %wildcard_notation
82-
<<
83-
{
84-
"result": "NON_COMPLIANT",
85-
"check_id": "PER004",
86-
"message": "Resource MUST NOT specify wildcard permissions for read handler"
87-
}
88-
>>
89-
}
90-
}
91-
}
92-
9349
rule ensure_resource_update_handler_exists_and_have_permissions {
9450
handlers.update exists
9551
<<
@@ -179,47 +135,3 @@ rule ensure_resource_delete_handler_exists_and_have_permissions {
179135
}
180136
}
181137
}
182-
183-
rule ensure_resource_list_handler_exists_and_have_permissions {
184-
handlers.list exists
185-
<<
186-
{
187-
"result": "NON_COMPLIANT",
188-
"check_id": "PER010",
189-
"message": "Resource MUST implement list handler"
190-
}
191-
>>
192-
193-
handlers.list.permissions exists
194-
<<
195-
{
196-
"result": "NON_COMPLIANT",
197-
"check_id": "PER011",
198-
"message": "Resource list handler MUST have permissions list specified"
199-
}
200-
>>
201-
202-
when handlers.list.permissions exists {
203-
handlers.list.permissions !empty
204-
<<
205-
{
206-
"result": "NON_COMPLIANT",
207-
"check_id": "PER011",
208-
"message": "Resource list handler MUST have non-empty permissions"
209-
}
210-
>>
211-
}
212-
213-
when handlers.list.permissions !empty {
214-
handlers.list.permissions.* {
215-
this != %wildcard_notation
216-
<<
217-
{
218-
"result": "NON_COMPLIANT",
219-
"check_id": "PER011",
220-
"message": "Resource MUST NOT specify wildcard permissions for list handler"
221-
}
222-
>>
223-
}
224-
}
225-
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
rule verify_property_notation_for_mutable_properties
2+
{
3+
let paths = paths
4+
when createOnlyProperties exists {
5+
createOnlyProperties[*] {
6+
this IN %paths
7+
<<
8+
{
9+
"result": "NON_COMPLIANT",
10+
"check_id": "PR006",
11+
"message": "createOnlyProperties MUST have properties defined in the schema"
12+
}
13+
>>
14+
}
15+
}
16+
17+
when writeOnlyProperties exists {
18+
writeOnlyProperties[*] {
19+
this IN %paths
20+
<<
21+
{
22+
"result": "NON_COMPLIANT",
23+
"check_id": "PR008",
24+
"message": "writeOnlyProperties MUST have properties defined in the schema"
25+
}
26+
>>
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)