Skip to content

Commit cb82592

Browse files
authored
feat(general): Update range includes to handle lists of ranges and lists of values (#6192)
* Update range includes * Add "not" tests and typing * Fix book typing * Fix test
1 parent 81636c3 commit cb82592

File tree

9 files changed

+168
-27
lines changed

9 files changed

+168
-27
lines changed
Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Optional, Any, Dict, List
1+
from typing import Optional, Any, Dict, List, Union
22
from checkov.common.checks_infra.solvers.attribute_solvers.base_attribute_solver import BaseAttributeSolver
33
from checkov.common.graph.checks_infra.enums import Operators
44
from checkov.common.util.type_forcers import force_int
@@ -8,16 +8,26 @@ class RangeIncludesAttributeSolver(BaseAttributeSolver):
88
operator = Operators.RANGE_INCLUDES # noqa: CCE003 # a static attribute
99

1010
def __init__(
11-
self, resource_types: List[str], attribute: Optional[str], value: Any, is_jsonpath_check: bool = False
11+
self, resource_types: List[str], attribute: Optional[str], value: Union[Any, List[Any]],
12+
is_jsonpath_check: bool = False
1213
) -> None:
13-
super().__init__(resource_types, attribute, force_int(value), is_jsonpath_check)
14+
# Convert value to a list if it's not already one to unify handling
15+
value = [force_int(v) if isinstance(v, (str, int)) else v for v in
16+
(value if isinstance(value, list) else [value])]
17+
super().__init__(resource_types, attribute, value, is_jsonpath_check)
1418

1519
def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
1620
attr = vertex.get(attribute) # type:ignore[arg-type] # due to attribute can be None
1721

1822
if attr is None:
1923
return False
2024

25+
if isinstance(attr, list):
26+
return any(self._check_value(value, attr_val) for attr_val in attr for value in self.value)
27+
28+
return any(self._check_value(value, attr) for value in self.value)
29+
30+
def _check_value(self, value: Any, attr: Any) -> bool:
2131
# expects one of the following values:
2232
# - an actual int
2333
# - a string that parses to an int
@@ -28,11 +38,15 @@ def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bo
2838
return True
2939

3040
if isinstance(attr, str) and attr.count("-") == 1:
31-
try:
32-
start, end = attr.split("-")
33-
return True if force_int(start) <= self.value <= force_int(end) else False
34-
except (TypeError, ValueError):
35-
# Occurs if there are not two entries or if one is not an int, in which case we just give up
36-
return False
37-
38-
return True if force_int(attr) == self.value else False
41+
return self._check_range(value, attr)
42+
43+
return bool(force_int(attr) == value)
44+
45+
@staticmethod
46+
def _check_range(value: Any, range_str: str) -> bool:
47+
try:
48+
start, end = range_str.split("-")
49+
return bool(force_int(start) <= value <= force_int(end))
50+
except (TypeError, ValueError):
51+
# Occurs if there are not two entries or if one is not an int, in which case we just give up
52+
return False
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
metadata:
2+
name: "example"
3+
category: "GENERAL_SECURITY"
4+
id: "JsonPathRangeIncludesList"
5+
scope:
6+
provider: "AWS"
7+
definition:
8+
cond_type: "attribute"
9+
resource_types:
10+
- "test"
11+
attribute: "range"
12+
operator: "jsonpath_range_includes"
13+
value:
14+
- "400"
15+
- 3000
16+
- 100
17+
- "1"
18+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
metadata:
2+
name: "example"
3+
category: "GENERAL_SECURITY"
4+
id: "RangeIncludesList"
5+
scope:
6+
provider: "AWS"
7+
definition:
8+
cond_type: "attribute"
9+
resource_types:
10+
- "test"
11+
attribute: "range"
12+
operator: "range_includes"
13+
value:
14+
- 200
15+
- 3000
16+
- 400
17+
- "500"
18+

tests/terraform/graph/checks_infra/attribute_solvers/range_includes_solver/resources/main.tf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ resource "test" "pass6" {
2222
range = "2000-4000"
2323
}
2424

25+
resource "test" "pass7" {
26+
range = ["2100","2000-4000","3400"]
27+
}
28+
2529
resource "test" "fail1" {
2630
range = 2000
2731
}
@@ -53,3 +57,7 @@ resource "test" "fail7" {
5357
resource "test" "fail8" {
5458
range = "1000-5000-6000"
5559
}
60+
61+
resource "test" "fail9" {
62+
range = ["1000","2000-2900"]
63+
}

tests/terraform/graph/checks_infra/attribute_solvers/range_includes_solver/test_solver.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,37 +17,59 @@ def setUp(self):
1717
def test_range_includes_int_solver(self):
1818
root_folder = 'resources'
1919
check_id = "RangeIncludesInt"
20-
should_pass = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6']
21-
should_fail = ['test.fail1', 'test.fail2', 'test.fail3', 'test.fail4', 'test.fail5', 'test.fail6', 'test.fail7', 'test.fail8']
20+
should_pass = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6', 'test.pass7']
21+
should_fail = ['test.fail1', 'test.fail2', 'test.fail3', 'test.fail4', 'test.fail5', 'test.fail6', 'test.fail7',
22+
'test.fail8', 'test.fail9']
2223
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
2324

2425
self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
2526

2627
def test_range_includes_string_solver(self):
2728
root_folder = 'resources'
2829
check_id = "RangeIncludesString"
29-
should_pass = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6']
30+
should_pass = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6', 'test.pass7']
3031
should_fail = ['test.fail1', 'test.fail2', 'test.fail3', 'test.fail4', 'test.fail5', 'test.fail6', 'test.fail7',
31-
'test.fail8']
32+
'test.fail8', 'test.fail9']
3233
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
3334

3435
self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
3536

3637
def test_range_includes_int_jsonpath_solver(self):
3738
root_folder = 'resources'
3839
check_id = "JsonPathRangeIncludesInt"
39-
should_pass = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6']
40-
should_fail = ['test.fail1', 'test.fail2', 'test.fail3', 'test.fail4', 'test.fail5', 'test.fail6', 'test.fail7', 'test.fail8']
40+
should_pass = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6', 'test.pass7']
41+
should_fail = ['test.fail1', 'test.fail2', 'test.fail3', 'test.fail4', 'test.fail5', 'test.fail6', 'test.fail7',
42+
'test.fail8', 'test.fail9']
4143
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
4244

4345
self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
4446

4547
def test_range_includes_string_jsonpath_solver(self):
4648
root_folder = 'resources'
4749
check_id = "JsonPathRangeIncludesString"
48-
should_pass = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6']
50+
should_pass = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6', 'test.pass7']
4951
should_fail = ['test.fail1', 'test.fail2', 'test.fail3', 'test.fail4', 'test.fail5', 'test.fail6', 'test.fail7',
50-
'test.fail8']
52+
'test.fail8', 'test.fail9']
5153
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
5254

5355
self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
56+
57+
def test_range_includes_list_solver(self):
58+
root_folder = 'resources'
59+
check_id = "RangeIncludesList"
60+
should_pass = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6', 'test.pass7']
61+
should_fail = ['test.fail1', 'test.fail2', 'test.fail3', 'test.fail4', 'test.fail5', 'test.fail6', 'test.fail7',
62+
'test.fail8', 'test.fail9']
63+
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
64+
65+
self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
66+
67+
def test_range_includes_list_jsonpath_solver(self):
68+
root_folder = 'resources'
69+
check_id = "JsonPathRangeIncludesList"
70+
should_pass = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6', 'test.pass7']
71+
should_fail = ['test.fail1', 'test.fail2', 'test.fail3', 'test.fail4', 'test.fail5', 'test.fail6', 'test.fail7',
72+
'test.fail8', 'test.fail9']
73+
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
74+
75+
self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
metadata:
2+
name: "example"
3+
category: "GENERAL_SECURITY"
4+
id: "JsonPathRangeNotIncludesList"
5+
scope:
6+
provider: "AWS"
7+
definition:
8+
cond_type: "attribute"
9+
resource_types:
10+
- "test"
11+
attribute: "range"
12+
operator: "jsonpath_range_not_includes"
13+
value:
14+
- "3001"
15+
- 3000
16+
- 3002
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
metadata:
2+
name: "example"
3+
category: "GENERAL_SECURITY"
4+
id: "RangeNotIncludesList"
5+
scope:
6+
provider: "AWS"
7+
definition:
8+
cond_type: "attribute"
9+
resource_types:
10+
- "test"
11+
attribute: "range"
12+
operator: "range_not_includes"
13+
value:
14+
- "3000"
15+
- 3001
16+
- "3002"

tests/terraform/graph/checks_infra/attribute_solvers/range_not_includes_solver/resources/main.tf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ resource "test" "pass6" {
2222
range = "2000-4000"
2323
}
2424

25+
resource "test" "pass7" {
26+
range = ["2000-2500","3000"]
27+
}
28+
2529
resource "test" "fail1" {
2630
range = 2000
2731
}
@@ -53,3 +57,7 @@ resource "test" "fail7" {
5357
resource "test" "fail8" {
5458
range = "1000-5000-6000"
5559
}
60+
61+
resource "test" "fail9" {
62+
range = ["1000-2900","3100-4000"]
63+
}

tests/terraform/graph/checks_infra/attribute_solvers/range_not_includes_solver/test_solver.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,38 +17,59 @@ def setUp(self):
1717
def test_range_not_includes_int_solver(self):
1818
root_folder = 'resources'
1919
check_id = "RangeNotIncludesInt"
20-
should_fail = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6']
21-
should_pass = ['test.fail1', 'test.fail2', 'test.fail3', 'test.fail4', 'test.fail5', 'test.fail6', 'test.fail7', 'test.fail8']
20+
should_fail = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6', 'test.pass7']
21+
should_pass = ['test.fail1', 'test.fail2', 'test.fail3', 'test.fail4', 'test.fail5', 'test.fail6', 'test.fail7',
22+
'test.fail8', 'test.fail9']
2223
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
2324

2425
self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
2526

2627
def test_range_not_includes_string_solver(self):
2728
root_folder = 'resources'
2829
check_id = "RangeNotIncludesString"
29-
should_fail = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6']
30+
should_fail = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6', 'test.pass7']
3031
should_pass = ['test.fail1', 'test.fail2', 'test.fail3', 'test.fail4', 'test.fail5', 'test.fail6', 'test.fail7',
31-
'test.fail8']
32+
'test.fail8', 'test.fail9']
3233
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
3334

3435
self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
3536

3637
def test_range_not_includes_int_jsonpath_solver(self):
3738
root_folder = 'resources'
3839
check_id = "JsonPathRangeNotIncludesInt"
39-
should_fail = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6']
40+
should_fail = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6', 'test.pass7']
4041
should_pass = ['test.fail1', 'test.fail2', 'test.fail3', 'test.fail4', 'test.fail5', 'test.fail6', 'test.fail7',
41-
'test.fail8']
42+
'test.fail8', 'test.fail9']
4243
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
4344

4445
self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
4546

4647
def test_range_not_includes_string_jsonpath_solver(self):
4748
root_folder = 'resources'
4849
check_id = "JsonPathRangeNotIncludesString"
49-
should_fail = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6']
50+
should_fail = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6', 'test.pass7']
51+
should_pass = ['test.fail1', 'test.fail2', 'test.fail3', 'test.fail4', 'test.fail5', 'test.fail6', 'test.fail7',
52+
'test.fail8', 'test.fail9']
53+
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
54+
55+
self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
56+
57+
def test_range_not_includes_list_solver(self):
58+
root_folder = 'resources'
59+
check_id = "RangeNotIncludesList"
60+
should_fail = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6', 'test.pass7']
61+
should_pass = ['test.fail1', 'test.fail2', 'test.fail3', 'test.fail4', 'test.fail5', 'test.fail6', 'test.fail7',
62+
'test.fail8', 'test.fail9']
63+
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
64+
65+
self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)
66+
67+
def test_range_not_includes_list_jsonpath_solver(self):
68+
root_folder = 'resources'
69+
check_id = "JsonPathRangeNotIncludesList"
70+
should_fail = ['test.pass1', 'test.pass2', 'test.pass3', 'test.pass4', 'test.pass5', 'test.pass6', 'test.pass7']
5071
should_pass = ['test.fail1', 'test.fail2', 'test.fail3', 'test.fail4', 'test.fail5', 'test.fail6', 'test.fail7',
51-
'test.fail8']
72+
'test.fail8', 'test.fail9']
5273
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}
5374

5475
self.run_test(root_folder=root_folder, expected_results=expected_results, check_id=check_id)

0 commit comments

Comments
 (0)