From b90946036567e8af808c8974975734e8973519de Mon Sep 17 00:00:00 2001 From: Harkirat Bhardwaj Date: Thu, 3 Mar 2022 11:57:05 +1100 Subject: [PATCH] updating s3 bucket policies for v4 aws provider (#1175) --- .../AWS.S3Bucket.LM.MEDIUM.0078.json | 4 +- .../aws/aws_s3_bucket/noS3BucketSseRules.rego | 105 +++++++++-- .../rego/aws/aws_s3_bucket/s3AclGrants.rego | 7 + .../s3BucketAccessLoggingDisabled.rego | 173 ++++++++++++++++- .../s3BucketNoWebsiteIndexDoc.rego | 9 +- .../s3BucketSseRulesWithKmsNull.rego | 114 ++++++++++-- .../rego/aws/aws_s3_bucket/s3Versioning.rego | 176 ++++++++++++++++-- .../aws_s3_bucket/s3VersioningMfaFalse.rego | 11 ++ 8 files changed, 546 insertions(+), 53 deletions(-) diff --git a/pkg/policies/opa/rego/aws/aws_s3_bucket/AWS.S3Bucket.LM.MEDIUM.0078.json b/pkg/policies/opa/rego/aws/aws_s3_bucket/AWS.S3Bucket.LM.MEDIUM.0078.json index b9a9d47cb..cbbd6e110 100644 --- a/pkg/policies/opa/rego/aws/aws_s3_bucket/AWS.S3Bucket.LM.MEDIUM.0078.json +++ b/pkg/policies/opa/rego/aws/aws_s3_bucket/AWS.S3Bucket.LM.MEDIUM.0078.json @@ -4,7 +4,9 @@ "policy_type": "aws", "resource_type": "aws_s3_bucket", "template_args": { - "prefix": "" + "prefix": "", + "defaultValue": "", + "defaultValue1": "" }, "severity": "MEDIUM", "description": "Ensure S3 buckets have access logging enabled.", diff --git a/pkg/policies/opa/rego/aws/aws_s3_bucket/noS3BucketSseRules.rego b/pkg/policies/opa/rego/aws/aws_s3_bucket/noS3BucketSseRules.rego index 5ff099e03..9a001c499 100755 --- a/pkg/policies/opa/rego/aws/aws_s3_bucket/noS3BucketSseRules.rego +++ b/pkg/policies/opa/rego/aws/aws_s3_bucket/noS3BucketSseRules.rego @@ -1,25 +1,61 @@ package accurics -{{.prefix}}noS3BucketSseRules[bucket.id] { +#this will satisfy version 4 provider and previous providers +{{.prefix}}noS3BucketSseRules[retVal] { bucket := input.aws_s3_bucket[_] - checkSSEConfig(input, bucket) -} + object.get(bucket.config, "versioning", "undefined") == ["undefined", null, ""][_] + object.get(input, "aws_s3_bucket_server_side_encryption_configuration", "undefined") == "undefined" + bucketName := bucket.config.bucket + decode_rc := `resource "aws_s3_bucket_server_side_encryption_configuration" "example" { + bucket = $aws_s3_bucket.##name##.bucket$ + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "aws:kms" + } + } + }` + + replacedName := replace(decode_rc, "##name##", bucketName) + + resourceWithoutQuotes := replace(replacedName, "$", " ") -checkSSEConfig(inobj, bucket) { - object.get(bucket.config, "server_side_encryption_configuration", "undefined") == [[], null, "undefined"][_] - object.get(inobj, "aws_s3_bucket_server_side_encryption_configuration", "undefined") == [[], null, "undefined"][_] + traverse = "" + retVal := {"Id": bucket.id, "ReplaceType": "add", "CodeType": "resource", "Traverse": traverse, "Attribute": "", "AttributeDataType": "base64", "Expected": base64.encode(resourceWithoutQuotes), "Actual": null} } -checkSSEConfig(inobj, bucket) { - object.get(bucket.config, "server_side_encryption_configuration", "undefined") == [[], null, "undefined"][_] - object.get(inobj, "aws_s3_bucket_server_side_encryption_configuration", "undefined") != [[], null, "undefined"][_] +#For terrascan when used without tfplan +{{.prefix}}noS3BucketSseRules[retVal] { + bucket := input.aws_s3_bucket[_] + contains(input.aws_s3_bucket_server_side_encryption_configuration[_].config.bucket, "{") + serverSideEncryptionBucketName := [bName | + sse := input.aws_s3_bucket_server_side_encryption_configuration[_] + bName := cleanSSEBucketID(sse.config.bucket) + ] + + not checkBucketEncrypted(serverSideEncryptionBucketName, bucket) + decode_rc := `resource "aws_s3_bucket_server_side_encryption_configuration" "example" { + bucket = $aws_s3_bucket.##name##.bucket$ + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "aws:kms" + } + } + }` - sse := inobj.aws_s3_bucket_server_side_encryption_configuration[_] - cleanID := cleanSSEBucketID(sseConfig.bucket) - bucketid != cleanID + replacedName := replace(decode_rc, "##name##", bucket.config.bucket) + + resourceWithoutQuotes := replace(replacedName, "$", " ") + + traverse = "" + retVal := {"Id": bucket.id, "ReplaceType": "add", "CodeType": "resource", "Traverse": traverse, "Attribute": "", "AttributeDataType": "base64", "Expected": base64.encode(resourceWithoutQuotes), "Actual": null} +} + +checkBucketEncrypted(arg1, arg2) { + arg2.id == arg1[_] } -# remove all id related prefix and suffix characters generated by terrascan cleanSSEBucketID(sseBucketID) = cleanID { v1 := trim_left(sseBucketID, "$") v2 := trim_left(v1, "{") @@ -35,4 +71,45 @@ cleanEnd(sseBucketID_v3) = cleanID { cleanEnd(sseBucketID_v3) = cleanID { endswith(sseBucketID_v3, ".bucket") cleanID = trim_right(sseBucketID_v3, ".bucket") -} \ No newline at end of file +} + +#For tfplan based scanning +{{.prefix}}noS3BucketSseRules[retVal] { + bucket := input.aws_s3_bucket[_] + bucket_name := input.aws_s3_bucket_server_side_encryption_configuration[_].config.bucket + not contains(bucket_name, "{") + serverSideEncryptionBucketName := [bName | + sse := input.aws_s3_bucket_server_side_encryption_configuration[_] + bName := sse.config.bucket + ] + + not checkBucketNameForTfPlan(serverSideEncryptionBucketName, bucket) + decode_rc := `resource "aws_s3_bucket_server_side_encryption_configuration" "example" { + bucket = $aws_s3_bucket.##name##.bucket$ + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "aws:kms" + } + } + }` + + replacedName := replace(decode_rc, "##name##", bucket.config.bucket) + + resourceWithoutQuotes := replace(replacedName, "$", " ") + + traverse = "" + retVal := {"Id": bucket.id, "ReplaceType": "add", "CodeType": "resource", "Traverse": traverse, "Attribute": "", "AttributeDataType": "base64", "Expected": base64.encode(resourceWithoutQuotes), "Actual": null} +} + +checkBucketNameForTfPlan(sse, buck) { + buck.id == sse[_] +} + +checkBucketNameForTfPlan(sse, buck) { + buck.config.id == sse[_] +} + +checkBucketNameForTfPlan(sse, buck) { + buck.config.bucket == sse[_] +} diff --git a/pkg/policies/opa/rego/aws/aws_s3_bucket/s3AclGrants.rego b/pkg/policies/opa/rego/aws/aws_s3_bucket/s3AclGrants.rego index fc83f4a0f..36e3c6226 100755 --- a/pkg/policies/opa/rego/aws/aws_s3_bucket/s3AclGrants.rego +++ b/pkg/policies/opa/rego/aws/aws_s3_bucket/s3AclGrants.rego @@ -5,4 +5,11 @@ package accurics bucket.config.acl == "{{.access}}" traverse = "acl" retVal := { "Id": bucket.id, "ReplaceType": "edit", "CodeType": "attribute", "Traverse": traverse, "Attribute": "acl", "AttributeDataType": "string", "Expected": "private", "Actual": bucket.config.acl } +} + +{{.prefix}}{{.name}}{{.suffix}}[retVal] { + bucket := input.aws_s3_bucket_acl[_] + bucket.config.acl == "{{.access}}" + traverse = "acl" + retVal := { "Id": bucket.id, "ReplaceType": "edit", "CodeType": "attribute", "Traverse": traverse, "Attribute": "acl", "AttributeDataType": "string", "Expected": "private", "Actual": bucket.config.acl } } \ No newline at end of file diff --git a/pkg/policies/opa/rego/aws/aws_s3_bucket/s3BucketAccessLoggingDisabled.rego b/pkg/policies/opa/rego/aws/aws_s3_bucket/s3BucketAccessLoggingDisabled.rego index 0de4ac711..4acfd7ee6 100644 --- a/pkg/policies/opa/rego/aws/aws_s3_bucket/s3BucketAccessLoggingDisabled.rego +++ b/pkg/policies/opa/rego/aws/aws_s3_bucket/s3BucketAccessLoggingDisabled.rego @@ -1,16 +1,169 @@ + package accurics -{{.prefix}}s3BucketAccessLoggingDisabled[s3_bucket.id] { - s3_bucket := input.aws_s3_bucket[_] - object.get(s3_bucket.config, "logging", "undefined") == "undefined" +{{.prefix}}s3BucketAccessLoggingDisabled[retVal] { + cloudTrail := input.aws_cloudtrail[_] + bucket := input.aws_s3_bucket[_] + + contains(cloudTrail.config.s3_bucket_name, "{") + cleanId := cleanSSEBucketID(cloudTrail.config.s3_bucket_name) + matchBucketName(bucket, cleanId) + object.get(bucket.config, "logging", "undefined") == ["undefined", [], null][_] + object.get(input, "aws_s3_bucket_logging", "undefined") == "undefined" + + decode_rc := `resource "aws_s3_bucket_logging" "{{.defaultValue}}" { + bucket = aws_s3_bucket.##name##.id + targetBucket = "{{.defaultValue1}}" + }` + + replaced_rc := replace(decode_rc, "##name##", split(cleanId, ".")[1]) + + retVal := { + "Id": bucket.id, + "ReplaceType": "add", + "CodeType": "resource", + "Traverse": "", + "Attribute": "", + "AttributeDataType": "base64", + "Expected": base64.encode(replaced_rc), + "Actual": null, + } +} + +{{.prefix}}s3BucketAccessLoggingDisabled[retVal] { + cloudTrail := input.aws_cloudtrail[_] + bucket := input.aws_s3_bucket[_] + + not contains(cloudTrail.config.s3_bucket_name, "{") + matchBucketName(bucket, cloudTrail.config.s3_bucket_name) + object.get(bucket.config, "logging", "undefined") == ["undefined", [], null][_] + object.get(input, "aws_s3_bucket_logging", "undefined") == "undefined" + + decode_rc := `resource "aws_s3_bucket_logging" "{{.defaultValue}}" { + bucket = aws_s3_bucket.##name##.id + targetBucket = "{{.defaultValue1}}" + }` + + replaced_rc := replace(decode_rc, "##name##", bucket.name) + + retVal := { + "Id": bucket.id, + "ReplaceType": "add", + "CodeType": "resource", + "Traverse": "", + "Attribute": "", + "AttributeDataType": "base64", + "Expected": base64.encode(replaced_rc), + "Actual": null, + } +} + +{{.prefix}}s3BucketAccessLoggingDisabled[retVal] { + bucket := input.aws_s3_bucket[_] + object.get(bucket.config, "logging", "undefined") == ["undefined", [], null][_] + + cloudTrail := input.aws_cloudtrail[_] + not contains(cloudTrail.config.s3_bucket_name, "{") + matchBucketName(bucket, cloudTrail.config.s3_bucket_name) + + bucket_logged := [bl | bucketlogging := input.aws_s3_bucket_logging[_]; + bl := bucketlogging.config.bucket] + not matchBucketName(bucket_logged, bucket) + + decode_rc := `resource "aws_s3_bucket_logging" "{{.defaultValue}}" { + bucket = aws_s3_bucket.##name##.id + targetBucket = "{{.defaultValue1}}" + }` + + replaced_rc := replace(decode_rc, "##name##", bucket.name) + + retVal := { + "Id": bucket.id, + "ReplaceType": "add", + "CodeType": "resource", + "Traverse": "", + "Attribute": "", + "AttributeDataType": "base64", + "Expected": base64.encode(replaced_rc), + "Actual": null, + } +} + +{{.prefix}}s3BucketAccessLoggingDisabled[retVal] { + bucket := input.aws_s3_bucket[_] + object.get(bucket.config, "logging", "undefined") == ["undefined", [], null][_] + + cloudTrail := input.aws_cloudtrail[_] + contains(cloudTrail.config.s3_bucket_name, "{") + + cleanId := cleanSSEBucketID(cloudTrail.config.s3_bucket_name) + matchBucketName(bucket, cleanId) + + bucket_logged := [bl | bucketlogging := input.aws_s3_bucket_logging[_]; + bl := cleanSSEBucketID(bucketlogging.config.bucket)] + not matchBucketName(bucket_logged, bucket) + + decode_rc := `resource "aws_s3_bucket_logging" "{{.defaultValue}}" { + bucket = aws_s3_bucket.##name##.id + targetBucket = "{{.defaultValue1}}" + }` + + replaced_rc := replace(decode_rc, "##name##", bucket.name) + + retVal := { + "Id": bucket.id, + "ReplaceType": "add", + "CodeType": "resource", + "Traverse": "", + "Attribute": "", + "AttributeDataType": "base64", + "Expected": base64.encode(replaced_rc), + "Actual": null, + } +} + +matchBucketName(arg1, arg2){ + arg1.id == arg2 +} + +matchBucketName(arg1, arg2){ + arg1[_] == arg2 +} + +matchBucketName(arg1, arg2){ + arg1[_] == arg2.id +} + +matchBucketName(arg1, arg2){ + arg1[_] == arg2.config.id +} + +matchBucketName(arg1, arg2){ + arg1[_] == arg2.config.bucket } -{{.prefix}}s3BucketAccessLoggingDisabled[s3_bucket.id] { - s3_bucket := input.aws_s3_bucket[_] - s3_bucket.config.logging == [] +matchBucketName(arg1, arg2){ + arg1.config.id == arg2 } -{{.prefix}}s3BucketAccessLoggingDisabled[s3_bucket.id] { - s3_bucket := input.aws_s3_bucket[_] - s3_bucket.config.logging == null -} \ No newline at end of file +matchBucketName(arg1, arg2){ + arg1.config.bucket == arg2 +} + + +cleanSSEBucketID(sseBucketID) = cleanID { + v1 := trim_left(sseBucketID, "$") + v2 := trim_left(v1, "{") + v3 := trim_right(v2, "}") + cleanID = cleanEnd(v3) +} + +cleanEnd(sseBucketID_v3) = cleanID { + endswith(sseBucketID_v3, ".id") + cleanID = trim_right(sseBucketID_v3, ".id") +} + +cleanEnd(sseBucketID_v3) = cleanID { + endswith(sseBucketID_v3, ".bucket") + cleanID = trim_right(sseBucketID_v3, ".bucket") +} diff --git a/pkg/policies/opa/rego/aws/aws_s3_bucket/s3BucketNoWebsiteIndexDoc.rego b/pkg/policies/opa/rego/aws/aws_s3_bucket/s3BucketNoWebsiteIndexDoc.rego index 7ee714f1e..0b6e19c53 100755 --- a/pkg/policies/opa/rego/aws/aws_s3_bucket/s3BucketNoWebsiteIndexDoc.rego +++ b/pkg/policies/opa/rego/aws/aws_s3_bucket/s3BucketNoWebsiteIndexDoc.rego @@ -5,4 +5,11 @@ package accurics count(bucket.config.website) > 0 traverse = "website" retVal := { "Id": bucket.id, "ReplaceType": "delete", "CodeType": "block", "Traverse": traverse, "Attribute": "website", "AttributeDataType": "block", "Expected": null, "Actual": null } -} \ No newline at end of file +} + +{{.prefix}}s3BucketNoWebsiteIndexDoc[retVal] { + bucket := input.aws_s3_bucket[_] + bucket_website_config := input.aws_s3_bucket_website_configuration[_] + bucket_website_config.config.bucket == bucket.config.bucket + retVal := { "Id": bucket_website_config.id, "ReplaceType": "delete", "CodeType": "resource", "Traverse": "", "Attribute": "", "AttributeDataType": "resource", "Expected": null, "Actual": null } +} diff --git a/pkg/policies/opa/rego/aws/aws_s3_bucket/s3BucketSseRulesWithKmsNull.rego b/pkg/policies/opa/rego/aws/aws_s3_bucket/s3BucketSseRulesWithKmsNull.rego index 8981af762..2616ff47a 100755 --- a/pkg/policies/opa/rego/aws/aws_s3_bucket/s3BucketSseRulesWithKmsNull.rego +++ b/pkg/policies/opa/rego/aws/aws_s3_bucket/s3BucketSseRulesWithKmsNull.rego @@ -2,6 +2,28 @@ package accurics {{.prefix}}s3BucketSseRulesWithKmsNull[retVal] { bucket := input.aws_s3_bucket[_] + object.get(bucket.config, "server_side_encryption_configuration", "undefined") == ["undefined", null, "", []][_] + sse := input.aws_s3_bucket_server_side_encryption_configuration[_] + not contains(sse.config.bucket, "{") + matchBucketWithSse(sse, bucket) + retVal := checkCMKKMSUsed(sse) +} + +{{.prefix}}s3BucketSseRulesWithKmsNull[retVal] { + bucket := input.aws_s3_bucket[_] + object.get(bucket.config, "server_side_encryption_configuration", "undefined") == ["undefined", null, "", []][_] + sse := input.aws_s3_bucket_server_side_encryption_configuration[_] + contains(sse.config.bucket, "{") + cleanId := cleanSSEBucketID(sse.config.bucket) + matchBucketWithSse(cleanId, bucket) + retVal := checkCMKKMSUsed(sse) +} + +#for AWS provider version < 4 +{{.prefix}}s3BucketSseRulesWithKmsNull[retVal] { + bucket := input.aws_s3_bucket[_] + object.get(bucket.config, "server_side_encryption_configuration", "undefined") != "undefined" + object.get(input, "aws_s3_bucket_server_side_encryption_configuration", "undefined") == "undefined" some i, j, k sse := bucket.config.server_side_encryption_configuration[i] sse_rule := sse.rule[j] @@ -15,21 +37,91 @@ package accurics } hasEncryption(sse) { - not check_empty(sse.kms_master_key_id) + sse.sse_algorithm == "AES256" } -hasEncryption(sse) { - check_empty(sse.kms_master_key_id) - sse.sse_algorithm == "AES256" +cleanSSEBucketID(sseBucketID) = cleanID { + v1 := trim_left(sseBucketID, "$") + v2 := trim_left(v1, "{") + v3 := trim_right(v2, "}") + cleanID = cleanEnd(v3) +} + +cleanEnd(sseBucketID_v3) = cleanID { + endswith(sseBucketID_v3, ".id") + cleanID = trim_right(sseBucketID_v3, ".id") } -check_empty(key) { - key == null + +cleanEnd(sseBucketID_v3) = cleanID { + endswith(sseBucketID_v3, ".bucket") + cleanID = trim_right(sseBucketID_v3, ".bucket") } -check_empty(key) { - key == "" + +matchBucketWithSse(sse, bucket) { + sse == bucket.name } -{{.prefix}}s3BucketSseRulesWithKmsNull[bucket.id] { - bucket := input.aws_s3_bucket[_] - object.get(bucket.config, "server_side_encryption_configuration", "undefined") == ["undefined", []][_] +matchBucketWithSse(sse, bucket) { + sse.config.bucket == bucket.config.bucket +} + +matchBucketWithSse(sse, bucket) { + sse.config.bucket == bucket.config.id +} + +matchBucketWithSse(sse, bucket) { + sse.config.bucket == bucket.id +} + +matchBucketWithSse(sse, bucket) { + sse == bucket.id +} + +checkCMKKMSUsed(serverSideEncryption) = retVal { + object.get(serverSideEncryption.config, "rule", "undefined") == ["undefined", [], null][_] + decode_rc := `rule { + apply_server_side_encryption_by_default { + kms_master_key_id = {{.defaultValue}} + sse_algorithm = "aws:kms" + } + } + }` + + traverse := "" + retVal := getRetVal(serverSideEncryption.id, "add", "block", traverse, traverse, "base64", base64.encode(decode_rc), serverSideEncryption.config.rule) +} + +checkCMKKMSUsed(serverSideEncryption) = retVal { + some i + object.get(serverSideEncryption.config.rule[i], "apply_server_side_encryption_by_default", "undefined") == ["undefined", [], null][_] + decode_rc := `apply_server_side_encryption_by_default { + kms_master_key_id = {{.defaultValue}} + sse_algorithm = "aws:kms" + } + }` + + traverse := sprintf("rule[%d]", [i]) + retVal := getRetVal(serverSideEncryption.id, "add", "block", traverse, "rule", "base64", base64.encode(decode_rc), null) +} + +checkCMKKMSUsed(serverSideEncryption) = retVal { + some i, j + rules := serverSideEncryption.config.rule[i] + algoUsed := rules.apply_server_side_encryption_by_default[j].sse_algorithm + algoUsed == "AES256" + traverse := sprintf("rule[%d].apply_server_side_encryption_by_default[%d].sse_algorithm", [i, j]) + retVal := getRetVal(serverSideEncryption.id, "edit", "attribute", traverse, "rule.apply_server_side_encryption_by_default.sse_algorithm", "string", "aws:kms", algoUsed) +} + +getRetVal(id, ReplaceType, CodeType, Traverse, Attribute, AttributeDataType, Expected, Actual) = retVal { + retVal := { + "Id": id, + "ReplaceType": ReplaceType, + "CodeType": CodeType, + "Traverse": Traverse, + "Attribute": Attribute, + "AttributeDataType": AttributeDataType, + "Expected": Expected, + "Actual": Actual, + } } diff --git a/pkg/policies/opa/rego/aws/aws_s3_bucket/s3Versioning.rego b/pkg/policies/opa/rego/aws/aws_s3_bucket/s3Versioning.rego index 47b009c8c..8208f7d66 100755 --- a/pkg/policies/opa/rego/aws/aws_s3_bucket/s3Versioning.rego +++ b/pkg/policies/opa/rego/aws/aws_s3_bucket/s3Versioning.rego @@ -1,31 +1,175 @@ package accurics -{{.prefix}}s3Versioning[bucket.id] { - bucket := input.aws_s3_bucket[_] - checkBucketVersioning(bucket) +#when resource is not declared, this is for every s3 bucket +{{.prefix}}s3Versioning[retVal] { + s3Bucket := input.aws_s3_bucket[_] + object.get(s3Bucket.config, "versioning", "undefined") == ["undefined", null, ""][_] + object.get(input, "aws_s3_bucket_versioning", "undefined") == "undefined" + bucketName := s3Bucket.config.bucket + + decode_rc := `resource "aws_s3_bucket_versioning" "example" { + bucket = "aws_s3_bucket.##name##.bucket" + + versioning_configuration { + status = "Enabled" + } + }` + + replacedName := replace(decode_rc, "##name##", bucketName) + + resourceWithoutQuotes := replace(replacedName, "\"", " ") + + traverse = "" + retVal := getRetVal(s3Bucket.id, "add", "resource", traverse, "", "base64", base64.encode(resourceWithoutQuotes), null) +} + +#to satisfy previous version of aws provider +{{.prefix}}s3Versioning[retVal] { + s3Bucket := input.aws_s3_bucket[_] + some i + versionData := s3Bucket.config.versioning[i] + versionData.enabled == false + + traverse := sprintf("versioning[%d].enabled", [i]) + retVal := getRetVal(s3Bucket.id, "edit", "attribute", traverse, "versioning.enabled", "bool", true, versionData.enabled) +} + +#To satisfy specific s3 buckets which do not have versioning configuration resource for terrascan without tfplan +{{.prefix}}s3Versioning[retVal] { + s3Bucket := input.aws_s3_bucket[_] + object.get(input, "aws_s3_bucket_versioning", "undefined") != "undefined" + contains(input.aws_s3_bucket_versioning[_].config.bucket, "{") + bucketVersioning := [bv | bucketVersion := input.aws_s3_bucket_versioning[_]; + bv := cleanSSEBucketID(bucketVersion.config.bucket)] + not checkBucketName(s3Bucket, bucketVersioning) + + bucketName := s3Bucket.config.bucket + + decode_rc := `resource "aws_s3_bucket_versioning" "example" { + bucket = "aws_s3_bucket.##name##.bucket" + + versioning_configuration { + status = "Enabled" + } + }` + + replacedName := replace(decode_rc, "##name##", bucketName) + + resourceWithoutQuotes := replace(replacedName, "\"", " ") + + traverse = "" + retVal := getRetVal(s3Bucket.id, "add", "resource", traverse, "", "base64", base64.encode(resourceWithoutQuotes), null) +} + +checkBucketName(arg1, arg2){ + arg1.id == arg2[_] +} + +#To satisfy specific s3 buckets which do not have versioning configuration resource with tfplan +{{.prefix}}s3Versioning[retVal] { + s3Bucket := input.aws_s3_bucket[_] + object.get(input, "aws_s3_bucket_versioning", "undefined") != "undefined" + bucket_version := input.aws_s3_bucket_versioning[_].config.bucket + not contains(bucket_version, "{") + bucketVersioning := [bv | bucketVersion := input.aws_s3_bucket_versioning[_]; + bv := bucketVersion.config.bucket] + not matchBucketVersion(s3Bucket, bucketVersioning) + + bucketName := s3Bucket.config.bucket + + decode_rc := `resource "aws_s3_bucket_versioning" "example" { + bucket = "aws_s3_bucket.##name##.bucket" + + versioning_configuration { + status = "Enabled" + } + }` + + replacedName := replace(decode_rc, "##name##", bucketName) + + resourceWithoutQuotes := replace(replacedName, "\"", " ") + + traverse = "" + retVal := getRetVal(s3Bucket.id, "add", "resource", traverse, "", "base64", base64.encode(resourceWithoutQuotes), null) } -checkBucketVersioning(bucket) { - object.get(bucket.config, "versioning", "undefined") != [[], null, "undefined"][_] +#To satisfy specific s3 buckets which have versioning configuration resource with status not enabled (for tfplan) +{{.prefix}}s3Versioning[retVal] { + buck := input.aws_s3_bucket[_] + object.get(buck.config, "versioning", "undefined") == [[], null, "undefined"][_] + object.get(input, "aws_s3_bucket_versioning", "undefined") != "undefined" + bucketVersion := input.aws_s3_bucket_versioning[_] + not contains(bucketVersion, "{") + bucketVersioning := bucketVersion.config.bucket + matchBucketVersion(buck, bucketVersioning) + + some i + lower(bucketVersion.config.versioning_configuration[i].status) != "enabled" + traverse := sprintf("versioning_configuration[%d].status", [i]) - versioning := bucket.config.versioning[_] - not versioning.enabled == true + retVal := getRetVal(bucketVersion.id, "edit", "attribute", traverse, "versioning_configuration.status", "string", "Enabled", bucketVersion.config.versioning_configuration[i].status ) } -checkBucketVersioning(bucket) { - object.get(bucket.config, "versioning", "undefined") == [[], null, "undefined"][_] +#To satisfy specific s3 buckets which have versioning configuration resource with status not enabled (without tfplan) +{{.prefix}}s3Versioning[retVal] { + buck := input.aws_s3_bucket[_] + object.get(buck.config, "versioning", "undefined") == [[], null, "undefined"][_] + object.get(input, "aws_s3_bucket_versioning", "undefined") != "undefined" + bucketVersion := input.aws_s3_bucket_versioning[_] + not contains(bucketVersion, "{") + bucketVersioning := cleanSSEBucketID(bucketVersion.config.bucket) + matchBucketVersion(buck, bucketVersioning) - versioning := input.aws_s3_bucket_versioning[_] + some i + lower(bucketVersion.config.versioning_configuration[i].status) != "enabled" + traverse := sprintf("versioning_configuration[%d].status", [i]) - cleanID := cleanVersioningBucketID(versioning.config.bucket) - bucket.id == cleanID + retVal := getRetVal(bucketVersion.id, "edit", "attribute", traverse, "versioning_configuration.status", "string", "Enabled", bucketVersion.config.versioning_configuration[i].status ) +} + +matchBucketVersion(buckt, bucketVer){ + name := bucketVer[_] + contains(name, buckt.name) +} + +matchBucketVersion(buckt, bucketVer){ + buckt.config.bucket == bucketVer[_] +} + +matchBucketVersion(buckt, bucketVer){ + buckt.config.bucket == bucketVer +} + +matchBucketVersion(buckt, bucketVer){ + buckt.config.id == bucketVer[_] +} + +matchBucketVersion(buckt, bucketVer){ + buckt.config.id == bucketVer +} + +matchBucketVersion(buckt, bucketVer){ + buckt.id == bucketVer[_] +} + +matchBucketVersion(buckt, bucketVer){ + buckt.id == bucketVer +} - versioning_status := lower(versioning.config.versioning_configuration[_].status) - versioning_status != "enabled" +getRetVal(id, ReplaceType, CodeType, Traverse, Attribute, AttributeDataType, Expected, Actual) = retVal { + retVal := { + "Id": id, + "ReplaceType": ReplaceType, + "CodeType": CodeType, + "Traverse": Traverse, + "Attribute": Attribute, + "AttributeDataType": AttributeDataType, + "Expected": Expected, + "Actual": Actual + } } -# remove all id related prefix and suffix characters generated by terrascan -cleanVersioningBucketID(sseBucketID) = cleanID { +cleanSSEBucketID(sseBucketID) = cleanID { v1 := trim_left(sseBucketID, "$") v2 := trim_left(v1, "{") v3 := trim_right(v2, "}") diff --git a/pkg/policies/opa/rego/aws/aws_s3_bucket/s3VersioningMfaFalse.rego b/pkg/policies/opa/rego/aws/aws_s3_bucket/s3VersioningMfaFalse.rego index d2c28b5b5..77452dc33 100755 --- a/pkg/policies/opa/rego/aws/aws_s3_bucket/s3VersioningMfaFalse.rego +++ b/pkg/policies/opa/rego/aws/aws_s3_bucket/s3VersioningMfaFalse.rego @@ -7,4 +7,15 @@ package accurics mfa.mfa_delete == false traverse := sprintf("versioning[%d].mfa_delete", [i]) retVal := { "Id": bucket.id, "ReplaceType": "edit", "CodeType": "attribute", "Traverse": traverse, "Attribute": "versioning.mfa_delete", "AttributeDataType": "bool", "Expected": true, "Actual": mfa.mfa_delete } +} + +{{.prefix}}s3VersioningMfaFalse[retVal] { + bucket := input.aws_s3_bucket[_] + object.get(bucket.config, "versioning", "undefined") == ["undefined", "", null, []][_] + bucket_versioning := input.aws_s3_bucket_versioning[_] + some i + mfa := bucket_versioning.config.versioning_configuration[i] + lower(mfa.mfa_delete) != "enabled" + traverse := sprintf("versioning_configuration[%d].mfa_delete", [i]) + retVal := { "Id": bucket_versioning.id, "ReplaceType": "edit", "CodeType": "attribute", "Traverse": traverse, "Attribute": "versioning_configuration.mfa_delete", "AttributeDataType": "bool", "Expected": true, "Actual": mfa.mfa_delete } } \ No newline at end of file