From 9562b39003f3346687d81a09748495342af0c0df Mon Sep 17 00:00:00 2001 From: Miguel Silva <100352574+cxMiguelSilva@users.noreply.github.com> Date: Mon, 10 Oct 2022 09:36:10 +0100 Subject: [PATCH] feat(bom): bill of materials for aws dynamodb (#5861) * add bom dynamo * update dynamo db BoM * update BoM docs * update --- .../aws_bom/dynamo/metadata.json | 12 ++ .../cloudFormation/aws_bom/dynamo/query.rego | 171 ++++++++++++++++++ .../aws_bom/dynamo/test/negative1.yaml | 7 + .../aws_bom/dynamo/test/positive1.yaml | 40 ++++ .../aws_bom/dynamo/test/positive2.yaml | 40 ++++ .../aws_bom/dynamo/test/positive3.yaml | 40 ++++ .../aws_bom/dynamo/test/positive4.yaml | 16 ++ .../dynamo/test/positive_expected_result.json | 26 +++ .../terraform/aws_bom/dynamo/metadata.json | 11 ++ .../terraform/aws_bom/dynamo/query.rego | 166 +++++++++++++++++ .../aws_bom/dynamo/test/negative1.tf | 20 ++ .../aws_bom/dynamo/test/positive1.tf | 63 +++++++ .../aws_bom/dynamo/test/positive2.tf | 63 +++++++ .../aws_bom/dynamo/test/positive3.tf | 63 +++++++ .../aws_bom/dynamo/test/positive4.tf | 47 +++++ .../dynamo/test/positive_expected_result.json | 26 +++ docs/bom.md | 16 +- 17 files changed, 819 insertions(+), 8 deletions(-) create mode 100644 assets/queries/cloudFormation/aws_bom/dynamo/metadata.json create mode 100644 assets/queries/cloudFormation/aws_bom/dynamo/query.rego create mode 100644 assets/queries/cloudFormation/aws_bom/dynamo/test/negative1.yaml create mode 100644 assets/queries/cloudFormation/aws_bom/dynamo/test/positive1.yaml create mode 100644 assets/queries/cloudFormation/aws_bom/dynamo/test/positive2.yaml create mode 100644 assets/queries/cloudFormation/aws_bom/dynamo/test/positive3.yaml create mode 100644 assets/queries/cloudFormation/aws_bom/dynamo/test/positive4.yaml create mode 100644 assets/queries/cloudFormation/aws_bom/dynamo/test/positive_expected_result.json create mode 100644 assets/queries/terraform/aws_bom/dynamo/metadata.json create mode 100644 assets/queries/terraform/aws_bom/dynamo/query.rego create mode 100644 assets/queries/terraform/aws_bom/dynamo/test/negative1.tf create mode 100644 assets/queries/terraform/aws_bom/dynamo/test/positive1.tf create mode 100644 assets/queries/terraform/aws_bom/dynamo/test/positive2.tf create mode 100644 assets/queries/terraform/aws_bom/dynamo/test/positive3.tf create mode 100644 assets/queries/terraform/aws_bom/dynamo/test/positive4.tf create mode 100644 assets/queries/terraform/aws_bom/dynamo/test/positive_expected_result.json diff --git a/assets/queries/cloudFormation/aws_bom/dynamo/metadata.json b/assets/queries/cloudFormation/aws_bom/dynamo/metadata.json new file mode 100644 index 00000000000..f913cfabeaa --- /dev/null +++ b/assets/queries/cloudFormation/aws_bom/dynamo/metadata.json @@ -0,0 +1,12 @@ +{ + "id": "4e67c0ae-38a0-47f4-a50c-f0c9b75826df", + "queryName": "BOM - AWS DynamoDB", + "severity": "TRACE", + "category": "Bill Of Materials", + "descriptionText": "A list of DynamoDB resources found. Amazon DynamoDB is a fully managed, serverless, key-value NoSQL database designed to run high-performance applications at any scale.", + "descriptionUrl": "https://kics.io", + "platform": "CloudFormation", + "descriptionID": "b0d40495", + "cloudProvider": "aws" + } + \ No newline at end of file diff --git a/assets/queries/cloudFormation/aws_bom/dynamo/query.rego b/assets/queries/cloudFormation/aws_bom/dynamo/query.rego new file mode 100644 index 00000000000..de5bcea2534 --- /dev/null +++ b/assets/queries/cloudFormation/aws_bom/dynamo/query.rego @@ -0,0 +1,171 @@ +package Cx + +import data.generic.cloudformation as cf_lib +import data.generic.common as common_lib + +CxPolicy[result] { + document := input.document + resource := document[i].Resources[name] + resource.Type == "AWS::DynamoDB::Table" + + info := get_accessibility(resource) + + bom_output = { + "resource_type": "AWS::DynamoDB::Table", + "resource_name": cf_lib.get_resource_name(resource, name), + "resource_accessibility": lower(info.accessibility), + "resource_encryption": get_encryption(resource), + "resource_vendor": "AWS", + "resource_category": "Storage", + } + + final_bom_output := common_lib.get_bom_output(bom_output, info.policy) + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("Resources.%s", [name]), + "issueType": "BillOfMaterials", + "keyExpectedValue": "", + "keyActualValue": "", + "searchLine": common_lib.build_search_line(["Resources", name], []), + "value": json.marshal(final_bom_output), + } +} + +get_accessibility(resource) = info{ + info:= check_vpc_endpoint(resource) +} else = info { + info := {"accessibility":"private", "policy": ""} +} + +check_vpc_endpoint(resource) = info{ + values := [x | + vpc_endpoint := input.document[_].Resources[_] + vpc_endpoint.Type == "AWS::EC2::VPCEndpoint" + policy_doc := vpc_endpoint.Properties.PolicyDocument + x := policy_accessibility(policy_doc, resource.Properties.TableName)] + + info := get_info(values) +} + +policy_accessibility(policy, table_name) = info { + st := common_lib.get_statement(policy) + statement := st[_] + + common_lib.is_allow_effect(statement) + common_lib.any_principal(statement.Principal) + check_actions(statement.Action) + + resources_arn := get_resource_arn(statement.Resource) + has_all_or_dynamob_arn(resources_arn, table_name) + + info := {"accessibility":"public", "policy": policy} +} else = info { + common_lib.get_statement(policy) + info := {"accessibility":"private", "policy": policy} +} else = info { + info := {"accessibility":"hasPolicy", "policy": policy} +} + +get_resource_arn(resources) = val { + is_array(resources) + val := resources[_] +} else = val { + val := resources +} + +has_all_or_dynamob_arn(arn, table_name){ + arn == "*" +} else { + startswith(arn, "arn:aws:dynamodb:") + suffix := concat( "", [":table/", table_name]) + endswith(arn, suffix) +} + +get_encryption(resource) = encryption{ + sse := resource.Properties.SSESpecification + sse.SSEEnabled == true + encryption := "encrypted" +} else = encryption{ + encryption := "unencrypted" +} + +dynamo_actions := { + "dynamodb:DescribeContributorInsights", + "dynamodb:RestoreTableToPointInTime", + "dynamodb:UpdateGlobalTable", + "dynamodb:DeleteTable", + "dynamodb:UpdateTableReplicaAutoScaling", + "dynamodb:DescribeTable", + "dynamodb:PartiQLInsert", + "dynamodb:GetItem", + "dynamodb:DescribeContinuousBackups", + "dynamodb:DescribeExport", + "dynamodb:ListImports", + "dynamodb:EnableKinesisStreamingDestination", + "dynamodb:BatchGetItem", + "dynamodb:DisableKinesisStreamingDestination", + "dynamodb:UpdateTimeToLive", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:PartiQLUpdate", + "dynamodb:Scan", + "dynamodb:StartAwsBackupJob", + "dynamodb:UpdateItem", + "dynamodb:UpdateGlobalTableSettings", + "dynamodb:CreateTable", + "dynamodb:RestoreTableFromAwsBackup", + "dynamodb:GetShardIterator", + "dynamodb:DescribeReservedCapacity", + "dynamodb:ExportTableToPointInTime", + "dynamodb:DescribeBackup", + "dynamodb:UpdateTable", + "dynamodb:GetRecords", + "dynamodb:DescribeTableReplicaAutoScaling", + "dynamodb:DescribeImport", + "dynamodb:ListTables", + "dynamodb:DeleteItem", + "dynamodb:PurchaseReservedCapacityOfferings", + "dynamodb:CreateTableReplica", + "dynamodb:ListTagsOfResource", + "dynamodb:UpdateContributorInsights", + "dynamodb:CreateBackup", + "dynamodb:UpdateContinuousBackups", + "dynamodb:DescribeReservedCapacityOfferings", + "dynamodb:TagResource", + "dynamodb:PartiQLSelect", + "dynamodb:CreateGlobalTable", + "dynamodb:DescribeKinesisStreamingDestination", + "dynamodb:DescribeLimits", + "dynamodb:ImportTable", + "dynamodb:ListExports", + "dynamodb:UntagResource", + "dynamodb:ConditionCheckItem", + "dynamodb:ListBackups", + "dynamodb:Query", + "dynamodb:DescribeStream", + "dynamodb:DeleteTableReplica", + "dynamodb:DescribeTimeToLive", + "dynamodb:ListStreams", + "dynamodb:ListContributorInsights", + "dynamodb:DescribeGlobalTableSettings", + "dynamodb:ListGlobalTables", + "dynamodb:DescribeGlobalTable", + "dynamodb:RestoreTableFromBackup", + "dynamodb:DeleteBackup", + "dynamodb:PartiQLDelete", + "dynamodb:*" +} + +check_actions(actions) { + common_lib.equalsOrInArray(actions, dynamo_actions[_]) +} else { + common_lib.equalsOrInArray(actions, "*") +} + +get_info(info_arr)= info{ + val := [ x | info_arr[x].accessibility == "public" ] + info := info_arr[val[0]] +} else = info{ + info := info_arr[0] +} diff --git a/assets/queries/cloudFormation/aws_bom/dynamo/test/negative1.yaml b/assets/queries/cloudFormation/aws_bom/dynamo/test/negative1.yaml new file mode 100644 index 00000000000..5759552661c --- /dev/null +++ b/assets/queries/cloudFormation/aws_bom/dynamo/test/negative1.yaml @@ -0,0 +1,7 @@ +AWSTemplateFormatVersion: '2010-09-09' +Resources: + myDistribution: + Type: AWS::CloudFront::Distribution + Properties: + DistributionConfig: + Enabled: true diff --git a/assets/queries/cloudFormation/aws_bom/dynamo/test/positive1.yaml b/assets/queries/cloudFormation/aws_bom/dynamo/test/positive1.yaml new file mode 100644 index 00000000000..768e352eaa1 --- /dev/null +++ b/assets/queries/cloudFormation/aws_bom/dynamo/test/positive1.yaml @@ -0,0 +1,40 @@ +AWSTemplateFormatVersion: '2010-09-09' +Resources: + DynamoDBEndpoint: + Type: "AWS::EC2::VPCEndpoint" + Properties: + RouteTableIds: + - !Ref PublicRouteTable + - !Ref Private0RouteTable + - !Ref Private1RouteTable + - !Ref Private2RouteTable + ServiceName: + !Sub "com.amazonaws.${AWS::Region}.dynamodb" + VpcId: !Ref VPC + PolicyDocument: { + "Id": "Policy", + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Statement", + "Action": "dynamodb:*", + "Effect": "Allow", + "Resource": "arn:aws:dynamodb:ap-southeast-2:123412341234:table/test", + "Principal": "*" + } + ] + } + DynamoDBOnDemandTable2: + Type: "AWS::DynamoDB::Table" + Properties: + TableName: test + AttributeDefinitions: + - AttributeName: pk + AttributeType: S + KeySchema: + - AttributeName: pk + KeyType: HASH + BillingMode: PAY_PER_REQUEST + SSESpecification: + SSEEnabled: false + SSEType: "KMS" diff --git a/assets/queries/cloudFormation/aws_bom/dynamo/test/positive2.yaml b/assets/queries/cloudFormation/aws_bom/dynamo/test/positive2.yaml new file mode 100644 index 00000000000..7a74e7bb5c6 --- /dev/null +++ b/assets/queries/cloudFormation/aws_bom/dynamo/test/positive2.yaml @@ -0,0 +1,40 @@ +AWSTemplateFormatVersion: '2010-09-09' +Resources: + DynamoDBEndpoint: + Type: "AWS::EC2::VPCEndpoint" + Properties: + RouteTableIds: + - !Ref PublicRouteTable + - !Ref Private0RouteTable + - !Ref Private1RouteTable + - !Ref Private2RouteTable + ServiceName: + !Sub "com.amazonaws.${AWS::Region}.dynamodb" + VpcId: !Ref VPC + PolicyDocument: { + "Id": "Policy", + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Statement", + "Action": "dynamodb:*", + "Effect": "Allow", + "Resource": "*", + "Principal": "*" + } + ] + } + DynamoDBOnDemandTable2: + Type: "AWS::DynamoDB::Table" + Properties: + TableName: test2 + AttributeDefinitions: + - AttributeName: pk + AttributeType: S + KeySchema: + - AttributeName: pk + KeyType: HASH + BillingMode: PAY_PER_REQUEST + SSESpecification: + SSEEnabled: false + SSEType: "KMS" diff --git a/assets/queries/cloudFormation/aws_bom/dynamo/test/positive3.yaml b/assets/queries/cloudFormation/aws_bom/dynamo/test/positive3.yaml new file mode 100644 index 00000000000..6ece128ab9d --- /dev/null +++ b/assets/queries/cloudFormation/aws_bom/dynamo/test/positive3.yaml @@ -0,0 +1,40 @@ +AWSTemplateFormatVersion: '2010-09-09' +Resources: + DynamoDBEndpoint: + Type: "AWS::EC2::VPCEndpoint" + Properties: + RouteTableIds: + - !Ref PublicRouteTable + - !Ref Private0RouteTable + - !Ref Private1RouteTable + - !Ref Private2RouteTable + ServiceName: + !Sub "com.amazonaws.${AWS::Region}.dynamodb" + VpcId: !Ref VPC + PolicyDocument: { + "Id": "Policy", + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Statement", + "Action": "dynamodb:*", + "Effect": "Allow", + "Resource": "arn:aws:dynamodb:ap-southeast-2:123412341234:table/other", + "Principal": "*" + } + ] + } + DynamoDBOnDemandTable2: + Type: "AWS::DynamoDB::Table" + Properties: + TableName: test3 + AttributeDefinitions: + - AttributeName: pk + AttributeType: S + KeySchema: + - AttributeName: pk + KeyType: HASH + BillingMode: PAY_PER_REQUEST + SSESpecification: + SSEEnabled: false + SSEType: "KMS" diff --git a/assets/queries/cloudFormation/aws_bom/dynamo/test/positive4.yaml b/assets/queries/cloudFormation/aws_bom/dynamo/test/positive4.yaml new file mode 100644 index 00000000000..da5ef7c27b1 --- /dev/null +++ b/assets/queries/cloudFormation/aws_bom/dynamo/test/positive4.yaml @@ -0,0 +1,16 @@ +AWSTemplateFormatVersion: '2010-09-09' +Resources: + DynamoDBOnDemandTable2: + Type: "AWS::DynamoDB::Table" + Properties: + TableName: test4 + AttributeDefinitions: + - AttributeName: pk + AttributeType: S + KeySchema: + - AttributeName: pk + KeyType: HASH + BillingMode: PAY_PER_REQUEST + SSESpecification: + SSEEnabled: false + SSEType: "KMS" diff --git a/assets/queries/cloudFormation/aws_bom/dynamo/test/positive_expected_result.json b/assets/queries/cloudFormation/aws_bom/dynamo/test/positive_expected_result.json new file mode 100644 index 00000000000..e2666a30034 --- /dev/null +++ b/assets/queries/cloudFormation/aws_bom/dynamo/test/positive_expected_result.json @@ -0,0 +1,26 @@ +[ + { + "queryName": "BOM - AWS DynamoDB", + "severity": "TRACE", + "line": 27, + "fileName": "positive1.yaml" + }, + { + "queryName": "BOM - AWS DynamoDB", + "severity": "TRACE", + "line": 27, + "fileName": "positive2.yaml" + }, + { + "queryName": "BOM - AWS DynamoDB", + "severity": "TRACE", + "line": 27, + "fileName": "positive3.yaml" + }, + { + "queryName": "BOM - AWS DynamoDB", + "severity": "TRACE", + "line": 3, + "fileName": "positive4.yaml" + } +] diff --git a/assets/queries/terraform/aws_bom/dynamo/metadata.json b/assets/queries/terraform/aws_bom/dynamo/metadata.json new file mode 100644 index 00000000000..161ad05f5a8 --- /dev/null +++ b/assets/queries/terraform/aws_bom/dynamo/metadata.json @@ -0,0 +1,11 @@ +{ + "id": "23edf35f-7c22-4ff9-87e6-0ca74261cfbf", + "queryName": "BOM - AWS DynamoDB", + "severity": "TRACE", + "category": "Bill Of Materials", + "descriptionText": "A list of DynamoDB resources found. Amazon DynamoDB is a fully managed, serverless, key-value NoSQL database designed to run high-performance applications at any scale.", + "descriptionUrl": "https://kics.io", + "platform": "Terraform", + "descriptionID": "c9007e7c", + "cloudProvider": "aws" +} diff --git a/assets/queries/terraform/aws_bom/dynamo/query.rego b/assets/queries/terraform/aws_bom/dynamo/query.rego new file mode 100644 index 00000000000..b2f5f4666b0 --- /dev/null +++ b/assets/queries/terraform/aws_bom/dynamo/query.rego @@ -0,0 +1,166 @@ +package Cx + +import data.generic.common as common_lib +import data.generic.terraform as tf_lib + +CxPolicy[result] { + resource := input.document[i].resource.aws_dynamodb_table[name] + + info := get_accessibility(resource, name) + + bom_output = { + "resource_type": "aws_dynamodb_table", + "resource_name": tf_lib.get_specific_resource_name(resource, "aws_dynamodb_table", name), + "resource_accessibility": info.accessibility, + "resource_encryption": get_encryption(resource), + "resource_vendor": "AWS", + "resource_category": "Storage", + } + + final_bom_output = common_lib.get_bom_output(bom_output, info.policy) + + result := { + "documentId": input.document[i].id, + "searchKey": sprintf("aws_dynamodb_table[%s]", [name]), + "issueType": "BillOfMaterials", + "keyExpectedValue": "", + "keyActualValue": "", + "searchLine": common_lib.build_search_line(["resource", "aws_dynamodb_table", name], []), + "value": json.marshal(final_bom_output), + } +} + +get_accessibility(resource, name) = info{ + values := [x | + vpc_endpoint_policy := input.document[_].resource.aws_vpc_endpoint_policy[_] + policy := common_lib.json_unmarshal(vpc_endpoint_policy.policy) + x := policy_accessibility(policy, resource.name)] + info := get_info(values) +} else = info { + info := {"accessibility":"private", "policy": ""} +} + +policy_accessibility(policy, table_name) = info { + st := common_lib.get_statement(policy) + statement := st[_] + + common_lib.is_allow_effect(statement) + common_lib.any_principal(statement.Principal) + check_actions(statement.Action) + + resources_arn := get_resource_arn(statement.Resource) + has_all_or_dynamob_arn(resources_arn, table_name) + + info := {"accessibility":"public", "policy": policy} +} else = info { + common_lib.get_statement(policy) + info := {"accessibility":"private", "policy": policy} +} else = info { + info := {"accessibility":"hasPolicy", "policy": policy} +} + +has_all_or_dynamob_arn(arn, table_name){ + arn == "*" +} else { + startswith(arn, "arn:aws:dynamodb:") + suffix := concat( "", [":table/", table_name]) + endswith(arn, suffix) +} + +get_resource_arn(resources) = val { + is_array(resources) + val := resources[_] +} else = val { + val := resources +} + +get_encryption(resource) = encryption{ + sse := resource.server_side_encryption + sse.enabled == true + encryption := "encrypted" +} else = encryption{ + encryption := "unencrypted" +} + +dynamo_actions := { + "dynamodb:DescribeContributorInsights", + "dynamodb:RestoreTableToPointInTime", + "dynamodb:UpdateGlobalTable", + "dynamodb:DeleteTable", + "dynamodb:UpdateTableReplicaAutoScaling", + "dynamodb:DescribeTable", + "dynamodb:PartiQLInsert", + "dynamodb:GetItem", + "dynamodb:DescribeContinuousBackups", + "dynamodb:DescribeExport", + "dynamodb:ListImports", + "dynamodb:EnableKinesisStreamingDestination", + "dynamodb:BatchGetItem", + "dynamodb:DisableKinesisStreamingDestination", + "dynamodb:UpdateTimeToLive", + "dynamodb:BatchWriteItem", + "dynamodb:PutItem", + "dynamodb:PartiQLUpdate", + "dynamodb:Scan", + "dynamodb:StartAwsBackupJob", + "dynamodb:UpdateItem", + "dynamodb:UpdateGlobalTableSettings", + "dynamodb:CreateTable", + "dynamodb:RestoreTableFromAwsBackup", + "dynamodb:GetShardIterator", + "dynamodb:DescribeReservedCapacity", + "dynamodb:ExportTableToPointInTime", + "dynamodb:DescribeBackup", + "dynamodb:UpdateTable", + "dynamodb:GetRecords", + "dynamodb:DescribeTableReplicaAutoScaling", + "dynamodb:DescribeImport", + "dynamodb:ListTables", + "dynamodb:DeleteItem", + "dynamodb:PurchaseReservedCapacityOfferings", + "dynamodb:CreateTableReplica", + "dynamodb:ListTagsOfResource", + "dynamodb:UpdateContributorInsights", + "dynamodb:CreateBackup", + "dynamodb:UpdateContinuousBackups", + "dynamodb:DescribeReservedCapacityOfferings", + "dynamodb:TagResource", + "dynamodb:PartiQLSelect", + "dynamodb:CreateGlobalTable", + "dynamodb:DescribeKinesisStreamingDestination", + "dynamodb:DescribeLimits", + "dynamodb:ImportTable", + "dynamodb:ListExports", + "dynamodb:UntagResource", + "dynamodb:ConditionCheckItem", + "dynamodb:ListBackups", + "dynamodb:Query", + "dynamodb:DescribeStream", + "dynamodb:DeleteTableReplica", + "dynamodb:DescribeTimeToLive", + "dynamodb:ListStreams", + "dynamodb:ListContributorInsights", + "dynamodb:DescribeGlobalTableSettings", + "dynamodb:ListGlobalTables", + "dynamodb:DescribeGlobalTable", + "dynamodb:RestoreTableFromBackup", + "dynamodb:DeleteBackup", + "dynamodb:PartiQLDelete", + "dynamodb:*" +} + +check_actions(actions) { + common_lib.equalsOrInArray(actions, dynamo_actions[_]) +} else { + common_lib.equalsOrInArray(actions, "*") +} + +get_info(info_arr)= info{ + val := [ x | info_arr[x].accessibility == "public" ] + info := info_arr[val[0]] +} else = info{ + info := info_arr[0] +} + + + diff --git a/assets/queries/terraform/aws_bom/dynamo/test/negative1.tf b/assets/queries/terraform/aws_bom/dynamo/test/negative1.tf new file mode 100644 index 00000000000..bdba45d6587 --- /dev/null +++ b/assets/queries/terraform/aws_bom/dynamo/test/negative1.tf @@ -0,0 +1,20 @@ +module "s3_bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + version = "3.7.0" + + bucket = "my-s3-bucket" + acl = "private" + + versioning = { + enabled = true + } + + server_side_encryption_configuration { + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = aws_kms_key.mykey.arn + sse_algorithm = "aws:kms" + } + } + } +} diff --git a/assets/queries/terraform/aws_bom/dynamo/test/positive1.tf b/assets/queries/terraform/aws_bom/dynamo/test/positive1.tf new file mode 100644 index 00000000000..97e2f36abcf --- /dev/null +++ b/assets/queries/terraform/aws_bom/dynamo/test/positive1.tf @@ -0,0 +1,63 @@ +resource "aws_vpc_endpoint_policy" "example" { + vpc_endpoint_id = aws_vpc_endpoint.example.id + policy = jsonencode({ + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "AllowAll", + "Effect" : "Allow", + "Principal" : { + "AWS" : "*" + }, + "Action" : [ + "dynamodb:*" + ], + "Resource" : "*" + } + ] + }) +} + +resource "aws_dynamodb_table" "basic-dynamodb-table" { + name = "GameScores" + billing_mode = "PROVISIONED" + read_capacity = 20 + write_capacity = 20 + hash_key = "UserId" + range_key = "GameTitle" + + attribute { + name = "UserId" + type = "S" + } + + attribute { + name = "GameTitle" + type = "S" + } + + attribute { + name = "TopScore" + type = "N" + } + + ttl { + attribute_name = "TimeToExist" + enabled = false + } + + global_secondary_index { + name = "GameTitleIndex" + hash_key = "GameTitle" + range_key = "TopScore" + write_capacity = 10 + read_capacity = 10 + projection_type = "INCLUDE" + non_key_attributes = ["UserId"] + } + + tags = { + Name = "dynamodb-table-1" + Environment = "production" + } +} diff --git a/assets/queries/terraform/aws_bom/dynamo/test/positive2.tf b/assets/queries/terraform/aws_bom/dynamo/test/positive2.tf new file mode 100644 index 00000000000..22b274bdaf4 --- /dev/null +++ b/assets/queries/terraform/aws_bom/dynamo/test/positive2.tf @@ -0,0 +1,63 @@ +resource "aws_vpc_endpoint_policy" "example2" { + vpc_endpoint_id = aws_vpc_endpoint.example2.id + policy = jsonencode({ + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "AllowAll", + "Effect" : "Allow", + "Principal" : { + "AWS" : "*" + }, + "Action" : [ + "*" + ], + "Resource" : "arn:aws:dynamodb:ap-southeast-2:123412341234:table/GameScores2", + } + ] + }) +} + +resource "aws_dynamodb_table" "example2-table" { + name = "GameScores2" + billing_mode = "PROVISIONED" + read_capacity = 20 + write_capacity = 20 + hash_key = "UserId" + range_key = "GameTitle" + + attribute { + name = "UserId" + type = "S" + } + + attribute { + name = "GameTitle" + type = "S" + } + + attribute { + name = "TopScore" + type = "N" + } + + ttl { + attribute_name = "TimeToExist" + enabled = false + } + + global_secondary_index { + name = "GameTitleIndex" + hash_key = "GameTitle" + range_key = "TopScore" + write_capacity = 10 + read_capacity = 10 + projection_type = "INCLUDE" + non_key_attributes = ["UserId"] + } + + tags = { + Name = "dynamodb-table-1" + Environment = "production" + } +} diff --git a/assets/queries/terraform/aws_bom/dynamo/test/positive3.tf b/assets/queries/terraform/aws_bom/dynamo/test/positive3.tf new file mode 100644 index 00000000000..2db0bab3855 --- /dev/null +++ b/assets/queries/terraform/aws_bom/dynamo/test/positive3.tf @@ -0,0 +1,63 @@ +resource "aws_vpc_endpoint_policy" "example3" { + vpc_endpoint_id = aws_vpc_endpoint.example3.id + policy = jsonencode({ + "Version" : "2012-10-17", + "Statement" : [ + { + "Sid" : "AllowAll", + "Effect" : "Allow", + "Principal" : { + "AWS" : "some" + }, + "Action" : [ + "*" + ], + "Resource" : "*" + } + ] + }) +} + +resource "aws_dynamodb_table" "example3-table" { + name = "GameScores3" + billing_mode = "PROVISIONED" + read_capacity = 20 + write_capacity = 20 + hash_key = "UserId" + range_key = "GameTitle" + + attribute { + name = "UserId" + type = "S" + } + + attribute { + name = "GameTitle" + type = "S" + } + + attribute { + name = "TopScore" + type = "N" + } + + ttl { + attribute_name = "TimeToExist" + enabled = false + } + + global_secondary_index { + name = "GameTitleIndex" + hash_key = "GameTitle" + range_key = "TopScore" + write_capacity = 10 + read_capacity = 10 + projection_type = "INCLUDE" + non_key_attributes = ["UserId"] + } + + tags = { + Name = "dynamodb-table-1" + Environment = "production" + } +} diff --git a/assets/queries/terraform/aws_bom/dynamo/test/positive4.tf b/assets/queries/terraform/aws_bom/dynamo/test/positive4.tf new file mode 100644 index 00000000000..e8bb1a7eb0c --- /dev/null +++ b/assets/queries/terraform/aws_bom/dynamo/test/positive4.tf @@ -0,0 +1,47 @@ +resource "aws_dynamodb_table" "example3-table" { + name = "GameScores3" + billing_mode = "PROVISIONED" + read_capacity = 20 + write_capacity = 20 + hash_key = "UserId" + range_key = "GameTitle" + + attribute { + name = "UserId" + type = "S" + } + + attribute { + name = "GameTitle" + type = "S" + } + + attribute { + name = "TopScore" + type = "N" + } + + ttl { + attribute_name = "TimeToExist" + enabled = false + } + + global_secondary_index { + name = "GameTitleIndex" + hash_key = "GameTitle" + range_key = "TopScore" + write_capacity = 10 + read_capacity = 10 + projection_type = "INCLUDE" + non_key_attributes = ["UserId"] + } + + sse { + enabled = true + } + + tags = { + Name = "dynamodb-table-1" + Environment = "production" + } +} diff --git a/assets/queries/terraform/aws_bom/dynamo/test/positive_expected_result.json b/assets/queries/terraform/aws_bom/dynamo/test/positive_expected_result.json new file mode 100644 index 00000000000..a7df71f21cb --- /dev/null +++ b/assets/queries/terraform/aws_bom/dynamo/test/positive_expected_result.json @@ -0,0 +1,26 @@ +[ + { + "queryName": "BOM - AWS DynamoDB", + "severity": "TRACE", + "line": 21, + "fileName": "positive1.tf" + }, + { + "queryName": "BOM - AWS DynamoDB", + "severity": "TRACE", + "line": 21, + "fileName": "positive2.tf" + }, + { + "queryName": "BOM - AWS DynamoDB", + "severity": "TRACE", + "line": 21, + "fileName": "positive3.tf" + }, + { + "queryName": "BOM - AWS DynamoDB", + "severity": "TRACE", + "line": 1, + "fileName": "positive4.tf" + } +] diff --git a/docs/bom.md b/docs/bom.md index f3fa1e347f3..450fc027907 100644 --- a/docs/bom.md +++ b/docs/bom.md @@ -25,13 +25,13 @@ Observe more detailed information about it in the table below. | **Field** | **Possible Values** | **Required** | **Resources** | **Type** | |:----------------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:------------:|:------------------------------------------------------------------------:|:---------------:| | acl | private,
public-read,
public-read-write,
aws-exec-read,
authenticated-read,
bucket-owner-read,
bucket-owner-full-control,
log-delivery-write | No | `aws_s3_bucket` | string | -| policy | policy content (in case `resource_accessibility` equals hasPolicy) | No | `aws_efs_file_system`,
`aws_s3_bucket`,
`aws_sns_topic`,
`aws_sqs_queue`,
`aws_db_instance`,
`aws_rds_cluster_instance` | JSON marshalled | -| resource_accessibility | public, private, hasPolicy or unknown for `aws_ebs_volume`, `aws_efs_file_system`, `aws_mq_broker`, `aws_msk_cluster`, `aws_s3_bucket`, `aws_sns_topic`,`aws_sqs_queue`,
`aws_db_instance`, and
`aws_rds_cluster_instance`

at least one security group associated with the elasticache is unrestricted, all security groups associated with the elasticache are restricted or unknown for `aws_elasticache_cluster` | Yes | all | string | -| resource_category | In Memory Data Structure for `aws_elasticache_cluster`

Messaging for `aws_sns_topic`

Queues for `aws_mq_broker` and `aws_sqs_queue`

Storage for `aws_ebs_volume`, `aws_efs_file_system`, `aws_s3_bucket`,
`aws_db_instance`, and
`aws_rds_cluster_instance`

Streaming for `aws_msk_cluster` | Yes | all | string | +| policy | policy content (in case `resource_accessibility` equals hasPolicy) | No | `aws_efs_file_system`,
`aws_s3_bucket`,
`aws_sns_topic`,
`aws_sqs_queue`,
`aws_dynamodb_table`| JSON marshalled | +| resource_accessibility | public, private, hasPolicy or unknown for
`aws_ebs_volume`,
`aws_efs_file_system`,
`aws_mq_broker`,
`aws_msk_cluster`,
`aws_s3_bucket`,
`aws_sns_topic`,
`aws_sqs_queue`,
`aws_db_instance`,
`aws_rds_cluster_instance`,
and `aws_dynamodb_table`

at least one security group associated with the elasticache is unrestricted, all security groups associated with the elasticache are restricted or unknown for `aws_elasticache_cluster` | Yes | all | string | +| resource_category | In Memory Data Structure for `aws_elasticache_cluster`

Messaging for `aws_sns_topic`

Queues for `aws_mq_broker` and `aws_sqs_queue`

Storage for `aws_ebs_volume`, `aws_efs_file_system`, `aws_s3_bucket`,
`aws_db_instance`,
`aws_rds_cluster_instance`, and `aws_dynamodb_table`

Streaming for `aws_msk_cluster` | Yes | all | string | | resource_encryption | encrypted,
unencrypted,
unknown | Yes | all | string | | resource_engine | memcached, redis or unknown for `aws_elasticache_cluster`

ActiveMQ or RabbitMQ for `aws_mq_broker`

parameter in [API action CreateDBInstance](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_CreateDBInstance.html) or unknown for
`aws_db_instance`, and
`aws_rds_cluster_instance` | No | `aws_elasticache_cluster`,
`aws_mq_broker`,
`aws_db_instance`,
`aws_rds_cluster_instance` | string | | resource_name | anything (if the name is defined),
unknown (if the name is not defined) | Yes | all | string | -| resource_type | aws_ebs_volume for `aws_ebs_volume`,
aws_efs_file_system for `aws_efs_file_system`,
aws_elasticache_cluster for `aws_elasticache_cluster`,
aws_mq_broker for `aws_mq_broker`,
aws_msk_cluster for `aws_msk_cluster`,
aws_s3_bucket for `aws_s3_bucket`,
aws_sns_topic for `aws_sns_topic`,
aws_sqs_queue for `aws_sqs_queue`
aws_db_instance for `aws_db_instance`,
aws_rds_cluster_instance for `aws_rds_cluster_instance` | Yes | all | string | +| resource_type | aws_ebs_volume for `aws_ebs_volume`,
aws_efs_file_system for `aws_efs_file_system`,
aws_elasticache_cluster for `aws_elasticache_cluster`,
aws_mq_broker for `aws_mq_broker`,
aws_msk_cluster for `aws_msk_cluster`,
aws_s3_bucket for `aws_s3_bucket`,
aws_sns_topic for `aws_sns_topic`,
aws_sqs_queue for `aws_sqs_queue`
aws_db_instance for `aws_db_instance`,
aws_rds_cluster_instance for `aws_rds_cluster_instance`,
aws_dynamodb_table for `aws_dynamodb_table` | Yes | all | string | | resource_vendor | AWS | Yes | all | string | @@ -139,13 +139,13 @@ Observe more detailed information about it in the table below. | **Field** | **Possible Values** | **Required** | **Resources** | **Type** | |:----------------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:------------:|:------------------------------------------------------------------------:|:---------------:| | acl | Private,
PublicRead,
PublicReadWrite,
AuthenticatedRead,
LogDeliveryWrite,
BucketOwnerRead,
BucketOwnerFullControl,
AwsExecRead | No | `AWS::S3::Bucket` | string | -| policy | policy content (in case `resource_accessibility` equals hasPolicy) | No | `AWS::EFS::FileSystem`,
`AWS::S3::Bucket`,
`AWS::SNS::Topic`,
`AWS::SQS::Queue`,
`AWS::RDS::DBInstance` | JSON marshalled | -| resource_accessibility | public, private, hasPolicy or unknown for `AWS::EC2::Volume`, `AWS::EFS::FileSystem`, `AWS::AmazonMQ::Broker`, `AWS::MSK::Cluster`, `AWS::S3::Bucket`, `AWS::SNS::Topic`, `AWS::SQS::Queue`, and `AWS::RDS::DBInstance`

at least one security group associated with the elasticache is unrestricted, all security groups associated with the elasticache are restricted or unknown for `AWS::ElastiCache::CacheCluster` | Yes | all | string | -| resource_category | In Memory Data Structure for `AWS::ElastiCache::CacheCluster`

Messaging for `AWS::SNS::Topic`

Queues for `AWS::AmazonMQ::Broker` and `AWS::SQS::Queue`

Storage for `AWS::EC2::Volume`, `AWS::EFS::FileSystem`, `AWS::S3::Bucket`, and `AWS::RDS::DBInstance`

Streaming for `AWS::MSK::Cluster` | Yes | all | string | +| policy | policy content (in case `resource_accessibility` equals hasPolicy) | No | `AWS::EFS::FileSystem`,
`AWS::S3::Bucket`,
`AWS::SNS::Topic`,
`AWS::SQS::Queue`,
`AWS::DynamoDB::Table`| JSON marshalled | +| resource_accessibility | public, private, hasPolicy or unknown for
`AWS::EC2::Volume`,
`AWS::EFS::FileSystem`,
`AWS::AmazonMQ::Broker`,
`AWS::MSK::Cluster`,
`AWS::S3::Bucket`,
`AWS::SNS::Topic`,
`AWS::SQS::Queue`,
`AWS::RDS::DBInstance`,
and `AWS::DynamoDB::Table`

at least one security group associated with the elasticache is unrestricted, all security groups associated with the elasticache are restricted or unknown for `AWS::ElastiCache::CacheCluster` | Yes | all | string | +| resource_category | In Memory Data Structure for `AWS::ElastiCache::CacheCluster`

Messaging for `AWS::SNS::Topic`

Queues for `AWS::AmazonMQ::Broker` and `AWS::SQS::Queue`

Storage for `AWS::EC2::Volume`, `AWS::EFS::FileSystem`, `AWS::S3::Bucket`, `AWS::RDS::DBInstance`, and `AWS::DynamoDB::Table`

Streaming for `AWS::MSK::Cluster` | Yes | all | string | | resource_encryption | encrypted,
unencrypted,
unknown | Yes | all | string | | resource_engine | memcached, redis or unknown for `AWS::ElastiCache::CacheCluster`

ACTIVEMQ or RABBITMQ for `AWS::AmazonMQ::Broker`

aurora, aurora-mysql, aurora-postgresql, mariadb, mysql, oracle-ee, oracle-ee-cdb, oracle-se2, oracle-se2-cdb, postgres, sqlserver-ee, sqlserver-se, sqlserver-ex, sqlserver-web or unknown for `AWS::RDS::DBInstance` | No | `AWS::ElastiCache::CacheCluster`,
`AWS::AmazonMQ::Broker`,
`AWS::RDS::DBInstance` | string | | resource_name | anything (if the name is defined),
unknown (if the name is not defined) | Yes | all | string | -| resource_type | AWS::EC2::Volume for `AWS::EC2::Volume`,
AWS::EFS::FileSystem for `AWS::EFS::FileSystem`,
AWS::ElastiCache::CacheCluster for `AWS::ElastiCache::CacheCluster`,
AWS::AmazonMQ::Broker for `AWS::AmazonMQ::Broker`,
AWS::MSK::Cluster for `AWS::MSK::Cluster`,
AWS::S3::Bucket for `AWS::S3::Bucket`,
AWS::SNS::Topic for `AWS::SNS::Topic`,
AWS::SQS::Queue for `AWS::SQS::Queue`,
AWS::RDS::DBInstance for `AWS::RDS::DBInstance`| Yes | all | string | +| resource_type | AWS::EC2::Volume for `AWS::EC2::Volume`,
AWS::EFS::FileSystem for `AWS::EFS::FileSystem`,
AWS::ElastiCache::CacheCluster for `AWS::ElastiCache::CacheCluster`,
AWS::AmazonMQ::Broker for `AWS::AmazonMQ::Broker`,
AWS::MSK::Cluster for `AWS::MSK::Cluster`,
AWS::S3::Bucket for `AWS::S3::Bucket`,
AWS::SNS::Topic for `AWS::SNS::Topic`,
AWS::SQS::Queue for `AWS::SQS::Queue`,
AWS::RDS::DBInstance for `AWS::RDS::DBInstance`,
AWS::DynamoDB::Table for `AWS::DynamoDB::Table`| Yes | all | string | | resource_vendor | AWS | Yes | all | string |