Skip to content

Terraform module to suppress specific events from security hub based on a dynamodb based configuration.

License

Notifications You must be signed in to change notification settings

schubergphilis/terraform-aws-mcaf-securityhub-findings-manager

Repository files navigation

Security Hub Findings Manager

The Security Hub Findings Manager is a framework designed to automatically manage findings recorded by the AWS Security Hub service based on a pre-defined and configurable rules list. At the moment only finding suppression is supported. This suppression is needed in case some controls or rules are not completely applicable to the resources of a given account. For example, you might want to suppress all DynamoDB Autoscaling configuration findings related to the control DynamoDB.1, simply because this feature is not applicable for your workload. Besides the findings management this module is also able to integrate with Jira and ServiceNow. To ensure tickets are created for all NEW findings with a severity higher than a definable threshold.

Tip

We recommend deploying this module in the Audit/Security Account of an AWS reference multi-account setup. This account receives events from all child accounts in an organization. This way, a comprehensive overview of the organization's security posture can be easily maintained.

Note

This module relies heavily on awsfindingsmanagerlib. See the documentation of this library on more detailed specifications of the suppression logic.

Terraform Runtime Requirements

  • The lambda's are built and zipped during runtime, this means that the terraform runners/agents needs to have python 3.8 installed.
  • Remark about Terraform Cloud: The remote runners from Terraform Cloud have python installed. If you run your own agents make sure that you use a custom TFC agent image with python installed.

Components

This is a high-level overview of the constituent components. For a more complete overview see Resources and Modules.

  • A rules backend (currently only S3 is supported).
  • 2 Lambda Functions:
    • Security Hub Findings Manager Events: triggered by EventBridge events for new Security Hub findings.
    • Security Hub Findings Manager Triggers: triggered by changes in the S3 backend rules list.
  • Infrastructure to facilitate the Lambda functions (IAM role, EventBridge integration, S3 Trigger Notifications).
  • (optional) Jira integration components.
  • (optional) ServiceNow integration components.

Deployment Modes

There are 3 different deployment modes for this module:

Important

In case of first time deploy, be mindful that there can be a delay between creating S3 triggers and those being fully functional. Re-create the rules object later to have rules run on your findings history in that case.

(Default) Without Jira & ServiceNow Integration

The module deploys 2 Lambda functions:

  • securityhub-findings-manager-events, this function is the target for the EventBridge rule Security Hub Findings - Imported events.
  • securityhub-findings-manager-trigger, this function is the target to the S3 PutObject trigger.

With Jira Integration

  • This deployment method can be used by setting the value of the variable jira_integration to true (default = false).
  • The module deploys an additional Jira lambda function along with a Step function which orchestrates these Lambda functions and Step Function as a target to the EventBridge rule.
  • If the finding is not suppressed a ticket is created for findings with a normalized severity higher than a definable threshold. The workflow status in Security Hub is updated from NEW to NOTIFIED.
  • You can enable auto-closing functionality by setting the value of the variable jira_integration.autoclose_enabled to true (default = false). If you do so, the step function will also forward findings updated to status RESOLVED to the lambda function. The function will then use the ticket number saved in the finding note and transition the issue using the transition defined in jira_integration.autoclose_transition_name with comment defined in jira_integration.autoclose_comment

Only events from Security Hub with a normalized severity level higher than a definable threshold (by default 70) trigger the Jira integration.

Normalized severity levels:

  • 0 - INFORMATIONAL
  • 1–39 - LOW
  • 40–69 - MEDIUM
  • 70–89 - HIGH
  • 90–100 - CRITICAL

Step Function Graph

With ServiceNow Integration

Reference design

  • 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 Security Hub fires, an event will be created by EventBridge and dropped onto an SQS Queue.
  • With the variable severity_label_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.

Warning

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)

How to format the rules.yaml file?

An example file is stored in this module under examples/rules.yaml. For more detailed information check out the awsfindingsmanagerlib.

The general syntax and allowed parameters are:

Rules:
  - note: 'str'
    action: 'SUPPRESSED'
    match_on:
      security_control_id: 'str' # When `Consolidated control findings` is On
      rule_or_control_id: 'str' # When `Consolidated control findings` is Off
      tags:
        - key: 'str'
          value: 'str'
      resource_id_regexps:
        - 'regex'

Important

security_control_id and rule_or_control_id are mutually exclusive, but one of them must be set!

Local development on the Python code

Since a lambda layer is used to provide the aws-lambda-powertools if you want to have the same dependencies available locally then install them using requirements-dev.txt stored with the source code.

Requirements

Name Version
terraform >= 1.3.0
archive >= 2.0
aws >= 4.9
external >= 2.0
local >= 1.0
null >= 2.0

Providers

Name Version
aws >= 4.9

Modules

Name Source Version
findings_manager_bucket schubergphilis/mcaf-s3/aws ~> 0.14.1
findings_manager_events_lambda schubergphilis/mcaf-lambda/aws ~> 1.4.1
findings_manager_lambda_iam_role schubergphilis/mcaf-role/aws ~> 0.4.0
findings_manager_trigger_lambda schubergphilis/mcaf-lambda/aws ~> 1.4.1
jira_eventbridge_iam_role schubergphilis/mcaf-role/aws ~> 0.3.2
jira_lambda schubergphilis/mcaf-lambda/aws ~> 1.4.1
jira_lambda_iam_role schubergphilis/mcaf-role/aws ~> 0.4.0
jira_step_function_iam_role schubergphilis/mcaf-role/aws ~> 0.3.2
servicenow_integration ./modules/servicenow/ n/a

Resources

Name Type
aws_cloudwatch_event_rule.securityhub_findings_events resource
aws_cloudwatch_event_target.findings_manager_events_lambda resource
aws_cloudwatch_event_target.jira_orchestrator resource
aws_cloudwatch_log_group.log_group_jira_orchestrator_sfn resource
aws_iam_role_policy_attachment.findings_manager_lambda_iam_role resource
aws_iam_role_policy_attachment.jira_lambda_iam_role_vpc_policy_attachment resource
aws_lambda_permission.eventbridge_invoke_findings_manager_events_lambda resource
aws_lambda_permission.s3_invoke_findings_manager_trigger_lambda resource
aws_s3_bucket_notification.findings_manager_trigger resource
aws_s3_object.findings_manager_lambdas_deployment_package resource
aws_s3_object.jira_lambda_deployment_package resource
aws_s3_object.rules resource
aws_sfn_state_machine.jira_orchestrator resource
aws_caller_identity.current data source
aws_iam_policy_document.findings_manager_lambda_iam_role data source
aws_iam_policy_document.jira_eventbridge_iam_role data source
aws_iam_policy_document.jira_lambda_iam_role data source
aws_iam_policy_document.jira_step_function_iam_role data source
aws_region.current data source

Inputs

Name Description Type Default Required
kms_key_arn The ARN of the KMS key used to encrypt the resources string n/a yes
s3_bucket_name The name for the S3 bucket which will be created for storing the function's deployment package string n/a yes
findings_manager_events_lambda Findings Manager Lambda settings - Manage Security Hub findings in response to EventBridge events
object({
name = optional(string, "securityhub-findings-manager-events")
log_level = optional(string, "INFO")
memory_size = optional(number, 256)
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
findings_manager_lambda_iam_role_name The name of the role which will be assumed by both Findings Manager Lambda functions string "SecurityHubFindingsManagerLambda" no
findings_manager_trigger_lambda Findings Manager Lambda settings - Manage Security Hub findings in response to S3 file upload triggers
object({
name = optional(string, "securityhub-findings-manager-trigger")
log_level = optional(string, "INFO")
memory_size = optional(number, 256)
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
jira_eventbridge_iam_role_name The name of the role which will be assumed by EventBridge rules for Jira integration string "SecurityHubFindingsManagerJiraEventBridge" no
jira_integration Findings Manager - Jira integration settings
object({
enabled = optional(bool, false)
autoclose_enabled = optional(bool, false)
autoclose_comment = optional(string, "Security Hub finding has been resolved. Autoclosing the issue.")
autoclose_transition_name = optional(string, "Close Issue")
credentials_secret_arn = string
exclude_account_ids = optional(list(string), [])
finding_severity_normalized_threshold = optional(number, 70)
issue_custom_fields = optional(map(string), {})
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-findings-manager-jira")
iam_role_name = optional(string, "SecurityHubFindingsManagerJiraLambda")
log_level = optional(string, "INFO")
memory_size = optional(number, 256)
timeout = optional(number, 60)
}), {
name = "securityhub-findings-manager-jira"
iam_role_name = "SecurityHubFindingsManagerJiraLambda"
log_level = "INFO"
memory_size = 256
timeout = 60
security_group_egress_rules = []
})

step_function_settings = optional(object({
log_level = optional(string, "ERROR")
retention = optional(number, 90)
}), {
log_level = "ERROR"
retention = 90
})

})
{
"credentials_secret_arn": null,
"enabled": false,
"project_key": null
}
no
jira_step_function_iam_role_name The name of the role which will be assumed by AWS Step Function for Jira integration string "SecurityHubFindingsManagerJiraStepFunction" no
lambda_runtime The version of Python to use for the Lambda functions string "python3.12" no
rules_filepath Pathname to the file that stores the manager rules string "" no
rules_s3_object_name The S3 object containing the rules to be applied to Security Hub findings manager string "rules.yaml" no
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
subnet_ids The subnet ids where the Lambda functions needs to run list(string) null no
tags A mapping of tags to assign to the resources map(string) {} no

Outputs

Name Description
findings_manager_events_lambda_sg_id This will output the security group id attached to the lambda_findings_manager_events Lambda. This can be used to tune ingress and egress rules.
findings_manager_trigger_lambda_sg_id This will output the security group id attached to the lambda_findings_manager_trigger Lambda. This can be used to tune ingress and egress rules.
jira_lambda_sg_id This will output the security group id attached to the jira_lambda Lambda. This can be used to tune ingress and egress rules.