Repository: bridgecrewio/checkov

Issue:
CKV_AZURE_116 fails with Terraform Azure provider >= v.2.97.0
**Describe the issue**
This issue is regarding CKV_AZURE_116.
Since v2.97.0 of the Azure Terraform provider, the Azure policies add-on is enabled by setting

```terraform
azure_policy_enabled = true
```

as a top-level property in a `azurerm_kubernetes_cluster` resource definition, instead of the following

```terraform
addon_profile {
  azure_policy {
    enabled = true
  }
}
```
The check should be updated to reflect the change.

**Examples**
```terraform
resource "azurerm_kubernetes_cluster" "example" {
  azure_policy_enabled = true
}
```
should pass the check for CKV_AZURE_116.

**Version:**
2.0.1160

**Additional context**
None

I will submit a PR to fix this.

Discussion:
gruebel: hi @dommaes thanks for the contribution 🚀 the docs are separate from the repository and you just need to click on **Suggest Edits**
regarding your changes, I'm totally up for only supporting the newer provider versions, but sadly our users need usually a bit longer to adapt to the new changes. I have to consult with my colleagues on how to best approach the transition time.
But thanks a lot for contributing and I will get back to you 🍻 
dommaes: Thanks for the explanation, I totally get that.

In order to support both the old and the new version, what about reading the provider version from the Terraform lock file?
With that it would be possible to implement the check in a version dependent way.

I suppose this would turn out to be a bit more complex than it sounds.
Still, any thoughts on that?
rotemavni: Hi @dommaes, thank you for your contribution!
I have a suggestion on how to support both versions.

Currently the check extends `BaseResourceValueCheck` and my suggestion is to convert it to `BaseResourceCheck`. The check will check if one of the attributes exists and equals `True`. 
In case one of the attributes exist, set `self.evaluated_keys = [<found attribute>]`.

What do you think about this idea?

Patch:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index b7685ea66..67104b727 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,1 +1,27 @@
-By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
+**By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.**
+
+## Description
+
+*Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.*
+
+Fixes # (issue)
+
+
+## New/Edited policies (Delete if not relevant)
+
+### Description
+*Include a description of what makes it a violation and any relevant external links.*
+
+### Fix
+*How does someone fix the issue in code and/or in runtime?*
+
+
+## Checklist:
+
+- [ ] My code follows the style guidelines of this project
+- [ ] I have performed a self-review of my own code
+- [ ] I have commented my code, particularly in hard-to-understand areas
+- [ ] I have made corresponding changes to the documentation
+- [ ] I have added tests that prove my feature, policy, or fix is effective and works
+- [ ] New and existing tests pass locally with my changes
+- [ ] Any dependent changes have been merged and published in downstream modules
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 37ff36907..8fc86e58a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -127,7 +127,7 @@ jobs:
           token: ${{ secrets.GH_PAT_SECRET }}
       - name: Import GPG key
         id: import_gpg
-        uses: crazy-max/ghaction-import-gpg@v4.4.0
+        uses: crazy-max/ghaction-import-gpg@v5.0.0
         with:
           gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
           passphrase: ${{ secrets.PASSPHRASE }}
diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml
index 978d91586..06b86ea73 100644
--- a/.github/workflows/coverage.yaml
+++ b/.github/workflows/coverage.yaml
@@ -14,7 +14,7 @@ jobs:
           token: ${{ secrets.GH_PAT_SECRET }}
       - name: Import GPG key
         id: import_gpg
-        uses: crazy-max/ghaction-import-gpg@v4.4.0
+        uses: crazy-max/ghaction-import-gpg@v5.0.0
         with:
           gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
           passphrase: ${{ secrets.PASSPHRASE }}
diff --git a/.github/workflows/pipenv-update.yml b/.github/workflows/pipenv-update.yml
index 449ff16f8..e5caa93b2 100644
--- a/.github/workflows/pipenv-update.yml
+++ b/.github/workflows/pipenv-update.yml
@@ -14,7 +14,7 @@ jobs:
           token: ${{ secrets.GH_PAT_SECRET }}
       - name: Import GPG key
         id: import_gpg
-        uses: crazy-max/ghaction-import-gpg@v4.4.0
+        uses: crazy-max/ghaction-import-gpg@v5.0.0
         with:
           gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
           passphrase: ${{ secrets.PASSPHRASE }}
diff --git a/admissioncontroller/checkov-requirements.txt b/admissioncontroller/checkov-requirements.txt
index 9339919f2..8489e1d9c 100644
--- a/admissioncontroller/checkov-requirements.txt
+++ b/admissioncontroller/checkov-requirements.txt
@@ -1,1 +1,1 @@
-checkov==2.0.1174
+checkov==2.0.1176
diff --git a/admissioncontroller/k8s/deployment.yaml b/admissioncontroller/k8s/deployment.yaml
index f8639fb35..a213048d3 100644
--- a/admissioncontroller/k8s/deployment.yaml
+++ b/admissioncontroller/k8s/deployment.yaml
@@ -30,7 +30,7 @@ spec:
           capabilities:
             drop:
               - ALL
-        image: bridgecrew/whorf@sha256:bcaf0f7b8863ea4c67fd902e68f282c9c0cb4971287b945e782a8c780fa1677c
+        image: bridgecrew/whorf@sha256:42334ed50956a8a72560098eb1f4b46a19767ea5cba064e6b3f1f05257ab56fc
         imagePullPolicy: Always
         resources:
           limits:
diff --git a/checkov/common/output/report.py b/checkov/common/output/report.py
index d12514535..9d04144a8 100644
--- a/checkov/common/output/report.py
+++ b/checkov/common/output/report.py
@@ -3,6 +3,7 @@ from __future__ import annotations
 import argparse
 import json
 import logging
+import os
 from collections.abc import Iterable
 from dataclasses import dataclass
 from typing import List, Dict, Union, Any, Optional, Set, TYPE_CHECKING, cast
@@ -16,6 +17,7 @@ from checkov import sca_package
 from checkov.common.bridgecrew.severities import Severities, BcSeverities
 from checkov.common.models.enums import CheckResult
 from checkov.common.output.record import Record
+from checkov.common.util.consts import PARSE_ERROR_FAIL_FLAG
 from checkov.common.util.json_utils import CustomJSONEncoder
 from checkov.common.util.type_forcers import convert_csv_string_arg_to_list
 from checkov.runner_filter import RunnerFilter
@@ -145,9 +147,13 @@ class Report:
         :return: Exit code 0 or 1.
         """
 
-        logging.debug(f'In get_exit_code; soft_fail: {soft_fail}, soft_fail_on: {soft_fail_on}, hard_fail_on: {hard_fail_on}')
+        hard_fail_on_parsing_errors = os.getenv(PARSE_ERROR_FAIL_FLAG, "false").lower() == 'true'
+        logging.debug(f'In get_exit_code; soft_fail: {soft_fail}, soft_fail_on: {soft_fail_on}, hard_fail_on: {hard_fail_on}, hard_fail_on_parsing_errors: {hard_fail_on_parsing_errors}')
 
-        if not self.failed_checks or (not soft_fail_on and not hard_fail_on and soft_fail):
+        if self.parsing_errors and hard_fail_on_parsing_errors:
+            logging.debug('hard_fail_on_parsing_errors is True and there were parsing errors - returning 1')
+            return 1
+        elif not self.failed_checks or (not soft_fail_on and not hard_fail_on and soft_fail):
             logging.debug('No failed checks, or soft_fail is True and soft_fail_on and hard_fail_on are empty - returning 0')
             return 0
         elif not soft_fail_on and not hard_fail_on and self.failed_checks:
diff --git a/checkov/common/util/consts.py b/checkov/common/util/consts.py
index bb855d9ab..30be236e4 100644
--- a/checkov/common/util/consts.py
+++ b/checkov/common/util/consts.py
@@ -9,3 +9,5 @@ DEV_API_POST_HEADERS = {
     'Accept': 'application/json',
     'Content-Type': 'application/json'
 }
+
+PARSE_ERROR_FAIL_FLAG = 'CKV_PARSE_ERROR_FAIL'
diff --git a/checkov/kubernetes/checks/resource/base_container_check.py b/checkov/kubernetes/checks/resource/base_container_check.py
index fa5bcb5ea..a0feef171 100644
--- a/checkov/kubernetes/checks/resource/base_container_check.py
+++ b/checkov/kubernetes/checks/resource/base_container_check.py
@@ -11,6 +11,7 @@ from checkov.kubernetes.checks.resource.registry import registry
 class BaseK8sContainerCheck(BaseK8Check):
     TEMPLATE_ENTITIES = (
         "Deployment",
+        "DeploymentConfig",
         "DaemonSet",
         "Job",
         "ReplicaSet",
diff --git a/checkov/main.py b/checkov/main.py
index 88de59222..aa48cebb2 100644
--- a/checkov/main.py
+++ b/checkov/main.py
@@ -366,6 +366,7 @@ def add_parser_args(parser: ArgumentParser) -> None:
                help='Filter scan to run only on specific infrastructure code frameworks',
                choices=checkov_runners + ["all"],
                default=["all"],
+               env_var='CKV_FRAMEWORK',
                nargs="+")
     parser.add('--skip-framework',
                help='Filter scan to skip specific infrastructure code frameworks. \n'
@@ -469,6 +470,7 @@ def add_parser_args(parser: ArgumentParser) -> None:
                default=DEFAULT_EXTERNAL_MODULES_DIR, env_var='EXTERNAL_MODULES_DIR')
     parser.add('--evaluate-variables',
                help="evaluate the values of variables and locals",
+               env_var="CKV_EVAL_VARS",
                default=True)
     parser.add('-ca', '--ca-certificate',
                help='Custom CA certificate (bundle) file', default=None, env_var='BC_CA_BUNDLE')
diff --git a/checkov/terraform/checks/resource/azure/AKSUsesAzurePoliciesAddon.py b/checkov/terraform/checks/resource/azure/AKSUsesAzurePoliciesAddon.py
index 5132754c7..83d9b415e 100644
--- a/checkov/terraform/checks/resource/azure/AKSUsesAzurePoliciesAddon.py
+++ b/checkov/terraform/checks/resource/azure/AKSUsesAzurePoliciesAddon.py
@@ -1,8 +1,10 @@
-from checkov.common.models.enums import CheckCategories
-from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+from typing import Dict, List, Any
 
+from checkov.common.models.enums import CheckCategories, CheckResult
+from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
 
-class AKSUsesAzurePoliciesAddon(BaseResourceValueCheck):
+
+class AKSUsesAzurePoliciesAddon(BaseResourceCheck):
     def __init__(self):
         name = "Ensure that AKS uses Azure Policies Add-on"
         id = "CKV_AZURE_116"
@@ -10,8 +12,20 @@ class AKSUsesAzurePoliciesAddon(BaseResourceValueCheck):
         categories = [CheckCategories.NETWORKING]
         super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
 
-    def get_inspected_key(self):
-        return "addon_profile/[0]/azure_policy/[0]/enabled"
+    def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
+        # since Azure provider v2.97.0
+        azure_policy_enabled = conf.get("azure_policy_enabled", [None])[0]
+        if azure_policy_enabled:
+            self.evaluated_keys = ["azure_policy_enabled"]
+            return CheckResult.PASSED
+        # up to and including Azure provider v2.96.0
+        self.evaluated_keys = ["addon_profile/[0]/azure_policy/[0]/enabled"]
+        addon_profile = conf.get("addon_profile", [None])[0]
+        if addon_profile and isinstance(addon_profile, dict):
+            azure_policy = addon_profile.get("azure_policy", [None])[0]
+            if azure_policy and isinstance(azure_policy, dict) and azure_policy.get("enabled", [None])[0]:
+                return CheckResult.PASSED
+        return CheckResult.FAILED
 
 
 check = AKSUsesAzurePoliciesAddon()
diff --git a/checkov/version.py b/checkov/version.py
index c3c00a819..f89dd3538 100644
--- a/checkov/version.py
+++ b/checkov/version.py
@@ -1,1 +1,1 @@
-version = '2.0.1174'
+version = '2.0.1176'
diff --git a/kubernetes/requirements.txt b/kubernetes/requirements.txt
index 9339919f2..8489e1d9c 100644
--- a/kubernetes/requirements.txt
+++ b/kubernetes/requirements.txt
@@ -1,1 +1,1 @@
-checkov==2.0.1174
+checkov==2.0.1176


Test patch:
diff --git a/tests/common/output/test_get_exit_code.py b/tests/common/output/test_get_exit_code.py
index 02680ceb8..f752ad2a1 100644
--- a/tests/common/output/test_get_exit_code.py
+++ b/tests/common/output/test_get_exit_code.py
@@ -1,9 +1,11 @@
+import os
 import unittest
 
 from checkov.common.bridgecrew.severities import BcSeverities, Severities
 from checkov.common.models.enums import CheckResult
 from checkov.common.output.report import Report
 from checkov.common.output.record import Record
+from checkov.common.util.consts import PARSE_ERROR_FAIL_FLAG
 
 
 class TestGetExitCode(unittest.TestCase):
@@ -128,6 +130,11 @@ class TestGetExitCode(unittest.TestCase):
         self.assertEqual(combined_test_soft_fail_id_hard_fail_sev, 1)
         self.assertEqual(combined_test_soft_fail_id_hard_fail_sev_fail, 0)
 
+        os.environ[PARSE_ERROR_FAIL_FLAG] = 'true'
+        r.add_parsing_error('some_file.tf')
+        self.assertEqual(r.get_exit_code(soft_fail=False, soft_fail_on=None, hard_fail_on=None), 1)
+        del os.environ[PARSE_ERROR_FAIL_FLAG]
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/kubernetes/checks/example_AllowPrivilegeEscalation/oc-deploymentConfig-FAILED.yml b/tests/kubernetes/checks/example_AllowPrivilegeEscalation/oc-deploymentConfig-FAILED.yml
new file mode 100644
index 000000000..9b81685db
--- /dev/null
+++ b/tests/kubernetes/checks/example_AllowPrivilegeEscalation/oc-deploymentConfig-FAILED.yml
@@ -0,0 +1,34 @@
+# Redhat Openshift DeploymentConfig example:
+# https://access.redhat.com/documentation/en-us/openshift_container_platform/4.2/html/applications/deployments
+apiVersion: v1
+kind: DeploymentConfig
+metadata:
+  name: frontend
+spec:
+  replicas: 5
+  selector:
+    name: frontend
+  template:
+    metadata:
+      labels:
+        tier: frontend
+    spec:
+      containers:
+      - image: openshift/hello-openshift
+        name: helloworld
+        ports:
+        - containerPort: 8080
+          protocol: TCP
+      restartPolicy: Always
+  triggers:
+  - type: ConfigChange
+  - imageChangeParams:
+      automatic: true
+      containerNames:
+      - helloworld
+      from:
+        kind: ImageStreamTag
+        name: hello-openshift:latest
+    type: ImageChange
+  strategy:
+    type: Rolling
diff --git a/tests/kubernetes/checks/example_AllowPrivilegeEscalation/oc-deploymentConfig-PASSED.yml b/tests/kubernetes/checks/example_AllowPrivilegeEscalation/oc-deploymentConfig-PASSED.yml
new file mode 100644
index 000000000..6bc606c39
--- /dev/null
+++ b/tests/kubernetes/checks/example_AllowPrivilegeEscalation/oc-deploymentConfig-PASSED.yml
@@ -0,0 +1,36 @@
+# Redhat Openshift DeploymentConfig example with securityContext added:
+# https://access.redhat.com/documentation/en-us/openshift_container_platform/4.2/html/applications/deployments
+apiVersion: v1
+kind: DeploymentConfig
+metadata:
+  name: frontend
+spec:
+  replicas: 5
+  selector:
+    name: frontend
+  template:
+    metadata:
+      labels:
+        tier: frontend
+    spec:
+      containers:
+      - image: openshift/hello-openshift
+        name: helloworld
+        ports:
+        - containerPort: 8080
+          protocol: TCP
+        securityContext:
+          allowPrivilegeEscalation: false
+      restartPolicy: Always
+  triggers:
+  - type: ConfigChange
+  - imageChangeParams:
+      automatic: true
+      containerNames:
+      - helloworld
+      from:
+        kind: ImageStreamTag
+        name: hello-openshift:latest
+    type: ImageChange
+  strategy:
+    type: Rolling
diff --git a/tests/kubernetes/checks/test_AllowProvilegeEscalation.py b/tests/kubernetes/checks/test_AllowProvilegeEscalation.py
index 143c01675..528b0b19b 100644
--- a/tests/kubernetes/checks/test_AllowProvilegeEscalation.py
+++ b/tests/kubernetes/checks/test_AllowProvilegeEscalation.py
@@ -16,8 +16,8 @@ class TestAllowPrivilegeEscalation(unittest.TestCase):
         report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id]))
         summary = report.get_summary()
 
-        self.assertEqual(summary['passed'], 3)
-        self.assertEqual(summary['failed'], 3)
+        self.assertEqual(summary['passed'], 4)
+        self.assertEqual(summary['failed'], 4)
         self.assertEqual(summary['skipped'], 0)
         self.assertEqual(summary['parsing_errors'], 0)
 
diff --git a/tests/terraform/checks/resource/azure/test_AKSUsesAzurePoliciesAddon.py b/tests/terraform/checks/resource/azure/test_AKSUsesAzurePoliciesAddon.py
index 33a84bc4f..399b6ec32 100644
--- a/tests/terraform/checks/resource/azure/test_AKSUsesAzurePoliciesAddon.py
+++ b/tests/terraform/checks/resource/azure/test_AKSUsesAzurePoliciesAddon.py
@@ -48,17 +48,17 @@ class TestAKSUsesAzurePoliciesAddon(unittest.TestCase):
                       enabled = false
                     }
                   }
-                  
+
                   default_node_pool {
                     name       = "default"
                     node_count = 1
                     vm_size    = "Standard_D2_v2"
                   }
-                
+
                   identity {
                     type = "SystemAssigned"
                   }
-                
+
                   tags = {
                     Environment = "Production"
                   }
@@ -68,6 +68,7 @@ class TestAKSUsesAzurePoliciesAddon(unittest.TestCase):
         scan_result = check.scan_resource_conf(conf=resource_conf)
         self.assertEqual(CheckResult.FAILED, scan_result)
 
+
     def test_failure3(self):
         hcl_res = hcl2.loads("""
                 resource "azurerm_kubernetes_cluster" "example" {
@@ -79,17 +80,47 @@ class TestAKSUsesAzurePoliciesAddon(unittest.TestCase):
                   azure_policy {
                     enabled = true
                   }
-                  
+
                   default_node_pool {
                     name       = "default"
                     node_count = 1
                     vm_size    = "Standard_D2_v2"
                   }
-                
+
                   identity {
                     type = "SystemAssigned"
                   }
-                
+
+                  tags = {
+                    Environment = "Production"
+                  }
+                }
+        """)
+        resource_conf = hcl_res['resource'][0]['azurerm_kubernetes_cluster']['example']
+        scan_result = check.scan_resource_conf(conf=resource_conf)
+        self.assertEqual(CheckResult.FAILED, scan_result)
+
+
+    def test_failure4(self):
+        hcl_res = hcl2.loads("""
+                resource "azurerm_kubernetes_cluster" "example" {
+                  name                = "example-aks1"
+                  location            = azurerm_resource_group.example.location
+                  resource_group_name = azurerm_resource_group.example.name
+                  dns_prefix          = "exampleaks1"
+
+                  azure_policy_enabled = false
+
+                  default_node_pool {
+                    name       = "default"
+                    node_count = 1
+                    vm_size    = "Standard_D2_v2"
+                  }
+
+                  identity {
+                    type = "SystemAssigned"
+                  }
+
                   tags = {
                     Environment = "Production"
                   }
@@ -107,12 +138,43 @@ class TestAKSUsesAzurePoliciesAddon(unittest.TestCase):
                   location            = azurerm_resource_group.example.location
                   resource_group_name = azurerm_resource_group.example.name
                   dns_prefix          = "exampleaks1"
+                  
                   addon_profile {
                     azure_policy {
                       enabled = true
                     }
                   }
 
+                  default_node_pool {
+                    name       = "default"
+                    node_count = 1
+                    vm_size    = "Standard_D2_v2"
+                  }
+
+                  identity {
+                    type = "SystemAssigned"
+                  }
+
+                  tags = {
+                    Environment = "Production"
+                  }
+                }
+        """)
+        resource_conf = hcl_res['resource'][0]['azurerm_kubernetes_cluster']['example']
+        scan_result = check.scan_resource_conf(conf=resource_conf)
+        self.assertEqual(CheckResult.PASSED, scan_result)
+
+
+    def test_success2(self):
+        hcl_res = hcl2.loads("""
+                resource "azurerm_kubernetes_cluster" "example" {
+                  name                = "example-aks1"
+                  location            = azurerm_resource_group.example.location
+                  resource_group_name = azurerm_resource_group.example.name
+                  dns_prefix          = "exampleaks1"
+                  
+                  azure_policy_enabled = true
+
                   default_node_pool {
                     name       = "default"
                     node_count = 1