Skip to content

Commit

Permalink
feat(graph): equals/not_equals_ignore_case operators (solvers) (#3698)
Browse files Browse the repository at this point in the history
* Add equal/not equal ignore case operators

* Add equal/not equal ignore case operators

Co-authored-by: egotfried <[email protected]>
  • Loading branch information
estergo and estergo authored Oct 23, 2022
1 parent 6ed886e commit 9aeab2e
Show file tree
Hide file tree
Showing 14 changed files with 195 additions and 37 deletions.
8 changes: 6 additions & 2 deletions checkov/common/checks_infra/checks_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@
IsTrueAttributeSolver,
IsFalseAttributeSolver,
IntersectsAttributeSolver,
NotIntersectsAttributeSolver
NotIntersectsAttributeSolver,
EqualsIgnoreCaseAttributeSolver,
NotEqualsIgnoreCaseAttributeSolver
)
from checkov.common.checks_infra.solvers.connections_solvers.connection_one_exists_solver import \
ConnectionOneExistsSolver
Expand Down Expand Up @@ -92,7 +94,9 @@
"is_true": IsTrueAttributeSolver,
"is_false": IsFalseAttributeSolver,
"intersects": IntersectsAttributeSolver,
"not_intersects": NotIntersectsAttributeSolver
"not_intersects": NotIntersectsAttributeSolver,
"equals_ignore_case": EqualsIgnoreCaseAttributeSolver,
"not_equals_ignore_case": NotEqualsIgnoreCaseAttributeSolver
}

operators_to_complex_solver_classes: dict[str, Type[BaseComplexSolver]] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@
from checkov.common.checks_infra.solvers.attribute_solvers.is_false_attribute_solver import IsFalseAttributeSolver # noqa
from checkov.common.checks_infra.solvers.attribute_solvers.intersects_attribute_solver import IntersectsAttributeSolver # noqa
from checkov.common.checks_infra.solvers.attribute_solvers.not_intersects_attribute_solver import NotIntersectsAttributeSolver # noqa
from checkov.common.checks_infra.solvers.attribute_solvers.equals_ignore_case_attribute_solver import EqualsIgnoreCaseAttributeSolver # noqa
from checkov.common.checks_infra.solvers.attribute_solvers.not_equals_ignore_case_attribute_solver import NotEqualsIgnoreCaseAttributeSolver # noqa
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import Optional, Any, Dict

from checkov.common.graph.checks_infra.enums import Operators
from checkov.common.checks_infra.solvers.attribute_solvers.base_attribute_solver import BaseAttributeSolver


class EqualsIgnoreCaseAttributeSolver(BaseAttributeSolver):
operator = Operators.EQUALS_IGNORE_CASE # noqa: CCE003 # a static attribute

def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
attr_val = vertex.get(attribute) # type:ignore[arg-type] # due to attribute can be None
# if this value contains an underendered variable, then we cannot evaluate the check,
# so return True (since we cannot return UNKNOWN)
# handle edge cases in some policies that explicitly look for blank values
if self.value != '' and self._is_variable_dependant(attr_val, vertex['source_']):
return True
return str(attr_val).lower() == str(self.value).lower()
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from typing import Optional, Any, Dict

from checkov.common.graph.checks_infra.enums import Operators
from .equals_ignore_case_attribute_solver import EqualsIgnoreCaseAttributeSolver


class NotEqualsIgnoreCaseAttributeSolver(EqualsIgnoreCaseAttributeSolver):
operator = Operators.NOT_EQUALS_IGNORE_CASE # noqa: CCE003 # a static attribute

def _get_operation(self, vertex: Dict[str, Any], attribute: Optional[str]) -> bool:
return not super()._get_operation(vertex, attribute)
2 changes: 2 additions & 0 deletions checkov/common/graph/checks_infra/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,5 @@ class Operators:
IS_FALSE = 'is_false'
INTERSECTS = 'intersects'
NOT_INTERSECTS = 'not_intersects'
EQUALS_IGNORE_CASE = 'equals_ignore_case'
NOT_EQUALS_IGNORE_CASE = 'not_equals_ignore_case'
72 changes: 37 additions & 35 deletions docs/3.Custom Policies/YAML Custom Policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,41 +88,43 @@ definition:

### Attribute Condition: Operators

| Operator | Value in YAML |
|-----------------------|-------------------------|
| Equals | `equals` |
| Not Equals | `not_equals` |
| Regex Match | `regex_match` |
| Not Regex Match | `not_regex_match` |
| Exists | `exists` |
| Not Exists | `not_exists` |
| One Exists | `one_exists` |
| Any | `any` |
| Contains | `contains` |
| Not Contains | `not_contains` |
| Within | `within` |
| Starts With | `starting_with` |
| Not Starts With | `not_starting_with` |
| Ends With | `ending_with` |
| Not Ends With | `not_ending_with` |
| Greater Than | `greater_than` |
| Greater Than Or Equal | `greater_than_or_equal` |
| Less Than | `less_than` |
| Less Than Or Equal | `less_than_or_equal` |
| Subset | `subset` |
| Not Subset | `not_subset` |
| Is Empty | `is_empty` |
| Is Not Empty | `is_not_empty` |
| Length Equals | `length_equals` |
| Length Not Equals | `length_equals` |
| Length Less Than | `length_less_than` |
| Length Less Than Or Equal | `length_less_than_or_equal` |
| Length Greater Than | `length_greater_than` |
| Length Greater Than Or Equal | `length_greater_than_or_equal` |
| Is False | `is_false` |
| Is True | `is_true` |
| Intersects | `intersects` |
| Not Intersects | `not_intersects` |
| Operator | Value in YAML |
|------------------------------|--------------------------------|
| Equals | `equals` |
| Not Equals | `not_equals` |
| Regex Match | `regex_match` |
| Not Regex Match | `not_regex_match` |
| Exists | `exists` |
| Not Exists | `not_exists` |
| One Exists | `one_exists` |
| Any | `any` |
| Contains | `contains` |
| Not Contains | `not_contains` |
| Within | `within` |
| Starts With | `starting_with` |
| Not Starts With | `not_starting_with` |
| Ends With | `ending_with` |
| Not Ends With | `not_ending_with` |
| Greater Than | `greater_than` |
| Greater Than Or Equal | `greater_than_or_equal` |
| Less Than | `less_than` |
| Less Than Or Equal | `less_than_or_equal` |
| Subset | `subset` |
| Not Subset | `not_subset` |
| Is Empty | `is_empty` |
| Is Not Empty | `is_not_empty` |
| Length Equals | `length_equals` |
| Length Not Equals | `length_equals` |
| Length Less Than | `length_less_than` |
| Length Less Than Or Equal | `length_less_than_or_equal` |
| Length Greater Than | `length_greater_than` |
| Length Greater Than Or Equal | `length_greater_than_or_equal` |
| Is False | `is_false` |
| Is True | `is_true` |
| Intersects | `intersects` |
| Not Intersects | `not_intersects` |
| Equals Ignore Case | `equals_ignore case` |
| Not Equals Ignore Case | `not_equals_ignore case` |

All those operators are supporting JSONPath attribute expression by adding the `jsonpath_` prefix to the operator, for example - `jsonpath_length_equals`

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
metadata:
id: "BooleanString"
scope:
provider: "Azure"
definition:
cond_type: "attribute"
resource_types:
- "azurerm_storage_account"
attribute: "allow_blob_public_access"
operator: "equals_ignore_case"
value: "TRUE"

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
metadata:
id: "EncryptedResources"
scope:
provider: "AWS"
definition:
cond_type: "attribute"
resource_types:
- "aws_rds_cluster"
- "aws_neptune_cluster"
- "aws_s3_bucket"
attribute: "encryption_"
operator: "equals_ignore_case"
value: "encrypted"

Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import os

from tests.terraform.graph.checks_infra.test_base import TestBaseSolver

TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))


class TestEqualsIgnoreCaseSolver(TestBaseSolver):
def setUp(self):
self.checks_dir = TEST_DIRNAME
super(TestEqualsIgnoreCaseSolver, self).setUp()

def test_equals_ignore_case_solver_wildcard(self):
root_folder = '../../../resources/encryption_test'
check_id = "EncryptedResources"
should_pass = ['aws_rds_cluster.rds_cluster_encrypted', 'aws_s3_bucket.encrypted_bucket',
'aws_neptune_cluster.encrypted_neptune']
should_fail = ['aws_rds_cluster.rds_cluster_unencrypted', 'aws_s3_bucket.unencrypted_bucket',
'aws_neptune_cluster.unencrypted_neptune']
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}

super(TestEqualsIgnoreCaseSolver, self).run_test(root_folder=root_folder, expected_results=expected_results,
check_id=check_id)

def test_equals_ignore_case_solver_boolean(self):
root_folder = '../../../resources/boolean_test'
check_id = "BooleanString"
should_pass = ['azurerm_storage_account.fail1', 'azurerm_storage_account.fail2',
'azurerm_storage_account.fail3']
should_fail = ['azurerm_storage_account.pass1', 'azurerm_storage_account.pass2']
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}

super(TestEqualsIgnoreCaseSolver, self).run_test(root_folder=root_folder, expected_results=expected_results,
check_id=check_id)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
metadata:
id: "BooleanString"
scope:
provider: "Azure"
definition:
cond_type: "attribute"
resource_types:
- "azurerm_storage_account"
attribute: "allow_blob_public_access"
operator: "not_equals_ignore_case"
value: "FALSE"

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
metadata:
id: "EncryptedResources"
scope:
provider: "AWS"
definition:
cond_type: "attribute"
resource_types:
- "aws_rds_cluster"
- "aws_neptune_cluster"
- "aws_s3_bucket"
attribute: "encryption_"
operator: "not_equals_ignore_case"
value: "unencrypted"

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import os

from tests.terraform.graph.checks_infra.test_base import TestBaseSolver

TEST_DIRNAME = os.path.dirname(os.path.realpath(__file__))


class TestNotEqualsIgnoreCaseSolver(TestBaseSolver):
def setUp(self):
self.checks_dir = TEST_DIRNAME
super(TestNotEqualsIgnoreCaseSolver, self).setUp()

def test_not_equals_ignore_case_solver_wildcard(self):
root_folder = '../../../resources/encryption_test'
check_id = "EncryptedResources"
should_pass = ['aws_rds_cluster.rds_cluster_encrypted', 'aws_s3_bucket.encrypted_bucket',
'aws_neptune_cluster.encrypted_neptune']
should_fail = ['aws_rds_cluster.rds_cluster_unencrypted', 'aws_s3_bucket.unencrypted_bucket',
'aws_neptune_cluster.unencrypted_neptune']
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}

super(TestNotEqualsIgnoreCaseSolver, self).run_test(root_folder=root_folder, expected_results=expected_results,
check_id=check_id)

def test_not_equals_ignore_case_solver_boolean(self):
root_folder = '../../../resources/boolean_test'
check_id = "BooleanString"
should_pass = ['azurerm_storage_account.fail1', 'azurerm_storage_account.fail2',
'azurerm_storage_account.fail3']
should_fail = ['azurerm_storage_account.pass1', 'azurerm_storage_account.pass2']
expected_results = {check_id: {"should_pass": should_pass, "should_fail": should_fail}}

super(TestNotEqualsIgnoreCaseSolver, self).run_test(root_folder=root_folder, expected_results=expected_results,
check_id=check_id)

0 comments on commit 9aeab2e

Please sign in to comment.