From 5546088618361ec1d130ed72f13f96eb14c05a3c Mon Sep 17 00:00:00 2001 From: Marcel de Vroed Date: Tue, 25 Jun 2024 13:09:43 +0200 Subject: [PATCH 1/2] feat: add variable to filter findings forwarded to SNOW --- README.md | 29 ++++++++++--------- UPGRADING.md | 2 +- modules/servicenow/README.md | 13 +++++---- modules/servicenow/eventbridge.tf | 9 ++---- .../templates/findings_filter.json.tftpl | 14 +++++++++ modules/servicenow/variables.tf | 6 ++++ servicenow.tf | 1 + variables.tf | 1 + 8 files changed, 47 insertions(+), 28 deletions(-) create mode 100644 modules/servicenow/templates/findings_filter.json.tftpl diff --git a/README.md b/README.md index f43f9a9..277aed2 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ There are 3 different deployment modes for this module. All the modes deploy a L * This deployment method can be used by setting the value of the variable `servicenow_integration` to `true` (default = false). * The module will deploy all the needed resources to support integration with ServiceNow, including (but not limited to): An SQS Queue, EventBridge Rule and the needed IAM user. * When an event in SecurityHub fires, an event will be created by EventBridge and dropped onto an SQS Queue. +* With the variable `severity_filter` it can be configured which findings will be forwarded based on the severity label. * ServiceNow will pull the events from the SQS queue with the `SCSyncUser` using `acccess_key` & `secret_access_key`. Note : The user will be created by the module, but the `acccess_key` & `secret_access_key` need to be generated in the AWS Console, to prevent storing this data in the Terraform state. If you want Terraform to create the `acccess_key` & `secret_access_key` (and output them), set variable `create_servicenow_access_keys` to `true` (default = false) @@ -195,21 +196,21 @@ Suppress finding for specific resources: ## Inputs -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the KMS key used to encrypt the resources | `string` | n/a | yes | -| [s3\_bucket\_name](#input\_s3\_bucket\_name) | The name for the S3 bucket which will be created for storing the function's deployment package | `string` | n/a | yes | -| [dynamodb\_deletion\_protection](#input\_dynamodb\_deletion\_protection) | The DynamoDB table deletion protection option. | `bool` | `true` | no | -| [dynamodb\_table](#input\_dynamodb\_table) | The DynamoDB table containing the items to be suppressed in Security Hub | `string` | `"securityhub-suppression-list"` | no | -| [eventbridge\_suppressor\_iam\_role\_name](#input\_eventbridge\_suppressor\_iam\_role\_name) | The name of the role which will be assumed by EventBridge rules | `string` | `"EventBridgeSecurityHubSuppressorRole"` | no | +| Name | Description | Type | Default | Required | +|------|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|:--------:| +| [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the KMS key used to encrypt the resources | `string` | n/a | yes | +| [s3\_bucket\_name](#input\_s3\_bucket\_name) | The name for the S3 bucket which will be created for storing the function's deployment package | `string` | n/a | yes | +| [dynamodb\_deletion\_protection](#input\_dynamodb\_deletion\_protection) | The DynamoDB table deletion protection option. | `bool` | `true` | no | +| [dynamodb\_table](#input\_dynamodb\_table) | The DynamoDB table containing the items to be suppressed in Security Hub | `string` | `"securityhub-suppression-list"` | no | +| [eventbridge\_suppressor\_iam\_role\_name](#input\_eventbridge\_suppressor\_iam\_role\_name) | The name of the role which will be assumed by EventBridge rules | `string` | `"EventBridgeSecurityHubSuppressorRole"` | no | | [jira\_integration](#input\_jira\_integration) | Jira integration settings |
object({
enabled = optional(bool, false)
credentials_secret_arn = string
exclude_account_ids = optional(list(string), [])
finding_severity_normalized_threshold = optional(number, 70)
issue_type = optional(string, "Security Advisory")
project_key = string

security_group_egress_rules = optional(list(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = string
from_port = optional(number, 0)
ip_protocol = optional(string, "-1")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
to_port = optional(number, 0)
})), [])

lambda_settings = optional(object({
name = optional(string, "securityhub-jira")
iam_role_name = optional(string, "LambdaJiraSecurityHubRole")
log_level = optional(string, "INFO")
memory_size = optional(number, 256)
runtime = optional(string, "python3.8")
timeout = optional(number, 60)
}), {
name = "securityhub-jira"
iam_role_name = "LambdaJiraSecurityHubRole"
log_level = "INFO"
memory_size = 256
runtime = "python3.8"
timeout = 60
security_group_egress_rules = []
})
})
|
{
"credentials_secret_arn": null,
"enabled": false,
"project_key": null
}
| no | -| [lambda\_events\_suppressor](#input\_lambda\_events\_suppressor) | Lambda Events Suppressor settings - Supresses the Security Hub findings in response to EventBridge Trigger |
object({
name = optional(string, "securityhub-events-suppressor")
log_level = optional(string, "INFO")
memory_size = optional(number, 256)
runtime = optional(string, "python3.8")
timeout = optional(number, 120)

security_group_egress_rules = optional(list(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = string
from_port = optional(number, 0)
ip_protocol = optional(string, "-1")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
to_port = optional(number, 0)
})), [])
})
| `{}` | no | -| [lambda\_streams\_suppressor](#input\_lambda\_streams\_suppressor) | Lambda Streams Suppressor settings - Supresses the Security Hub findings in response to DynamoDB streams |
object({
name = optional(string, "securityhub-streams-suppressor")
log_level = optional(string, "INFO")
memory_size = optional(number, 256)
runtime = optional(string, "python3.8")
timeout = optional(number, 120)

security_group_egress_rules = optional(list(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = string
from_port = optional(number, 0)
ip_protocol = optional(string, "-1")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
to_port = optional(number, 0)
})), [])
})
| `{}` | no | -| [lambda\_suppressor\_iam\_role\_name](#input\_lambda\_suppressor\_iam\_role\_name) | The name of the role which will be assumed by both Suppressor Lambda functions | `string` | `"LambdaSecurityHubSuppressorRole"` | no | -| [servicenow\_integration](#input\_servicenow\_integration) | ServiceNow integration settings |
object({
enabled = optional(bool, false)
create_access_keys = optional(bool, false)
cloudwatch_retention_days = optional(number, 365)
})
|
{
"enabled": false
}
| no | -| [step\_function\_suppressor\_iam\_role\_name](#input\_step\_function\_suppressor\_iam\_role\_name) | The name of the role which will be assumed by Suppressor Step function | `string` | `"StepFunctionSecurityHubSuppressorRole"` | no | -| [subnet\_ids](#input\_subnet\_ids) | The subnet ids where the lambda's needs to run | `list(string)` | `null` | no | -| [tags](#input\_tags) | A mapping of tags to assign to the resources | `map(string)` | `{}` | no | +| [lambda\_events\_suppressor](#input\_lambda\_events\_suppressor) | Lambda Events Suppressor settings - Supresses the Security Hub findings in response to EventBridge Trigger |
object({
name = optional(string, "securityhub-events-suppressor")
log_level = optional(string, "INFO")
memory_size = optional(number, 256)
runtime = optional(string, "python3.8")
timeout = optional(number, 120)

security_group_egress_rules = optional(list(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = string
from_port = optional(number, 0)
ip_protocol = optional(string, "-1")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
to_port = optional(number, 0)
})), [])
})
| `{}` | no | +| [lambda\_streams\_suppressor](#input\_lambda\_streams\_suppressor) | Lambda Streams Suppressor settings - Supresses the Security Hub findings in response to DynamoDB streams |
object({
name = optional(string, "securityhub-streams-suppressor")
log_level = optional(string, "INFO")
memory_size = optional(number, 256)
runtime = optional(string, "python3.8")
timeout = optional(number, 120)

security_group_egress_rules = optional(list(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = string
from_port = optional(number, 0)
ip_protocol = optional(string, "-1")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
to_port = optional(number, 0)
})), [])
})
| `{}` | no | +| [lambda\_suppressor\_iam\_role\_name](#input\_lambda\_suppressor\_iam\_role\_name) | The name of the role which will be assumed by both Suppressor Lambda functions | `string` | `"LambdaSecurityHubSuppressorRole"` | no | +| [servicenow\_integration](#input\_servicenow\_integration) | ServiceNow integration settings |
object({
enabled = optional(bool, false)
create_access_keys = optional(bool, false)
cloudwatch_retention_days = optional(number, 365)
severity_label_filter = optional(list(string),[])
})
|
{
"enabled": false
}
| no | +| [step\_function\_suppressor\_iam\_role\_name](#input\_step\_function\_suppressor\_iam\_role\_name) | The name of the role which will be assumed by Suppressor Step function | `string` | `"StepFunctionSecurityHubSuppressorRole"` | no | +| [subnet\_ids](#input\_subnet\_ids) | The subnet ids where the lambda's needs to run | `list(string)` | `null` | no | +| [tags](#input\_tags) | A mapping of tags to assign to the resources | `map(string)` | `{}` | no | ## Outputs diff --git a/UPGRADING.md b/UPGRADING.md index 24f63a0..170e53e 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -18,7 +18,7 @@ The following variable defaults have been modified: ### Behaviour -The need to provide a `providers = { aws = aws }` argument has been removed, but is still allowed. E.g. when deploying this module in the audit account typically `providers = { aws = aws.audit }` is passed. +The need to provide a `providers = { aws = aws }` argument has been removed, but is still allowed. E.g. when deploying this module in the audit account typically `providers = { aws = aws.audit }` is passed. ## Upgrading to v1.0.0 diff --git a/modules/servicenow/README.md b/modules/servicenow/README.md index d303d3d..d089617 100644 --- a/modules/servicenow/README.md +++ b/modules/servicenow/README.md @@ -15,12 +15,13 @@ ## Inputs -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| kms\_key\_arn | The ARN of the KMS key used to encrypt the resources | `string` | n/a | yes | -| tags | A mapping of tags to assign to the resources | `map(string)` | n/a | yes | -| cloudwatch\_retention\_days | Time to retain the CloudWatch Logs for the ServiceNow integration | `number` | `14` | no | -| create\_access\_keys | Whether to create an access\_key and secret\_access key for the ServiceNow user | `bool` | `false` | no | +| Name | Description | Type | Default | Required | +|-----------------------------|------------------------------------------------------------------------------------------------------------------------|----------------|--------|:--------:| +| kms\_key\_arn | The ARN of the KMS key used to encrypt the resources | `string` | n/a | yes | +| tags | A mapping of tags to assign to the resources | `map(string)` | n/a | yes | +| cloudwatch\_retention\_days | Time to retain the CloudWatch Logs for the ServiceNow integration | `number` | `14` | no | +| create\_access\_keys | Whether to create an access\_key and secret\_access key for the ServiceNow user | `bool` | `false` | no | +| severity\_label\_filter | Only forward findings to ServiceNow with severity labels from this list (by default all severity labels are forwarded) | `list(string)` | `[]` | no | ## Outputs diff --git a/modules/servicenow/eventbridge.tf b/modules/servicenow/eventbridge.tf index c862764..d1e5004 100644 --- a/modules/servicenow/eventbridge.tf +++ b/modules/servicenow/eventbridge.tf @@ -1,13 +1,8 @@ resource "aws_cloudwatch_event_rule" "securityhub" { name = "snow-RuleLifeCycleEvents" description = "Send Security Hub imported findings to the AwsServiceManagementConnectorForSecurityHubQueue SQS." - - event_pattern = < 0 ~} + , + "detail": { + "findings": { + "Severity": { + "Label": ${severity_label_filter} + } + } + } + %{ endif ~} +} diff --git a/modules/servicenow/variables.tf b/modules/servicenow/variables.tf index 432fdff..cf0ddb7 100644 --- a/modules/servicenow/variables.tf +++ b/modules/servicenow/variables.tf @@ -10,6 +10,12 @@ variable "create_access_keys" { description = "Whether to create an access_key and secret_access key for the ServiceNow user" } +variable "severity_label_filter" { + type = list(string) + default = [] + description = "Only forward findings to ServiceNow with severity labels from this list (by default all severity labels are forwarded)" +} + variable "kms_key_arn" { type = string description = "The ARN of the KMS key used to encrypt the resources" diff --git a/servicenow.tf b/servicenow.tf index 77ae9ed..ee5cd5c 100644 --- a/servicenow.tf +++ b/servicenow.tf @@ -5,6 +5,7 @@ module "servicenow_integration" { cloudwatch_retention_days = var.servicenow_integration.cloudwatch_retention_days create_access_keys = var.servicenow_integration.create_access_keys + severity_label_filter = var.servicenow_integration.severity_label_filter kms_key_arn = var.kms_key_arn tags = var.tags } diff --git a/variables.tf b/variables.tf index 08463f5..eac3fd2 100644 --- a/variables.tf +++ b/variables.tf @@ -143,6 +143,7 @@ variable "servicenow_integration" { enabled = optional(bool, false) create_access_keys = optional(bool, false) cloudwatch_retention_days = optional(number, 365) + severity_label_filter = optional(list(string), []) }) default = { enabled = false From 6d671e4909b3e233672360d35d0e1319c7df1478 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 26 Jun 2024 06:12:11 +0000 Subject: [PATCH 2/2] docs(readme): update module usage --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 277aed2..25c0f62 100644 --- a/README.md +++ b/README.md @@ -196,21 +196,21 @@ Suppress finding for specific resources: ## Inputs -| Name | Description | Type | Default | Required | -|------|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|:--------:| -| [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the KMS key used to encrypt the resources | `string` | n/a | yes | -| [s3\_bucket\_name](#input\_s3\_bucket\_name) | The name for the S3 bucket which will be created for storing the function's deployment package | `string` | n/a | yes | -| [dynamodb\_deletion\_protection](#input\_dynamodb\_deletion\_protection) | The DynamoDB table deletion protection option. | `bool` | `true` | no | -| [dynamodb\_table](#input\_dynamodb\_table) | The DynamoDB table containing the items to be suppressed in Security Hub | `string` | `"securityhub-suppression-list"` | no | -| [eventbridge\_suppressor\_iam\_role\_name](#input\_eventbridge\_suppressor\_iam\_role\_name) | The name of the role which will be assumed by EventBridge rules | `string` | `"EventBridgeSecurityHubSuppressorRole"` | no | +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of the KMS key used to encrypt the resources | `string` | n/a | yes | +| [s3\_bucket\_name](#input\_s3\_bucket\_name) | The name for the S3 bucket which will be created for storing the function's deployment package | `string` | n/a | yes | +| [dynamodb\_deletion\_protection](#input\_dynamodb\_deletion\_protection) | The DynamoDB table deletion protection option. | `bool` | `true` | no | +| [dynamodb\_table](#input\_dynamodb\_table) | The DynamoDB table containing the items to be suppressed in Security Hub | `string` | `"securityhub-suppression-list"` | no | +| [eventbridge\_suppressor\_iam\_role\_name](#input\_eventbridge\_suppressor\_iam\_role\_name) | The name of the role which will be assumed by EventBridge rules | `string` | `"EventBridgeSecurityHubSuppressorRole"` | no | | [jira\_integration](#input\_jira\_integration) | Jira integration settings |
object({
enabled = optional(bool, false)
credentials_secret_arn = string
exclude_account_ids = optional(list(string), [])
finding_severity_normalized_threshold = optional(number, 70)
issue_type = optional(string, "Security Advisory")
project_key = string

security_group_egress_rules = optional(list(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = string
from_port = optional(number, 0)
ip_protocol = optional(string, "-1")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
to_port = optional(number, 0)
})), [])

lambda_settings = optional(object({
name = optional(string, "securityhub-jira")
iam_role_name = optional(string, "LambdaJiraSecurityHubRole")
log_level = optional(string, "INFO")
memory_size = optional(number, 256)
runtime = optional(string, "python3.8")
timeout = optional(number, 60)
}), {
name = "securityhub-jira"
iam_role_name = "LambdaJiraSecurityHubRole"
log_level = "INFO"
memory_size = 256
runtime = "python3.8"
timeout = 60
security_group_egress_rules = []
})
})
|
{
"credentials_secret_arn": null,
"enabled": false,
"project_key": null
}
| no | -| [lambda\_events\_suppressor](#input\_lambda\_events\_suppressor) | Lambda Events Suppressor settings - Supresses the Security Hub findings in response to EventBridge Trigger |
object({
name = optional(string, "securityhub-events-suppressor")
log_level = optional(string, "INFO")
memory_size = optional(number, 256)
runtime = optional(string, "python3.8")
timeout = optional(number, 120)

security_group_egress_rules = optional(list(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = string
from_port = optional(number, 0)
ip_protocol = optional(string, "-1")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
to_port = optional(number, 0)
})), [])
})
| `{}` | no | -| [lambda\_streams\_suppressor](#input\_lambda\_streams\_suppressor) | Lambda Streams Suppressor settings - Supresses the Security Hub findings in response to DynamoDB streams |
object({
name = optional(string, "securityhub-streams-suppressor")
log_level = optional(string, "INFO")
memory_size = optional(number, 256)
runtime = optional(string, "python3.8")
timeout = optional(number, 120)

security_group_egress_rules = optional(list(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = string
from_port = optional(number, 0)
ip_protocol = optional(string, "-1")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
to_port = optional(number, 0)
})), [])
})
| `{}` | no | -| [lambda\_suppressor\_iam\_role\_name](#input\_lambda\_suppressor\_iam\_role\_name) | The name of the role which will be assumed by both Suppressor Lambda functions | `string` | `"LambdaSecurityHubSuppressorRole"` | no | -| [servicenow\_integration](#input\_servicenow\_integration) | ServiceNow integration settings |
object({
enabled = optional(bool, false)
create_access_keys = optional(bool, false)
cloudwatch_retention_days = optional(number, 365)
severity_label_filter = optional(list(string),[])
})
|
{
"enabled": false
}
| no | -| [step\_function\_suppressor\_iam\_role\_name](#input\_step\_function\_suppressor\_iam\_role\_name) | The name of the role which will be assumed by Suppressor Step function | `string` | `"StepFunctionSecurityHubSuppressorRole"` | no | -| [subnet\_ids](#input\_subnet\_ids) | The subnet ids where the lambda's needs to run | `list(string)` | `null` | no | -| [tags](#input\_tags) | A mapping of tags to assign to the resources | `map(string)` | `{}` | no | +| [lambda\_events\_suppressor](#input\_lambda\_events\_suppressor) | Lambda Events Suppressor settings - Supresses the Security Hub findings in response to EventBridge Trigger |
object({
name = optional(string, "securityhub-events-suppressor")
log_level = optional(string, "INFO")
memory_size = optional(number, 256)
runtime = optional(string, "python3.8")
timeout = optional(number, 120)

security_group_egress_rules = optional(list(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = string
from_port = optional(number, 0)
ip_protocol = optional(string, "-1")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
to_port = optional(number, 0)
})), [])
})
| `{}` | no | +| [lambda\_streams\_suppressor](#input\_lambda\_streams\_suppressor) | Lambda Streams Suppressor settings - Supresses the Security Hub findings in response to DynamoDB streams |
object({
name = optional(string, "securityhub-streams-suppressor")
log_level = optional(string, "INFO")
memory_size = optional(number, 256)
runtime = optional(string, "python3.8")
timeout = optional(number, 120)

security_group_egress_rules = optional(list(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = string
from_port = optional(number, 0)
ip_protocol = optional(string, "-1")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
to_port = optional(number, 0)
})), [])
})
| `{}` | no | +| [lambda\_suppressor\_iam\_role\_name](#input\_lambda\_suppressor\_iam\_role\_name) | The name of the role which will be assumed by both Suppressor Lambda functions | `string` | `"LambdaSecurityHubSuppressorRole"` | no | +| [servicenow\_integration](#input\_servicenow\_integration) | ServiceNow integration settings |
object({
enabled = optional(bool, false)
create_access_keys = optional(bool, false)
cloudwatch_retention_days = optional(number, 365)
severity_label_filter = optional(list(string), [])
})
|
{
"enabled": false
}
| no | +| [step\_function\_suppressor\_iam\_role\_name](#input\_step\_function\_suppressor\_iam\_role\_name) | The name of the role which will be assumed by Suppressor Step function | `string` | `"StepFunctionSecurityHubSuppressorRole"` | no | +| [subnet\_ids](#input\_subnet\_ids) | The subnet ids where the lambda's needs to run | `list(string)` | `null` | no | +| [tags](#input\_tags) | A mapping of tags to assign to the resources | `map(string)` | `{}` | no | ## Outputs