Repository: bridgecrewio/checkov

Issue:
find_all_interpolations crashing cloudformation with UserData
**Describe the issue**
Explain what you expected to happen when checkov crashed.
https://github.com/bridgecrewio/checkov/blob/master/checkov/cloudformation/graph_builder/utils.py#L38
this Line Crashes, when parsing cloudformation templates where the UserData is a dict
the function appears to expect a string

`def find_all_interpolations(str_value: str) -> List[str]:
    return re.findall(FIND_INTERPOLATION_PATTERN, str_value)
`

**Source code screenshot**
<img width="784" alt="source_template" src="https://user-images.githubusercontent.com/1858146/172553239-efc2c505-92a9-4956-8f8b-5b3899a0713b.png">
<img width="772" alt="source_referenece" src="https://user-images.githubusercontent.com/1858146/172553966-e349391d-6485-4252-8a24-63131a71bc11.png">


**Examples**
images provided
<img width="1661" alt="trace_console" src="https://user-images.githubusercontent.com/1858146/172552704-28d382af-8a38-4536-8d26-2362d36c9d03.png">
<img width="1783" alt="debug" src="https://user-images.githubusercontent.com/1858146/172552711-99dbc2cd-6264-4b18-a08f-58d578b456fa.png">
<img width="1789" alt="trace" src="https://user-images.githubusercontent.com/1858146/172552713-88dc41b4-3893-4216-9362-633a8ea6a573.png">

**Exception Trace**


**Desktop (please complete the following information):**
 - OS: MAC
 - Checkov Version [2.0.1204] tried newer and older versions seems to remain

**Additional context**




Discussion:
Hi @coffeecoco, can you share a Cloudformation template to reproduce this issue?
Thanks!
> find_all_interpolations


> > find_all_interpolations

i have think i done a find job removing any personal info from the template
it seems im forced to zip up this template as github wont support json files
[data-squid-asgs-eng-apse2.json.zip](https://github.com/bridgecrewio/checkov/files/8865891/data-squid-asgs-eng-apse2.json.zip)



Resolving patch:
diff --git a/checkov/cloudformation/graph_builder/variable_rendering/renderer.py b/checkov/cloudformation/graph_builder/variable_rendering/renderer.py
--- a/checkov/cloudformation/graph_builder/variable_rendering/renderer.py
+++ b/checkov/cloudformation/graph_builder/variable_rendering/renderer.py
@@ -285,8 +285,8 @@ def _evaluate_getatt_connection(value: List[str], dest_vertex_attributes: Dict[s
 
     def _evaluate_sub_connection(self, value: str, dest_vertex_attributes: Dict[str, Any]) -> (
             Optional[str], Optional[str]):
-        if isinstance(value, list):
-            # TODO: Render values of list type
+        if isinstance(value, (list, dict)):
+            # TODO: Render values of list/dict types
             return None, None
         evaluated_value = None
         attribute_at_dest = None


Test patch:
diff --git a/tests/cloudformation/runner/resources/skip_sub_dict.json b/tests/cloudformation/runner/resources/skip_sub_dict.json
new file mode 100644
--- /dev/null
+++ b/tests/cloudformation/runner/resources/skip_sub_dict.json
@@ -0,0 +1,81 @@
+{
+    "AWSTemplateFormatVersion": "2010-09-09",
+    "Description": "Stack",
+    "Parameters": {
+        "RequirePostfix": {
+            "Type": "String",
+            "AllowedValues": [
+                "Yes",
+                "No"
+            ]
+        },
+        "MaxPriority": {
+            "Type": "String",
+            "AllowedValues": [
+                "high",
+                "low",
+                "ticket",
+                "notify"
+            ]
+        }
+    },
+    "Mappings": {
+        "Blobs": {
+            "Code": {
+                "userdata": "set -eux -o pipefail\n\n# usage: complete_asg_lifecycle asg_name lifecycle_name [CONTINUE|ABANDON]\ncomplete_asg_lifecycle() (\n  instance_id=\"$(curl -s http://169.254.169.254/latest/meta-data/instance-id)\"\n)\nget_instance_id() { curl -s http://169.254.169.254/latest/meta-data/instance-id; }\nget_az() { curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone; }\n\ncleanup() {\n  code=\"$?\"\n  (( !code )) || complete_asg_lifecycle \"$StackName\" StartupLifecycleHook ABANDON\n}\ntrap cleanup EXIT\n\nsetup_monitoring --asg \"$StackName-\" /var/log/squid/{access.log,cache.log}\n\n# disable source/destination check\nINSTANCE_ID=\"$(get_instance_id)\"\n\n# get the EIP for this instance and use it instead of the default\nif [ -n \"$SquidEIP\" ]; then\nfi\n\n# trust the rhui certs\nln -s /etc/pki/rhui/cdn.redhat.com-chain.crt /etc/pki/ca-trust/source/anchors\nupdate-ca-trust\n\n# install squid\nyum install -y squid\nmv /etc/squid /etc/squid.base\nln -s /etc/ssl/squid/ /etc/squid.base/ssl\ncp -r /etc/squid.base /etc/squid-current\nln -sfT /etc/squid-current /etc/squid\n\nmkdir -p /etc/systemd/system/squid.service.d\ncat >/etc/systemd/system/squid.service.d/shutdown.conf <<EOF\n[Unit]\nRequires=squid-shutdown.service\nEOF\n\nscript=/usr/local/bin/kill-instance.sh\ncat >/etc/systemd/system/squid-shutdown.service <<EOF\n[Unit]\nRequires=squid.service\nAfter=squid.service\n[Service]\nEnvironment=AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION\nRemainAfterExit=yes\nExecStop=$script $INSTANCE_ID\nExecStop=/bin/sleep 10\nEOF\ncat >\"$script\" <<EOF\n#!/bin/bash\naws autoscaling set-instance-health --instance-id \\$1 --health-status Unhealthy\necho \"\\$output\"\nEOF\nchmod +x \"$script\"\n\nscript=/usr/local/bin/healthcheck.sh\ncat >/etc/systemd/system/proxy-healthcheck.service <<EOF\n[Unit]\nAfter=network.service\n[Service]\nEnvironment=AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION\nExecStart=$script $TargetGroup\nRestart=always\nEOF\ncat >\"$script\" <<'EOF'\n#!/bin/bash\nset -eu -o pipefail\ntarget_group=\"$1\"\nEOF\nchmod +x \"$script\"\n\nsystemctl daemon-reload\nsystemctl enable squid\nsystemctl enable --now proxy-healthcheck\n\n# configure iptables\niptables -t nat -A PREROUTING -s 0.0.0.0/0 -p tcp --dport 80 -j REDIRECT --to-port 3129\niptables -t nat -A PREROUTING -s 0.0.0.0/0 -p tcp --dport 443 -j REDIRECT --to-port 3130\nINSTANCE_IP=$(curl -s http://169.254.169.254/latest/meta-data/local-ipv4)\niptables -t nat -A PREROUTING -s 0.0.0.0/0 -p tcp --dport 25 -j DNAT --to-destination $INSTANCE_IP\n\n# create ssl certificate\nmkdir -p /etc/ssl/squid\n(\n  cd /etc/ssl/squid\n  openssl genrsa -out squid.key 4096\n  openssl req -new -key squid.key -out squid.csr -subj \"/CN=squid.proxy\"\n  openssl x509 -req -days 3650 -in squid.csr -signkey squid.key -out squid.crt\n  cat squid.key squid.crt >squid.pem\n  chmod -R 400 *\n)\n\n# configure squid\ncat > /usr/local/bin/squid-config-refresh.sh << EOF\n#!/bin/bash\nset -eu -o pipefail\n\nold=\"\\$(readlink /etc/squid)\"\nnew=\"/etc/squid-\\$(date +%s)\"\nonexit() {\n  code=\\$?\n  put-custom-metric.sh SquidConfigStatus \\$code\n  (( code )) && ln -sfT \"\\$old\" /etc/squid && rm -rf \"\\$new\"\n}\ntrap onexit EXIT\n\nrm -rf \"\\$new\"\ncp -r /etc/squid.base \"\\$new\"\naws s3 cp --recursive --quiet $ProxyConfigLocation \"\\$new\"\nchmod a+x \"\\$new\"\nchmod -R a+r \"\\$new\"\nmy_ip=\"\\$(curl -s http://169.254.169.254/latest/meta-data/local-ipv4)\"\nsed -i \"s/LOCAL_IP/\\$my_ip/g\" \"\\$new/squid.conf\"\n\nif diff --brief --recursive \"\\$old\" \"\\$new\"; then\n  echo 'Config has not changed' >&2\n  rm -rf \"\\$new\"\n  exit\nfi\n\necho 'Config has changed, reloading' >&2\nln -sfT \"\\$new\" /etc/squid\nsquid -k parse\n! squid -k parse 2>&1 | fgrep FATAL\nsystemctl start squid\nsleep 15\nsystemctl reload squid\nrm -rf \"\\$old\" || true\nEOF\nchmod +x /usr/local/bin/squid-config-refresh.sh\nsquid-config-refresh.sh || true\n\nsubnets=$(aws ec2 describe-subnets --filters Name=availability-zone,Values=$(get_az) | jq -r '.Subnets|map(.SubnetId)|join(\",\")')\nroute_tables=\"$(aws ec2 describe-route-tables --route-table-ids ${RouteTables//,/ } --filters Name=association.subnet-id,Values=\"$subnets\" | jq -r .RouteTables[].RouteTableId)\"\n\ncat >/usr/local/bin/takeover-route.sh <<EOF\n#!/bin/bash\nset \"\\$@\" --\nsystemctl is-active squid || exit 1\nhealth=\"\\$(\n  aws elbv2 describe-target-health --target-group-arn $TargetGroup --targets Id=$INSTANCE_ID \\\n  | jq -r '.TargetHealthDescriptions[].TargetHealth.State'\n)\"\n[ \"\\$health\" != healthy ] && exit 1\nif [ -n '$SquidEIP' ]; then\n    current_nat=\"\\$(\n        aws ec2 describe-network-interfaces --filters=Name=association.allocation-id,Values=$SquidEIP \\\n        | jq -r .NetworkInterfaces[0].Attachment.InstanceId\n    )\"\n    [ \"\\$current_nat\" != $INSTANCE_ID ] && exit 1\nfi\n\nfor route in $route_tables; do\n    current=\"\\$(\n      aws ec2 describe-route-tables --route-table-ids \\$route \\\n      | jq -r '.RouteTables[].Routes[] | select(.DestinationCidrBlock==\"0.0.0.0/0\").InstanceId'\n    )\"\n    if [ \"\\$current\" != $INSTANCE_ID ]; then\n      echo Taking over \\$route >&2\n      args=( --destination-cidr-block 0.0.0.0/0 --route-table-id \\$route --instance-id $INSTANCE_ID )\n      aws ec2 replace-route \"\\${args[@]}\" || aws ec2 create-route \"\\${args[@]}\"\n    fi\ndone\nEOF\nchmod +x /usr/local/bin/takeover-route.sh\n\n# configure cron jobs\ncrontab << EOF\nPATH=$PATH\n* * * * * squid-config-refresh.sh\n0 15 * * * sleep $((NUM*3600)); yum -y update --security\n0 0 * * * squid -k rotate\n*/10 * * * * AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION takeover-route.sh\nEOF\n\n# configure email relay for Data Squids only\nrun_postfix_config() {\n  cat > /usr/local/bin/postfix-config-refresh.sh << EOF\n#!/bin/bash\nset -eu -o pipefail\n\nyum install cyrus-sasl-plain -y\nmv /etc/postfix/main.cf /etc/postfix/main.cf.orig\naws s3 cp \"$ProxyConfigLocation\"-mail/main.cf /etc/postfix/main.cf\naws s3 cp \"$ProxyConfigLocation\"-mail/recipient_whitelist /etc/postfix/recipient_whitelist\nmy_ip=\"\\$(curl -s http://169.254.169.254/latest/meta-data/local-ipv4)\"\naws ssm put-parameter --name \"/config/email/relay$NUM\" --value \"\\$my_ip\" --type String --overwrite --region ap-southeast-2\nsed -i \"s/LOCAL_IP/\\$my_ip/g\" /etc/postfix/main.cf\necho \"mynetworks = $VpcCidr, 127.0.0.0/8\" >> /etc/postfix/main.cf\nmail_user=\"\\$(aws secretsmanager get-secret-value --secret-id proxy/email --region ap-southeast-2 --query SecretString|jq -r .|jq -r '.user')\"\nmail_password=\"\\$(aws secretsmanager get-secret-value --secret-id proxy/email --region ap-southeast-2 --query SecretString|jq -r .|jq -r '.secret')\"\necho \"email-smtp.ap-southeast-2.amazonaws.com \\$mail_user:\\$mail_password\" > /etc/postfix/relay_creds\npostmap /etc/postfix/recipient_whitelist\npostmap /etc/postfix/relay_creds\nrm /etc/postfix/recipient_whitelist\nrm /etc/postfix/relay_creds\nsystemctl restart postfix\nEOF\n\n  chmod +x /usr/local/bin/postfix-config-refresh.sh && postfix-config-refresh.sh\n\n  # modify crontab to add postfix config refresh\n  crontab << EOF\nPATH=$PATH\n* * * * * squid-config-refresh.sh\n0 15 * * * sleep $((NUM*3600)); yum -y update --security\n0 0 * * * squid -k rotate\n*/10 * * * * AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION takeover-route.sh\n*/30 * * * * postfix-config-refresh.sh\nEOF\n}\n\nif [[ $RequirePostfix == \"Yes\" ]]; then\n  run_postfix_config;\nfi\n\ncomplete_asg_lifecycle \"$StackName\" StartupLifecycleHook CONTINUE\nuntil takeover-route.sh -x; do sleep 10; done",
+                "lambda": "redacted"
+            }
+        }
+    },
+    "Resources": {
+        "LaunchConfiguration1": {
+            "Type": "AWS::AutoScaling::LaunchConfiguration",
+            "Properties": {
+                "AssociatePublicIpAddress": true,
+                "InstanceType": {
+                    "Ref": "SquidInstanceType"
+                },
+                "BlockDeviceMappings": [
+                    {
+                        "DeviceName": "/dev/xvda",
+                        "Ebs": {
+                            "VolumeSize" : "50",
+                            "Encrypted": true
+                        }
+                    }
+                ],
+                "ImageId": {
+                    "Ref": "ImageId"
+                },
+                "UserData": {
+                    "Fn::Base64": {
+                        "Fn::Sub": [
+                            "#!/bin/bash\n\nRequirePostfix=${RequirePostfix}\nVpcCidr=${VpcCidr}\nProxyConfigLocation=s3://${ProxyConfigBucket}/${ProxyConfigPath}\nStackName=${AWS::StackName}\nexport AWS_DEFAULT_REGION=${AWS::Region}\nASGName=${ASGName}\nSquidEIP=${SquidEIP}\nTargetGroup=${ProxyTargetGroup}\nRouteTables=${RouteTables}\nProxyLambda=${TransparentProxyLambda}\nNUM=1\n${UserData}\n",
+                            {
+                                "RouteTables": {
+                                    "Ref": "RouteTables"
+                                },
+                                "ASGName": "AutoScalingGroup1",
+                                "SquidEIP": {
+                                    "Fn::Select": [
+                                        1,
+                                        {
+                                            "Ref": "SquidEIPs"
+                                        }
+                                    ]
+                                },
+                                "UserData": {
+                                    "Fn::FindInMap": [
+                                        "Blobs",
+                                        "Code",
+                                        "userdata"
+                                    ]
+                                }
+                            }
+                        ]
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/cloudformation/runner/test_runner.py b/tests/cloudformation/runner/test_runner.py
--- a/tests/cloudformation/runner/test_runner.py
+++ b/tests/cloudformation/runner/test_runner.py
@@ -329,6 +329,14 @@ def test_parsing_error_yaml(self):
                             runner_filter=RunnerFilter(framework='cloudformation'))
         self.assertEqual(report.parsing_errors, [scan_file_path])
 
+    def test_skip_sub_dict_json(self):
+        current_dir = os.path.dirname(os.path.realpath(__file__))
+        scan_file_path = os.path.join(current_dir, "resources", "skip_sub_dict.json")
+        runner = Runner()
+        report = runner.run(root_folder=None, external_checks_dir=None, files=[scan_file_path],
+                            runner_filter=RunnerFilter(framework='cloudformation'))
+        self.assertEqual(len(report.failed_checks), 0)
+
     def test_parsing_error_json(self):
         current_dir = os.path.dirname(os.path.realpath(__file__))
         scan_file_path = os.path.join(current_dir, "resources", "invalid.json")
diff --git a/tests/cloudformation/test_graph_manager.py b/tests/cloudformation/test_graph_manager.py
--- a/tests/cloudformation/test_graph_manager.py
+++ b/tests/cloudformation/test_graph_manager.py
@@ -13,7 +13,8 @@ class TestCloudformationGraphManager(TestCase):
     def test_build_graph_from_source_directory_no_rendering(self):
         root_dir = os.path.realpath(os.path.join(TEST_DIRNAME, "./runner/resources"))
         graph_manager = CloudformationGraphManager(db_connector=NetworkxConnector())
-        local_graph, definitions = graph_manager.build_graph_from_source_directory(root_dir, render_variables=False)
+        local_graph, definitions = graph_manager.build_graph_from_source_directory(root_dir, render_variables=False,
+                                                                                   excluded_paths=["skip*"])
 
         expected_resources_by_file = {
             os.path.join(root_dir, "no_properties.yaml"): [