diff --git a/.bumpversion.cfg b/.bumpversion.cfg index fab32b7..b49e513 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.0.0 +current_version = 2.0.0 commit = True message = Bumps version to {new_version} tag = False diff --git a/.gitignore b/.gitignore index f325ca8..724e9c9 100644 --- a/.gitignore +++ b/.gitignore @@ -15,9 +15,8 @@ tardigrade-ci/ .tardigrade-ci -# eclint - -.git/ - # terratest tests/go.* + +# terraform lock file +.terraform.lock.hcl diff --git a/CHANGELOG.md b/CHANGELOG.md index 40e2664..c71c213 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,60 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +### 2.0.0 + +**Released**: 2021.03.03 + +**Commit Delta**: [Change from 1.0.0 release](https://github.com/plus3it/terraform-aws-tardigrade-security-hub/compare/1.0.0...2.0.0) + +**Summary**: + +* Replaces python accepter with new resource, `aws_securityhub_invite_accepter`. + See [PR #60](https://github.com/plus3it/terraform-aws-tardigrade-security-hub/pull/60). +* Moves cross-account workflow into separate module. This eliminates the extra + "provider" when *not* using the cross-account workflow. It also streamlines + the inputs for each use case. See [PR #60](https://github.com/plus3it/terraform-aws-tardigrade-security-hub/pull/60). +* Renames the `master` provider to `administrator` for the cross-account workflow. + See [PR #60](https://github.com/plus3it/terraform-aws-tardigrade-security-hub/pull/60). + +### 1.0.0 + +**Released**: 2020.10.07 + +**Commit Delta**: [Change from 0.0.3 release](https://github.com/plus3it/terraform-aws-tardigrade-security-hub/compare/0.0.3...1.0.0) + +**Summary**: + +* Splits resources into submodules to support separate master/member workflows. + See [PR #44](https://github.com/plus3it/terraform-aws-tardigrade-security-hub/pull/44). + +### 0.0.3 + +**Released**: 2020.05.14 + +**Commit Delta**: [Change from 0.0.2 release](https://github.com/plus3it/terraform-aws-tardigrade-security-hub/compare/0.0.2...0.0.3) + +**Summary**: + +* Avoids error, "Cannot include a null value in a string template". + See [PR #16](https://github.com/plus3it/terraform-aws-tardigrade-security-hub/pull/16). + +### 0.0.2 + +**Released**: 2020.05.13 + +**Commit Delta**: [Change from 0.0.1 release](https://github.com/plus3it/terraform-aws-tardigrade-security-hub/compare/0.0.1...0.0.2) + +**Summary**: + +* Passes profile and region through the refreshable credential properly. + See [PR #15](https://github.com/plus3it/terraform-aws-tardigrade-security-hub/pull/15). + ### 0.0.1 **Released**: 2020.05.08 -**Commit Delta**: [Change from 1.0.4 release](https://github.com/plus3it/terraform-aws-tardigrade-security-hub/compare/0.0.0...0.0.1) +**Commit Delta**: [Change from 0.0.0 release](https://github.com/plus3it/terraform-aws-tardigrade-security-hub/compare/0.0.0...0.0.1) **Summary**: diff --git a/README.md b/README.md index 484ab0d..a4d11c1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # terraform-aws-tardigrade-security-hub -Terraform module to enable SecurityHub in a child account and link it -to a pre-existing SecurityHub instance in the parent account +Terraform module to enable and configure SecurityHub. The module supports independent +accounts with the top-level module, and the cross-account invite/accept workflow with +the `modules/cross-account-member` module. ## Testing You can find example implementations of this module in the tests folder. This module @@ -16,23 +17,17 @@ Note: the implementation `tests/create_securityhub_member` will require you to p | Name | Version | |------|---------| | terraform | >= 0.13 | +| aws | >= 3.29.0 | ## Providers -| Name | Version | -|------|---------| -| aws | n/a | -| aws.master | n/a | +No provider. ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| accepter\_profile | (Optional) Used by null\_resource accepter to establish botocore session. Required for the cross-account SecurityHub member accept workflow | `string` | `""` | no | -| accepter\_region | (Optional) Used by null\_resource accepter to establish botocore client. Required for the cross-account SecurityHub member accept workflow | `string` | `""` | no | -| accepter\_role\_arn | (Optional) Used by null\_resource accepter to assume a role in the accepter account. Required for the cross-account SecurityHub member accept workflow | `string` | `""` | no | | action\_targets | Schema list of SecurityHub action targets. |
list(object({| `[]` | no | -| member\_email | (Optional) Email address associated with the member account. Required for the cross-account SecurityHub member invite workflow | `string` | `null` | no | | product\_subscription\_arns | List of product arns to subscribe to. See https://www.terraform.io/docs/providers/aws/r/securityhub_product_subscription.html | `list(string)` | `[]` | no | | standard\_subscription\_arns | List of standard arns to subscribe to. See https://www.terraform.io/docs/providers/aws/r/securityhub_standards_subscription.html | `list(string)` | `[]` | no | @@ -40,10 +35,8 @@ Note: the implementation `tests/create_securityhub_member` will require you to p | Name | Description | |------|-------------| -| accept | Object containing the SecurityHub (null) accepter resource | | account | Object containing the SecurityHub account resource | | action\_targets | Object containing the SecurityHub action targets resources | -| member | Object containing the SecurityHub member resource | | subscriptions | Object containing the SecurityHub subscriptions resources | diff --git a/main.tf b/main.tf index 4c7f4e6..f15b60d 100644 --- a/main.tf +++ b/main.tf @@ -1,44 +1,8 @@ -provider "aws" { - alias = "master" -} - # Enable SecurityHub module "account" { source = "./modules/account" } -# Send invite from master -module "member" { - source = "./modules/member" - count = local.cross_account ? 1 : 0 - providers = { - aws = aws.master - } - - account_id = data.aws_caller_identity.this.account_id - email = var.member_email - - depends_on = [ - module.account - ] -} - -# Accept invite -module "accept" { - source = "./modules/accepter" - count = local.cross_account ? 1 : 0 - - master_account_id = data.aws_caller_identity.master.account_id - - profile = var.accepter_profile - role_arn = var.accepter_role_arn - region = var.accepter_region - - depends_on = [ - module.member - ] -} - # Manage subscriptions module "subscriptions" { source = "./modules/subscriptions" @@ -60,13 +24,3 @@ module "action_targets" { description = each.value.description identifier = each.value.identifier } - -locals { - cross_account = data.aws_caller_identity.this.account_id != data.aws_caller_identity.master.account_id -} - -data "aws_caller_identity" "this" {} - -data "aws_caller_identity" "master" { - provider = aws.master -} diff --git a/modules/accepter/README.md b/modules/accepter/README.md index 45647f8..3df8866 100644 --- a/modules/accepter/README.md +++ b/modules/accepter/README.md @@ -9,21 +9,18 @@ No requirements. | Name | Version | |------|---------| -| null | n/a | +| aws | n/a | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | master\_account\_id | Account ID of the AWS SecurityHub master account that sent the invite | `string` | n/a | yes | -| profile | Used by null\_resource to establish botocore session | `string` | n/a | yes | -| region | Used by null\_resource to establish botocore client | `string` | n/a | yes | -| role\_arn | Used by null\_resource to assume a role in the accepter account | `string` | n/a | yes | ## Outputs | Name | Description | |------|-------------| -| accepter | Object containing SecurityHub accepter (null) resource | +| accepter | Object containing SecurityHub accepter resource | diff --git a/modules/accepter/main.tf b/modules/accepter/main.tf index 1be09c6..4b899d8 100644 --- a/modules/accepter/main.tf +++ b/modules/accepter/main.tf @@ -1,63 +1,3 @@ -resource "null_resource" "accepter" { - provisioner "local-exec" { - command = join(" ", local.create) - } - - provisioner "local-exec" { - when = destroy - command = self.triggers.destroy_command - } - - provisioner "local-exec" { - when = destroy - command = "python -c 'import time; time.sleep(5)'" - } - - lifecycle { - ignore_changes = [triggers["destroy_command"]] - } - - triggers = { - destroy_command = join(" ", local.destroy) - } -} - -locals { - # Replace a terraform-aws-provider sts assumed role with the equivalent iam role, i.e: - # arn:aws:sts::
name = string
description = string
identifer = string
}))
list(object({| `[]` | no | +| product\_subscription\_arns | List of product arns to subscribe to. See https://www.terraform.io/docs/providers/aws/r/securityhub_product_subscription.html | `list(string)` | `[]` | no | +| standard\_subscription\_arns | List of standard arns to subscribe to. See https://www.terraform.io/docs/providers/aws/r/securityhub_standards_subscription.html | `list(string)` | `[]` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| accepter | Object containing the SecurityHub accepter resource | +| account | Object containing the SecurityHub account resource | +| action\_targets | Object containing the SecurityHub action targets resources | +| member | Object containing the SecurityHub member resource | +| subscriptions | Object containing the SecurityHub subscriptions resources | + + diff --git a/modules/cross-account-member/main.tf b/modules/cross-account-member/main.tf new file mode 100644 index 0000000..eda9cb2 --- /dev/null +++ b/modules/cross-account-member/main.tf @@ -0,0 +1,44 @@ +provider "aws" { + alias = "administrator" +} + +# Enables/configures Security Hub in member account +module "account" { + source = "../../" + + action_targets = var.action_targets + product_subscription_arns = var.product_subscription_arns + standard_subscription_arns = var.standard_subscription_arns +} + +# Send invite from administrator account +module "member" { + source = "../member" + providers = { + aws = aws.administrator + } + + account_id = data.aws_caller_identity.this.account_id + email = var.member_email + + depends_on = [ + module.account + ] +} + +# Accept invite +module "accept" { + source = "../accepter" + + master_account_id = data.aws_caller_identity.administrator.account_id + + depends_on = [ + module.member + ] +} + +data "aws_caller_identity" "this" {} + +data "aws_caller_identity" "administrator" { + provider = aws.administrator +} diff --git a/modules/cross-account-member/outputs.tf b/modules/cross-account-member/outputs.tf new file mode 100644 index 0000000..624b223 --- /dev/null +++ b/modules/cross-account-member/outputs.tf @@ -0,0 +1,24 @@ +output "account" { + description = "Object containing the SecurityHub account resource" + value = module.account.account +} + +output "member" { + description = "Object containing the SecurityHub member resource" + value = module.member.member +} + +output "accepter" { + description = "Object containing the SecurityHub accepter resource" + value = module.accept.accepter +} + +output "subscriptions" { + description = "Object containing the SecurityHub subscriptions resources" + value = module.account.subscriptions +} + +output "action_targets" { + description = "Object containing the SecurityHub action targets resources" + value = module.account.action_targets +} diff --git a/modules/cross-account-member/variables.tf b/modules/cross-account-member/variables.tf new file mode 100644 index 0000000..36e20bd --- /dev/null +++ b/modules/cross-account-member/variables.tf @@ -0,0 +1,26 @@ +variable "member_email" { + description = "Email address associated with the member account. Required for the cross-account SecurityHub member invite workflow" + type = string +} + +variable "action_targets" { + description = "Schema list of SecurityHub action targets." + type = list(object({ + name = string + description = string + identifer = string + })) + default = [] +} + +variable "product_subscription_arns" { + description = "List of product arns to subscribe to. See https://www.terraform.io/docs/providers/aws/r/securityhub_product_subscription.html" + type = list(string) + default = [] +} + +variable "standard_subscription_arns" { + description = "List of standard arns to subscribe to. See https://www.terraform.io/docs/providers/aws/r/securityhub_standards_subscription.html" + type = list(string) + default = [] +} diff --git a/modules/cross-account-member/versions.tf b/modules/cross-account-member/versions.tf new file mode 100644 index 0000000..966e9bd --- /dev/null +++ b/modules/cross-account-member/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 0.13" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29.0" + } + } +} diff --git a/modules/member/main.tf b/modules/member/main.tf index 3986d19..6c0748d 100644 --- a/modules/member/main.tf +++ b/modules/member/main.tf @@ -2,10 +2,4 @@ resource "aws_securityhub_member" "this" { account_id = var.account_id email = var.email invite = true - - # The invite sometimes takes a few seconds to register before it can be accepted in the target account, - # so we pause for 5 seconds to let the invite propagate - provisioner "local-exec" { - command = "python -c 'import time; time.sleep(5)'" - } } diff --git a/outputs.tf b/outputs.tf index 591fec1..ae3247f 100644 --- a/outputs.tf +++ b/outputs.tf @@ -3,16 +3,6 @@ output "account" { value = module.account.account } -output "member" { - description = "Object containing the SecurityHub member resource" - value = local.cross_account ? module.member[0].member : null -} - -output "accept" { - description = "Object containing the SecurityHub (null) accepter resource" - value = local.cross_account ? module.accept[0].accepter : null -} - output "subscriptions" { description = "Object containing the SecurityHub subscriptions resources" value = module.subscriptions diff --git a/tests/securityhub_cross_account/main.tf b/tests/securityhub_cross_account/main.tf index f13b9cb..fce6db6 100644 --- a/tests/securityhub_cross_account/main.tf +++ b/tests/securityhub_cross_account/main.tf @@ -9,17 +9,15 @@ provider "aws" { profile = "resource-owner" } -module "securityhub_member" { - source = "../../" +module "securityhub" { + source = "../../modules/cross-account-member" providers = { - aws = aws - aws.master = aws.resource-owner + aws = aws + aws.administrator = aws.resource-owner } - member_email = var.member_email - accepter_region = data.aws_region.current.name - accepter_profile = "resource-member" + member_email = var.member_email standard_subscription_arns = [ "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0", @@ -31,8 +29,6 @@ module "securityhub_member" { ] } -data "aws_region" "current" {} - output "securityhub" { - value = module.securityhub_member + value = module.securityhub } diff --git a/tests/securityhub_cross_account/prereq/main.tf b/tests/securityhub_cross_account/prereq/main.tf index 9ade683..ecfeb59 100644 --- a/tests/securityhub_cross_account/prereq/main.tf +++ b/tests/securityhub_cross_account/prereq/main.tf @@ -1,19 +1,8 @@ provider "aws" { region = "us-east-1" - profile = "resource-member" -} - -provider "aws" { - region = "us-east-1" - alias = "resource-owner" profile = "resource-owner" } module "securityhub_owner" { source = "../../../" - - providers = { - aws = aws.resource-owner - aws.master = aws.resource-owner - } } diff --git a/tests/securityhub_same_account/main.tf b/tests/securityhub_same_account/main.tf index f6d4ca2..512c14e 100644 --- a/tests/securityhub_same_account/main.tf +++ b/tests/securityhub_same_account/main.tf @@ -3,20 +3,9 @@ provider "aws" { profile = "resource-owner" } -provider "aws" { - region = "us-east-1" - alias = "resource-owner" - profile = "resource-owner" -} - module "securityhub" { source = "../../" - providers = { - aws = aws - aws.master = aws.resource-owner - } - standard_subscription_arns = [ "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0", "arn:aws:securityhub:us-east-1::standards/pci-dss/v/3.2.1", diff --git a/variables.tf b/variables.tf index b23164c..885497d 100644 --- a/variables.tf +++ b/variables.tf @@ -19,27 +19,3 @@ variable "product_subscription_arns" { type = list(string) default = [] } - -variable "member_email" { - description = "(Optional) Email address associated with the member account. Required for the cross-account SecurityHub member invite workflow" - type = string - default = null -} - -variable "accepter_profile" { - description = "(Optional) Used by null_resource accepter to establish botocore session. Required for the cross-account SecurityHub member accept workflow" - type = string - default = "" -} - -variable "accepter_role_arn" { - description = "(Optional) Used by null_resource accepter to assume a role in the accepter account. Required for the cross-account SecurityHub member accept workflow" - type = string - default = "" -} - -variable "accepter_region" { - description = "(Optional) Used by null_resource accepter to establish botocore client. Required for the cross-account SecurityHub member accept workflow" - type = string - default = "" -} diff --git a/versions.tf b/versions.tf index 6b6318d..966e9bd 100644 --- a/versions.tf +++ b/versions.tf @@ -1,3 +1,10 @@ terraform { required_version = ">= 0.13" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.29.0" + } + } }
name = string
description = string
identifer = string
}))