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 |