Skip to content

Commit

Permalink
chore: replace policyuniverse with policy-sentry equivalent (#5640)
Browse files Browse the repository at this point in the history
* replace policyuniverse with policy-sentry equivalent

* fix test and typing

* migrate CKV_AWS_32

* fix typing
  • Loading branch information
gruebel authored Oct 23, 2023
1 parent 6950eb2 commit fd2697c
Show file tree
Hide file tree
Showing 16 changed files with 840 additions and 847 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: debug-statements
- repo: https://github.com/PyCQA/flake8
Expand Down Expand Up @@ -33,7 +33,7 @@ repos:
additional_dependencies:
- vistir<0.7.0 # can be removed, when v4.0.0 of pipenv-setup comes out
- repo: https://github.com/seddonym/import-linter # checks the import dependencies between each other
rev: v1.11.1
rev: v1.12.0
hooks:
- id: import-linter
language_version: python3.9
Expand Down
3 changes: 1 addition & 2 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,13 @@ tqdm = "*"
update-checker = "*"
semantic-version = "*"
packaging = "*"
cloudsplaining = ">=0.4.3"
cloudsplaining = ">=0.6.2"
networkx = "<2.7"
igraph = "<0.11.0" # there will be some breaking changes, but not sure yet, if we will be affected
dockerfile-parse ="*"
docker = "*"
configargparse = "*"
argcomplete = "*"
policyuniverse = "*"
typing-extensions = ">=4.1.0"
importlib-metadata = ">=0.12"
cachetools = "*"
Expand Down
1,536 changes: 775 additions & 761 deletions Pipfile.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion checkov/common/bridgecrew/vulnerability_scanning/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from pathlib import Path
from typing import Any, TYPE_CHECKING

from aiomultiprocess import Pool # type:ignore[import]
from aiomultiprocess import Pool # type:ignore[import-untyped]

from checkov.common.bridgecrew.vulnerability_scanning.integrations.package_scanning import PackageScanningIntegration

Expand Down
4 changes: 2 additions & 2 deletions checkov/common/graph/graph_builder/graph_components/blocks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from collections.abc import Collection
from typing import Union, Dict, Any, List
from typing import Union, Dict, Any, List, cast

from checkov.common.graph.graph_builder.graph_components.attribute_names import CustomAttributes
from checkov.common.graph.graph_builder.utils import calculate_hash, join_trimmed_strings
Expand Down Expand Up @@ -119,7 +119,7 @@ def get_origin_attributes(self, base_attributes: Dict[str, Any]) -> None:

def get_hash(self) -> str:
attributes_dict = self.get_attribute_dict()
return attributes_dict.get(CustomAttributes.HASH, "")
return cast("str", attributes_dict.get(CustomAttributes.HASH, ""))

def update_attribute(
self,
Expand Down
2 changes: 1 addition & 1 deletion checkov/common/parsers/json/decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from json.decoder import WHITESPACE, WHITESPACE_STR, BACKSLASH, STRINGCHUNK, JSONArray # type:ignore[attr-defined] # they are not explicitly exported
from typing import Any, Callable, Pattern, Match

from json.scanner import NUMBER_RE # type:ignore[import] # is not explicitly exported
from json.scanner import NUMBER_RE # type:ignore[import-not-found] # is not explicitly exported

from checkov.common.parsers.node import StrNode, DictNode, ListNode
from checkov.common.parsers.json.errors import NullError, DuplicateError, DecodeError
Expand Down
8 changes: 4 additions & 4 deletions checkov/common/sca/commons.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import logging
from typing import List, Optional, Any
from typing import List, Optional, Any, cast

from checkov.common.output.common import SCADetails

Expand Down Expand Up @@ -39,8 +39,8 @@ def get_package_type(package_name: str, package_version: str, sca_details: SCADe

def get_registry_url(package: dict[str, Any]) -> str:
if "registry" in package:
return package.get("registry", "")
return package.get("registryUrl", "")
return cast("str", package.get("registry", ""))
return cast("str", package.get("registryUrl", ""))


def normalize_twistcli_language(language: str) -> str:
Expand All @@ -52,7 +52,7 @@ def normalize_twistcli_language(language: str) -> str:


def get_package_lines(package: dict[str, Any]) -> list[int] | None:
return package.get("linesNumbers", package.get("lines"))
return cast("list[int] | None", package.get("linesNumbers", package.get("lines")))


def get_record_file_line_range(package: dict[str, Any], file_line_range: list[int] | None) -> list[int]:
Expand Down
5 changes: 4 additions & 1 deletion checkov/common/util/http_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,10 @@ def request_wrapper(
logging.exception("request_wrapper connection error")
raise connection_error
except requests.exceptions.HTTPError as http_error:
status_code = http_error.response.status_code
status_code = 520 # set unknown error, if http_error.response is None
if http_error.response is not None:
status_code = http_error.response.status_code

logging.error(f"HTTP error on request {method}:{url},\ndata:\n{data}\njson:{json if log_json_body else 'Redacted'}\nheaders:{headers}")
if (status_code >= 500 or status_code == 403) and i != request_max_tries - 1:
sleep_secs = sleep_between_request_tries * (i + 1)
Expand Down
4 changes: 2 additions & 2 deletions checkov/kubernetes/image_referencer/base_provider.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import os
from typing import Any
from typing import Any, cast

from checkov.common.graph.graph_builder import CustomAttributes
from checkov.common.images.graph.image_referencer_provider import GraphImageReferencerProvider
Expand Down Expand Up @@ -37,4 +37,4 @@ def extract_images_from_resources(self) -> list[Image]:
return images

def _get_resource_path(self, resource: dict[str, Any]) -> str:
return resource.get(CustomAttributes.FILE_PATH, "")
return cast("str", resource.get(CustomAttributes.FILE_PATH, ""))
56 changes: 16 additions & 40 deletions checkov/terraform/checks/resource/aws/ECRPolicy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from typing import Any

from cloudsplaining.scan.resource_policy_document import ResourcePolicyDocument

from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck

Expand All @@ -11,52 +13,26 @@ def __init__(self) -> None:
name = "Ensure ECR policy is not set to public"
id = "CKV_AWS_32"
supported_resources = ("aws_ecr_repository_policy",)
categories = (CheckCategories.GENERAL_SECURITY,)
categories = (CheckCategories.IAM,)
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)

def scan_resource_conf(self, conf: dict[str, list[Any]]) -> CheckResult:
"""
Looks for public * policy for ecr repository:
https://www.terraform.io/docs/providers/aws/r/ecr_repository_policy.html
:param conf: aws_ecr_repository configuration
:return: <CheckResult>
"""
if "policy" in conf.keys():
policy = conf["policy"][0]
if isinstance(policy, str):
return CheckResult.PASSED

statement = policy["Statement"][0]
if statement and isinstance(statement, dict):
principal = statement.get("Principal")
if principal and isinstance(principal, str) and principal == "*" and not self.check_for_constrained_condition(statement):
self.evaluated_keys = ["policy/Statement/Principal"]
return CheckResult.FAILED
def scan_resource_conf(self, conf: dict[str, Any]) -> CheckResult:
conf_policy = conf.get("policy")
if conf_policy and conf_policy[0]:
if isinstance(conf_policy[0], dict):
try:
policy = ResourcePolicyDocument(policy=conf_policy[0])
if policy.internet_accessible_actions:
return CheckResult.FAILED
except (TypeError, AttributeError):
return CheckResult.UNKNOWN
else:
return CheckResult.UNKNOWN

return CheckResult.PASSED

def get_evaluated_keys(self) -> list[str]:
return ["policy"]

def check_for_constrained_condition(self, statement: dict[str, Any]) -> bool:
"""
Checks to see if there is a constraint on a wildcarded principal
:param statement: statement from aws_repository_configuration
:return: True if there is a constraint
"""
if "Condition" in statement and isinstance(statement["Condition"], dict):
condition = statement["Condition"]
string_equals = None
if "StringEquals" in condition:
string_equals = condition["StringEquals"]
elif "ForAllValues:StringEquals" in condition:
string_equals = condition["ForAllValues:StringEquals"]
elif "ForAnyValue:StringEquals" in condition:
string_equals = condition["ForAnyValue:StringEquals"]

if isinstance(string_equals, dict) and "aws:PrincipalOrgID" in string_equals:
return True

return False


check = ECRPolicy()
29 changes: 16 additions & 13 deletions checkov/terraform/checks/resource/aws/GlacierVaultAnyPrincipal.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
from __future__ import annotations

import json
import re
from typing import List, Any

from cloudsplaining.scan.resource_policy_document import ResourcePolicyDocument

from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from policyuniverse.policy import Policy
from typing import List

DATA_TO_JSON_PATTERN = re.compile(r"\$?\{?(.+?)(?=.json).json\}?")


class GlacierVaultAnyPrincipal(BaseResourceCheck):
def __init__(self):
def __init__(self) -> None:
name = "Ensure Glacier Vault access policy is not public by only allowing specific services or principals to access it"
id = "CKV_AWS_167"
supported_resources = ['aws_glacier_vault']
categories = [CheckCategories.GENERAL_SECURITY]
supported_resources = ("aws_glacier_vault",)
categories = (CheckCategories.IAM,)
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)

def scan_resource_conf(self, conf):
if 'access_policy' not in conf:
def scan_resource_conf(self, conf: dict[str, list[Any]]) -> CheckResult:
if "access_policy" not in conf:
return CheckResult.PASSED
policy_obj = conf['access_policy'][0]
policy_obj = conf["access_policy"][0]
if isinstance(policy_obj, str):
if re.match(DATA_TO_JSON_PATTERN, policy_obj):
return CheckResult.UNKNOWN
Expand All @@ -30,15 +33,15 @@ def scan_resource_conf(self, conf):
except Exception:
return CheckResult.UNKNOWN
try:
policy = Policy(policy_obj)
except TypeError:
policy = ResourcePolicyDocument(policy=policy_obj)
if policy.internet_accessible_actions:
return CheckResult.FAILED
except (TypeError, AttributeError):
return CheckResult.UNKNOWN
if policy.is_internet_accessible():
return CheckResult.FAILED
return CheckResult.PASSED

def get_evaluated_keys(self) -> List[str]:
return ['access_policy']
return ["access_policy"]


check = GlacierVaultAnyPrincipal()
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import Any

from policyuniverse.policy import Policy
from cloudsplaining.scan.resource_policy_document import ResourcePolicyDocument

from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
Expand All @@ -13,20 +13,19 @@ def __init__(self) -> None:
name = "Ensure SNS topic policy is not public by only allowing specific services or principals to access it"
id = "CKV_AWS_169"
supported_resources = ("aws_sns_topic_policy",)
categories = (CheckCategories.GENERAL_SECURITY,)
categories = (CheckCategories.IAM,)
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)

def scan_resource_conf(self, conf: dict[str, Any]) -> CheckResult:
conf_policy = conf.get("policy")
if conf_policy:
if isinstance(conf_policy[0], dict):
policy = conf_policy[0]
condition_values = policy.get('Statement', [{}])[0].get('Condition', {}).values()
if condition_values and not any(isinstance(condition, dict) for condition in condition_values):
try:
policy = ResourcePolicyDocument(policy=conf_policy[0])
if policy.internet_accessible_actions:
return CheckResult.FAILED
except (TypeError, AttributeError):
return CheckResult.UNKNOWN
policy = Policy(policy)
if policy.is_internet_accessible():
return CheckResult.FAILED
else:
return CheckResult.UNKNOWN
return CheckResult.PASSED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import Any

from policyuniverse.policy import Policy
from cloudsplaining.scan.resource_policy_document import ResourcePolicyDocument

from checkov.common.models.enums import CheckResult, CheckCategories
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
Expand All @@ -13,16 +13,16 @@ def __init__(self) -> None:
name = "Ensure SQS queue policy is not public by only allowing specific services or principals to access it"
id = "CKV_AWS_168"
supported_resources = ("aws_sqs_queue_policy", "aws_sqs_queue")
categories = (CheckCategories.GENERAL_SECURITY,)
categories = (CheckCategories.IAM,)
super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)

def scan_resource_conf(self, conf: dict[str, Any]) -> CheckResult:
conf_policy = conf.get("policy")
if conf_policy:
if isinstance(conf_policy[0], dict):
try:
policy = Policy(conf_policy[0])
if policy.is_internet_accessible():
policy = ResourcePolicyDocument(policy=conf_policy[0])
if policy.internet_accessible_actions:
return CheckResult.FAILED
except (TypeError, AttributeError):
return CheckResult.UNKNOWN
Expand Down
2 changes: 1 addition & 1 deletion checkov/terraform/plan_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ def _get_module_call_resources(module_address: str, root_module_conf: dict[str,
continue
root_module_conf = root_module_conf.get("module_calls", {}).get(module_name, {}).get("module", {})

return root_module_conf.get("resources", [])
return cast("list[dict[str, Any]]", root_module_conf.get("resources", []))


def _get_resource_changes(template: dict[str, Any]) -> dict[str, dict[str, Any]]:
Expand Down
3 changes: 1 addition & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,13 @@ def run(self) -> None:
"update-checker",
"semantic-version",
"packaging",
"cloudsplaining>=0.4.3",
"cloudsplaining>=0.6.2",
"networkx<2.7",
"igraph<0.11.0",
"dockerfile-parse",
"docker",
"configargparse",
"argcomplete",
"policyuniverse",
"typing-extensions>=4.1.0",
"importlib-metadata>=0.12",
"cachetools",
Expand Down
6 changes: 3 additions & 3 deletions tests/common/output/test_spdx.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ def test_sca_package_output():
"## Creation Information\n",
"Creator: Tool: checkov\n",
"Creator: Organization: bridgecrew ([email protected])\n",
"Created: 2022-12-24T00:00:00+00:00Z\n",
"Created: 2022-12-24T00:00:00Z\n",
"\n",
"## Package Information\n",
"PackageName: django\n",
"SPDXID: SPDXRef-django\n",
"PackageVersion: 1.2\n",
"PackageFileName: /requirements.txt\n",
"PackageDownloadLocation: NONE\n",
"FilesAnalyzed: True\n",
"FilesAnalyzed: true\n",
"PackageLicenseInfoFromFiles: OSI_BDS\n",
"\n",
"## Package Information\n",
Expand All @@ -102,7 +102,7 @@ def test_sca_package_output():
"PackageVersion: 1.1.1\n",
"PackageFileName: /requirements.txt\n",
"PackageDownloadLocation: NONE\n",
"FilesAnalyzed: True\n",
"FilesAnalyzed: true\n",
"PackageLicenseInfoFromFiles: MIT\n",
"\n",
"\n",
Expand Down

0 comments on commit fd2697c

Please sign in to comment.