├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE.md ├── README.md ├── asset ├── architecture.png ├── issue-workflow.xml ├── jira-cloud-status.png ├── jira-cloud-transitions.png └── workflow.png ├── conf └── params_prod.sh ├── deploy.sh ├── src ├── code │ ├── config │ │ └── config.json │ ├── security_hub_integration.py │ ├── sync_securityhub.py │ ├── test_securityhub_integration.py │ └── utils.py └── template │ ├── cloudformation_template.yaml │ └── lpt-basic.yaml ├── test.sh └── test ├── custom_new.template ├── custom_notified_existing.template ├── imported_archived_existing.template ├── imported_archived_new.template ├── imported_archived_notified.template ├── imported_new_automated_standard_check.template └── imported_new_notautomated.template /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | **/dist 4 | *.drawio 5 | *.vsdx -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Change Log 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semv 7 | 8 | ## [1.0] - 2022-03-14 9 | 10 | Initial release 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /NOTICE.md: -------------------------------------------------------------------------------- 1 | jira 2 | 3.1.1 3 | BSD License 4 | Copyright (c) 2012, Atlassian Pty Ltd. 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 8 | following conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following 11 | disclaimer. 12 | 13 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # aws-securityhub-jira-software-integration 3 | 4 | This solution supports a bidirectional integration between AWS Security Hub and Jira. Using this solution, you can automatically and manually create and update JIRA tickets from Security Hub findings. Security teams can use this integration to notify developer teams of severe security findings that require action. 5 | 6 | The solution allows you to: 7 | 8 | - Select which Security Hub controls automatically create or update tickets in Jira. 9 | - In the Security Hub console, use Security Hub custom actions to manually escalate tickets in Jira. 10 | - Automatically assign tickets in Jira based on the AWS account tags defined in AWS Organizations. If this tag is not defined, a default assignee is used. 11 | - Automatically suppress Security Hub findings that are marked as false positive or accepted risk in Jira. 12 | - Automatically close a Jira ticket when its related finding is archived in Security Hub. 13 | - Reopen Jira tickets when Security Hub findings reoccur. 14 | 15 | For the architecture diagram, prerequisites, and instructions for using this AWS Prescriptive Guidance pattern, see [Bidirectionally integrate AWS Security Hub with Jira software](https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/bidirectionally-integrate-aws-security-hub-with-jira-software.html). 16 | -------------------------------------------------------------------------------- /asset/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-securityhub-jira-software-integration/65898ab05357015fb39718d7f78aae2d38303368/asset/architecture.png -------------------------------------------------------------------------------- /asset/issue-workflow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JIRAUSER10000 6 | 1638056261643 7 | 8 | 9 | 10 | 11 | Create Issue 12 | com.atlassian.jira.workflow.validator.PermissionValidator 13 | 14 | 15 | 16 | 17 | 18 | 19 | com.atlassian.jira.workflow.function.issue.IssueCreateFunction 20 | 21 | 22 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 23 | 24 | 25 | 1 26 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 1 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 45 | 46 | 47 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 48 | 49 | 50 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 51 | 52 | 53 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 54 | 55 | 56 | 13 57 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 71 | 72 | 73 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 74 | 75 | 76 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 77 | 78 | 79 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 80 | 81 | 82 | 13 83 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 97 | 98 | 99 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 100 | 101 | 102 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 103 | 104 | 105 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 106 | 107 | 108 | 13 109 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 10002 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 128 | 129 | 130 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 131 | 132 | 133 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 134 | 135 | 136 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 137 | 138 | 139 | 13 140 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 154 | 155 | 156 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 157 | 158 | 159 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 160 | 161 | 162 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 163 | 164 | 165 | 13 166 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 180 | 181 | 182 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 183 | 184 | 185 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 186 | 187 | 188 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 189 | 190 | 191 | 13 192 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 10003 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 211 | 212 | 213 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 214 | 215 | 216 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 217 | 218 | 219 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 220 | 221 | 222 | 13 223 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 237 | 238 | 239 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 240 | 241 | 242 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 243 | 244 | 245 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 246 | 247 | 248 | 13 249 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 10004 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 268 | 269 | 270 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 271 | 272 | 273 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 274 | 275 | 276 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 277 | 278 | 279 | 13 280 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 10005 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 299 | 300 | 301 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 302 | 303 | 304 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 305 | 306 | 307 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 308 | 309 | 310 | 13 311 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 325 | 326 | 327 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 328 | 329 | 330 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 331 | 332 | 333 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 334 | 335 | 336 | 13 337 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 10006 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 356 | 357 | 358 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 359 | 360 | 361 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 362 | 363 | 364 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 365 | 366 | 367 | 13 368 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 382 | 383 | 384 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 385 | 386 | 387 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 388 | 389 | 390 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 391 | 392 | 393 | 13 394 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 10007 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 413 | 414 | 415 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 416 | 417 | 418 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 419 | 420 | 421 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 422 | 423 | 424 | 13 425 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 439 | 440 | 441 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 442 | 443 | 444 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 445 | 446 | 447 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 448 | 449 | 450 | 13 451 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 5 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 470 | 471 | 472 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 473 | 474 | 475 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 476 | 477 | 478 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 479 | 480 | 481 | 13 482 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 10008 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 501 | 502 | 503 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 504 | 505 | 506 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 507 | 508 | 509 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 510 | 511 | 512 | 13 513 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 527 | 528 | 529 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 530 | 531 | 532 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 533 | 534 | 535 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 536 | 537 | 538 | 13 539 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 10009 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | com.atlassian.jira.workflow.function.issue.UpdateIssueStatusFunction 558 | 559 | 560 | com.atlassian.jira.workflow.function.misc.CreateCommentFunction 561 | 562 | 563 | com.atlassian.jira.workflow.function.issue.GenerateChangeHistoryFunction 564 | 565 | 566 | com.atlassian.jira.workflow.function.issue.IssueReindexFunction 567 | 568 | 569 | 13 570 | com.atlassian.jira.workflow.function.event.FireIssueEventFunction 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | -------------------------------------------------------------------------------- /asset/jira-cloud-status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-securityhub-jira-software-integration/65898ab05357015fb39718d7f78aae2d38303368/asset/jira-cloud-status.png -------------------------------------------------------------------------------- /asset/jira-cloud-transitions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-securityhub-jira-software-integration/65898ab05357015fb39718d7f78aae2d38303368/asset/jira-cloud-transitions.png -------------------------------------------------------------------------------- /asset/workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-securityhub-jira-software-integration/65898ab05357015fb39718d7f78aae2d38303368/asset/workflow.png -------------------------------------------------------------------------------- /conf/params_prod.sh: -------------------------------------------------------------------------------- 1 | # Stack parameter 2 | export ORG_ACCOUNT_ID='' # ID for Organization Management account 3 | export ORG_ROLE=OrganizationsReadOnlyAccess 4 | export AWS_REGION=eu-west-1 5 | export EXTERNAL_ID='' #Optional 6 | export JIRA_DEFAULT_ASSIGNEE='' #ID for default assignee for all Security Issues 7 | export JIRA_INSTANCE="" #HTTPS address for JIRA server (exclude schema "https://") 8 | export JIRA_PROJECT_KEY="" # JIRA Project Key 9 | export ISSUE_TYPE="" #JIRA Issuetype name: Example, "Bug", "Security Issue" 10 | export REGIONS=("eu-west-1") # List of regions deployed 11 | 12 | PARAMETERS=( 13 | "OrganizationManagementAccountId=$ORG_ACCOUNT_ID" 14 | "JIRADefaultAssignee=$JIRA_DEFAULT_ASSIGNEE" 15 | "OrganizationAccessExternalId=$EXTERNAL_ID" 16 | "AutomatedChecks=$AUTOMATED_CHECKS" 17 | "JIRAInstance=$JIRA_INSTANCE" 18 | "JIRAIssueType"="$ISSUE_TYPE" 19 | "JIRAProjectKey"="$JIRA_PROJECT_KEY" 20 | ) 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eio pipefail 3 | 4 | function print_usage (){ 5 | echo "------------------------------------------------------------------------------" 6 | echo " This script deploys the code without pipeline" 7 | echo "------------------------------------------------------------------------------" 8 | echo "Usage: ./deploy.sh [env]" 9 | echo "For example 1: ./deploy.sh prod" 10 | echo "For example 2: ./deploy.sh local" 11 | echo 12 | echo "environment points to config file to load" 13 | } 14 | 15 | # Load the config 16 | if [ -z $1 ]; then 17 | echo "Assuming params already configured" 18 | else 19 | echo "loading params from \"conf/params_${1}.sh\" with environment variables" 20 | . conf/params_${1}.sh 21 | fi 22 | 23 | for regx in ${REGIONS[@]}; do 24 | 25 | ENVIRONMENT="$1" 26 | export AWS_REGION=$regx 27 | ACCOUNT=$(aws sts get-caller-identity --query Account --output text) 28 | BASE="securityhub-jira-$1" 29 | 30 | function ensure_basic { 31 | $AWS_CFN deploy --stack-name "${BASE}-artifact" --template-file src/template/lpt-basic.yaml --no-fail-on-empty-changeset --parameter-overrides BucketName="artifact-securityhub-jira-$ENVIRONMENT-$AWS_REGION-$ACCOUNT" 32 | BUCKET=$($AWS_CFN describe-stacks --stack-name "${BASE}-artifact" --query Stacks[].Outputs[*].[OutputKey,OutputValue] --output text | grep LptBucket | awk '{ print $2 }') 33 | } 34 | 35 | function ensure_environment_variable_is_set { 36 | test -n "${!1}" || { echo >&2 "Required environment variable '$1' not found. Aborting."; exit 1; } 37 | } 38 | 39 | ensure_environment_variable_is_set AWS_REGION 40 | 41 | export AWS_CFN="aws cloudformation --region $AWS_REGION" 42 | 43 | ensure_basic 44 | 45 | # Clean up dist folder 46 | rm -r dist/* || mkdir -p dist 47 | 48 | # Restore .gitignore 49 | #echo '*' > dist/.gitignore 50 | 51 | # Copy new versions of Lambda 52 | cp -r src/* dist/ 53 | 54 | # Install JIRA from master (assign_issue GDPR) 55 | INSTALL_PREREQUISITES=$(cd dist/code && pip3 install -t . jira==3.1.1) 56 | 57 | # ZIP Dependency 58 | INSTALL_LAMBDA=$(cd dist/code && zip -r9 ../lambda.zip ./) 59 | 60 | # Package cloudformation into S3 artifact 61 | $AWS_CFN package --template src/template/cloudformation_template.yaml --s3-bucket "$BUCKET" --output-template-file dist/template/deployment-version.yaml 62 | 63 | # Validate template 64 | $AWS_CFN validate-template --template-body file://$(pwd)/dist/template/deployment-version.yaml > /dev/null || exit 1 65 | 66 | $AWS_CFN deploy --template-file dist/template/deployment-version.yaml --capabilities CAPABILITY_NAMED_IAM --stack-name "$BASE-solution" --parameter-overrides "${PARAMETERS[@]}" 67 | 68 | done -------------------------------------------------------------------------------- /src/code/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "Controls" : { 3 | "eu-west-1": [ 4 | "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0/rule/1.22" 5 | ], 6 | "default": [ 7 | ] 8 | } 9 | } -------------------------------------------------------------------------------- /src/code/security_hub_integration.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | import logging 5 | import json 6 | import os 7 | import boto3 8 | import sys 9 | from jira import JIRA 10 | import utils 11 | from datetime import datetime, timezone 12 | 13 | # set global logger 14 | logger = logging.getLogger('') 15 | logger.setLevel(logging.INFO) 16 | logging.getLogger().addHandler(logging.StreamHandler()) 17 | 18 | securityhub = boto3.client('securityhub') 19 | secretsmanager = boto3.client('secretsmanager') 20 | 21 | 22 | def finding_parser(finding): 23 | account = finding["AwsAccountId"] 24 | description = finding["Description"] 25 | severity = finding["Severity"]["Label"] 26 | title = finding["Title"] 27 | finding_id = finding["Id"] 28 | product_arn = finding["ProductArn"] 29 | resources = [resource.get('Id') for resource in finding["Resources"]] 30 | status = finding["Workflow"]["Status"] 31 | recordstate = finding["RecordState"] 32 | 33 | return account, description, severity, title, finding_id, product_arn, resources, status, recordstate # returns data 34 | 35 | 36 | def create_jira(jira_client, project_key, issuetype_name, product_arn, account, region, description, resources, severity, title, id): 37 | 38 | resources = "Resources: %s" % resources if not "default" in product_arn else "" 39 | 40 | new_issue = utils.create_ticket( 41 | jira_client, project_key, issuetype_name, account, region, description, resources, severity, title, id) 42 | utils.update_securityhub( 43 | securityhub, id, product_arn, "NOTIFIED", 'JIRA Ticket: {0}'.format(new_issue)) 44 | utils.update_jira_assignee(jira_client, new_issue, account) 45 | 46 | 47 | def is_automated_check(finding): 48 | script_dir=os.path.dirname(__file__) 49 | with open(os.path.join(script_dir, "config/config.json")) as config_file: 50 | automated_controls=json.load(config_file) 51 | region=os.environ['AWS_REGION'] 52 | if region in automated_controls["Controls"]: 53 | return finding["GeneratorId"] in automated_controls["Controls"][region] 54 | else: 55 | return finding["GeneratorId"] in automated_controls["Controls"]["default"] 56 | 57 | def lambda_handler(event, context): # Main function 58 | utils.validate_environments( 59 | ["JIRA_API_TOKEN", "AWS_REGION"]) 60 | 61 | account_id=event["account"] 62 | region=os.environ['AWS_REGION'] 63 | os.environ.get("JIRA_CREDENTIALS") 64 | project_key=os.environ['JIRA_PROJECT_KEY'] 65 | issuetype_name=os.environ['JIRA_ISSUETYPE'] 66 | jira_instance = os.environ['JIRA_INSTANCE'] 67 | jira_credentials = os.environ.get("JIRA_API_TOKEN") 68 | 69 | for finding in event["detail"]["findings"]: 70 | account, description, severity, title, finding_id, product_arn, resources, status, recordstate=finding_parser( 71 | finding) 72 | try: 73 | if event["detail-type"] == "Security Hub Findings - Custom Action" and event["detail"]["actionName"] == "CreateJiraIssue": 74 | if status != "NEW": 75 | raise UserWarning( 76 | "Finding workflow is not NEW: %s" % finding_id) 77 | if recordstate != "ACTIVE": 78 | raise UserWarning("Finding is not ACTIVE: %s" % finding_id) 79 | jira_client=utils.get_jira_client(secretsmanager,jira_instance,jira_credentials) 80 | jira_issue=utils.get_jira_finding( 81 | jira_client, finding_id, project_key, issuetype_name) 82 | if not jira_issue: 83 | logger.info( 84 | "Creating ticket manually for {0}".format(finding_id)) 85 | create_jira(jira_client, project_key, issuetype_name, product_arn, account, 86 | region, description, resources, severity, title, finding_id) 87 | else: 88 | logger.info("Finding {0} already reported in ticket {1}".format( 89 | finding_id, jira_issue)) 90 | elif event["detail-type"] == "Security Hub Findings - Imported": 91 | if recordstate == "ARCHIVED" and status == "NOTIFIED": 92 | # Move to resolved 93 | jira_client=utils.get_jira_client(secretsmanager,jira_instance,jira_credentials) 94 | jira_issue=utils.get_jira_finding( 95 | jira_client, finding_id, project_key, issuetype_name) 96 | 97 | if(jira_issue): 98 | utils.close_jira_issue(jira_client, jira_issue) 99 | utils.update_securityhub(securityhub, finding_id, product_arn, "RESOLVED", 100 | 'Closed JIRA Ticket {0}'.format(jira_issue)) 101 | elif recordstate == "ACTIVE" and status == "RESOLVED": 102 | # Move to resolved 103 | jira_client=utils.get_jira_client(secretsmanager,jira_instance,jira_credentials) 104 | jira_issue=utils.get_jira_finding( 105 | jira_client, finding_id, project_key, issuetype_name) 106 | 107 | if(jira_issue) and utils.is_closed(jira_client, jira_issue): 108 | # Reopen closed ticket as it was re-detected 109 | utils.reopen_jira_issue(jira_client, jira_issue) 110 | utils.update_securityhub(securityhub, finding_id, product_arn, "NOTIFIED", 111 | 'Reopening JIRA Ticket {0}'.format(jira_issue)) 112 | elif recordstate == "ACTIVE" and status == "NEW" and is_automated_check(finding): 113 | # Check if in automatically list of findings to create automatically 114 | jira_client=utils.get_jira_client(secretsmanager,jira_instance,jira_credentials) 115 | jira_issue=utils.get_jira_finding( 116 | jira_client, finding_id, project_key, issuetype_name) 117 | 118 | if not jira_issue: 119 | logger.info( 120 | "Creating ticket automatically for {0}".format(finding_id)) 121 | create_jira(jira_client, project_key, issuetype_name, product_arn, account, 122 | region, description, resources, severity, title, finding_id) 123 | 124 | else: 125 | logger.info( 126 | "Not performing any action for {}".format(finding_id)) 127 | else: 128 | logger.info("Unknown custom action {} {}".format( 129 | event["detail-type"], event["detail"]["actionName"])) 130 | except UserWarning as e: 131 | logger.error(e) 132 | 133 | 134 | if __name__ == "__main__": 135 | if not len(sys.argv) - 1 > 0: 136 | print("Usage: python security_hub_integration.py event.template") 137 | template=sys.argv[1] 138 | with open(template, "r") as event_file: 139 | security_hub_event=json.load(event_file) 140 | local_time=datetime.now(timezone.utc).astimezone().isoformat() 141 | for securityhub_finding in security_hub_event["detail"]["findings"]: 142 | securityhub_finding["UpdatedAt"]=local_time 143 | lambda_handler(security_hub_event, None) 144 | -------------------------------------------------------------------------------- /src/code/sync_securityhub.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | import logging 5 | import sys 6 | import os 7 | import boto3 8 | from jira import JIRA 9 | import utils 10 | 11 | sys.path.append('lib') 12 | 13 | logger = logging.getLogger('') 14 | logger.setLevel(logging.INFO) 15 | logging.getLogger().addHandler(logging.StreamHandler()) 16 | 17 | securityhub = boto3.client('securityhub') 18 | secretsmanager = boto3.client('secretsmanager') 19 | 20 | 21 | def lambda_handler(event, context): 22 | utils.validate_environments( 23 | ["JIRA_API_TOKEN", "AWS_REGION"]) 24 | 25 | region = os.environ['AWS_REGION'] 26 | jira_instance = os.environ['JIRA_INSTANCE'] 27 | jira_credentials = os.environ.get("JIRA_API_TOKEN") 28 | project_key = os.environ['JIRA_PROJECT_KEY'] 29 | issuetype_name = os.environ['JIRA_ISSUETYPE'] 30 | 31 | jira_client = utils.get_jira_client(secretsmanager,jira_instance,jira_credentials) 32 | latest = utils.get_jira_latest_updated_findings( 33 | jira_client, project_key, issuetype_name) 34 | for ticket in latest: 35 | try: 36 | logger.info("Checking {0}".format(ticket)) 37 | finding_id = utils.get_finding_id_from(ticket) 38 | if finding_id: 39 | results = securityhub.get_findings(Filters={"Id": [{ 40 | 'Value': finding_id, 41 | 'Comparison': 'EQUALS' 42 | }]} 43 | ) 44 | if len(results["Findings"]) > 0: 45 | finding = results["Findings"][0] 46 | finding_status = finding["Workflow"]["Status"] 47 | product_arn = finding["ProductArn"] 48 | record_state = finding["RecordState"] 49 | 50 | if utils.is_suppressed(jira_client, ticket) and finding_status != "SUPPRESSED": 51 | # If accepted or false positive in JIRA, mark as suppressed in Security Hub 52 | logger.info("Suppress {0} based on {1}".format( 53 | finding_id, ticket)) 54 | utils.update_securityhub( 55 | securityhub, finding_id, product_arn, "SUPPRESSED", 'JIRA Ticket: {0}'.format(ticket)) 56 | 57 | if utils.is_closed(jira_client, ticket) and finding_status != "RESOLVED": 58 | # If closed in JIRA, mark as Resolved in Security Hub 59 | logger.info("Marking as resolved {0} based on {1}".format( 60 | finding_id, ticket)) 61 | utils.update_securityhub( 62 | securityhub, finding_id, product_arn, "RESOLVED", 'JIRA Ticket was resolved') 63 | 64 | if not utils.is_closed(jira_client, ticket) and not utils.is_suppressed(jira_client, ticket): 65 | if record_state != "ARCHIVED" and finding_status != "NOTIFIED": 66 | # If Security Hub finding is still ACTIVE but supressed and JIRA is not closed, move back to NOTIFIED 67 | logger.info("Reopen {0} based on {1}".format( 68 | finding_id, ticket)) 69 | utils.update_securityhub( 70 | securityhub, finding_id, product_arn, "NOTIFIED", 'JIRA Ticket: {0}'.format(ticket)) 71 | 72 | if record_state == "ARCHIVED" and finding_status != "RESOLVED": 73 | # If Security Hub finding is still ARCHIVED, then it was resolved, close JIRA issue and resolve Security Hub 74 | logger.info("Closing {1} based on {0} archived status".format( 75 | finding_id, ticket)) 76 | utils.close_jira_issue(jira_client, ticket) 77 | utils.update_securityhub( 78 | securityhub, finding_id, product_arn, "RESOLVED", 'Closed JIRA Ticket {0}'.format(ticket)) 79 | else: 80 | raise UserWarning( 81 | "aws-sec label found for {0} but couldn't find the related Security Hub finding".format(ticket)) 82 | except UserWarning as e: 83 | logger.error(e) 84 | 85 | 86 | if __name__ == "__main__": 87 | lambda_handler(None, None) 88 | -------------------------------------------------------------------------------- /src/code/test_securityhub_integration.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | import unittest 5 | from unittest.mock import patch, MagicMock 6 | from datetime import datetime, timezone 7 | import json 8 | import security_hub_integration 9 | import warnings 10 | import utils 11 | import logging 12 | 13 | class TestSecurityHubtoJiraIntegration(unittest.TestCase): 14 | 15 | def setUp(self): 16 | # https://github.com/boto/boto3/issues/454 17 | warnings.filterwarnings( 18 | "ignore", category=ResourceWarning, message="unclosed.*") 19 | 20 | def load_test(self, template): 21 | with open(template, "r") as event_file: 22 | event = json.load(event_file) 23 | local_time = datetime.now(timezone.utc).astimezone().isoformat() 24 | for finding in event["detail"]["findings"]: 25 | finding["UpdatedAt"] = local_time 26 | return event 27 | 28 | @patch('security_hub_integration.utils.get_jira_client') 29 | @patch('security_hub_integration.utils.create_ticket') 30 | @patch('security_hub_integration.utils.update_securityhub') 31 | @patch('security_hub_integration.utils.update_jira_assignee') 32 | def test_custom_action_new_finding(self, get_jira_client, create_ticket, update_securityhub, update_jira_assignee): 33 | event = self.load_test('test/custom_new.template') 34 | get_jira_client.return_value = {} 35 | security_hub_integration.lambda_handler(event, None) 36 | create_ticket.assert_called_once() 37 | update_securityhub.assert_called_once() 38 | update_jira_assignee.assert_called_once() 39 | 40 | @patch('security_hub_integration.utils.update_securityhub') 41 | @patch('security_hub_integration.utils.close_jira_issue') 42 | def test_imported_archived_new_finding(self, update_securityhub, close_jira_issue): 43 | event = self.load_test('test/imported_archived_new.template') 44 | security_hub_integration.lambda_handler(event, None) 45 | update_securityhub.assert_not_called() 46 | close_jira_issue.assert_not_called() 47 | 48 | @patch('security_hub_integration.utils.get_jira_client') 49 | @patch('security_hub_integration.utils.get_jira_finding') 50 | @patch('security_hub_integration.utils.update_securityhub') 51 | @patch('security_hub_integration.utils.close_jira_issue') 52 | def test_imported_archived_existing_ticket(self, get_jira_client, get_jira_finding, update_securityhub, close_jira_issue): 53 | event = self.load_test( 54 | 'test/imported_archived_existing.template') 55 | get_jira_client.return_value = {} 56 | get_jira_finding.return_value = ["Mock-123"] 57 | security_hub_integration.lambda_handler(event, None) 58 | update_securityhub.assert_called() 59 | close_jira_issue.assert_called() 60 | 61 | @patch('security_hub_integration.utils.get_jira_client') 62 | @patch('security_hub_integration.utils.create_ticket') 63 | @patch('security_hub_integration.utils.update_securityhub') 64 | @patch('security_hub_integration.utils.update_jira_assignee') 65 | def test_imported_new_automated_standard_finding(self, get_jira_client, create_ticket, update_securityhub, update_jira_assignee): 66 | event = self.load_test( 67 | 'test/imported_new_automated_standard_check.template') 68 | get_jira_client.return_value = {} 69 | security_hub_integration.lambda_handler(event, None) 70 | create_ticket.assert_called() 71 | update_securityhub.assert_called() 72 | update_jira_assignee.assert_called() 73 | 74 | @patch('security_hub_integration.utils.create_ticket') 75 | @patch('security_hub_integration.utils.update_securityhub') 76 | @patch('security_hub_integration.utils.update_jira_assignee') 77 | def test_imported_new_not_automated_finding(self, create_ticket, update_securityhub, update_jira_assignee): 78 | event = self.load_test('test/imported_new_notautomated.template') 79 | security_hub_integration.lambda_handler(event, None) 80 | create_ticket.assert_not_called() 81 | update_securityhub.assert_not_called() 82 | update_jira_assignee.assert_not_called() 83 | 84 | 85 | if __name__ == '__main__': 86 | unittest.main() 87 | -------------------------------------------------------------------------------- /src/code/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | import logging 5 | import os 6 | import hashlib 7 | import base64 8 | import re 9 | import boto3 10 | import json 11 | from jira import JIRA, JIRAError 12 | from botocore.exceptions import ClientError 13 | import jira 14 | 15 | logger = logging.getLogger('') 16 | 17 | 18 | def validate_environments(envs): 19 | undefined = [] 20 | 21 | for env in envs: 22 | is_defined = env in os.environ 23 | if not is_defined: 24 | undefined.append(env) 25 | logger.error('Environment variable %s not set', env) 26 | if len(undefined) > 0: 27 | raise UserWarning( 28 | "Missing environment variables: {}".format(",".join(undefined))) 29 | 30 | 31 | def assume_role(aws_account_number, role_name, external_id=None): 32 | """ 33 | Assumes the provided role in each account and returns a GuardDuty client 34 | :param aws_account_number: AWS Account Number 35 | :param role_name: Role to assume in target account 36 | """ 37 | sts_client = boto3.client('sts') 38 | partition = sts_client.get_caller_identity()['Arn'].split(":")[1] 39 | 40 | parameters = {"RoleArn": 'arn:{}:iam::{}:role/{}'.format( 41 | partition, 42 | aws_account_number, 43 | role_name, 44 | ), "RoleSessionName": "SecurityScanner"} 45 | 46 | if external_id: 47 | parameters["ExternalId"] = external_id 48 | response = sts_client.assume_role(**parameters) 49 | 50 | account_session = boto3.Session( 51 | aws_access_key_id=response['Credentials']['AccessKeyId'], 52 | aws_secret_access_key=response['Credentials']['SecretAccessKey'], 53 | aws_session_token=response['Credentials']['SessionToken']) 54 | 55 | session = {} 56 | session['session'] = account_session 57 | session['aws_access_key_id'] = response['Credentials']['AccessKeyId'] 58 | session['aws_secret_access_key'] = response['Credentials']['SecretAccessKey'] 59 | session['aws_session_token'] = response['Credentials']['SessionToken'] 60 | return session 61 | 62 | 63 | def update_unassigned_ticket(jira_client, issue, message): 64 | jira_client.assign_issue(issue, os.environ.get("JIRA_DEFAULT_ASSIGNEE")) 65 | issue.fields.labels.append("aws-sec-not-assigned") 66 | issue.update(fields={"labels": issue.fields.labels}) 67 | jira_client.add_comment(issue, message) 68 | 69 | 70 | def get_account_organization_tags(account): 71 | org_id = os.environ.get("ORG_ACCOUNT_ID") 72 | org_role = os.environ.get("ORG_ROLE") 73 | external_id = os.environ.get("EXTERNAL_ID") 74 | if org_role: 75 | session = assume_role(org_id, org_role, external_id)['session'] 76 | org_client = session.client('organizations') 77 | tags = org_client.list_tags_for_resource(ResourceId=account) 78 | return tags 79 | return {} 80 | 81 | # assign ticket based on Organization account 82 | 83 | 84 | def update_jira_assignee(jira_client, issue, account): 85 | tags = get_account_organization_tags(account) 86 | merged_tags = {} 87 | for tag in tags['Tags']: 88 | merged_tags[tag['Key']] = tag['Value'] 89 | if merged_tags.get("SecurityContactID"): 90 | assignee = merged_tags.get("SecurityContactID") 91 | try: 92 | jira_client.assign_issue(issue, assignee) 93 | except JIRAError: 94 | logger.warning("User {0} couldn't be assigned to {1}".format( 95 | assignee, jira_client)) 96 | message = "Security responsible not in JIRA\n Id: {0}".format( 97 | assignee) 98 | update_unassigned_ticket(jira_client, issue, message) 99 | else: 100 | logger.info("Account owner could not be identified {0} - {1}".format(account,issue)) 101 | message = "Account owner could not be identified" 102 | update_unassigned_ticket(jira_client, issue, message) 103 | 104 | 105 | def get_finding_id_from(jira_ticket): 106 | if jira_ticket is None or jira_ticket.fields.description is None: 107 | logger.warning("The jira_ticket or its description is None, cannot extract finding ID.") 108 | return None 109 | 110 | description = jira_ticket.fields.description 111 | # Searching for regex in description 112 | matched = re.search( 113 | 'Id%3D%255Coperator%255C%253AEQUALS%255C%253A([a-zA-Z0-9\\.\\-\\_\\:\\/]+)', description) 114 | return matched.group(1) if matched and matched.group(1) else None 115 | 116 | def get_jira_client(secretsmanager_client,jira_instance,jira_credentials_secret): 117 | region = os.environ['AWS_REGION'] 118 | jira_credentials = get_secret(secretsmanager_client, jira_credentials_secret, region) 119 | auth_type = jira_credentials['auth'] 120 | jira_client = None 121 | if auth_type == "basic_auth": 122 | jira_client=JIRA("https://"+jira_instance, basic_auth=(jira_credentials['email'], jira_credentials['token'])) 123 | else: 124 | jira_client=JIRA(jira_instance, token_auth=jira_credentials['token']) 125 | 126 | return jira_client 127 | 128 | 129 | def get_finding_digest(finding_id): 130 | m = hashlib.md5() # nosec 131 | m.update(finding_id.encode("utf-8")) 132 | one_way_digest = m.hexdigest() 133 | return one_way_digest 134 | 135 | 136 | def get_jira_finding(jira_client, finding_id,project_key, issuetype_name): 137 | digest = get_finding_digest(finding_id) 138 | created_before = jira_client.search_issues( 139 | 'Project = {0} AND issuetype = "{1}" AND (labels = aws-sec-{2})'.format(project_key, issuetype_name,digest)) 140 | # Should only exist once 141 | return created_before[0] if len(created_before) > 0 else None 142 | 143 | def get_jira_latest_updated_findings(jira_client,project_key, issuetype_name): 144 | return jira_client.search_issues('Project = {0} AND issuetype = "{1}" AND updated >= -2w'.format(project_key, issuetype_name), maxResults=False) 145 | 146 | # creates ticket based on the Security Hub finding 147 | def create_ticket(jira_client, project_key, issuetype_name, account, region, description, resources, severity, title, id): 148 | digest = get_finding_digest(id) 149 | 150 | finding_link = "https://{0}.console.aws.amazon.com/securityhub/home?region={0}#/findings?search=Id%3D%255Coperator%255C%253AEQUALS%255C%253A{1}".format( 151 | region, id) 152 | issue_dict = { 153 | "project": {"key": project_key}, 154 | "issuetype": {"name": issuetype_name}, 155 | "summary": "AWS Security Issue :: {} :: {} :: {}".format(account, region, title), 156 | "labels": ["aws-sec-%s" % digest], 157 | "priority": {"name": severity.capitalize()}, 158 | "description": """ *What is the problem?* 159 | We detected a security finding within the AWS account {} you are responsible for. 160 | {} 161 | 162 | {} 163 | 164 | [Link to Security Hub finding|{}] 165 | 166 | *What do I need to do with the ticket?* 167 | * Access the account and verify the configuration. 168 | Acknowledge working on ticket by moving it to "Allocated for Fix". 169 | Once fixed, moved to test fix so Security validates the issue is addressed. 170 | * If you think risk should be accepted, move it to "Awaiting Risk acceptance". 171 | This will require review by a Security engineer. 172 | * If you think is a false positive, transition it to "Mark as False Positive". 173 | This will get reviewed by a Security engineer and reopened/closed accordingly. 174 | """.format(account, resources, description, finding_link) 175 | } 176 | new_issue = jira_client.create_issue( 177 | fields=issue_dict) # writes dict to jira 178 | return new_issue 179 | 180 | 181 | def update_securityhub(securityhub_client, id, product_arn, status, note): 182 | response = securityhub_client.batch_update_findings( 183 | FindingIdentifiers=[ 184 | {'Id': id, 185 | 'ProductArn': product_arn 186 | }], 187 | Workflow={'Status': status}, Note={ 188 | 'Text': note, 189 | 'UpdatedBy': 'security-hub-integration' 190 | }) 191 | if response.get('FailedFindings'): 192 | for element in response['FailedFindings']: 193 | logger.error("Update error - FindingId {0}".format(element["Id"])) 194 | logger.error( 195 | "Update error - ErrorCode {0}".format(element["ErrorCode"])) 196 | logger.error( 197 | "Update error - ErrorMessage {0}".format(element["ErrorMessage"])) 198 | 199 | 200 | def is_closed(jira_client, issue): 201 | return issue.fields.status.name == "Resolved" 202 | 203 | 204 | def is_suppressed(jira_client, issue): 205 | return issue.fields.status.name == "Risk approved" or issue.fields.status.name == "Accepted false positive" 206 | 207 | 208 | def is_test_fix(jira_client, issue): 209 | return issue.fields.status.name == "Test fix" 210 | 211 | 212 | def reopen_jira_issue(jira_client, issue): 213 | jira_client.transition_issue(issue, 'Reopen') 214 | 215 | 216 | def close_jira_issue(jira_client, issue): 217 | status = issue.fields.status.name 218 | if status in ["Open"]: 219 | jira_client.transition_issue(issue, "Allocate for fix") 220 | if status in ["Open", "Allocated for fix"]: 221 | jira_client.transition_issue(issue, "Mark for testing") 222 | if status in ["Open", "Allocated for fix", "Test fix"]: 223 | jira_client.transition_issue(issue, "Mark as resolved", comment="Resolved automatically by security-hub-integration") 224 | else: 225 | logger.error( 226 | "Cannot transition issue {0} as it's either marked as closed, awaiting risk acceptance or as false positive".format(issue)) 227 | 228 | 229 | def get_secret(client, secret_arn, region_name): 230 | 231 | secret = None 232 | try: 233 | get_secret_value_response = client.get_secret_value( 234 | SecretId=secret_arn 235 | ) 236 | except ClientError as e: 237 | if e.response['Error']['Code'] == 'DecryptionFailureException': 238 | raise e 239 | elif e.response['Error']['Code'] == 'InternalServiceErrorException': 240 | raise e 241 | elif e.response['Error']['Code'] == 'InvalidParameterException': 242 | raise e 243 | elif e.response['Error']['Code'] == 'InvalidRequestException': 244 | raise e 245 | elif e.response['Error']['Code'] == 'ResourceNotFoundException': 246 | raise e 247 | else: 248 | if 'SecretString' in get_secret_value_response: 249 | secret = get_secret_value_response['SecretString'] 250 | else: 251 | secret = base64.b64decode( 252 | get_secret_value_response['SecretBinary']) 253 | return json.loads(secret) 254 | -------------------------------------------------------------------------------- /src/template/cloudformation_template.yaml: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | AWSTemplateFormatVersion: 2010-09-09 5 | Description: Bidirectional integration of Security Hub with JIRA (v1.0) 6 | 7 | Parameters: 8 | ScheduleExpression: 9 | Type: String 10 | Default: "rate(1 day)" 11 | Environment: 12 | Description: Environment Name. Used for tagging. 13 | Type: String 14 | Default: prod 15 | OrganizationAccessRole: 16 | Default: OrganizationsReadOnlyAccess 17 | Type: String 18 | Description: Role to assume the organization account to read organization tags 19 | OrganizationAccessExternalId: 20 | Type: String 21 | Description: External Id used to assume role to organization management account 22 | Default: '' 23 | OrganizationManagementAccountId: 24 | Description: Organization management account Id 25 | Type: String 26 | MinLength: 12 27 | MaxLength: 12 28 | JIRADefaultAssignee: 29 | Description: JIRA User Id for default assignee 30 | Type: String 31 | JIRAInstance: 32 | Description: JIRA Instance URL 33 | Type: String 34 | JIRAProjectKey: 35 | Description: JIRA Project Key 36 | Type: String 37 | JIRAIssueType: 38 | Description: JIRA Issue Type 39 | Type: String 40 | 41 | Resources: 42 | LambdaImportRole: 43 | Type: "AWS::IAM::Role" 44 | Properties: 45 | Description: "Lambda role for importing findings from Security Hub to JIRA" 46 | AssumeRolePolicyDocument: 47 | Version: 2012-10-17 48 | Statement: 49 | - Effect: Allow 50 | Principal: 51 | Service: 52 | - lambda.amazonaws.com 53 | Action: 54 | - "sts:AssumeRole" 55 | Policies: 56 | - PolicyName: !Sub securityhub-jira-lambda-import-${Environment}-${AWS::Region} 57 | PolicyDocument: 58 | Statement: 59 | - Effect: Allow 60 | Action: 61 | - "securityhub:BatchImportFindings" 62 | - "securityhub:UpdateFindings" 63 | - "securityhub:BatchUpdateFindings" 64 | - "securityhub:GetFindings" 65 | Resource: 66 | - "*" 67 | - Effect: Allow 68 | Action: 69 | - "logs:CreateLogGroup" 70 | - "logs:CreateLogStream" 71 | - "logs:PutLogEvents" 72 | Resource: "*" 73 | - Effect: Allow 74 | Action: 75 | - "sts:AssumeRole" 76 | Resource: 77 | - !Sub "arn:aws:iam::*:role/${OrganizationAccessRole}" 78 | - Effect: Allow 79 | Action: 80 | - secretsmanager:GetResourcePolicy 81 | - secretsmanager:GetSecretValue 82 | - secretsmanager:DescribeSecret 83 | - secretsmanager:ListSecretVersionIds 84 | Resource: 85 | - !Ref JIRAAPITokenSecret 86 | 87 | 88 | LambdaRefreshRole: 89 | Type: "AWS::IAM::Role" 90 | Properties: 91 | Description: "Lambda role for refreshing findings in JIRA and Security Hub" 92 | AssumeRolePolicyDocument: 93 | Version: 2012-10-17 94 | Statement: 95 | - Effect: Allow 96 | Principal: 97 | Service: 98 | - lambda.amazonaws.com 99 | Action: 100 | - "sts:AssumeRole" 101 | Policies: 102 | - PolicyName: !Sub securityhub-jira-lambda-refresh-${Environment}-${AWS::Region} 103 | PolicyDocument: 104 | Statement: 105 | - Effect: Allow 106 | Action: 107 | - "securityhub:BatchImportFindings" 108 | - "securityhub:UpdateFindings" 109 | - "securityhub:BatchUpdateFindings" 110 | - "securityhub:GetFindings" 111 | Resource: 112 | - "*" 113 | - Effect: Allow 114 | Action: 115 | - "logs:CreateLogGroup" 116 | - "logs:CreateLogStream" 117 | - "logs:PutLogEvents" 118 | Resource: "*" 119 | - Effect: Allow 120 | Action: 121 | - secretsmanager:GetResourcePolicy 122 | - secretsmanager:GetSecretValue 123 | - secretsmanager:DescribeSecret 124 | - secretsmanager:ListSecretVersionIds 125 | Resource: 126 | - !Ref JIRAAPITokenSecret 127 | 128 | JIRASecHubCWRule: 129 | Type: AWS::Events::Rule 130 | Properties: 131 | Description: This CW rule helps keep Security Hub in sync with JIRA updates 132 | Name: !Sub securityhub-change-status-${Environment} 133 | EventPattern: 134 | source: 135 | - aws.securityhub 136 | detail-type: 137 | - Security Hub Findings - Custom Action 138 | - Security Hub Findings - Imported 139 | State: "ENABLED" 140 | Targets: 141 | - Arn: !GetAtt JIRASecHubFunction.Arn 142 | Id: "TargetFunctionV1" 143 | 144 | JIRAAPITokenSecret: 145 | Type: "AWS::SecretsManager::Secret" 146 | Properties: 147 | Name: !Sub JiraAPIToken-${Environment} 148 | Description: "JIRA API Token" 149 | 150 | PermissionForEventsToInvokeIntegrationLambda: 151 | Type: AWS::Lambda::Permission 152 | Properties: 153 | FunctionName: 154 | Ref: "JIRASecHubFunction" 155 | Action: "lambda:InvokeFunction" 156 | Principal: "events.amazonaws.com" 157 | SourceArn: 158 | Fn::GetAtt: 159 | - "JIRASecHubCWRule" 160 | - "Arn" 161 | 162 | JIRASecHubFunction: 163 | Type: AWS::Lambda::Function 164 | Properties: 165 | FunctionName: !Sub securityhub-jira-lambda-import-${Environment} 166 | Description: Lambda integrates Security Hub to JIRA 167 | Handler: "security_hub_integration.lambda_handler" 168 | Role: 169 | Fn::GetAtt: 170 | - LambdaImportRole 171 | - Arn 172 | Runtime: python3.11 173 | Timeout: 300 174 | Code: ../../dist/lambda.zip 175 | Environment: 176 | Variables: 177 | JIRA_API_TOKEN: !Ref JIRAAPITokenSecret 178 | ORG_ACCOUNT_ID: !Ref OrganizationManagementAccountId 179 | ORG_ROLE: !Ref OrganizationAccessRole 180 | EXTERNAL_ID: !Ref OrganizationAccessExternalId 181 | JIRA_DEFAULT_ASSIGNEE: !Ref JIRADefaultAssignee 182 | JIRA_ISSUETYPE: !Ref JIRAIssueType 183 | JIRA_PROJECT_KEY: !Ref JIRAProjectKey 184 | JIRA_INSTANCE: !Ref JIRAInstance 185 | 186 | RefreshJIRASecHubCWRule: 187 | Type: AWS::Events::Rule 188 | Properties: 189 | Description: Keep Security Hub findings in sync with JIRA updates 190 | Name: !Sub securityhub-jira-refresh-${Environment} 191 | ScheduleExpression: !Ref ScheduleExpression 192 | State: "ENABLED" 193 | Targets: 194 | - Arn: !GetAtt RefreshJIRASecHubFunction.Arn 195 | Id: "TargetFunctionV1" 196 | 197 | PermissionForEventsToInvokeRefreshLambda: 198 | Type: AWS::Lambda::Permission 199 | Properties: 200 | FunctionName: 201 | Ref: "RefreshJIRASecHubFunction" 202 | Action: "lambda:InvokeFunction" 203 | Principal: "events.amazonaws.com" 204 | SourceArn: 205 | Fn::GetAtt: 206 | - "RefreshJIRASecHubCWRule" 207 | - "Arn" 208 | 209 | RefreshJIRASecHubFunction: 210 | Type: AWS::Lambda::Function 211 | Properties: 212 | FunctionName: !Sub securityhub-jira-refresh-${Environment} 213 | Description: Update findings in Security Hub according to JIRA changes 214 | Handler: "sync_securityhub.lambda_handler" 215 | Role: 216 | Fn::GetAtt: 217 | - LambdaRefreshRole 218 | - Arn 219 | Runtime: python3.11 220 | Timeout: 300 221 | Code: ../../dist/lambda.zip 222 | Environment: 223 | Variables: 224 | JIRA_API_TOKEN: !Ref JIRAAPITokenSecret 225 | JIRA_INSTANCE: !Ref JIRAInstance 226 | JIRA_ISSUETYPE: !Ref JIRAIssueType 227 | JIRA_PROJECT_KEY: !Ref JIRAProjectKey 228 | 229 | AlarmSNSTopic: 230 | Type: AWS::SNS::Topic 231 | Properties: 232 | KmsMasterKeyId: alias/aws/sns 233 | TopicName: !Sub "securityhub-jira-alarm-topic-${Environment}" 234 | 235 | CloudWatchAlarmImport: 236 | Type: "AWS::CloudWatch::Alarm" 237 | Properties: 238 | AlarmDescription: "Lambda Critical Error Alarm for Security Hub -> JIRA integration" 239 | ActionsEnabled: true 240 | AlarmActions: 241 | - !Ref AlarmSNSTopic 242 | MetricName: "Errors" 243 | Namespace: "AWS/Lambda" 244 | Statistic: "Sum" 245 | Dimensions: 246 | - Name: "FunctionName" 247 | Value: !Ref JIRASecHubFunction 248 | Period: 300 249 | EvaluationPeriods: 1 250 | DatapointsToAlarm: 1 251 | Threshold: 1 252 | ComparisonOperator: "GreaterThanThreshold" 253 | TreatMissingData: "notBreaching" 254 | 255 | CloudWatchAlarmRefresh: 256 | Type: "AWS::CloudWatch::Alarm" 257 | Properties: 258 | AlarmDescription: "Lambda Critical Error Alarm for JIRA -> Security Hub integration" 259 | ActionsEnabled: true 260 | AlarmActions: 261 | - !Ref AlarmSNSTopic 262 | MetricName: "Errors" 263 | Namespace: "AWS/Lambda" 264 | Statistic: "Sum" 265 | Dimensions: 266 | - Name: "FunctionName" 267 | Value: !Ref RefreshJIRASecHubFunction 268 | Period: 300 269 | EvaluationPeriods: 1 270 | DatapointsToAlarm: 1 271 | Threshold: 1 272 | ComparisonOperator: "GreaterThanThreshold" 273 | TreatMissingData: "notBreaching" 274 | -------------------------------------------------------------------------------- /src/template/lpt-basic.yaml: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | AWSTemplateFormatVersion: '2010-09-09' 5 | Description: Resoures needed by LPT 6 | 7 | Parameters: 8 | BucketName: 9 | Description: Bucket for the artifacts 10 | Type: String 11 | Resources: 12 | LptBucket: 13 | Type: AWS::S3::Bucket 14 | Properties: 15 | AccessControl: Private 16 | BucketName: !Ref BucketName 17 | PublicAccessBlockConfiguration: 18 | BlockPublicAcls: true 19 | BlockPublicPolicy: true 20 | IgnorePublicAcls: true 21 | RestrictPublicBuckets: true 22 | BucketEncryption: 23 | ServerSideEncryptionConfiguration: 24 | - ServerSideEncryptionByDefault: 25 | SSEAlgorithm: AES256 26 | VersioningConfiguration: 27 | Status: Enabled 28 | LifecycleConfiguration: 29 | Rules: 30 | - Status: Enabled 31 | ExpirationInDays: 365 32 | NoncurrentVersionExpirationInDays: 365 33 | Transitions: 34 | - TransitionInDays: 60 35 | StorageClass: GLACIER 36 | - TransitionInDays: 30 37 | StorageClass: STANDARD_IA 38 | NoncurrentVersionTransitions: 39 | - TransitionInDays: 60 40 | StorageClass: GLACIER 41 | - TransitionInDays: 30 42 | StorageClass: STANDARD_IA 43 | Outputs: 44 | LptBucket: 45 | Value: !Ref LptBucket 46 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eio pipefail 3 | 4 | # Load the config 5 | if [ -z $1 ]; then 6 | echo "Please specify the environment, Example: ./test.sh prod" 7 | exit 1 8 | else 9 | echo "loading params from \"conf/params_${1}.sh\" environment variables" 10 | . conf/params_${1}.sh 11 | fi 12 | 13 | . conf/params_${1}.sh 14 | export JIRA_API_TOKEN='mock_token' 15 | export JIRA_ISSUETYPE='mock_issuetype' 16 | 17 | python3 -m unittest discover src/code -------------------------------------------------------------------------------- /test/custom_new.template: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "e215f5c7-a866-e0cd-6d11-fc7ecf97e381", 4 | "detail-type": "Security Hub Findings - Custom Action", 5 | "source": "aws.securityhub", 6 | "account": "123456789012", 7 | "time": "2019-04-11T22:06:13Z", 8 | "region": "us-east-1", 9 | "resources": [ 10 | "arn:aws:securityhub:us-east-1:123456789012:action/custom/slackMessaging" 11 | ], 12 | "detail": { 13 | "actionName": "CreateJiraIssue", 14 | "actionDescription": "Create Jira Issue", 15 | "findings": [ 16 | { 17 | "SchemaVersion": "2018-10-08", 18 | "Id": "arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.8/finding/fba5a282-b865-41fe-bc9c-30357a1e87a7", 19 | "ProductArn": "arn:aws:securityhub:eu-west-1::product/aws/securityhub", 20 | "ProductName": "Security Hub", 21 | "CompanyName": "AWS", 22 | "Region": "eu-west-1", 23 | "GeneratorId": "aws-foundational-security-best-practices/v/1.0.0/EC2.8", 24 | "AwsAccountId": "123456789012", 25 | "Types": [ 26 | "Software and Configuration Checks/Industry and Regulatory Standards/AWS-Foundational-Security-Best-Practices" 27 | ], 28 | "FirstObservedAt": "2021-02-04T07:57:13.662Z", 29 | "LastObservedAt": "2022-03-13T18:33:41.172Z", 30 | "CreatedAt": "2021-02-04T07:57:13.662Z", 31 | "UpdatedAt": "2022-03-13T18:33:38.017Z", 32 | "Severity": { 33 | "Product": 70, 34 | "Label": "HIGH", 35 | "Normalized": 70, 36 | "Original": "HIGH" 37 | }, 38 | "Title": "EC2.8 EC2 instances should use Instance Metadata Service Version 2 (IMDSv2)", 39 | "Description": "This control checks whether your Amazon Elastic Compute Cloud (Amazon EC2) instance metadata version is configured with Instance Metadata Service Version 2 (IMDSv2). The control passes if HttpTokens is set to required for IMDSv2. The control fails if HttpTokens is set to optional.", 40 | "Remediation": { 41 | "Recommendation": { 42 | "Text": "For directions on how to fix this issue, consult the AWS Security Hub Foundational Security Best Practices documentation.", 43 | "Url": "https://docs.aws.amazon.com/console/securityhub/EC2.8/remediation" 44 | } 45 | }, 46 | "ProductFields": { 47 | "StandardsArn": "arn:aws:securityhub:::standards/aws-foundational-security-best-practices/v/1.0.0", 48 | "StandardsSubscriptionArn": "arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0", 49 | "ControlId": "EC2.8", 50 | "RecommendationUrl": "https://docs.aws.amazon.com/console/securityhub/EC2.8/remediation", 51 | "RelatedAWSResources:0/name": "securityhub-ec2-imdsv2-check-3604c928", 52 | "RelatedAWSResources:0/type": "AWS::Config::ConfigRule", 53 | "StandardsControlArn": "arn:aws:securityhub:eu-west-1:123456789012:control/aws-foundational-security-best-practices/v/1.0.0/EC2.8", 54 | "aws/securityhub/ProductName": "Security Hub", 55 | "aws/securityhub/CompanyName": "AWS", 56 | "Resources:0/Id": "arn:aws:ec2:eu-west-1:123456789012:instance/i-0000000aaaaaaaaaa", 57 | "aws/securityhub/FindingId": "arn:aws:securityhub:eu-west-1::product/aws/securityhub/arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.8/finding/fba5a282-b865-41fe-bc9c-30357a1e87a7" 58 | }, 59 | "Resources": [ 60 | { 61 | "Type": "AwsEc2Instance", 62 | "Id": "arn:aws:ec2:eu-west-1:123456789012:instance/i-0000000aaaaaaaaaa", 63 | "Partition": "aws", 64 | "Region": "eu-west-1", 65 | "Details": { 66 | "AwsEc2Instance": { 67 | "ImageId": "ami-05df1afb28e4fcaee", 68 | "VpcId": "vpc-aaaaaaaa", 69 | "SubnetId": "subnet-3242d868", 70 | "LaunchedAt": "2021-02-04T07:54:51.000Z", 71 | "NetworkInterfaces": [ 72 | { 73 | "NetworkInterfaceId": "eni-bbbbbbbbbbbbbbbbb" 74 | } 75 | ] 76 | } 77 | } 78 | } 79 | ], 80 | "Compliance": { 81 | "Status": "FAILED" 82 | }, 83 | "WorkflowState": "NEW", 84 | "Workflow": { 85 | "Status": "NEW" 86 | }, 87 | "RecordState": "ACTIVE", 88 | "FindingProviderFields": { 89 | "Severity": { 90 | "Label": "HIGH", 91 | "Original": "HIGH" 92 | }, 93 | "Types": [ 94 | "Software and Configuration Checks/Industry and Regulatory Standards/AWS-Foundational-Security-Best-Practices" 95 | ] 96 | } 97 | } 98 | ] 99 | } 100 | } -------------------------------------------------------------------------------- /test/custom_notified_existing.template: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "e215f5c7-a866-e0cd-6d11-fc7ecf97e381", 4 | "detail-type": "Security Hub Findings - Custom Action", 5 | "source": "aws.securityhub", 6 | "account": "123456789012", 7 | "time": "2019-04-11T22:06:13Z", 8 | "region": "us-east-1", 9 | "resources": [ 10 | "arn:aws:securityhub:us-east-1:123456789012:action/custom/slackMessaging" 11 | ], 12 | "detail": { 13 | "actionName": "CreateJiraIssue", 14 | "actionDescription": "Create Jira Issue", 15 | "findings": [ 16 | { 17 | "SchemaVersion": "2018-10-08", 18 | "Id": "arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.8/finding/fba5a282-b865-41fe-bc9c-30357a1e87a7", 19 | "ProductArn": "arn:aws:securityhub:eu-west-1::product/aws/securityhub", 20 | "ProductName": "Security Hub", 21 | "CompanyName": "AWS", 22 | "Region": "eu-west-1", 23 | "GeneratorId": "aws-foundational-security-best-practices/v/1.0.0/EC2.8", 24 | "AwsAccountId": "123456789012", 25 | "Types": [ 26 | "Software and Configuration Checks/Industry and Regulatory Standards/AWS-Foundational-Security-Best-Practices" 27 | ], 28 | "FirstObservedAt": "2021-02-04T07:57:13.662Z", 29 | "LastObservedAt": "2022-03-13T18:33:41.172Z", 30 | "CreatedAt": "2021-02-04T07:57:13.662Z", 31 | "UpdatedAt": "2022-03-13T18:33:38.017Z", 32 | "Severity": { 33 | "Product": 70, 34 | "Label": "HIGH", 35 | "Normalized": 70, 36 | "Original": "HIGH" 37 | }, 38 | "Title": "EC2.8 EC2 instances should use Instance Metadata Service Version 2 (IMDSv2)", 39 | "Description": "This control checks whether your Amazon Elastic Compute Cloud (Amazon EC2) instance metadata version is configured with Instance Metadata Service Version 2 (IMDSv2). The control passes if HttpTokens is set to required for IMDSv2. The control fails if HttpTokens is set to optional.", 40 | "Remediation": { 41 | "Recommendation": { 42 | "Text": "For directions on how to fix this issue, consult the AWS Security Hub Foundational Security Best Practices documentation.", 43 | "Url": "https://docs.aws.amazon.com/console/securityhub/EC2.8/remediation" 44 | } 45 | }, 46 | "ProductFields": { 47 | "StandardsArn": "arn:aws:securityhub:::standards/aws-foundational-security-best-practices/v/1.0.0", 48 | "StandardsSubscriptionArn": "arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0", 49 | "ControlId": "EC2.8", 50 | "RecommendationUrl": "https://docs.aws.amazon.com/console/securityhub/EC2.8/remediation", 51 | "RelatedAWSResources:0/name": "securityhub-ec2-imdsv2-check-3604c928", 52 | "RelatedAWSResources:0/type": "AWS::Config::ConfigRule", 53 | "StandardsControlArn": "arn:aws:securityhub:eu-west-1:123456789012:control/aws-foundational-security-best-practices/v/1.0.0/EC2.8", 54 | "aws/securityhub/ProductName": "Security Hub", 55 | "aws/securityhub/CompanyName": "AWS", 56 | "Resources:0/Id": "arn:aws:ec2:eu-west-1:123456789012:instance/i-0000000aaaaaaaaaa", 57 | "aws/securityhub/FindingId": "arn:aws:securityhub:eu-west-1::product/aws/securityhub/arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.8/finding/fba5a282-b865-41fe-bc9c-30357a1e87a7" 58 | }, 59 | "Resources": [ 60 | { 61 | "Type": "AwsEc2Instance", 62 | "Id": "arn:aws:ec2:eu-west-1:123456789012:instance/i-0000000aaaaaaaaaa", 63 | "Partition": "aws", 64 | "Region": "eu-west-1", 65 | "Details": { 66 | "AwsEc2Instance": { 67 | "ImageId": "ami-05df1afb28e4fcaee", 68 | "VpcId": "vpc-aaaaaaaa", 69 | "SubnetId": "subnet-3242d868", 70 | "LaunchedAt": "2021-02-04T07:54:51.000Z", 71 | "NetworkInterfaces": [ 72 | { 73 | "NetworkInterfaceId": "eni-bbbbbbbbbbbbbbbbb" 74 | } 75 | ] 76 | } 77 | } 78 | } 79 | ], 80 | "Compliance": { 81 | "Status": "FAILED" 82 | }, 83 | "WorkflowState": "NOTIFIED", 84 | "Workflow": { 85 | "Status": "NOTIFIED" 86 | }, 87 | "RecordState": "ACTIVE", 88 | "FindingProviderFields": { 89 | "Severity": { 90 | "Label": "HIGH", 91 | "Original": "HIGH" 92 | }, 93 | "Types": [ 94 | "Software and Configuration Checks/Industry and Regulatory Standards/AWS-Foundational-Security-Best-Practices" 95 | ] 96 | } 97 | } 98 | ] 99 | } 100 | } -------------------------------------------------------------------------------- /test/imported_archived_existing.template: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "8e5622f9-d81c-4d81-612a-9319e7ee2506", 4 | "detail-type": "Security Hub Findings - Imported", 5 | "source": "aws.securityhub", 6 | "account": "123456789012", 7 | "time": "2019-04-11T21:52:17Z", 8 | "region": "eu-west-1", 9 | "resources": [ 10 | "arn:aws:securityhub:eu-west-1::product/aws/macie/arn:aws:macie:us-west-2:123456789012:integtest/trigger/6294d71b927c41cbab915159a8f326a3/alert/f2893b211841" 11 | ], 12 | "detail": { 13 | "findings": [ 14 | { 15 | "SchemaVersion": "2018-10-08", 16 | "Id": "arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.8/finding/fba5a282-b865-41fe-bc9c-30357a1e87a7", 17 | "ProductArn": "arn:aws:securityhub:eu-west-1::product/aws/securityhub", 18 | "ProductName": "Security Hub", 19 | "CompanyName": "AWS", 20 | "Region": "eu-west-1", 21 | "GeneratorId": "aws-foundational-security-best-practices/v/1.0.0/EC2.8", 22 | "AwsAccountId": "123456789012", 23 | "Types": [ 24 | "Software and Configuration Checks/Industry and Regulatory Standards/AWS-Foundational-Security-Best-Practices" 25 | ], 26 | "FirstObservedAt": "2021-02-04T07:57:13.662Z", 27 | "LastObservedAt": "2022-03-13T18:33:41.172Z", 28 | "CreatedAt": "2021-02-04T07:57:13.662Z", 29 | "UpdatedAt": "2022-03-13T18:33:38.017Z", 30 | "Severity": { 31 | "Product": 70, 32 | "Label": "HIGH", 33 | "Normalized": 70, 34 | "Original": "HIGH" 35 | }, 36 | "Title": "EC2.8 EC2 instances should use Instance Metadata Service Version 2 (IMDSv2)", 37 | "Description": "This control checks whether your Amazon Elastic Compute Cloud (Amazon EC2) instance metadata version is configured with Instance Metadata Service Version 2 (IMDSv2). The control passes if HttpTokens is set to required for IMDSv2. The control fails if HttpTokens is set to optional.", 38 | "Remediation": { 39 | "Recommendation": { 40 | "Text": "For directions on how to fix this issue, consult the AWS Security Hub Foundational Security Best Practices documentation.", 41 | "Url": "https://docs.aws.amazon.com/console/securityhub/EC2.8/remediation" 42 | } 43 | }, 44 | "ProductFields": { 45 | "StandardsArn": "arn:aws:securityhub:::standards/aws-foundational-security-best-practices/v/1.0.0", 46 | "StandardsSubscriptionArn": "arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0", 47 | "ControlId": "EC2.8", 48 | "RecommendationUrl": "https://docs.aws.amazon.com/console/securityhub/EC2.8/remediation", 49 | "RelatedAWSResources:0/name": "securityhub-ec2-imdsv2-check-3604c928", 50 | "RelatedAWSResources:0/type": "AWS::Config::ConfigRule", 51 | "StandardsControlArn": "arn:aws:securityhub:eu-west-1:123456789012:control/aws-foundational-security-best-practices/v/1.0.0/EC2.8", 52 | "aws/securityhub/ProductName": "Security Hub", 53 | "aws/securityhub/CompanyName": "AWS", 54 | "Resources:0/Id": "arn:aws:ec2:eu-west-1:123456789012:instance/i-0000000aaaaaaaaaa", 55 | "aws/securityhub/FindingId": "arn:aws:securityhub:eu-west-1::product/aws/securityhub/arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.8/finding/fba5a282-b865-41fe-bc9c-30357a1e87a7" 56 | }, 57 | "Resources": [ 58 | { 59 | "Type": "AwsEc2Instance", 60 | "Id": "arn:aws:ec2:eu-west-1:123456789012:instance/i-0000000aaaaaaaaaa", 61 | "Partition": "aws", 62 | "Region": "eu-west-1", 63 | "Details": { 64 | "AwsEc2Instance": { 65 | "ImageId": "ami-05df1afb28e4fcaee", 66 | "VpcId": "vpc-aaaaaaaa", 67 | "SubnetId": "subnet-3242d868", 68 | "LaunchedAt": "2021-02-04T07:54:51.000Z", 69 | "NetworkInterfaces": [ 70 | { 71 | "NetworkInterfaceId": "eni-bbbbbbbbbbbbbbbbb" 72 | } 73 | ] 74 | } 75 | } 76 | } 77 | ], 78 | "FindingProviderFields": { 79 | "Severity": { 80 | "Label": "HIGH", 81 | "Original": "HIGH" 82 | }, 83 | "Types": [ 84 | "Software and Configuration Checks/Industry and Regulatory Standards/AWS-Foundational-Security-Best-Practices" 85 | ] 86 | }, 87 | "Compliance": { 88 | "Status": "FAILED" 89 | }, 90 | "WorkflowState": "NOTIFIED", 91 | "Workflow": { 92 | "Status": "NOTIFIED" 93 | }, 94 | "RecordState": "ARCHIVED" 95 | } 96 | ] 97 | } 98 | } -------------------------------------------------------------------------------- /test/imported_archived_new.template: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "8e5622f9-d81c-4d81-612a-9319e7ee2506", 4 | "detail-type": "Security Hub Findings - Imported", 5 | "source": "aws.securityhub", 6 | "account": "123456789012", 7 | "time": "2019-04-11T21:52:17Z", 8 | "region": "eu-west-1", 9 | "resources": [ 10 | "arn:aws:securityhub:eu-west-1::product/aws/macie/arn:aws:macie:us-west-2:123456789012:integtest/trigger/6294d71b927c41cbab915159a8f326a3/alert/f2893b211841" 11 | ], 12 | "detail": { 13 | "findings": [ 14 | { 15 | "SchemaVersion": "2018-10-08", 16 | "Id": "arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.8/finding/fba5a282-b865-41fe-bc9c-30357a1e87a7", 17 | "ProductArn": "arn:aws:securityhub:eu-west-1::product/aws/securityhub", 18 | "ProductName": "Security Hub", 19 | "CompanyName": "AWS", 20 | "Region": "eu-west-1", 21 | "GeneratorId": "aws-foundational-security-best-practices/v/1.0.0/EC2.8", 22 | "AwsAccountId": "123456789012", 23 | "Types": [ 24 | "Software and Configuration Checks/Industry and Regulatory Standards/AWS-Foundational-Security-Best-Practices" 25 | ], 26 | "FirstObservedAt": "2021-02-04T07:57:13.662Z", 27 | "LastObservedAt": "2022-03-13T18:33:41.172Z", 28 | "CreatedAt": "2021-02-04T07:57:13.662Z", 29 | "UpdatedAt": "2022-03-13T18:33:38.017Z", 30 | "Severity": { 31 | "Product": 70, 32 | "Label": "HIGH", 33 | "Normalized": 70, 34 | "Original": "HIGH" 35 | }, 36 | "Title": "EC2.8 EC2 instances should use Instance Metadata Service Version 2 (IMDSv2)", 37 | "Description": "This control checks whether your Amazon Elastic Compute Cloud (Amazon EC2) instance metadata version is configured with Instance Metadata Service Version 2 (IMDSv2). The control passes if HttpTokens is set to required for IMDSv2. The control fails if HttpTokens is set to optional.", 38 | "Remediation": { 39 | "Recommendation": { 40 | "Text": "For directions on how to fix this issue, consult the AWS Security Hub Foundational Security Best Practices documentation.", 41 | "Url": "https://docs.aws.amazon.com/console/securityhub/EC2.8/remediation" 42 | } 43 | }, 44 | "ProductFields": { 45 | "StandardsArn": "arn:aws:securityhub:::standards/aws-foundational-security-best-practices/v/1.0.0", 46 | "StandardsSubscriptionArn": "arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0", 47 | "ControlId": "EC2.8", 48 | "RecommendationUrl": "https://docs.aws.amazon.com/console/securityhub/EC2.8/remediation", 49 | "RelatedAWSResources:0/name": "securityhub-ec2-imdsv2-check-3604c928", 50 | "RelatedAWSResources:0/type": "AWS::Config::ConfigRule", 51 | "StandardsControlArn": "arn:aws:securityhub:eu-west-1:123456789012:control/aws-foundational-security-best-practices/v/1.0.0/EC2.8", 52 | "aws/securityhub/ProductName": "Security Hub", 53 | "aws/securityhub/CompanyName": "AWS", 54 | "Resources:0/Id": "arn:aws:ec2:eu-west-1:123456789012:instance/i-0000000aaaaaaaaaa", 55 | "aws/securityhub/FindingId": "arn:aws:securityhub:eu-west-1::product/aws/securityhub/arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.8/finding/fba5a282-b865-41fe-bc9c-30357a1e87a7" 56 | }, 57 | "Resources": [ 58 | { 59 | "Type": "AwsEc2Instance", 60 | "Id": "arn:aws:ec2:eu-west-1:123456789012:instance/i-0000000aaaaaaaaaa", 61 | "Partition": "aws", 62 | "Region": "eu-west-1", 63 | "Details": { 64 | "AwsEc2Instance": { 65 | "ImageId": "ami-05df1afb28e4fcaee", 66 | "VpcId": "vpc-aaaaaaaa", 67 | "SubnetId": "subnet-3242d868", 68 | "LaunchedAt": "2021-02-04T07:54:51.000Z", 69 | "NetworkInterfaces": [ 70 | { 71 | "NetworkInterfaceId": "eni-bbbbbbbbbbbbbbbbb" 72 | } 73 | ] 74 | } 75 | } 76 | } 77 | ], 78 | "FindingProviderFields": { 79 | "Severity": { 80 | "Label": "HIGH", 81 | "Original": "HIGH" 82 | }, 83 | "Types": [ 84 | "Software and Configuration Checks/Industry and Regulatory Standards/AWS-Foundational-Security-Best-Practices" 85 | ] 86 | }, 87 | "Compliance": { 88 | "Status": "FAILED" 89 | }, 90 | "WorkflowState": "NEW", 91 | "Workflow": { 92 | "Status": "NEW" 93 | }, 94 | "RecordState": "ARCHIVED" 95 | } 96 | ] 97 | } 98 | } -------------------------------------------------------------------------------- /test/imported_archived_notified.template: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "8e5622f9-d81c-4d81-612a-9319e7ee2506", 4 | "detail-type": "Security Hub Findings - Imported", 5 | "source": "aws.securityhub", 6 | "account": "123456789012", 7 | "time": "2019-04-11T21:52:17Z", 8 | "region": "eu-west-1", 9 | "resources": [ 10 | "arn:aws:securityhub:eu-west-1::product/aws/macie/arn:aws:macie:us-west-2:123456789012:integtest/trigger/6294d71b927c41cbab915159a8f326a3/alert/f2893b211841" 11 | ], 12 | "detail": { 13 | "findings": [ 14 | { 15 | "SchemaVersion": "2018-10-08", 16 | "Id": "arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.8/finding/fba5a282-b865-41fe-bc9c-30357a1e87a7", 17 | "ProductArn": "arn:aws:securityhub:eu-west-1::product/aws/securityhub", 18 | "ProductName": "Security Hub", 19 | "CompanyName": "AWS", 20 | "Region": "eu-west-1", 21 | "GeneratorId": "aws-foundational-security-best-practices/v/1.0.0/EC2.8", 22 | "AwsAccountId": "123456789012", 23 | "Types": [ 24 | "Software and Configuration Checks/Industry and Regulatory Standards/AWS-Foundational-Security-Best-Practices" 25 | ], 26 | "FirstObservedAt": "2021-02-04T07:57:13.662Z", 27 | "LastObservedAt": "2022-03-13T18:33:41.172Z", 28 | "CreatedAt": "2021-02-04T07:57:13.662Z", 29 | "UpdatedAt": "2022-03-13T18:33:38.017Z", 30 | "Severity": { 31 | "Product": 70, 32 | "Label": "HIGH", 33 | "Normalized": 70, 34 | "Original": "HIGH" 35 | }, 36 | "Title": "EC2.8 EC2 instances should use Instance Metadata Service Version 2 (IMDSv2)", 37 | "Description": "This control checks whether your Amazon Elastic Compute Cloud (Amazon EC2) instance metadata version is configured with Instance Metadata Service Version 2 (IMDSv2). The control passes if HttpTokens is set to required for IMDSv2. The control fails if HttpTokens is set to optional.", 38 | "Remediation": { 39 | "Recommendation": { 40 | "Text": "For directions on how to fix this issue, consult the AWS Security Hub Foundational Security Best Practices documentation.", 41 | "Url": "https://docs.aws.amazon.com/console/securityhub/EC2.8/remediation" 42 | } 43 | }, 44 | "ProductFields": { 45 | "StandardsArn": "arn:aws:securityhub:::standards/aws-foundational-security-best-practices/v/1.0.0", 46 | "StandardsSubscriptionArn": "arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0", 47 | "ControlId": "EC2.8", 48 | "RecommendationUrl": "https://docs.aws.amazon.com/console/securityhub/EC2.8/remediation", 49 | "RelatedAWSResources:0/name": "securityhub-ec2-imdsv2-check-3604c928", 50 | "RelatedAWSResources:0/type": "AWS::Config::ConfigRule", 51 | "StandardsControlArn": "arn:aws:securityhub:eu-west-1:123456789012:control/aws-foundational-security-best-practices/v/1.0.0/EC2.8", 52 | "aws/securityhub/ProductName": "Security Hub", 53 | "aws/securityhub/CompanyName": "AWS", 54 | "Resources:0/Id": "arn:aws:ec2:eu-west-1:123456789012:instance/i-0000000aaaaaaaaaa", 55 | "aws/securityhub/FindingId": "arn:aws:securityhub:eu-west-1::product/aws/securityhub/arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.8/finding/fba5a282-b865-41fe-bc9c-30357a1e87a7" 56 | }, 57 | "Resources": [ 58 | { 59 | "Type": "AwsEc2Instance", 60 | "Id": "arn:aws:ec2:eu-west-1:123456789012:instance/i-0000000aaaaaaaaaa", 61 | "Partition": "aws", 62 | "Region": "eu-west-1", 63 | "Details": { 64 | "AwsEc2Instance": { 65 | "ImageId": "ami-05df1afb28e4fcaee", 66 | "VpcId": "vpc-aaaaaaaa", 67 | "SubnetId": "subnet-3242d868", 68 | "LaunchedAt": "2021-02-04T07:54:51.000Z", 69 | "NetworkInterfaces": [ 70 | { 71 | "NetworkInterfaceId": "eni-bbbbbbbbbbbbbbbbb" 72 | } 73 | ] 74 | } 75 | } 76 | } 77 | ], 78 | "FindingProviderFields": { 79 | "Severity": { 80 | "Label": "HIGH", 81 | "Original": "HIGH" 82 | }, 83 | "Types": [ 84 | "Software and Configuration Checks/Industry and Regulatory Standards/AWS-Foundational-Security-Best-Practices" 85 | ] 86 | }, 87 | "Compliance": { 88 | "Status": "FAILED" 89 | }, 90 | "WorkflowState": "NOTIFIED", 91 | "Workflow": { 92 | "Status": "NOTIFIED" 93 | }, 94 | "RecordState": "ARCHIVED" 95 | } 96 | ] 97 | } 98 | } -------------------------------------------------------------------------------- /test/imported_new_automated_standard_check.template: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "8e5622f9-d81c-4d81-612a-9319e7ee2506", 4 | "detail-type": "Security Hub Findings - Imported", 5 | "source": "aws.securityhub", 6 | "account": "123456789012", 7 | "time": "2019-04-11T21:52:17Z", 8 | "region": "eu-west-1", 9 | "resources": [ 10 | "arn:aws:securityhub:eu-west-1::product/aws/macie/arn:aws:macie:us-west-2:123456789012:integtest/trigger/6294d71b927c41cbab915159a8f326a3/alert/f2893b211841" 11 | ], 12 | "detail": { 13 | "findings": [ 14 | { 15 | "SchemaVersion": "2018-10-08", 16 | "Id": "arn:aws:securityhub:eu-west-1:123456789012:subscription/cis-aws-foundations-benchmark/v/1.2.0/1.22/finding/dedc1e61-e4e6-448e-87d3-c7d1709334a3", 17 | "ProductArn": "arn:aws:securityhub:eu-west-1::product/aws/securityhub", 18 | "GeneratorId": "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0/rule/1.22", 19 | "AwsAccountId": "123456789012", 20 | "Types": [ 21 | "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark" 22 | ], 23 | "FirstObservedAt": "2020-07-22T17:12:27.005Z", 24 | "LastObservedAt": "2020-11-10T21:07:43.968Z", 25 | "CreatedAt": "2020-07-22T17:12:27.005Z", 26 | "UpdatedAt": "2020-11-10T21:07:43.287Z", 27 | "Severity": { 28 | "Product": 70, 29 | "Label": "HIGH", 30 | "Normalized": 70, 31 | "Original": "HIGH" 32 | }, 33 | "Title": "1.22 Ensure IAM policies that allow full \"*:*\" administrative privileges are not created", 34 | "Description": "IAM policies are the means by which privileges are granted to users, groups, or roles. It is recommended and considered a standard security advice to grant least privilege—that is, granting only the permissions required to perform a task. Determine what users need to do and then craft policies for them that let the users perform only those tasks, instead of allowing full administrative privileges.", 35 | "Remediation": { 36 | "Recommendation": { 37 | "Text": "For directions on how to fix this issue, please consult the AWS Security Hub CIS documentation.", 38 | "Url": "https://docs.aws.amazon.com/console/securityhub/standards-cis-1.22/remediation" 39 | } 40 | }, 41 | "ProductFields": { 42 | "StandardsGuideArn": "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0", 43 | "StandardsGuideSubscriptionArn": "arn:aws:securityhub:eu-west-1:123456789012:subscription/cis-aws-foundations-benchmark/v/1.2.0", 44 | "RuleId": "1.22", 45 | "RecommendationUrl": "https://docs.aws.amazon.com/console/securityhub/standards-cis-1.22/remediation", 46 | "RelatedAWSResources:0/name": "securityhub-iam-policy-no-statements-with-admin-access-dcb0d040", 47 | "RelatedAWSResources:0/type": "AWS::Config::ConfigRule", 48 | "StandardsControlArn": "arn:aws:securityhub:eu-west-1:123456789012:control/cis-aws-foundations-benchmark/v/1.2.0/1.22", 49 | "aws/securityhub/ProductName": "Security Hub", 50 | "aws/securityhub/CompanyName": "AWS", 51 | "aws/securityhub/FindingId": "arn:aws:securityhub:eu-west-1::product/aws/securityhub/arn:aws:securityhub:eu-west-1:123456789012:subscription/cis-aws-foundations-benchmark/v/1.2.0/1.22/finding/dedc1e61-e4e6-448e-87d3-c7d1709334a3" 52 | }, 53 | "Resources": [ 54 | { 55 | "Type": "AwsIamPolicy", 56 | "Id": "arn:aws:iam::123456789012:policy/AdminAccessCustom", 57 | "Partition": "aws", 58 | "Region": "eu-west-1", 59 | "Details": { 60 | "AwsIamPolicy": { 61 | "AttachmentCount": 1, 62 | "CreateDate": "2020-07-22T17:00:49.000Z", 63 | "DefaultVersionId": "v1", 64 | "IsAttachable": true, 65 | "Path": "/", 66 | "PermissionsBoundaryUsageCount": 0, 67 | "PolicyId": "ANPA6CQ2O7EC27CMBJAJF", 68 | "PolicyName": "AdminAccessCustom", 69 | "PolicyVersionList": [ 70 | { 71 | "VersionId": "v1", 72 | "IsDefaultVersion": true, 73 | "CreateDate": "2020-07-22T17:00:49.000Z" 74 | } 75 | ], 76 | "UpdateDate": "2020-07-22T17:00:49.000Z" 77 | } 78 | } 79 | } 80 | ], 81 | "Compliance": { 82 | "Status": "FAILED" 83 | }, 84 | "WorkflowState": "NEW", 85 | "Workflow": { 86 | "Status": "NEW" 87 | }, 88 | "RecordState": "ACTIVE" 89 | } 90 | ] 91 | } 92 | } -------------------------------------------------------------------------------- /test/imported_new_notautomated.template: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "8e5622f9-d81c-4d81-612a-9319e7ee2506", 4 | "detail-type": "Security Hub Findings - Imported", 5 | "source": "aws.securityhub", 6 | "account": "123456789012", 7 | "time": "2019-04-11T21:52:17Z", 8 | "region": "eu-west-1", 9 | "resources": [ 10 | "arn:aws:securityhub:eu-west-1::product/aws/macie/arn:aws:macie:us-west-2:123456789012:integtest/trigger/6294d71b927c41cbab915159a8f326a3/alert/f2893b211841" 11 | ], 12 | "detail": { 13 | "findings": [ 14 | { 15 | "SchemaVersion": "2018-10-08", 16 | "Id": "arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.8/finding/fba5a282-b865-41fe-bc9c-30357a1e87a7", 17 | "ProductArn": "arn:aws:securityhub:eu-west-1::product/aws/securityhub", 18 | "ProductName": "Security Hub", 19 | "CompanyName": "AWS", 20 | "Region": "eu-west-1", 21 | "GeneratorId": "aws-foundational-security-best-practices/v/1.0.0/EC2.8", 22 | "AwsAccountId": "123456789012", 23 | "Types": [ 24 | "Software and Configuration Checks/Industry and Regulatory Standards/AWS-Foundational-Security-Best-Practices" 25 | ], 26 | "FirstObservedAt": "2021-02-04T07:57:13.662Z", 27 | "LastObservedAt": "2022-03-13T18:33:41.172Z", 28 | "CreatedAt": "2021-02-04T07:57:13.662Z", 29 | "UpdatedAt": "2022-03-13T18:33:38.017Z", 30 | "Severity": { 31 | "Product": 70, 32 | "Label": "HIGH", 33 | "Normalized": 70, 34 | "Original": "HIGH" 35 | }, 36 | "Title": "EC2.8 EC2 instances should use Instance Metadata Service Version 2 (IMDSv2)", 37 | "Description": "This control checks whether your Amazon Elastic Compute Cloud (Amazon EC2) instance metadata version is configured with Instance Metadata Service Version 2 (IMDSv2). The control passes if HttpTokens is set to required for IMDSv2. The control fails if HttpTokens is set to optional.", 38 | "Remediation": { 39 | "Recommendation": { 40 | "Text": "For directions on how to fix this issue, consult the AWS Security Hub Foundational Security Best Practices documentation.", 41 | "Url": "https://docs.aws.amazon.com/console/securityhub/EC2.8/remediation" 42 | } 43 | }, 44 | "ProductFields": { 45 | "StandardsArn": "arn:aws:securityhub:::standards/aws-foundational-security-best-practices/v/1.0.0", 46 | "StandardsSubscriptionArn": "arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0", 47 | "ControlId": "EC2.8", 48 | "RecommendationUrl": "https://docs.aws.amazon.com/console/securityhub/EC2.8/remediation", 49 | "RelatedAWSResources:0/name": "securityhub-ec2-imdsv2-check-3604c928", 50 | "RelatedAWSResources:0/type": "AWS::Config::ConfigRule", 51 | "StandardsControlArn": "arn:aws:securityhub:eu-west-1:123456789012:control/aws-foundational-security-best-practices/v/1.0.0/EC2.8", 52 | "aws/securityhub/ProductName": "Security Hub", 53 | "aws/securityhub/CompanyName": "AWS", 54 | "Resources:0/Id": "arn:aws:ec2:eu-west-1:123456789012:instance/i-0000000aaaaaaaaaa", 55 | "aws/securityhub/FindingId": "arn:aws:securityhub:eu-west-1::product/aws/securityhub/arn:aws:securityhub:eu-west-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.8/finding/fba5a282-b865-41fe-bc9c-30357a1e87a7" 56 | }, 57 | "Resources": [ 58 | { 59 | "Type": "AwsEc2Instance", 60 | "Id": "arn:aws:ec2:eu-west-1:123456789012:instance/i-0000000aaaaaaaaaa", 61 | "Partition": "aws", 62 | "Region": "eu-west-1", 63 | "Details": { 64 | "AwsEc2Instance": { 65 | "ImageId": "ami-05df1afb28e4fcaee", 66 | "VpcId": "vpc-aaaaaaaa", 67 | "SubnetId": "subnet-3242d868", 68 | "LaunchedAt": "2021-02-04T07:54:51.000Z", 69 | "NetworkInterfaces": [ 70 | { 71 | "NetworkInterfaceId": "eni-bbbbbbbbbbbbbbbbb" 72 | } 73 | ] 74 | } 75 | } 76 | } 77 | ], 78 | "FindingProviderFields": { 79 | "Severity": { 80 | "Label": "HIGH", 81 | "Original": "HIGH" 82 | }, 83 | "Types": [ 84 | "Software and Configuration Checks/Industry and Regulatory Standards/AWS-Foundational-Security-Best-Practices" 85 | ] 86 | }, 87 | "Compliance": { 88 | "Status": "FAILED" 89 | }, 90 | "WorkflowState": "NEW", 91 | "Workflow": { 92 | "Status": "NEW" 93 | }, 94 | "RecordState": "ACTIVE" 95 | } 96 | ] 97 | } 98 | } --------------------------------------------------------------------------------