{"id":14957848,"url":"https://github.com/infralicious/terraform-aws-securityhub-batchupdatefindings","last_synced_at":"2025-10-24T12:31:09.947Z","repository":{"id":242475993,"uuid":"809628717","full_name":"infralicious/terraform-aws-securityhub-batchupdatefindings","owner":"infralicious","description":"Terraform module to update AWS securityhub findings such as suppressions","archived":false,"fork":false,"pushed_at":"2024-09-22T23:47:24.000Z","size":51,"stargazers_count":4,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-05T01:59:49.374Z","etag":null,"topics":["aws","module","security","security-hub","securityhub","suppression","suppressions","terraform","terraform-module"],"latest_commit_sha":null,"homepage":"","language":"HCL","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/infralicious.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-03T06:28:51.000Z","updated_at":"2024-09-22T23:47:27.000Z","dependencies_parsed_at":"2024-06-19T05:58:46.932Z","dependency_job_id":null,"html_url":"https://github.com/infralicious/terraform-aws-securityhub-batchupdatefindings","commit_stats":null,"previous_names":["infralicious/terraform-aws-securityhub-batchupdatefindings"],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infralicious%2Fterraform-aws-securityhub-batchupdatefindings","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infralicious%2Fterraform-aws-securityhub-batchupdatefindings/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infralicious%2Fterraform-aws-securityhub-batchupdatefindings/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/infralicious%2Fterraform-aws-securityhub-batchupdatefindings/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/infralicious","download_url":"https://codeload.github.com/infralicious/terraform-aws-securityhub-batchupdatefindings/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":237964545,"owners_count":19394419,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","module","security","security-hub","securityhub","suppression","suppressions","terraform","terraform-module"],"created_at":"2024-09-24T13:15:42.427Z","updated_at":"2025-10-24T12:31:04.604Z","avatar_url":"https://github.com/infralicious.png","language":"HCL","funding_links":[],"categories":[],"sub_categories":[],"readme":"# terraform-aws-securityhub-batch-update-findings\n\nThis is handy when codifying suppressions using terraform and a map structure such as YAML.\n\n## Usage\n\nExample of module usage\n\n```hcl\nmodule \"securityhub_batch_update_findings\" {\n  source  = \"infralicious/securityhub-batchupdatefindings/aws\"\n  # It's recommended to pin every module to a specific version\n  # version = \"x.x.x\"\n\n  findings            = yamldecode(file(\"${path.module}/findings.yaml\")).findings\n  default_product_arn = \"arn:aws:securityhub:us-east-1:ACCOUNTID:product/ACCOUNTID/default\"\n  default_workflow    = \"SUPPRESSED\"\n  note_suffix         = \"\\n\\nAdded using terraform\"\n}\n```\n\nExample of `findings.yaml` file\n\n```yaml\n# findings.yaml\nfindings:\n  # Every finding should have an adequate note for the suppression.\n  # A single resource can have multiple findings.\n  # We can codify the resource either in the note or in an inline comment.\n  - id: \"arn:aws:securityhub:us-east-1:ACCOUNTID:subscription/aws-foundational-security-best-practices/v/1.0.0/S3.11/finding/e4c171dc-12e6-433b-8a51-a382e8d24e37\"\n    product_arn: \"arn:aws:securityhub:us-east-1:ACCOUNTID:product/ACCOUNTID/default\"\n    note:\n      text: \"INFOSEC-1234: Suppressed since public IP ingress is for data partner\"\n    workflow:\n      status: \"SUPPRESSED\"\n```\n\n## Misc\n\n### Generate the YAML file\n\nThe yaml file can be autogenerated from existing suppressions using this `awscli` command with `yq`.\nRemember to add the `findings` parent key.\nThe `title` and `resource_id` are for inline comments on what the control the finding was for.\n\n```bash\naws securityhub get-findings \\\n  --filters '{\"WorkflowStatus\": [{\"Value\": \"SUPPRESSED\", \"Comparison\": \"EQUALS\"}] }' \\\n  --query 'Findings[].{\n    id: Id,\n    product_arn: ProductArn,\n    note: { text: Note.Text },\n    workflow: { status: `\"SUPPRESSED\"` }\n    title: Title,\n    resource_id: Resources[0].Id,\n  }' | yq -P . \u003e findings.yaml\n```\n\nThis can be done for specific controls and with more information.\nAdditional information in the YAML is helpful and is ignored by the terraform.\n\nHere is an example with `RDS.13`, sorting by engine version, and populating the yaml with the desired fields\n\n```bash\naws securityhub get-findings \\\n  --filters '{\"SeverityLabel\": [{\"Value\": \"HIGH\", \"Comparison\": \"EQUALS\"}], \"WorkflowStatus\": [{\"Value\": \"NEW\", \"Comparison\": \"EQUALS\"}], \"ComplianceSecurityControlId\": [{\"Value\": \"RDS.13\", \"Comparison\": \"EQUALS\"}] }' \\\n  --query 'sort_by(\n    Findings,\n    \u0026Resources[0].Details.AwsRdsDbInstance.EngineVersion\n  )[\n    ?contains(GeneratorId, `\"security-control\"`)\n  ].{\n    id: Id,\n    product_arn: ProductArn,\n    workflow: { status: `\"SUPPRESSED\"` },\n    title: Title,\n    engine_version: Resources[0].Details.AwsRdsDbInstance.EngineVersion,\n    resource_id: Resources[0].Id\n  }' | yq -P . \u003e findings.yaml\n```\n\n### Test\n\n1. Run a plan\n1. Retrieve the existing suppression for a specific finding\n1. Use `terraform apply -target` to suppress and add a note to the same finding\n1. Repeat the previous retrieval to see the new result\n1. Compare with the old result and see if there are differences\n\n### Compare the counts between suppressions and codified suppressions\n\nThis will give the count of suppressions in aws.\n\n```bash\naws securityhub get-findings \\\n  --filters '{\"WorkflowStatus\": [{\"Value\": \"SUPPRESSED\", \"Comparison\": \"EQUALS\"}] }' \\\n  --query 'Findings[] | length(@)\n```\n\nThis will give the codified suppression count.\n\n```bash\nyq '.findings | length' findings.yaml\n```\n\nIf the counts differ, then the clickops'ed suppression(s) can be moved to the yaml file.\n\n### Instead of a single file, use multiple files\n\nIf the `findings.yaml` file is too long, consider breaking it up by each control.\n\n```bash\n~ tree findings/\nfindings\n├── EC2.1.yaml\n├── EC2.2.yaml\n├── EC2.3.yaml\n└── S3.1.yaml\n\n1 directory, 4 files\n```\n\nThe terraform can then be modified\n\n```hcl\nlocals {\n  findings = flatten(concat([\n    for file in fileset(path.module, \"findings/*.yaml\"):\n    yamldecode(file(\"${path.module}/${file}\")).findings\n  ]))\n}\n\nmodule \"securityhub_batch_update_findings\" {\n  source  = \"infralicious/securityhub-batchupdatefindings/aws\"\n  # It's recommended to pin every module to a specific version\n  # version = \"x.x.x\"\n\n  for_each = local.findings\n\n  findings = yamldecode(file(each.key)).findings\n  # ...\n}\n```\n\n---\n\n\u003c!-- BEGIN_TF_DOCS --\u003e\n## Requirements\n\n| Name | Version |\n|------|---------|\n| \u003ca name=\"requirement_terraform\"\u003e\u003c/a\u003e [terraform](#requirement\\_terraform) | \u003e= 1.1.0 |\n| \u003ca name=\"requirement_null\"\u003e\u003c/a\u003e [null](#requirement\\_null) | \u003e 1 |\n\n## Providers\n\n| Name | Version |\n|------|---------|\n| \u003ca name=\"provider_null\"\u003e\u003c/a\u003e [null](#provider\\_null) | \u003e 1 |\n\n## Resources\n\n| Name | Type |\n|------|------|\n| [null_resource.default](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |\n\n## Inputs\n\n| Name | Description | Type | Default | Required |\n|------|-------------|------|---------|:--------:|\n| \u003ca name=\"input_default_product_arn\"\u003e\u003c/a\u003e [default\\_product\\_arn](#input\\_default\\_product\\_arn) | The default product ARN for each finding. This can be overridden using the key `product_arn`. | `string` | n/a | yes |\n| \u003ca name=\"input_findings\"\u003e\u003c/a\u003e [findings](#input\\_findings) | The list of findings to run the awscli command on. | \u003cpre\u003elist(object({\u003cbr\u003e    id = string\u003cbr\u003e    note = object({\u003cbr\u003e      text       = string\u003cbr\u003e      updated_by = optional(string)\u003cbr\u003e    })\u003cbr\u003e    workflow = object({\u003cbr\u003e      status = string\u003cbr\u003e    })\u003cbr\u003e    product_arn        = optional(string)\u003cbr\u003e    verification_state = optional(string)\u003cbr\u003e    confidence         = optional(number)\u003cbr\u003e    criticality        = optional(number)\u003cbr\u003e  }))\u003c/pre\u003e | n/a | yes |\n| \u003ca name=\"input_awscli_additional_arguments\"\u003e\u003c/a\u003e [awscli\\_additional\\_arguments](#input\\_awscli\\_additional\\_arguments) | n/a | `string` | `\"\"` | no |\n| \u003ca name=\"input_awscli_command\"\u003e\u003c/a\u003e [awscli\\_command](#input\\_awscli\\_command) | n/a | `string` | `\"aws\"` | no |\n| \u003ca name=\"input_default_note_updated_by\"\u003e\u003c/a\u003e [default\\_note\\_updated\\_by](#input\\_default\\_note\\_updated\\_by) | The default UpdatedBy for each finding for its note if a note is provided. This can be overridden using the key `note_updatedby`. | `string` | `\"terraform\"` | no |\n| \u003ca name=\"input_default_workflow\"\u003e\u003c/a\u003e [default\\_workflow](#input\\_default\\_workflow) | The default workflow for each finding. This can be overridden using the key `workflow`. | `string` | `\"SUPPRESSED\"` | no |\n| \u003ca name=\"input_dryrun_enabled\"\u003e\u003c/a\u003e [dryrun\\_enabled](#input\\_dryrun\\_enabled) | Whether or not to add an echo before the command to verify the commands prior to applying. | `bool` | `false` | no |\n| \u003ca name=\"input_note_suffix\"\u003e\u003c/a\u003e [note\\_suffix](#input\\_note\\_suffix) | Add a suffix to each note. | `string` | `\"\"` | no |\n\u003c!-- END_TF_DOCS --\u003e\n\n## References\n\n- https://ekantmate.medium.com/how-to-suppress-particular-findings-in-aws-security-hub-using-terraform-558bd3819b31\n- https://github.com/hashicorp/terraform-provider-aws/issues/29164\n- https://registry.terraform.io/modules/infralicious/securityhub-batchupdatefindings/aws\n- https://library.tf/modules/infralicious/securityhub-batchupdatefindings/aws/latest\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finfralicious%2Fterraform-aws-securityhub-batchupdatefindings","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finfralicious%2Fterraform-aws-securityhub-batchupdatefindings","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finfralicious%2Fterraform-aws-securityhub-batchupdatefindings/lists"}