Skip to content

Commit

Permalink
update(BoM): update BoM queries and BoM docs (Checkmarx#5074)
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaela-soares authored Mar 29, 2022
1 parent 57a885a commit ef26485
Show file tree
Hide file tree
Showing 14 changed files with 101 additions and 59 deletions.
20 changes: 16 additions & 4 deletions assets/libraries/common.rego
Original file line number Diff line number Diff line change
Expand Up @@ -338,13 +338,15 @@ find_selector_by_value(filter, str) = rtn {
get_tag_name_if_exists(resource) = name {
name := resource.tags.Name
} else = name {
name := ""
name := "unknown"
}

get_encryption_if_exists(resource) = encryption {
encryption := resource.encrypted
resource.encrypted == true
encryption := "encrypted"
} else = encryption {
valid_key(resource.encryption_info, "encryption_at_rest_kms_key_arn")
options := {"encryption_at_rest_kms_key_arn", "encryption_in_transit"}
valid_key(resource.encryption_info, options[_])
encryption := "encrypted"
} else = encryption {
fields := {"sqs_managed_sse_enabled", "kms_master_key_id", "encryption_options", "server_side_encryption_configuration"}
Expand Down Expand Up @@ -745,8 +747,18 @@ remove_last_point(searchKey) = sk {
sk := searchKey
}

# This function is based on this docs(https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-optimized.html#describe-ebs-optimization)
# if accessibility is "hasPolicy", bom_output should also display the policy content
get_bom_output(bom_output, policy) = output {
bom_output.resource_accessibility == "hasPolicy"

out := {"policy": policy}

output := object.union(bom_output, out)
} else = output {
output := bom_output
}

# This function is based on this docs(https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-optimized.html#describe-ebs-optimization)
is_aws_ebs_optimized_by_default(instanceType) {
inArray(data.common_lib.aws_ebs_optimized_by_default, instanceType)
}
21 changes: 2 additions & 19 deletions assets/libraries/terraform.rego
Original file line number Diff line number Diff line change
Expand Up @@ -454,32 +454,15 @@ is_publicly_accessible(policy) {

get_accessibility(resource, name, resourcePolicyName, resourceTarget) = info {
policy := common_lib.json_unmarshal(resource.policy)
is_publicly_accessible(policy)
info = {"accessibility": "public", "policy": policy}
} else = info {
policy := common_lib.json_unmarshal(resource.policy)
not is_publicly_accessible(policy)

info = {"accessibility": "restrict", "policy": policy}
} else = info {
not common_lib.valid_key(resource, "policy")

resourcePolicy := input.document[_].resource[resourcePolicyName][_]
split(resourcePolicy[resourceTarget], ".")[1] == name

policy := common_lib.json_unmarshal(resourcePolicy.policy)
is_publicly_accessible(policy)

info = {"accessibility": "public", "policy": policy}
info = {"accessibility": "hasPolicy", "policy": policy}
} else = info {
not common_lib.valid_key(resource, "policy")

resourcePolicy := input.document[_].resource[resourcePolicyName][_]
split(resourcePolicy[resourceTarget], ".")[1] == name

policy := common_lib.json_unmarshal(resourcePolicy.policy)
not is_publicly_accessible(policy)
info = {"accessibility": "restrict", "policy": policy}
info = {"accessibility": "hasPolicy", "policy": policy}
} else = info {
info = {"accessibility": "unknown", "policy": ""}
}
Expand Down
10 changes: 10 additions & 0 deletions assets/queries/terraform/aws_bom/ebs/test/positive2.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
resource "aws_ebs_volume" "positive2" {
availability_zone = "us-west-2a"
size = 40

tags = {
Name = "HelloWorld2"
}

encrypted = true
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,11 @@
"severity": "TRACE",
"line": 1,
"fileName": "positive1.tf"
},
{
"queryName": "BOM - AWS EBS",
"severity": "TRACE",
"line": 1,
"fileName": "positive2.tf"
}
]
7 changes: 4 additions & 3 deletions assets/queries/terraform/aws_bom/efs/query.rego
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,21 @@ CxPolicy[result] {
bom_output = {
"resource_type": "aws_efs_file_system",
"resource_name": common_lib.get_tag_name_if_exists(efs_file_system),
"resource_accessibility": info.accessibility,
"resource_accessibility": info.accessibility,
"resource_encryption": common_lib.get_encryption_if_exists(efs_file_system),
"resource_vendor": "AWS",
"resource_category": "Storage",
"policy": info.policy,
}

final_bom_output := common_lib.get_bom_output(bom_output, info.policy)

result := {
"documentId": input.document[i].id,
"searchKey": sprintf("aws_efs_file_system[%s]", [name]),
"issueType": "BillOfMaterials",
"keyExpectedValue": "",
"keyActualValue": "",
"searchLine": common_lib.build_search_line(["resource", "aws_efs_file_system", name], []),
"value": json.marshal(bom_output),
"value": json.marshal(final_bom_output),
}
}
2 changes: 1 addition & 1 deletion assets/queries/terraform/aws_bom/elasticache/query.rego
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ CxPolicy[result] {
}

get_engine_type(aws_elasticache_cluster) = engine_type {
engine_type := aws_elasticache_cluster.engine_type
engine_type := aws_elasticache_cluster.engine
} else = engine_type {
not aws_elasticache_cluster.replication_group_id
engine_type := "unknown"
Expand Down
9 changes: 5 additions & 4 deletions assets/queries/terraform/aws_bom/mq/query.rego
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ CxPolicy[result] {
"resource_encryption": common_lib.get_encryption_if_exists(aws_mq_broker_resource),
"resource_vendor": "AWS",
"resource_category": "Queues",
"user_name": aws_mq_broker_resource.user.username,
"is_default_password": terra_lib.is_default_password(aws_mq_broker_resource.user.password),
# "user_name": aws_mq_broker_resource.user.username, # needs attention in the future
# "is_default_password": terra_lib.is_default_password(aws_mq_broker_resource.user.password), # needs attention in the future
}

result := {
Expand All @@ -31,7 +31,8 @@ CxPolicy[result] {
}

check_publicly_accessible(resource) = accessibility {
accessibility := resource.publicly_accessible
resource.publicly_accessible == true
accessibility := "public"
} else = accessibility {
accessibility := false
accessibility := "private"
}
5 changes: 5 additions & 0 deletions assets/queries/terraform/aws_bom/mq/test/positive2.tf
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ resource "aws_mq_broker" "positive2" {
password = "111111111111"
}

user {
username = "ExampleUser"
password = "MindTheGap"
}

encryption_options {
kms_key_id = var.encryption_options.kms_key_id
}
Expand Down
20 changes: 13 additions & 7 deletions assets/queries/terraform/aws_bom/s3_bucket/query.rego
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,28 @@ import data.generic.terraform as terra_lib
CxPolicy[result] {
bucket_resource := input.document[i].resource.aws_s3_bucket[name]

info := get_accessibility(bucket_resource, name)

bom_output = {
"resource_type": "aws_s3_bucket",
"resource_name": get_bucket_name(bucket_resource),
"resource_accessibility": get_accessibility(bucket_resource, name),
"resource_accessibility": info.accessibility,
"resource_encryption": common_lib.get_encryption_if_exists(bucket_resource),
"resource_vendor": "AWS",
"resource_category": "Storage",
"acl": get_bucket_acl(bucket_resource),
}

final_bom_output = common_lib.get_bom_output(bom_output, info.policy)

result := {
"documentId": input.document[i].id,
"searchKey": sprintf("aws_s3_bucket[%s]", [name]),
"issueType": "BillOfMaterials",
"keyExpectedValue": "",
"keyActualValue": "",
"searchLine": common_lib.build_search_line(["resource", "aws_s3_bucket", name], []),
"value": json.marshal(bom_output),
"value": json.marshal(final_bom_output),
}
}

Expand All @@ -49,17 +53,19 @@ get_accessibility(bucket, bucketName) = accessibility {
s3BucketPublicAccessBlock := input.document[i].resource.aws_s3_bucket_public_access_block[_]
split(s3BucketPublicAccessBlock.bucket, ".")[1] == bucketName
is_public_access_blocked(s3BucketPublicAccessBlock)
accessibility := "private"
accessibility = {"accessibility": "private", "policy": ""}
} else = accessibility {
# cases when there is a unrestriced policy
accessibility := terra_lib.get_accessibility(bucket, bucketName, "aws_s3_bucket_policy", "bucket").accessibility
accessibility != "unknown"
acc := terra_lib.get_accessibility(bucket, bucketName, "aws_s3_bucket_policy", "bucket")
acc.accessibility == "hasPolicy"

accessibility = {"accessibility": "hasPolicy", "policy": acc.policy}
} else = accessibility {
# last cases: acl definition
accessibility := bucket.acl
accessibility = {"accessibility": bucket.acl, "policy": ""}
} else = accessibility {
# last cases: acl definition
not common_lib.valid_key(bucket, "acl")
accessibility := "private"
accessibility = {"accessibility": "private", "policy": ""}
}

11 changes: 6 additions & 5 deletions assets/queries/terraform/aws_bom/sns/query.rego
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,30 @@ CxPolicy[result] {

bom_output = {
"resource_type": "aws_sns_topic",
"resource_name": get_queue_name(aws_sns_topic_resource),
"resource_name": get_topic_name(aws_sns_topic_resource),
"resource_accessibility": info.accessibility,
"resource_encryption": common_lib.get_encryption_if_exists(aws_sns_topic_resource),
"resource_vendor": "AWS",
"resource_category": "Messaging",
"policy": info.policy,
}

final_bom_output := common_lib.get_bom_output(bom_output, info.policy)

result := {
"documentId": input.document[i].id,
"searchKey": sprintf("aws_sns_topic[%s]", [name]),
"issueType": "BillOfMaterials",
"keyExpectedValue": "",
"keyActualValue": "",
"searchLine": common_lib.build_search_line(["resource", "aws_sns_topic", name], []),
"value": json.marshal(bom_output),
"value": json.marshal(final_bom_output),
}
}

get_queue_name(aws_sns_topic_resource) = name {
get_topic_name(aws_sns_topic_resource) = name {
name := aws_sns_topic_resource.name
} else = name {
name := sprintf("%s<unknown-sufix>", [aws_sns_topic_resource.name_prefix])
} else = name {
name := "unknown"
name := common_lib.get_tag_name_if_exists(aws_sns_topic_resource)
}
4 changes: 3 additions & 1 deletion assets/queries/terraform/aws_bom/sns/test/positive5.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
resource "aws_sns_topic" "positive5" {
name = "user-updates-topic"
tags = {
Name = "SNS Topic"
}

kms_master_key_id = "alias/aws/sns"

Expand Down
7 changes: 4 additions & 3 deletions assets/queries/terraform/aws_bom/sqs/query.rego
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@ CxPolicy[result] {
"resource_encryption": common_lib.get_encryption_if_exists(aws_sqs_queue_resource),
"resource_vendor": "AWS",
"resource_category": "Queues",
"policy": info.policy,
}

final_bom_output := common_lib.get_bom_output(bom_output, info.policy)

result := {
"documentId": input.document[i].id,
"searchKey": sprintf("aws_sqs_queue[%s]", [name]),
"issueType": "BillOfMaterials",
"keyExpectedValue": "",
"keyActualValue": "",
"searchLine": common_lib.build_search_line(["resource", "aws_sqs_queue", name], []),
"value": json.marshal(bom_output),
"value": json.marshal(final_bom_output),
}
}

Expand All @@ -34,5 +35,5 @@ get_queue_name(aws_sqs_queue_resource) = name {
} else = name {
name := sprintf("%s<unknown-sufix>", [aws_sqs_queue_resource.name_prefix])
} else = name {
name := "unknown"
name := common_lib.get_tag_name_if_exists(aws_sqs_queue_resource)
}
30 changes: 22 additions & 8 deletions docs/bom.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Bill Of Materials
## [Terraform] Bill Of Materials

This feature uses Rego queries to extract a list of used Terraform resources along with its metadata in the scanned IaC.

Expand All @@ -9,19 +9,33 @@ BoM queries extracts metadata about the resources and organizes it in the follow
```go
billOfMaterialsRequiredFields := map[string]bool{
"acl": false,
"is_default_password": false,
"policy": false,
"resource_type": true,
"resource_name": true,
"resource_engine": false,
"resource_accessibility": true,
"resource_vendor": true,
"resource_category": true,
"user_name": false,
"resource_encryption": true,
"resource_engine": false,
"resource_name": true,
"resource_type": true,
"resource_vendor": true,
}
```

After extracting the information, the query stores the stringified JSON structure inside the `value` field in the `result`:
Observe more detailed information about it in the table below.

| **Field** | **Possible Values** | **Required** | **Resources** | **Type** |
|:----------------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:------------:|:------------------------------------------------------------------------:|:---------------:|
| acl | private,<br /> public-read,<br /> public-read-write,<br /> aws-exec-read,<br /> authenticated-read,<br /> bucket-owner-read,<br /> bucket-owner-full-control,<br /> log-delivery-write | No | `aws_s3_bucket` | string |
| policy | policy content (in case `resource_accessibility` equals hasPolicy) | No | `aws_efs_file_system`,<br /> `aws_s3_bucket`,<br /> `aws_sns_topic`,<br /> `aws_sqs_queue` | 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`, and `aws_sqs_queue` <br /> <br /> 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`<br /><br /> Messaging for `aws_sns_topic`<br /><br /> Queues for `aws_mq_broker` and `aws_sqs_queue`<br /><br /> Storage for `aws_ebs_volume`, `aws_efs_file_system`, and `aws_s3_bucket`<br /><br /> Streaming for `aws_msk_cluster` | Yes | all | string |
| resource_encryption | encrypted,<br /> unencrypted,<br /> unknown | Yes | all | string |
| resource_engine | memcached, redis or unknown for `aws_elasticache_cluster`<br /><br /> ActiveMQ or RabbitMQ for `aws_mq_broker` | No | `aws_elasticache_cluster`, <br /> `aws_mq_broker` | string |
| resource_name | anything (if the name is defined),<br /> unknown (if the name is not defined) | Yes | all | string |
| resource_type | aws_ebs_volume for `aws_ebs_volume`,<br /> aws_efs_file_system for `aws_efs_file_system`,<br /> aws_elasticache_cluster for `aws_elasticache_cluster`,<br /> aws_mq_broker for `aws_mq_broker`,<br /> aws_msk_cluster for `aws_msk_cluster`,<br /> aws_s3_bucket for `aws_s3_bucket`,<br /> aws_sns_topic for `aws_sns_topic`, aws_sqs_queue for `aws_sqs_queue` | Yes | all | string |
| resource_vendor | AWS | Yes | all | string |


### BoM query example

```rego
CxPolicy[result] {
Expand Down
8 changes: 4 additions & 4 deletions test/queries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,17 +227,17 @@ func validateQueryResultFields(tb testing.TB, vulnerabilies []model.Vulnerabilit
bomResult := make(map[string]string)
json.Unmarshal([]byte(*vulnerabilies[idx].Value), &bomResult)
bomOutputRequiredFields := map[string]bool{
"acl": false,
"is_default_password": false,
"acl": false,
// "is_default_password": false,
"policy": false,
"resource_type": true,
"resource_name": true,
"resource_engine": false,
"resource_accessibility": true,
"resource_vendor": true,
"resource_category": true,
"user_name": false,
"resource_encryption": true,
// "user_name": false,
"resource_encryption": true,
}
for key := range bomOutputRequiredFields {
_, ok := bomResult[key]
Expand Down

0 comments on commit ef26485

Please sign in to comment.