Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update(BoM): update BoM queries and BoM docs #5074

Merged
merged 3 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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