Repository: bridgecrewio/checkov

Issue:
Add new check: Glue Enable Encryption of data at rest
Both Glue Jobs and Glue Crawlers use a security configuration resource:
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-job.html
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-crawler.html
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-securityconfiguration.html

https://www.terraform.io/docs/providers/aws/r/glue_job.html
https://www.terraform.io/docs/providers/aws/r/glue_crawler.html
https://www.terraform.io/docs/providers/aws/r/glue_security_configuration.html


Discussion:
check to ensure that glue job has security config set
check to ensure that glue crawler  has security config set
check to ensure that security config is all encrypted?

Resolving patch:
diff --git a/checkov/cloudformation/checks/resource/aws/GlueSecurityConfigurationEnabled.py b/checkov/cloudformation/checks/resource/aws/GlueSecurityConfigurationEnabled.py
new file mode 100644
--- /dev/null
+++ b/checkov/cloudformation/checks/resource/aws/GlueSecurityConfigurationEnabled.py
@@ -0,0 +1,28 @@
+from typing import Any
+
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class GlueSecurityConfigurationEnabled(BaseResourceValueCheck):
+    def __init__(self) -> None:
+        name = "Ensure Glue component has a security configuration associated"
+        id = "CKV_AWS_195"
+        supported_resources = ("AWS::Glue::Crawler", "AWS::Glue::DevEndpoint", "AWS::Glue::Job")
+        categories = (CheckCategories.ENCRYPTION,)
+        super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+    def get_inspected_key(self) -> str:
+        if self.entity_type == "AWS::Glue::Crawler":
+            return "Properties/CrawlerSecurityConfiguration"
+        elif self.entity_type in ("AWS::Glue::DevEndpoint", "AWS::Glue::Job"):
+            return "Properties/SecurityConfiguration"
+
+        return ""
+
+    def get_expected_value(self) -> Any:
+        return ANY_VALUE
+
+
+check = GlueSecurityConfigurationEnabled()
diff --git a/checkov/cloudformation/runner.py b/checkov/cloudformation/runner.py
--- a/checkov/cloudformation/runner.py
+++ b/checkov/cloudformation/runner.py
@@ -1,7 +1,7 @@
 import json
 import logging
 import os
-from typing import Optional, List
+from typing import Optional, List, Type
 
 from checkov.cloudformation import cfn_utils
 from checkov.cloudformation.cfn_utils import create_definitions, build_definitions_context
@@ -13,8 +13,11 @@
 from checkov.cloudformation.graph_builder.local_graph import CloudformationLocalGraph
 from checkov.cloudformation.graph_manager import CloudformationGraphManager
 from checkov.common.checks_infra.registry import get_graph_checks_registry
+from checkov.common.graph.checks_infra.registry import BaseRegistry
 from checkov.common.graph.db_connectors.networkx.networkx_db_connector import NetworkxConnector
 from checkov.common.graph.graph_builder import CustomAttributes
+from checkov.common.graph.graph_builder.local_graph import LocalGraph
+from checkov.common.graph.graph_manager import GraphManager
 from checkov.common.output.graph_record import GraphRecord
 from checkov.common.output.record import Record
 from checkov.common.output.report import Report, merge_reports
@@ -27,12 +30,12 @@ class Runner(BaseRunner):
 
     def __init__(
         self,
-        db_connector=NetworkxConnector(),
-        source="CloudFormation",
-        graph_class=CloudformationLocalGraph,
-        graph_manager=None,
-        external_registries=None,
-    ):
+        db_connector: NetworkxConnector = NetworkxConnector(),
+        source: str = "CloudFormation",
+        graph_class: Type[LocalGraph] = CloudformationLocalGraph,
+        graph_manager: Optional[GraphManager] = None,
+        external_registries: Optional[List[BaseRegistry]] = None,
+    ) -> None:
         self.external_registries = [] if external_registries is None else external_registries
         self.graph_class = graph_class
         self.graph_manager = (
diff --git a/checkov/terraform/checks/resource/aws/GlueSecurityConfigurationEnabled.py b/checkov/terraform/checks/resource/aws/GlueSecurityConfigurationEnabled.py
new file mode 100644
--- /dev/null
+++ b/checkov/terraform/checks/resource/aws/GlueSecurityConfigurationEnabled.py
@@ -0,0 +1,23 @@
+from typing import Any
+
+from checkov.common.models.consts import ANY_VALUE
+from checkov.common.models.enums import CheckCategories
+from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
+
+
+class GlueSecurityConfigurationEnabled(BaseResourceValueCheck):
+    def __init__(self) -> None:
+        name = "Ensure Glue component has a security configuration associated"
+        id = "CKV_AWS_195"
+        supported_resources = ("aws_glue_crawler", "aws_glue_dev_endpoint", "aws_glue_job")
+        categories = (CheckCategories.ENCRYPTION,)
+        super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
+
+    def get_inspected_key(self) -> str:
+        return "security_configuration"
+
+    def get_expected_value(self) -> Any:
+        return ANY_VALUE
+
+
+check = GlueSecurityConfigurationEnabled()


Test patch:
diff --git a/tests/cloudformation/checks/resource/aws/example_GlueSecurityConfigurationEnabled/FAIL.yaml b/tests/cloudformation/checks/resource/aws/example_GlueSecurityConfigurationEnabled/FAIL.yaml
new file mode 100644
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_GlueSecurityConfigurationEnabled/FAIL.yaml
@@ -0,0 +1,23 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+  CrawlerDefault:
+    Type: AWS::Glue::Crawler
+    Properties:
+      Name: example
+      Role: arn:aws:iam::123456789012:role/role
+      Targets:
+        S3Targets:
+          - Path: s3-bucket
+  DevEndpointDefault:
+    Type: AWS::Glue::DevEndpoint
+    Properties:
+      EndpointName: example
+      RoleArn: arn:aws:iam::123456789012:role/role
+  JobDefault:
+    Type: AWS::Glue::Job
+    Properties:
+      Command:
+        Name: glueetl
+        ScriptLocation: s3://aws-glue-scripts//prod-job
+      Name: example
+      Role: arn:aws:iam::123456789012:role/role
diff --git a/tests/cloudformation/checks/resource/aws/example_GlueSecurityConfigurationEnabled/PASS.yaml b/tests/cloudformation/checks/resource/aws/example_GlueSecurityConfigurationEnabled/PASS.yaml
new file mode 100644
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/example_GlueSecurityConfigurationEnabled/PASS.yaml
@@ -0,0 +1,26 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Resources:
+  CrawlerEnabled:
+    Type: AWS::Glue::Crawler
+    Properties:
+      Name: example
+      Role: arn:aws:iam::123456789012:role/role
+      Targets:
+        S3Targets:
+          - Path: s3-bucket
+      CrawlerSecurityConfiguration: security-conf
+  DevEndpointEnabled:
+    Type: AWS::Glue::DevEndpoint
+    Properties:
+      EndpointName: example
+      RoleArn: arn:aws:iam::123456789012:role/role
+      SecurityConfiguration: security-conf
+  JobEnabled:
+    Type: AWS::Glue::Job
+    Properties:
+      Command:
+        Name: glueetl
+        ScriptLocation: s3://aws-glue-scripts//prod-job
+      Name: example
+      Role: arn:aws:iam::123456789012:role/role
+      SecurityConfiguration: security-conf
diff --git a/tests/cloudformation/checks/resource/aws/test_GlueSecurityConfigurationEnabled.py b/tests/cloudformation/checks/resource/aws/test_GlueSecurityConfigurationEnabled.py
new file mode 100644
--- /dev/null
+++ b/tests/cloudformation/checks/resource/aws/test_GlueSecurityConfigurationEnabled.py
@@ -0,0 +1,44 @@
+import unittest
+from pathlib import Path
+
+from checkov.cloudformation.checks.resource.aws.GlueSecurityConfigurationEnabled import check
+from checkov.cloudformation.runner import Runner
+from checkov.runner_filter import RunnerFilter
+
+
+class TestGlueSecurityConfigurationEnabled(unittest.TestCase):
+    def test_summary(self):
+        # given
+        test_files_dir = Path(__file__).parent / "example_GlueSecurityConfigurationEnabled"
+
+        # when
+        report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id]))
+
+        # then
+        summary = report.get_summary()
+
+        passing_resources = {
+            "AWS::Glue::Crawler.CrawlerEnabled",
+            "AWS::Glue::DevEndpoint.DevEndpointEnabled",
+            "AWS::Glue::Job.JobEnabled",
+        }
+        failing_resources = {
+            "AWS::Glue::Crawler.CrawlerDefault",
+            "AWS::Glue::DevEndpoint.DevEndpointDefault",
+            "AWS::Glue::Job.JobDefault",
+        }
+
+        passed_check_resources = {c.resource for c in report.passed_checks}
+        failed_check_resources = {c.resource for c in report.failed_checks}
+
+        self.assertEqual(summary["passed"], 3)
+        self.assertEqual(summary["failed"], 3)
+        self.assertEqual(summary["skipped"], 0)
+        self.assertEqual(summary["parsing_errors"], 0)
+
+        self.assertEqual(passing_resources, passed_check_resources)
+        self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/terraform/checks/resource/aws/example_GlueSecurityConfigurationEnabled/main.tf b/tests/terraform/checks/resource/aws/example_GlueSecurityConfigurationEnabled/main.tf
new file mode 100644
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/example_GlueSecurityConfigurationEnabled/main.tf
@@ -0,0 +1,49 @@
+# pass
+
+resource "aws_glue_crawler" "enabled" {
+  database_name = "aws_glue_catalog_database.example.name"
+  name          = "example"
+  role          = "aws_iam_role.example.arn"
+
+  security_configuration = "aws_glue_security_configuration.example.name"
+}
+
+resource "aws_glue_dev_endpoint" "enabled" {
+  name     = "example"
+  role_arn = "aws_iam_role.example.arn"
+
+  security_configuration = "aws_glue_security_configuration.example.name"
+}
+
+resource "aws_glue_job" "enabled" {
+  name     = "example"
+  role_arn = "aws_iam_role.example.arn"
+
+  security_configuration = "aws_glue_security_configuration.example.name"
+
+  command {
+    script_location = "s3://aws_s3_bucket.example.bucket/example.py"
+  }
+}
+
+# fail
+
+resource "aws_glue_crawler" "default" {
+  database_name = "aws_glue_catalog_database.example.name"
+  name          = "example"
+  role          = "aws_iam_role.example.arn"
+}
+
+resource "aws_glue_dev_endpoint" "default" {
+  name     = "example"
+  role_arn = "aws_iam_role.example.arn"
+}
+
+resource "aws_glue_job" "default" {
+  name     = "example"
+  role_arn = "aws_iam_role.example.arn"
+
+  command {
+    script_location = "s3://aws_s3_bucket.example.bucket/example.py"
+  }
+}
diff --git a/tests/terraform/checks/resource/aws/test_GlueSecurityConfigurationEnabled.py b/tests/terraform/checks/resource/aws/test_GlueSecurityConfigurationEnabled.py
new file mode 100644
--- /dev/null
+++ b/tests/terraform/checks/resource/aws/test_GlueSecurityConfigurationEnabled.py
@@ -0,0 +1,44 @@
+import unittest
+from pathlib import Path
+
+from checkov.terraform.runner import Runner
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.checks.resource.aws.GlueSecurityConfigurationEnabled import check
+
+
+class TestGlueSecurityConfigurationEnabled(unittest.TestCase):
+    def test(self):
+        # given
+        test_files_dir = Path(__file__).parent / "example_GlueSecurityConfigurationEnabled"
+
+        # when
+        report = Runner().run(root_folder=str(test_files_dir), runner_filter=RunnerFilter(checks=[check.id]))
+
+        # then
+        summary = report.get_summary()
+
+        passing_resources = {
+            "aws_glue_crawler.enabled",
+            "aws_glue_dev_endpoint.enabled",
+            "aws_glue_job.enabled",
+        }
+        failing_resources = {
+            "aws_glue_crawler.default",
+            "aws_glue_dev_endpoint.default",
+            "aws_glue_job.default",
+        }
+
+        passed_check_resources = {c.resource for c in report.passed_checks}
+        failed_check_resources = {c.resource for c in report.failed_checks}
+
+        self.assertEqual(summary["passed"], 3)
+        self.assertEqual(summary["failed"], 3)
+        self.assertEqual(summary["skipped"], 0)
+        self.assertEqual(summary["parsing_errors"], 0)
+
+        self.assertEqual(passing_resources, passed_check_resources)
+        self.assertEqual(failing_resources, failed_check_resources)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/terraform/graph/checks/resources/SGAttachedToResource/expected.yaml b/tests/terraform/graph/checks/resources/SGAttachedToResource/expected.yaml
--- a/tests/terraform/graph/checks/resources/SGAttachedToResource/expected.yaml
+++ b/tests/terraform/graph/checks/resources/SGAttachedToResource/expected.yaml
@@ -21,6 +21,7 @@ pass:
   - "aws_security_group.pass_emr"
   - "aws_security_group.pass_eni"
   - "aws_security_group.pass_es"
+  - "aws_security_group.pass_glue"
   - "aws_security_group.pass_lambda"
   - "aws_security_group.pass_lb"
   - "aws_security_group.pass_mq_broker"
diff --git a/tests/terraform/graph/checks/resources/SGAttachedToResource/main.tf b/tests/terraform/graph/checks/resources/SGAttachedToResource/main.tf
--- a/tests/terraform/graph/checks/resources/SGAttachedToResource/main.tf
+++ b/tests/terraform/graph/checks/resources/SGAttachedToResource/main.tf
@@ -413,6 +413,25 @@ resource "aws_elasticsearch_domain" "pass_es" {
   }
 }
 
+# Glue
+
+resource "aws_security_group" "pass_glue" {
+  ingress {
+    description = "TLS from VPC"
+    from_port   = 443
+    to_port     = 443
+    protocol    = "tcp"
+    cidr_blocks = 0.0.0.0/0
+  }
+}
+
+resource "aws_glue_dev_endpoint" "pass_glue" {
+  name     = "example"
+  role_arn = "aws_iam_role.example.arn"
+
+  security_group_ids = [aws_security_group.pass_glue.id]
+}
+
 # Lambda
 
 resource "aws_security_group" "pass_lambda" {