Terraform 0.11 support
* Support for Terraform 0.11 parsing as a fallback
HCL2 parsing is attempted first with a fallback to HCL1 parsing (via `pyhcl`)
if HCL2 parsing fails.
Fixes:
* Path fix
* Merge remote-tracking branch 'upstream/master' into 409-terraform-0.11
# Conflicts:
#	Pipfile.lock
MODIFY
Pipfile
Pipfile
@@ -14,6 +14,7 @@ urllib3-mock = "*"
[packages]
python-hcl2 = "*"
+pyhcl = "*"
tabulate = "*"
colorama="*"
termcolor="*"

MODIFY
Pipfile.lock
Pipfile.lock
{
"_meta": {
"hash": {
- "sha256": "f0e158ca2524e64cdaf7235a4d64cc8279cd99a29a3ee4d486ce14d4729346d4"
+ "sha256": "4d8c9c7efdd59f848266c8d5afa16bb18158857dd497a80f84aca930bae8242f"
},
"pipfile-spec": 6,
"requires": {
"default": {
"boto3": {
"hashes": [
- "sha256:4c2f5f9f28930e236845e2cddbe01cb093ca96dc1f5c6e2b2b254722018a2268",
- "sha256:87beffba2360b8077413f2d473cb828d0a5bda513bd1d6fb7b137c57b686aeb6"
+ "sha256:3c654c1b8f9708e0b457ea1d312ee53451368d09b571ce737dc1f46484112bc1",
+ "sha256:974a4a495fa876f4e8180e2690c840152f06cc3badbe865f05ed731efadcbf44"
],
"index": "pypi",
- "version": "==1.14.14"
+ "version": "==1.14.15"
},
"botocore": {
"hashes": [
- "sha256:6a2e9768dad8ae9771302d5922b977dca6bb9693f9b6a5f6ed0e7ac375e2ca40",
- "sha256:96d668ae5246d236ea83e4586349552d6584e8b1551ae2fccc0bd4ed528a746f"
+ "sha256:34ebc56471a75ea28bfd39f1665d58ee13229c75e8cd6c62b2e2abf1f3e75f0f",
+ "sha256:bb3d3e6aa1fb0caac5909421404218fa7fdcdfc5a1b597ec93cb3affb8326d26"
],
- "version": "==1.17.14"
+ "version": "==1.17.15"
},
"colorama": {
"hashes": [
],
"version": "==0.7.8"
},
+ "pyhcl": {
+ "hashes": [
+ "sha256:2d9b9dcdf1023d812bfed561ba72c99104c5b3f52e558d595130a44ce081b003"
+ ],
+ "index": "pypi",
+ "version": "==0.4.4"
+ },
"python-dateutil": {
"hashes": [
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",

MODIFY
checkov/terraform/parser.py
checkov/terraform/parser.py
+import hcl
import hcl2
import logging
import os
@@ -20,9 +21,17 @@ class Parser:
@staticmethod
def _parse_tf_definitions(tf_file):
+ try:
with(open(tf_file, 'r')) as file:
file.seek(0)
tf_definition = hcl2.load(file)
+ except Exception as hcl2_exception:
+ try:
+ with(open(tf_file, 'r')) as file:
+ file.seek(0)
+ tf_definition = hcl.load(file)
+ except Exception:
+ raise hcl2_exception
for resource_type in tf_definition.get('resource', []):
for resource in resource_type.values():
for named_resource in resource.values():

ADD
tests/terraform/parser/__init__.py
tests/terraform/parser/__init__.py
ADD
tests/terraform/parser/hcl1_example.tf
+# Example of HCL1 syntax that won't parse in HCL2
+# (from https://github.com/amplify-education/python-hcl2/issues/13)
+
+output "ebs_sizes" {
+ value = {
+ "x" : 600,
+ "y" : 350,
+ "z" : 350,
+ "xx" : 200
+ }
+}
\ No newline at end of file

ADD
tests/terraform/parser/hcl1_example_expected.json
+{
+ "output": {
+ "ebs_sizes": {
+ "value": {
+ "x": 600,
+ "y": 350,
+ "z": 350,
+ "xx": 200
+ }
+ }
+ }
+}
\ No newline at end of file

ADD
tests/terraform/parser/hcl2_example.tf
+# Example HCL2 syntax that won't parse in HCL1
+# (from https://github.com/virtuald/pyhcl/issues/74)
+
+locals {
+ map = {
+ r1 = "21.0.0.0/16"
+ r2 = "22.4.0.0/16"
+ }
+
+ sg = {
+ "test" = [
+ "r1",
+ "r2"
+ ]
+ }
+
+}
+
+resource "aws_security_group" "test" {
+ name = "test"
+ vpc_id = "vpc-xxxxxxxxx"
+
+ dynamic "ingress" {
+ for_each = local.sg.test
+ content {
+ from_port = 80
+ to_port = 80
+ protocol = "tcp"
+ cidr_blocks = split(",", lookup(local.map, ingress.value, ingress.value))
+ }
+ }
+}
\ No newline at end of file

ADD
tests/terraform/parser/hcl2_example_expected.json
+{
+ "locals": [
+ {
+ "map": [
+ {
+ "r1": "21.0.0.0/16",
+ "r2": "22.4.0.0/16"
+ }
+ ],
+ "sg": [
+ {
+ "test": [
+ "r1",
+ "r2"
+ ]
+ }
+ ]
+ }
+ ],
+ "resource": [
+ {
+ "aws_security_group": {
+ "test": {
+ "name": [
+ "test"
+ ],
+ "vpc_id": [
+ "vpc-xxxxxxxxx"
+ ],
+ "dynamic": [
+ {
+ "ingress": {
+ "for_each": [
+ "${local.sg.test}"
+ ],
+ "content": [
+ {
+ "from_port": [
+ 80
+ ],
+ "to_port": [
+ 80
+ ],
+ "protocol": [
+ "tcp"
+ ],
+ "cidr_blocks": [
+ "${split(\",\",lookup(local.map,ingress.value,ingress.value))}"
+ ]
+ }
+ ]
+ }
+ }
+ ],
+ "ingress": [
+ "${local.sg.test}"
+ ]
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file

ADD
tests/terraform/parser/test_terraform_hcl.py
+import json
+import os
+import unittest
+
+from checkov.terraform.parser import Parser
+
+
+class TestTerraformHcl(unittest.TestCase):
+
+ def test_hcl1_parsing(self):
+ self._load_and_test('hcl1_example.tf', 'hcl1_example_expected.json')
+
+ def test_hcl2_parsing(self):
+ self._load_and_test('hcl2_example.tf', 'hcl2_example_expected.json')
+
+ def _load_and_test(self, test_file, test_expected_file):
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+
+ parser = Parser()
+ parse_errors = {}
+ result = parser.parse_file(f'{current_dir}/{test_file}', parse_errors)
+
+ with open(f'{current_dir}/{test_expected_file}', 'r') as f:
+ expected_result = json.load(f)
+
+ self.assertEqual(expected_result, result)
+
+
+if __name__ == '__main__':
+ unittest.main()

Terraform 0.11 support (#411)

* Support for Terraform 0.11 parsing as a fallback

HCL2 parsing is attempted first with a fallback to HCL1 parsing (via `pyhcl`)
if HCL2 parsing fails.

Fixes: #409

* Path fix

* Merge remote-tracking branch 'upstream/master' into 409-terraform-0.11

# Conflicts:
#	Pipfile.lock