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

Previously called: Security Hub Findings Suppressor | Based on: https://github.com/schubergphilis/aws-security-hub-suppressor

The Security Hub Findings Manager is a framework designed to automatically suppress findings recorded by the AWS Security Hub service based on a pre-defined and configurable suppression list. 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 suppression of findings this module is also able to create Jira tickets for all NEW findings with a severity higher than a definable threshold.

This logic is intended to be executed in the Audit Account which is part of the AWS Control Tower default account posture and therefore receives events from all child accounts in an organization.

Note The Security Hub Findings Manager does not support consolidated control findings. You have to turn this off if you want to use this module. If you manage this setting via code, set the control findings generator to STANDARD_CONTROL.

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

  • DynamoDB Table, referenced as suppression list
  • 3 Lambda Functions:
    • Security Hub Events: triggered by EventBridge on events from SecurityHub.
    • Security Hub Streams: triggered by changes in the DynamoDB suppression table using a DynamoDB Stream.
    • (optional) Security Hub Jira: triggered by EventBridge on events from SecurityHub with a normalized severity higher than a definable threshold (by default 70)
      • Normalized severity levels:
        • 0 - INFORMATIONAL
        • 1–39 - LOW
        • 40–69 - MEDIUM
        • 70–89 - HIGH
        • 90–100 - CRITICAL
  • (optional) Step Function, to orchestrate the Suppressor and Jira lambdas.
  • YML Configuration File (suppressor.yaml) that contains the list of products and the field mapping

Deployment Modes

There are 3 different deployment modes for this module. All the modes deploy a Lambda function which triggers in response to upserts in DynamoDB table and a EventBridge rule with a pattern which detects the import of a new Security Hub finding. In addition to these, additional resources are deployed depending on the chosen deployment mode.

(Default) Without Jira & ServiceNow Integration

  • The module deploys 1 Lambda function: Suppressor and configures this Lambda as a target to the EventBridge rule.

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 two Lambda functions: Suppressor and Jira 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.

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 SecurityHub fires, an event will be created by EventBridge and dropped onto an SQS Queue.
  • 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)

How it works

The Security Hub Findings Suppressor listens to AWS EventBridge event bus and triggers an execution when a Security Hub Findings - Imported event happens.

Once the event is delivered, the function securityhub-events-suppressor will be triggered and will perform the following steps:

  • Parse the event to determine what is the linked product. For example: Firewall Manager, Inspector or Security Hub.
  • Check whether this product is properly mapped and configured in the YAML configuration file.
  • Extract the AWS resource from the event payload.
  • Upon having the resource and its ARN, the logic checks if that resource is listed in the suppression list.
  • The suppression list contains a collection of items, one per controlId.

How to add a new product to the suppression list

  • All resources required by The Security Hub Findings Manager are deployed by this module. But the module does not update the DynamoDB Table (the suppression list). This can be updates using a variety of methods, via GitHub actions is described below:

  • In the repository calling this module, create a folder called sechub-suppressor, add requirements.txt, put_suppressions.py, and suppressions.yml to this folder. Example files are stored in this module under files/dynamodb-upserts-artifacts. An example GitHub action is stored in this folder as well.

  • Add a new element to the suppressions.yml configuration file containing the product name, key and status. Key and status fields must be JMESPath expressions.

    • Fields:
      • controlId: the key field from the event (it is usually a Control Id or a RuleId present in the event)
        • action: the status that will be applied in Security Hub
        • dry_run: a read-only mode to preview what the logic will be handling
        • notes: notes added to the security hub finding. Usually it is a Jira Ticket with the exception approval
        • rules: a list of regular expressions to be matched against the resource ARN present in the EventBridge Event
  • Commit your changes, push and merge. The pipeline will automatically maintain the set of suppressions and store them in DynamoDB. If all above steps succeed, the finding is suppressed.

Examples

Suppress a finding where the resource is the account, i.e. the 'MFA should be enabled for all IAM users that have a console password' finding:

Suppressions:
  "1.13":
    - action: SUPPRESSED
      rules:
        - ^AWS::::Account:[0-9]{12}$
      notes: A note about this suppression

Suppress a finding for all resources in a specific account:

Suppressions:
  "EC2.17":
    - action: SUPPRESSED
      rules:
        - ^arn:aws:[^:]*:[^:]*:111111111111:.*$
      notes: A note about this suppression

Suppress a finding in some accounts (with comments):

Suppressions:
  EC2.17:
    - action: SUPPRESSED
      rules:
        - ^arn:aws:ec2:eu-west-1:111111111111:instance/i-[0-9a-z]+$ # can add comments here like
        - ^arn:aws:ec2:eu-west-1:222222222222:instance/i-[0-9a-z]+$ # the friendly IAM alias to more
        - ^arn:aws:ec2:eu-west-1:333333333333:instance/i-[0-9a-z]+$ # easily identify matches resources
      notes: A note about this suppression

Suppress finding for specific resources:

   EC2.18:
     - action: SUPPRESSED
       rules:
         - ^arn:aws:ec2:eu-west-1:111111111111:security-group/sg-0ae8d23e1d28b1437$
         - ^arn:aws:ec2:eu-west-1:222222222222:security-group/sg-01f1aa5f8407c98b9$
       notes: A note about this suppression

Note There is also a leading ^ and trailing $ as the rule is always matched as a regexp. This means that if you do not start with a ^ and end with a $ it will be matched as a substring and you might match more than anticipated.

Usage

Requirements

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

Providers

Name Version
aws >= 4.9

Modules

Name Source Version
eventbridge_security_hub_suppressor_role github.com/schubergphilis/terraform-aws-mcaf-role v0.3.2
lambda_artifacts_bucket schubergphilis/mcaf-s3/aws ~> 0.11.0
lambda_jira_deployment_package terraform-aws-modules/lambda/aws ~> 3.3.0
lambda_jira_security_hub schubergphilis/mcaf-lambda/aws ~> 1.1.0
lambda_jira_security_hub_role github.com/schubergphilis/terraform-aws-mcaf-role v0.3.2
lambda_security_hub_suppressor_role github.com/schubergphilis/terraform-aws-mcaf-role v0.3.2
lambda_securityhub_events_suppressor schubergphilis/mcaf-lambda/aws ~> 1.1.0
lambda_securityhub_streams_suppressor schubergphilis/mcaf-lambda/aws ~> 1.1.0
lambda_suppressor_deployment_package terraform-aws-modules/lambda/aws ~> 3.3.0
servicenow_integration ./modules/servicenow/ n/a
step_function_security_hub_suppressor_role github.com/schubergphilis/terraform-aws-mcaf-role v0.3.2

Resources

Name Type
aws_cloudwatch_event_rule.securityhub_events_suppressor_failed_events resource
aws_cloudwatch_event_target.lambda_securityhub_events_suppressor resource
aws_cloudwatch_event_target.securityhub_suppressor_orchestrator_step_function resource
aws_dynamodb_table.suppressor_dynamodb_table resource
aws_iam_role_policy_attachment.lambda_jira_security_hub_role_vpc_policy resource
aws_iam_role_policy_attachment.lambda_security_hub_suppressor_role_vpc_policy resource
aws_lambda_event_source_mapping.lambda_securityhub_streams_mapping resource
aws_lambda_permission.allow_eventbridge_to_invoke_suppressor_lambda resource
aws_sfn_state_machine.securityhub_suppressor_orchestrator resource
aws_caller_identity.current data source
aws_iam_policy_document.eventbridge_security_hub_suppressor data source
aws_iam_policy_document.lambda_jira_security_hub data source
aws_iam_policy_document.lambda_security_hub_suppressor data source
aws_iam_policy_document.step_function_security_hub_suppressor 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
dynamodb_deletion_protection The DynamoDB table deletion protection option. bool true no
dynamodb_table The DynamoDB table containing the items to be suppressed in Security Hub string "securityhub-suppression-list" no
eventbridge_suppressor_iam_role_name The name of the role which will be assumed by EventBridge rules string "EventBridgeSecurityHubSuppressorRole" no
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 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 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 The name of the role which will be assumed by both Suppressor Lambda functions string "LambdaSecurityHubSuppressorRole" no
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 The name of the role which will be assumed by Suppressor Step function string "StepFunctionSecurityHubSuppressorRole" no
subnet_ids The subnet ids where the lambda's needs to run list(string) null no
tags A mapping of tags to assign to the resources map(string) {} no

Outputs

Name Description
dynamodb_arn ARN of the DynamoDB table
lambda_jira_security_hub_sg_id This will output the security group id attached to the jira_security_hub Lambda. This can be used to tune ingress and egress rules.
lambda_securityhub_events_suppressor_sg_id This will output the security group id attached to the securityhub_events_suppressor Lambda. This can be used to tune ingress and egress rules.
lambda_securityhub_streams_suppressor_sg_id This will output the security group id attached to the securityhub_streams_suppressor Lambda. This can be used to tune ingress and egress rules.