diff --git a/checkov/terraform/checks/resource/aws/ELBwListenerNotTLSSSL.py b/checkov/terraform/checks/resource/aws/ELBwListenerNotTLSSSL.py new file mode 100644 index 00000000000..5f31e2a3845 --- /dev/null +++ b/checkov/terraform/checks/resource/aws/ELBwListenerNotTLSSSL.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +from typing import Any + +from checkov.common.models.enums import CheckCategories, CheckResult +from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck + + +class ELBwListenerNotTLSSSL(BaseResourceCheck): + def __init__(self) -> None: + name = "Ensure AWS Elastic Load Balancer listener uses TLS/SSL" + id = "CKV_AWS_376" + supported_resource = ("aws_elb",) + categories = (CheckCategories.NETWORKING,) + super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resource) + + def scan_resource_conf(self, conf: dict[str, list[Any]]) -> CheckResult: + if 'listener' in conf: + for listener in conf.get('listener'): + if 'instance_protocol' in listener: + if listener.get('instance_protocol')[0].lower() in ('http', 'tcp'): + return CheckResult.FAILED + if listener.get('instance_protocol')[0].lower() in ('https', 'ssl') and \ + ('ssl_certificate_id' not in listener or listener.get('ssl_certificate_id') == ""): + return CheckResult.FAILED + + return CheckResult.PASSED + + +check = ELBwListenerNotTLSSSL() diff --git a/checkov/terraform/checks/resource/aws/LBTargetGroup.py b/checkov/terraform/checks/resource/aws/LBTargetGroup.py new file mode 100644 index 00000000000..09f90e2ab64 --- /dev/null +++ b/checkov/terraform/checks/resource/aws/LBTargetGroup.py @@ -0,0 +1,22 @@ +from typing import Any, List + +from checkov.common.models.enums import CheckCategories +from checkov.terraform.checks.resource.base_resource_negative_value_check import BaseResourceNegativeValueCheck + + +class LBTargetGroup(BaseResourceNegativeValueCheck): + def __init__(self) -> None: + name = "Ensure AWS Load Balancer doesn't use HTTP protocol" + id = "CKV_AWS_378" + supported_resources = ('aws_lb_target_group', 'aws_alb_target_group',) + categories = (CheckCategories.NETWORKING,) + super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources) + + def get_inspected_key(self) -> str: + return 'protocol' + + def get_forbidden_values(self) -> List[Any]: + return ["HTTP"] + + +check = LBTargetGroup() diff --git a/checkov/terraform/checks/resource/aws/Route53TransferLock.py b/checkov/terraform/checks/resource/aws/Route53TransferLock.py new file mode 100644 index 00000000000..c8116f6e6f8 --- /dev/null +++ b/checkov/terraform/checks/resource/aws/Route53TransferLock.py @@ -0,0 +1,22 @@ +from typing import Any, List + +from checkov.common.models.enums import CheckCategories +from checkov.terraform.checks.resource.base_resource_negative_value_check import BaseResourceNegativeValueCheck + + +class Route53TransferLock(BaseResourceNegativeValueCheck): + def __init__(self) -> None: + name = "Ensure Route 53 domains have transfer lock protection" + id = "CKV_AWS_377" + supported_resources = ('aws_route53domains_registered_domain',) + categories = (CheckCategories.NETWORKING,) + super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources) + + def get_inspected_key(self) -> str: + return 'transfer_lock' + + def get_forbidden_values(self) -> List[Any]: + return [False] + + +check = Route53TransferLock() diff --git a/checkov/terraform/checks/resource/aws/S3GlobalViewACL.py b/checkov/terraform/checks/resource/aws/S3GlobalViewACL.py new file mode 100644 index 00000000000..36645dd518c --- /dev/null +++ b/checkov/terraform/checks/resource/aws/S3GlobalViewACL.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +from typing import Any + +from checkov.common.models.enums import CheckCategories, CheckResult +from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck + + +class S3GlobalViewACL(BaseResourceCheck): + def __init__(self) -> None: + name = "Ensure AWS S3 bucket does not have global view ACL permissions enabled" + id = "CKV_AWS_375" + supported_resource = ("aws_s3_bucket_acl",) + categories = (CheckCategories.NETWORKING,) + super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resource) + + def scan_resource_conf(self, conf: dict[str, list[Any]]) -> CheckResult: + if 'access_control_policy' in conf: + for policy in conf.get('access_control_policy'): + if 'grant' in policy: + for grant in policy.get('grant'): + if 'permission' in grant and ('FULL_CONTROL' in grant.get('permission') or 'READ_ACP' in grant.get('permission')): + if 'grantee' in grant: + for grantee in grant.get('grantee'): + if 'uri' in grantee and 'http://acs.amazonaws.com/groups/global/AllUsers' in grantee.get('uri'): + return CheckResult.FAILED + + return CheckResult.PASSED + + +check = S3GlobalViewACL() diff --git a/tests/terraform/checks/resource/aws/example_ELBwListenerNotTLSSSL/main.tf b/tests/terraform/checks/resource/aws/example_ELBwListenerNotTLSSSL/main.tf new file mode 100644 index 00000000000..1013b791ff7 --- /dev/null +++ b/tests/terraform/checks/resource/aws/example_ELBwListenerNotTLSSSL/main.tf @@ -0,0 +1,133 @@ +# Fail: 1 bad, 1 good +resource "aws_elb" "fail" { + name = "foobar-terraform-elb" + availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"] + + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } + + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 443 + lb_protocol = "https" + ssl_certificate_id = "foo" + } + + health_check { + healthy_threshold = 2 + unhealthy_threshold = 2 + timeout = 3 + target = "HTTP:8000/" + interval = 30 + } + + instances = [aws_instance.foo.id] + cross_zone_load_balancing = true + idle_timeout = 400 + connection_draining = true + connection_draining_timeout = 400 +} + +# Fail: 1 has cert, 1 doesn't +resource "aws_elb" "fail2" { + name = "foobar-terraform-elb" + availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"] + + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "https" + ssl_certificate_id = "foo" + } + + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 443 + lb_protocol = "https" + ssl_certificate_id = "" + } + + health_check { + healthy_threshold = 2 + unhealthy_threshold = 2 + timeout = 3 + target = "HTTP:8000/" + interval = 30 + } + + instances = [aws_instance.foo.id] + cross_zone_load_balancing = true + idle_timeout = 400 + connection_draining = true + connection_draining_timeout = 400 +} + +# Fail: 1 has cert, 1 doesn't +resource "aws_elb" "fail3" { + name = "foobar-terraform-elb" + availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"] + + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "https" + ssl_certificate_id = "foo" + } + + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 443 + lb_protocol = "https" + } + + health_check { + healthy_threshold = 2 + unhealthy_threshold = 2 + timeout = 3 + target = "HTTP:8000/" + interval = 30 + } + + instances = [aws_instance.foo.id] + cross_zone_load_balancing = true + idle_timeout = 400 + connection_draining = true + connection_draining_timeout = 400 +} + +# Pass: SSL and has cert +resource "aws_elb" "pass" { + name = "foobar-terraform-elb" + availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"] + + listener { + instance_port = 8000 + instance_protocol = "SSL" + lb_port = 80 + lb_protocol = "https" + ssl_certificate_id = "foo" + } + + health_check { + healthy_threshold = 2 + unhealthy_threshold = 2 + timeout = 3 + target = "HTTP:8000/" + interval = 30 + } + + instances = [aws_instance.foo.id] + cross_zone_load_balancing = true + idle_timeout = 400 + connection_draining = true + connection_draining_timeout = 400 +} \ No newline at end of file diff --git a/tests/terraform/checks/resource/aws/example_LBTargetGroup/main.tf b/tests/terraform/checks/resource/aws/example_LBTargetGroup/main.tf new file mode 100644 index 00000000000..e895c15c95f --- /dev/null +++ b/tests/terraform/checks/resource/aws/example_LBTargetGroup/main.tf @@ -0,0 +1,32 @@ +resource "aws_lb_target_group" "fail" { + name = "tf-example-lb-tg" + port = 80 + protocol = "HTTP" + vpc_id = aws_vpc.main.id +} + +resource "aws_lb_target_group" "pass" { + name = "tf-example-lb-alb-tg" + target_type = "alb" + port = 80 + protocol = "TCP" + vpc_id = aws_vpc.main.id +} + +resource "aws_alb_target_group" "fail" { + name = "tf-example-lb-tg" + port = 80 + protocol = "HTTP" + vpc_id = aws_vpc.main.id +} + +resource "aws_alb_target_group" "pass" { + name = "tf-example-lb-nlb-tg" + port = 25 + protocol = "TCP" + vpc_id = aws_vpc.main.id + + target_health_state { + enable_unhealthy_connection_termination = false + } +} \ No newline at end of file diff --git a/tests/terraform/checks/resource/aws/example_Route53TransferLock/main.tf b/tests/terraform/checks/resource/aws/example_Route53TransferLock/main.tf new file mode 100644 index 00000000000..5e39bdb3d61 --- /dev/null +++ b/tests/terraform/checks/resource/aws/example_Route53TransferLock/main.tf @@ -0,0 +1,49 @@ +resource "aws_route53domains_registered_domain" "pass_missing" { + domain_name = "example.com" + + name_server { + name = "ns-195.awsdns-24.com" + } + + name_server { + name = "ns-874.awsdns-45.net" + } + + tags = { + Environment = "test" + } +} + +resource "aws_route53domains_registered_domain" "pass_true" { + domain_name = "example.com" + transfer_lock = true + + name_server { + name = "ns-195.awsdns-24.com" + } + + name_server { + name = "ns-874.awsdns-45.net" + } + + tags = { + Environment = "test" + } +} + +resource "aws_route53domains_registered_domain" "fail" { + domain_name = "example.com" + transfer_lock = false + + name_server { + name = "ns-195.awsdns-24.com" + } + + name_server { + name = "ns-874.awsdns-45.net" + } + + tags = { + Environment = "test" + } +} \ No newline at end of file diff --git a/tests/terraform/checks/resource/aws/example_S3GlobalViewACL/main.tf b/tests/terraform/checks/resource/aws/example_S3GlobalViewACL/main.tf new file mode 100644 index 00000000000..8810e7cf040 --- /dev/null +++ b/tests/terraform/checks/resource/aws/example_S3GlobalViewACL/main.tf @@ -0,0 +1,111 @@ +# fail 2 bad +resource "aws_s3_bucket_acl" "fail" { + depends_on = [aws_s3_bucket_ownership_controls.example] + + bucket = aws_s3_bucket.example.id + access_control_policy { + grant { + grantee { + type = "Group" + uri = "http://acs.amazonaws.com/groups/global/AllUsers" + } + permission = "FULL_CONTROL" + } + + grant { + grantee { + type = "Group" + uri = "http://acs.amazonaws.com/groups/global/AllUsers" + } + permission = "READ_ACP" + } + + owner { + id = data.aws_canonical_user_id.current.id + } + } +} + +# fail 1 good, 1 bad +resource "aws_s3_bucket_acl" "fail2" { + depends_on = [aws_s3_bucket_ownership_controls.example] + + bucket = aws_s3_bucket.example.id + access_control_policy { + grant { + grantee { + type = "Group" + uri = "http://acs.amazonaws.com/groups/global/Other" + } + permission = "FULL_CONTROL" + } + + grant { + grantee { + type = "Group" + uri = "http://acs.amazonaws.com/groups/global/AllUsers" + } + permission = "READ_ACP" + } + + owner { + id = data.aws_canonical_user_id.current.id + } + } +} + +# pass 2 other uris +resource "aws_s3_bucket_acl" "pass" { + depends_on = [aws_s3_bucket_ownership_controls.example] + + bucket = aws_s3_bucket.example.id + access_control_policy { + grant { + grantee { + type = "Group" + uri = "http://acs.amazonaws.com/groups/global/Other" + } + permission = "FULL_CONTROL" + } + + grant { + grantee { + type = "Group" + uri = "http://acs.amazonaws.com/groups/global/Other" + } + permission = "READ_ACP" + } + + owner { + id = data.aws_canonical_user_id.current.id + } + } +} + +# pass 2 other permissions +resource "aws_s3_bucket_acl" "pass2" { + depends_on = [aws_s3_bucket_ownership_controls.example] + + bucket = aws_s3_bucket.example.id + access_control_policy { + grant { + grantee { + type = "Group" + uri = "http://acs.amazonaws.com/groups/global/AllUsers" + } + permission = "READ" + } + + grant { + grantee { + type = "Group" + uri = "http://acs.amazonaws.com/groups/global/AllUsers" + } + permission = "READ" + } + + owner { + id = data.aws_canonical_user_id.current.id + } + } +} \ No newline at end of file diff --git a/tests/terraform/checks/resource/aws/test_ELBwListenerNotTLSSSL.py b/tests/terraform/checks/resource/aws/test_ELBwListenerNotTLSSSL.py new file mode 100644 index 00000000000..5cd02ba3228 --- /dev/null +++ b/tests/terraform/checks/resource/aws/test_ELBwListenerNotTLSSSL.py @@ -0,0 +1,41 @@ +import os +import unittest + +from checkov.runner_filter import RunnerFilter +from checkov.terraform.runner import Runner +from checkov.terraform.checks.resource.aws.ELBwListenerNotTLSSSL import check + + +class TestELBwListenerNotTLSSSL(unittest.TestCase): + + def test(self): + runner = Runner() + current_dir = os.path.dirname(os.path.realpath(__file__)) + + test_files_dir = os.path.join(current_dir, "example_ELBwListenerNotTLSSSL") + report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id])) + summary = report.get_summary() + + passing_resources = { + "aws_elb.pass", + } + failing_resources = { + "aws_elb.fail", + "aws_elb.fail2", + "aws_elb.fail3", + } + + passed_check_resources = set([c.resource for c in report.passed_checks]) + failed_check_resources = set([c.resource for c in report.failed_checks]) + + self.assertEqual(summary["passed"], 1) + 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/test_LBTargetGroup.py b/tests/terraform/checks/resource/aws/test_LBTargetGroup.py new file mode 100644 index 00000000000..fb8ca6dde46 --- /dev/null +++ b/tests/terraform/checks/resource/aws/test_LBTargetGroup.py @@ -0,0 +1,41 @@ +import os +import unittest + +from checkov.runner_filter import RunnerFilter +from checkov.terraform.runner import Runner +from checkov.terraform.checks.resource.aws.LBTargetGroup import check + + +class TestLBTargetGroup(unittest.TestCase): + + def test(self): + runner = Runner() + current_dir = os.path.dirname(os.path.realpath(__file__)) + + test_files_dir = os.path.join(current_dir, "example_LBTargetGroup") + report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id])) + summary = report.get_summary() + + passing_resources = { + "aws_lb_target_group.pass", + "aws_alb_target_group.pass", + } + failing_resources = { + "aws_lb_target_group.fail", + "aws_alb_target_group.fail" + } + + passed_check_resources = set([c.resource for c in report.passed_checks]) + failed_check_resources = set([c.resource for c in report.failed_checks]) + + self.assertEqual(summary["passed"], 2) + self.assertEqual(summary["failed"], 2) + 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/test_Route53TransferLock.py b/tests/terraform/checks/resource/aws/test_Route53TransferLock.py new file mode 100644 index 00000000000..f5bfa1d21cb --- /dev/null +++ b/tests/terraform/checks/resource/aws/test_Route53TransferLock.py @@ -0,0 +1,40 @@ +import os +import unittest + +from checkov.runner_filter import RunnerFilter +from checkov.terraform.runner import Runner +from checkov.terraform.checks.resource.aws.Route53TransferLock import check + + +class TestRoute53TransferLock(unittest.TestCase): + + def test(self): + runner = Runner() + current_dir = os.path.dirname(os.path.realpath(__file__)) + + test_files_dir = os.path.join(current_dir, "example_Route53TransferLock") + report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id])) + summary = report.get_summary() + + passing_resources = { + "aws_route53domains_registered_domain.pass_missing", + "aws_route53domains_registered_domain.pass_true", + } + failing_resources = { + "aws_route53domains_registered_domain.fail", + } + + passed_check_resources = set([c.resource for c in report.passed_checks]) + failed_check_resources = set([c.resource for c in report.failed_checks]) + + self.assertEqual(summary["passed"], 2) + self.assertEqual(summary["failed"], 1) + 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/test_S3GlobalViewACL.py b/tests/terraform/checks/resource/aws/test_S3GlobalViewACL.py new file mode 100644 index 00000000000..789fa947e14 --- /dev/null +++ b/tests/terraform/checks/resource/aws/test_S3GlobalViewACL.py @@ -0,0 +1,41 @@ +import os +import unittest + +from checkov.runner_filter import RunnerFilter +from checkov.terraform.runner import Runner +from checkov.terraform.checks.resource.aws.S3GlobalViewACL import check + + +class TestS3GlobalViewACL(unittest.TestCase): + + def test(self): + runner = Runner() + current_dir = os.path.dirname(os.path.realpath(__file__)) + + test_files_dir = os.path.join(current_dir, "example_S3GlobalViewACL") + report = runner.run(root_folder=test_files_dir, runner_filter=RunnerFilter(checks=[check.id])) + summary = report.get_summary() + + passing_resources = { + "aws_s3_bucket_acl.pass", + "aws_s3_bucket_acl.pass2", + } + failing_resources = { + "aws_s3_bucket_acl.fail", + "aws_s3_bucket_acl.fail2" + } + + passed_check_resources = set([c.resource for c in report.passed_checks]) + failed_check_resources = set([c.resource for c in report.failed_checks]) + + self.assertEqual(summary["passed"], 2) + self.assertEqual(summary["failed"], 2) + 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/runner/test_plan_runner.py b/tests/terraform/runner/test_plan_runner.py index fdfcd858815..292c8c94f2d 100644 --- a/tests/terraform/runner/test_plan_runner.py +++ b/tests/terraform/runner/test_plan_runner.py @@ -276,7 +276,7 @@ def test_plan_runner_with_empty_vpc_connection(self): self.assertEqual(report.get_exit_code({'soft_fail': False, 'soft_fail_checks': [], 'soft_fail_threshold': None, 'hard_fail_checks': [], 'hard_fail_threshold': None}), 1) self.assertEqual(report.get_exit_code({'soft_fail': True, 'soft_fail_checks': [], 'soft_fail_threshold': None, 'hard_fail_checks': [], 'hard_fail_threshold': None}), 0) - self.assertEqual(report.get_summary()["failed"], 106) + self.assertEqual(report.get_summary()["failed"], 107) def test_runner_child_modules(self): current_dir = os.path.dirname(os.path.realpath(__file__))