Repository: bridgecrewio/checkov

Issue:
CKV_AWS_144 false negative after updating to AWS Provider 4.0 
**Describe the issue**
After updating our AWS Provider to ~>4.0, we started getting a failure on `CKV_AWS_144` in our bucket module, despite having a properly configured `aws_s3_bucket_lifecycle_configuration` block.

**Examples**
Sample code:

```hcl
provider "aws" {
  alias  = "aws-primary"
  region = "us-east-1"
}

provider "aws" {
  alias  = "aws-dr"
  region = "us-west-2"
}

resource "aws_s3_bucket" "test_bucket" {
  bucket = var.bucket_name
}

resource "aws_s3_bucket" "test_dr_bucket" {
  provider = aws.aws-dr
  bucket   = "${var.bucket_name}-dr"
}

resource "aws_s3_bucket_versioning" "test_bucket_versioning" {
  bucket = aws_s3_bucket.test_bucket.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_versioning" "test_dr_bucket_versioning" {
  provider = aws.aws-dr
  bucket = aws_s3_bucket.test_dr_bucket.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_iam_role" "dr_replication" {
  name_prefix = "replication"
  description = "Allow S3 to assume the role for replication"

  assume_role_policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "s3ReplicationAssume",
      "Effect": "Allow",
      "Principal": {
        "Service": "s3.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
POLICY
}

resource "aws_iam_policy" "dr_replication" {
  name_prefix = "replication"
  description = "Allows reading for replication."

  policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "s3:GetReplicationConfiguration",
        "s3:ListBucket"
      ],
      "Effect": "Allow",
      "Resource": [
        "${aws_s3_bucket.test_bucket.arn}"
      ]
    },
    {
      "Action": [
        "s3:GetObjectVersion",
        "s3:GetObjectVersionForReplication",
        "s3:GetObjectVersionAcl"
      ],
      "Effect": "Allow",
      "Resource": [
        "${aws_s3_bucket.test_bucket.arn}/*"
      ]
    },
    {
      "Action": [
        "s3:ReplicateObject",
        "s3:ReplicateTags",
        "s3:ObjectOwnerOverrideToBucketOwner"
      ],
      "Effect": "Allow",
      "Resource": "${aws_s3_bucket.test_dr_bucket.arn}/*"
    }
  ]
}
POLICY
}

resource "aws_iam_policy_attachment" "dr_replication" {
  name       = "replication"
  roles      = [aws_iam_role.dr_replication.name]
  policy_arn = aws_iam_policy.dr_replication.arn
}

resource "aws_s3_bucket_replication_configuration" "dr_bucket_replication" {

  # Must have bucket versioning enabled first
  depends_on = [
    aws_s3_bucket_versioning.test_bucket_versioning,
    aws_s3_bucket_versioning.test_dr_bucket_versioning,
  ]

  role   = aws_iam_role.dr_replication.arn
  bucket = aws_s3_bucket.test_bucket.id

  rule {
    id     = "entire_bucket"
    status = "Enabled"

    destination {
      bucket        = aws_s3_bucket.test_dr_bucket.arn
      storage_class = "DEEP_ARCHIVE"
    }
  }
}

```

Expected: `CKV_AWS_144` will pass.

Actual: 
```
Check: CKV_AWS_144: "Ensure that S3 bucket has cross-region replication enabled"
        FAILED for resource: aws_s3_bucket.test_bucket
        File: /dr_test.tf:11-13
        Guide: https://docs.bridgecrew.io/docs/ensure-that-s3-bucket-has-cross-region-replication-enabled

                11 | resource "aws_s3_bucket" "test_bucket" {
                12 |   bucket = var.bucket_name
                13 | }
```

**Desktop (please complete the following information):**
 - OS: MacOS 10.14.6
 - Checkov Version 2.0.1074

**Additional context**
On the surface, this looks like related to  https://github.com/bridgecrewio/checkov/issues/2399 and https://github.com/bridgecrewio/checkov/pull/2724, but to the `CKV_AWS_144` rule.


Discussion:
hey @devbyaccident thanks for reporting this. You are right we forgot to adjust the replication check to also accept the new v4 syntax. I will create a PR today.

Resolving patch:
diff --git a/checkov/terraform/checks/resource/aws/S3BucketReplicationConfiguration.py b/checkov/terraform/checks/resource/aws/S3BucketReplicationConfiguration.py
deleted file mode 100644
--- a/checkov/terraform/checks/resource/aws/S3BucketReplicationConfiguration.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from checkov.common.models.consts import ANY_VALUE
-from checkov.terraform.checks.resource.base_resource_value_check import BaseResourceValueCheck
-from checkov.common.models.enums import CheckCategories
-
-
-class S3BucketReplicationConfiguration(BaseResourceValueCheck):
-    def __init__(self):
-        name = "Ensure that S3 bucket has cross-region replication enabled"
-        id = "CKV_AWS_144"
-        supported_resources = ['aws_s3_bucket']
-        categories = [CheckCategories.GENERAL_SECURITY]
-        super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
-
-    def get_inspected_key(self):
-        return "replication_configuration/[0]/role"
-
-    def get_expected_value(self):
-        return ANY_VALUE
-
-
-check = S3BucketReplicationConfiguration()


Test patch:
diff --git a/tests/terraform/checks/resource/aws/test_S3BucketReplicationConfiguration.py b/tests/terraform/checks/resource/aws/test_S3BucketReplicationConfiguration.py
deleted file mode 100644
--- a/tests/terraform/checks/resource/aws/test_S3BucketReplicationConfiguration.py
+++ /dev/null
@@ -1,85 +0,0 @@
-import unittest
-import hcl2
-
-from checkov.terraform.checks.resource.aws.S3BucketReplicationConfiguration import check
-from checkov.common.models.enums import CheckResult
-
-
-class TestS3BucketReplicationConfiguration(unittest.TestCase):
-
-    def test_failure(self):
-        hcl_res = hcl2.loads("""
-                resource "aws_s3_bucket" "test" {
-                      bucket = "my-tf-test-bucket"
-                      acl    = "private"
-                    
-                      tags = {
-                        Name        = "My bucket"
-                        Environment = "Dev"
-                      }
-                      replication_configuration {                    
-                        rules {
-                          id     = "foobar"
-                          prefix = "foo"
-                          status = "Enabled"
-                    
-                          destination {
-                            bucket        = aws_s3_bucket.destination.arn
-                            storage_class = "STANDARD"
-                          }
-                        }
-                      }
-                }
-        """)
-        resource_conf = hcl_res['resource'][0]['aws_s3_bucket']['test']
-        scan_result = check.scan_resource_conf(conf=resource_conf)
-        self.assertEqual(CheckResult.FAILED, scan_result)
-
-    def test_failure_no_param(self):
-        hcl_res = hcl2.loads("""
-                resource "aws_s3_bucket" "test" {
-                      bucket = "my-tf-test-bucket"
-                      acl    = "private"
-
-                      tags = {
-                        Name        = "My bucket"
-                        Environment = "Dev"
-                      }
-                }
-        """)
-        resource_conf = hcl_res['resource'][0]['aws_s3_bucket']['test']
-        scan_result = check.scan_resource_conf(conf=resource_conf)
-        self.assertEqual(CheckResult.FAILED, scan_result)
-
-    def test_success(self):
-        hcl_res = hcl2.loads("""
-                resource "aws_s3_bucket" "test" {
-                      bucket = "my-tf-test-bucket"
-                      acl    = "private"
-                    
-                      tags = {
-                        Name        = "My bucket"
-                        Environment = "Dev"
-                      }
-                      replication_configuration { 
-                        role = aws_iam_role.replication.arn
-                        rules {
-                          id     = "foobar"
-                          prefix = "foo"
-                          status = "Enabled"
-                    
-                          destination {
-                            bucket        = aws_s3_bucket.destination.arn
-                            storage_class = "STANDARD"
-                          }
-                        }
-                      }
-                }
-        """)
-        resource_conf = hcl_res['resource'][0]['aws_s3_bucket']['test']
-        scan_result = check.scan_resource_conf(conf=resource_conf)
-        self.assertEqual(CheckResult.PASSED, scan_result)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/terraform/graph/checks/resources/S3BucketReplicationConfiguration/expected.yaml b/tests/terraform/graph/checks/resources/S3BucketReplicationConfiguration/expected.yaml
new file mode 100644
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/S3BucketReplicationConfiguration/expected.yaml
@@ -0,0 +1,11 @@
+pass:
+  - "aws_s3_bucket.enabled"
+  - "aws_s3_bucket.enabled_v4"
+  - "aws_s3_bucket.enabled_var"
+  - "aws_s3_bucket.unknown_var"
+  - "aws_s3_bucket.legacy_syntax"
+  - "aws_s3_bucket.legacy_syntax_v4"
+fail:
+  - "aws_s3_bucket.default"
+  - "aws_s3_bucket.disabled"
+  - "aws_s3_bucket.disabled_v4"
diff --git a/tests/terraform/graph/checks/resources/S3BucketReplicationConfiguration/main.tf b/tests/terraform/graph/checks/resources/S3BucketReplicationConfiguration/main.tf
new file mode 100644
--- /dev/null
+++ b/tests/terraform/graph/checks/resources/S3BucketReplicationConfiguration/main.tf
@@ -0,0 +1,245 @@
+# pass
+
+resource "aws_s3_bucket" "enabled" {
+  bucket = "example"
+
+  replication_configuration {
+    role = aws_iam_role.replication.arn
+
+    rules {
+      id     = "example"
+      status = "Enabled"
+
+      filter {
+        tags = {}
+      }
+      destination {
+        bucket        = aws_s3_bucket.destination.arn
+        storage_class = "STANDARD"
+
+        replication_time {
+          status  = "Enabled"
+          minutes = 15
+        }
+
+        metrics {
+          status  = "Enabled"
+          minutes = 15
+        }
+      }
+    }
+  }
+}
+
+resource "aws_s3_bucket" "enabled_var" {
+  bucket = "example"
+
+  replication_configuration {
+    role = aws_iam_role.replication.arn
+
+    rules {
+      id     = "foobar"
+      status = var.replication_enabled
+
+      filter {
+        tags = {}
+      }
+      destination {
+        bucket        = aws_s3_bucket.destination.arn
+        storage_class = "STANDARD"
+
+        replication_time {
+          status  = "Enabled"
+          minutes = 15
+        }
+
+        metrics {
+          status  = "Enabled"
+          minutes = 15
+        }
+      }
+    }
+  }
+}
+
+resource "aws_s3_bucket" "unknown_var" {
+  bucket = "example"
+
+  replication_configuration {
+    role = aws_iam_role.replication.arn
+
+    rules {
+      id     = "foobar"
+      status = var.unknown_var
+
+      filter {
+        tags = {}
+      }
+      destination {
+        bucket        = aws_s3_bucket.destination.arn
+        storage_class = "STANDARD"
+
+        replication_time {
+          status  = "Enabled"
+          minutes = 15
+        }
+
+        metrics {
+          status  = "Enabled"
+          minutes = 15
+        }
+      }
+    }
+  }
+}
+
+variable "unknown_var" {
+  description = "unknown value"
+}
+
+
+variable "replication_enabled" {
+  default = "Enabled"
+}
+
+resource "aws_s3_bucket" "legacy_syntax" {
+  bucket = "example"
+
+  replication_configuration {
+    role = aws_iam_role.replication.arn
+
+    rules {
+      id     = "foobar"
+      status = "${var.unknown_var}"
+
+      filter {
+        tags = {}
+      }
+      destination {
+        bucket        = aws_s3_bucket.destination.arn
+        storage_class = "STANDARD"
+
+        replication_time {
+          status  = "Enabled"
+          minutes = 15
+        }
+
+        metrics {
+          status  = "Enabled"
+          minutes = 15
+        }
+      }
+    }
+  }
+}
+
+# fail
+
+resource "aws_s3_bucket" "default" {
+  bucket = "example"
+}
+
+resource "aws_s3_bucket" "disabled" {
+  bucket = "example"
+
+  replication_configuration {
+    role = aws_iam_role.replication.arn
+
+    rules {
+      id     = "foobar"
+      status = "Disabled"
+
+      filter {
+        tags = {}
+      }
+      destination {
+        bucket        = aws_s3_bucket.destination.arn
+        storage_class = "STANDARD"
+
+        replication_time {
+          status  = "Enabled"
+          minutes = 15
+        }
+
+        metrics {
+          status  = "Enabled"
+          minutes = 15
+        }
+      }
+    }
+  }
+}
+
+# provider version 4
+
+resource "aws_s3_bucket" "enabled_v4" {
+  bucket = "example"
+}
+
+resource "aws_s3_bucket_replication_configuration" "enabled_v4" {
+  role   = aws_iam_role.replication.arn
+  bucket = aws_s3_bucket.enabled_v4.id
+
+  rule {
+    id = "foobar"
+
+    filter {
+      prefix = "foo"
+    }
+
+    status = "Enabled"
+
+    destination {
+      bucket        = aws_s3_bucket.destination.arn
+      storage_class = "STANDARD"
+    }
+  }
+}
+
+resource "aws_s3_bucket" "disabled_v4" {
+  bucket = "example"
+}
+
+resource "aws_s3_bucket_replication_configuration" "disabled_v4" {
+  role   = aws_iam_role.replication.arn
+  bucket = aws_s3_bucket.disabled_v4.id
+
+  rule {
+    id = "foobar"
+
+    filter {
+      prefix = "foo"
+    }
+
+    status = "Disabled"
+
+    destination {
+      bucket        = aws_s3_bucket.destination.arn
+      storage_class = "STANDARD"
+    }
+  }
+}
+
+resource "aws_s3_bucket" "legacy_syntax_v4" {
+  bucket = "example"
+}
+
+resource "aws_s3_bucket_replication_configuration" "legacy_syntax_v4" {
+  role   = aws_iam_role.replication.arn
+  bucket = aws_s3_bucket.legacy_syntax_v4.id
+
+  rule {
+    id = "foobar"
+
+    filter {
+      prefix = "foo"
+    }
+
+    status = "${var.whatever}"
+
+    destination {
+      bucket        = aws_s3_bucket.destination.arn
+      storage_class = "STANDARD"
+    }
+  }
+}
diff --git a/tests/terraform/graph/checks/test_yaml_policies.py b/tests/terraform/graph/checks/test_yaml_policies.py
--- a/tests/terraform/graph/checks/test_yaml_policies.py
+++ b/tests/terraform/graph/checks/test_yaml_policies.py
@@ -239,6 +239,9 @@ def test_AdministratorUserNotAssociatedWithAPIKey(self):
     def test_S3KMSEncryptedByDefault(self):
         self.go("S3KMSEncryptedByDefault")
 
+    def test_S3BucketReplicationConfiguration(self):
+        self.go("S3BucketReplicationConfiguration")
+
     def test_registry_load(self):
         registry = Registry(parser=NXGraphCheckParser(), checks_dir=str(
             Path(__file__).parent.parent.parent.parent.parent / "checkov" / "terraform" / "checks" / "graph_checks"))
diff --git a/tests/terraform/graph/runner/test_graph_builder.py b/tests/terraform/graph/runner/test_graph_builder.py
--- a/tests/terraform/graph/runner/test_graph_builder.py
+++ b/tests/terraform/graph/runner/test_graph_builder.py
@@ -43,7 +43,7 @@ def test_run_persistent_data(self):
         runner.set_external_data(tf_definitions, definitions_context, breadcrumbs)
         report = runner.run(root_folder=resources_path)
         # note that we dont count graph violations in this case
-        self.assertGreaterEqual(len(report.failed_checks), 1)
+        self.assertEqual(len(report.failed_checks), 0)
         self.assertEqual(len(report.passed_checks), 2)
         self.assertEqual(len(report.skipped_checks), 0)
 
diff --git a/tests/terraform/runner/test_plan_runner.py b/tests/terraform/runner/test_plan_runner.py
--- a/tests/terraform/runner/test_plan_runner.py
+++ b/tests/terraform/runner/test_plan_runner.py
@@ -352,7 +352,7 @@ def test_runner_root_dir(self):
         self.assertEqual(report.get_exit_code(soft_fail=False), 1)
         self.assertEqual(report.get_exit_code(soft_fail=True), 0)
 
-        self.assertGreaterEqual(report.get_summary()["failed"], 76)
+        self.assertGreaterEqual(report.get_summary()["failed"], 71)
         self.assertGreaterEqual(report.get_summary()["passed"], 65)
 
         files_scanned = list(set(map(lambda rec: rec.file_path, report.failed_checks)))