diff --git a/CHANGELOG.md b/CHANGELOG.md index ef1c54d..96be3ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v0.5.0] + +### Added + +- Option to provide replicator technical user permission to close accounts based on tag restrictions. This allows for more granular control over which accounts can be closed by the replicator service. +- Possibility to set `meshstack_access_role_name` from the top-level module. + +### Removed + +- Option to provide replicator technical user permission to close accounts for specified resource org paths (i.e. landing zones) - this feature has been replaced by the tag-based closure configuration, since using Organization Units other than root did not work as expected. + ## [v0.4.0] ### Added @@ -37,8 +48,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial Release -[unreleased]: https://github.com/meshcloud/terraform-aws-meshplatform/compare/v0.4.0...HEAD +[unreleased]: https://github.com/meshcloud/terraform-aws-meshplatform/compare/v0.5.0...HEAD [v0.1.0]: https://github.com/meshcloud/terraform-aws-meshplatform/releases/tag/v0.1.0 [v0.2.0]: https://github.com/meshcloud/terraform-aws-meshplatform/releases/tag/v0.2.0 [v0.3.0]: https://github.com/meshcloud/terraform-aws-meshplatform/releases/tag/v0.3.0 [v0.4.0]: https://github.com/meshcloud/terraform-aws-meshplatform/releases/tag/v0.4.0 +[v0.5.0]: https://github.com/meshcloud/terraform-aws-meshplatform/releases/tag/v0.5.0 diff --git a/README.md b/README.md index cda8e85..66996f7 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,33 @@ See the `aws` [provider documentation](https://registry.terraform.io/providers/h For an overview of the module structure, refer to [generated terraform docs](./TERRAFORM_DOCS.md) +## Account Closure Configuration + +You can configure which AWS accounts can be closed by meshStack using the `can_close_accounts_with_tags` variable. This allows you to define tag-based restrictions for account closure. + +> **Note:** In meshStack, you can configure additional tags for AWS accounts as part of the platform configuration. For more details, see [Tags in Cloud Tenants](https://docs.meshcloud.io/meshstack.metadata-tags/#tags-in-cloud-tenants). + +### Example Configuration + +```hcl +# No account closure (default) - no accounts can be closed +can_close_accounts_with_tags = {} + +# Allow closing accounts with specific tags +# Accounts with Environment=dev, Environment=staging, or Team=platform can be closed +can_close_accounts_with_tags = { + "Environment" = ["dev", "staging"] # Account with Environment=dev OR Environment=staging + "Team" = ["platform"] # Account with Team=platform +} +# Note: Multiple tag keys use OR logic - account needs to match ANY criteria +# Examples: ✓ Environment=dev, ✓ Team=platform, ✗ Environment=prod, ✗ no tags + +# Allow closing all accounts without restrictions +can_close_accounts_with_tags = { + "__meshstack_allow_close_all__" = ["true"] +} +``` + ## How to Use This Module ### Using AWS Portal @@ -197,7 +224,7 @@ Before opening a Pull Request, please do the following: |------|-------------|------|---------|:--------:| | [automation\_account\_service\_role\_name](#input\_automation\_account\_service\_role\_name) | Name of the custom role in the automation account. See https://docs.meshcloud.io/docs/meshstack.how-to.integrate-meshplatform-aws-manually.html#set-up-aws-account-3-automation | `string` | `"MeshfedAutomationRole"` | no | | [aws\_sso\_instance\_arn](#input\_aws\_sso\_instance\_arn) | AWS SSO Instance ARN. Needs to be of the form arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxx. Setup instructions https://docs.meshcloud.io/docs/meshstack.aws.sso-setup.html. | `string` | n/a | yes | -| [can\_close\_accounts\_in\_resource\_org\_paths](#input\_can\_close\_accounts\_in\_resource\_org\_paths) | AWS ResourceOrgPaths that are used in Landing Zones and where meshStack is allowed to close accounts. | `list(string)` | `[]` | no | +| [can\_close\_accounts\_with\_tags](#input\_can\_close\_accounts\_with\_tags) | Map of tag keys to lists of tag values that allow account closure. Account closure is opt-in only - if not set or empty, no accounts can be closed. When set, only accounts with matching tags can be closed. Special case: {"\_\_meshstack\_allow\_close\_all\_\_" = ["true"]} allows closing all accounts without restrictions. Example: { "Environment" = ["dev", "staging"], "Team" = ["platform"] } | `map(list(string))` | `{}` | no | | [control\_tower\_enrollment\_enabled](#input\_control\_tower\_enrollment\_enabled) | Set to true, to allow meshStack to enroll Accounts via AWS Control Tower for the meshPlatform. | `bool` | `false` | no | | [control\_tower\_portfolio\_id](#input\_control\_tower\_portfolio\_id) | Must be set for AWS Control Tower | `string` | `""` | no | | [cost\_explorer\_management\_account\_service\_role\_name](#input\_cost\_explorer\_management\_account\_service\_role\_name) | Name of the custom role in the management account used by the cost explorer user. | `string` | `"MeshCostExplorerServiceRole"` | no | diff --git a/main.tf b/main.tf index 28708e6..cca0283 100644 --- a/main.tf +++ b/main.tf @@ -68,17 +68,17 @@ module "management_account_replicator_access" { providers = { aws = aws.management } - meshcloud_account_id = data.aws_caller_identity.meshcloud.account_id - privileged_external_id = var.replicator_privileged_external_id - support_root_account_via_aws_sso = var.support_root_account_via_aws_sso - aws_sso_instance_arn = var.aws_sso_instance_arn - control_tower_enrollment_enabled = var.control_tower_enrollment_enabled - control_tower_portfolio_id = var.control_tower_portfolio_id - meshcloud_account_service_user_name = var.meshcloud_account_service_user_name - management_account_service_role_name = var.management_account_service_role_name - meshstack_access_role_name = var.meshstack_access_role_name - landing_zone_ou_arns = var.landing_zone_ou_arns - can_close_accounts_in_resource_org_paths = var.can_close_accounts_in_resource_org_paths + meshcloud_account_id = data.aws_caller_identity.meshcloud.account_id + privileged_external_id = var.replicator_privileged_external_id + support_root_account_via_aws_sso = var.support_root_account_via_aws_sso + aws_sso_instance_arn = var.aws_sso_instance_arn + control_tower_enrollment_enabled = var.control_tower_enrollment_enabled + control_tower_portfolio_id = var.control_tower_portfolio_id + meshcloud_account_service_user_name = var.meshcloud_account_service_user_name + management_account_service_role_name = var.management_account_service_role_name + meshstack_access_role_name = var.meshstack_access_role_name + landing_zone_ou_arns = var.landing_zone_ou_arns + can_close_accounts_with_tags = var.can_close_accounts_with_tags allow_federated_role = var.workload_identity_federation != null diff --git a/modules/meshcloud-replicator/replicator-management-account-access/README.md b/modules/meshcloud-replicator/replicator-management-account-access/README.md index ff4f77f..19a6bdd 100644 --- a/modules/meshcloud-replicator/replicator-management-account-access/README.md +++ b/modules/meshcloud-replicator/replicator-management-account-access/README.md @@ -39,7 +39,7 @@ No modules. |------|-------------|------|---------|:--------:| | [allow\_federated\_role](#input\_allow\_federated\_role) | n/a | `bool` | `false` | no | | [aws\_sso\_instance\_arn](#input\_aws\_sso\_instance\_arn) | ARN of the AWS SSO instance to use | `string` | n/a | yes | -| [can\_close\_accounts\_in\_resource\_org\_paths](#input\_can\_close\_accounts\_in\_resource\_org\_paths) | AWS ResourceOrgPaths that are used in Landing Zones and where meshStack is allowed to close accounts. | `list(string)` | `[]` | no | +| [can\_close\_accounts\_with\_tags](#input\_can\_close\_accounts\_with\_tags) | Map of tag keys to lists of tag values that allow account closure. Account closure is opt-in only - if not set or empty, no accounts can be closed. When set, only accounts with matching tags can be closed. Special case: {"\_\_meshstack\_allow\_close\_all\_\_" = ["true"]} allows closing all accounts without restrictions. Example: { "Environment" = ["dev", "staging"], "Team" = ["platform"] } | `map(list(string))` | `{}` | no | | [control\_tower\_enrollment\_enabled](#input\_control\_tower\_enrollment\_enabled) | Set to true, to allow meshStack to enroll Accounts via AWS Control Tower for the meshPlatform | `bool` | `false` | no | | [control\_tower\_portfolio\_id](#input\_control\_tower\_portfolio\_id) | Must be set for AWS Control Tower | `string` | `""` | no | | [landing\_zone\_ou\_arns](#input\_landing\_zone\_ou\_arns) | Organizational Unit ARNs that are used in Landing Zones. We recommend to explicitly list the OU ARNs that meshStack should manage. | `list(string)` | `[]` | no | diff --git a/modules/meshcloud-replicator/replicator-management-account-access/data.tf b/modules/meshcloud-replicator/replicator-management-account-access/data.tf index 6aa1852..e032977 100644 --- a/modules/meshcloud-replicator/replicator-management-account-access/data.tf +++ b/modules/meshcloud-replicator/replicator-management-account-access/data.tf @@ -1,5 +1,15 @@ locals { account_id = data.aws_caller_identity.current.account_id # current account number. + + # Check if unrestricted account closure is enabled using reserved key + allow_close_all_accounts = ( + length(var.can_close_accounts_with_tags) == 1 && + contains(keys(var.can_close_accounts_with_tags), "__meshstack_allow_close_all__") && + var.can_close_accounts_with_tags["__meshstack_allow_close_all__"][0] == "true" + ) + + # Get tag restrictions (empty if unrestricted access) + tag_restrictions = local.allow_close_all_accounts ? {} : var.can_close_accounts_with_tags } data "aws_caller_identity" "current" {} @@ -58,20 +68,32 @@ data "aws_iam_policy_document" "meshfed_service" { var.landing_zone_ou_arns) } - statement { - sid = "OrgManagementAccessCloseAccount" - effect = "Allow" - actions = [ - "organizations:CloseAccount" - ] - resources = [ - // allow acting on any account owned by this org - "arn:${data.aws_partition.current.partition}:organizations::*:account/o-*/*", - ] - condition { - test = "ForAnyValue:StringLike" - variable = "aws:ResourceOrgPaths" - values = var.can_close_accounts_in_resource_org_paths + # Account closure policy with tag-based conditions: + # Account closure is opt-in only - requires can_close_accounts_with_tags to be explicitly set + # Special case: {"__meshstack_allow_close_all__" = ["true"]} allows closing all accounts without restrictions + # Otherwise: Only accounts with matching tags can be closed + dynamic "statement" { + for_each = length(var.can_close_accounts_with_tags) > 0 ? [1] : [] + content { + sid = "OrgManagementAccessCloseAccount" + effect = "Allow" + actions = [ + "organizations:CloseAccount" + ] + resources = [ + // allow acting on any account owned by this org + "arn:${data.aws_partition.current.partition}:organizations::*:account/o-*/*", + ] + + # Apply tag-based conditions (empty if unrestricted access) + dynamic "condition" { + for_each = local.tag_restrictions + content { + test = "ForAnyValue:StringEquals" + variable = "aws:ResourceTag/${condition.key}" + values = condition.value + } + } } } diff --git a/modules/meshcloud-replicator/replicator-management-account-access/variables.tf b/modules/meshcloud-replicator/replicator-management-account-access/variables.tf index 12c1e58..dfa8d1c 100644 --- a/modules/meshcloud-replicator/replicator-management-account-access/variables.tf +++ b/modules/meshcloud-replicator/replicator-management-account-access/variables.tf @@ -55,11 +55,10 @@ variable "landing_zone_ou_arns" { default = [] } -variable "can_close_accounts_in_resource_org_paths" { - type = list(string) - // see https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-resourceorgpaths - description = "AWS ResourceOrgPaths that are used in Landing Zones and where meshStack is allowed to close accounts." - default = [] // example: o-a1b2c3d4e5/r-f6g7h8i9j0example/ou-ghi0-awsccccc/ou-jkl0-awsddddd/ +variable "can_close_accounts_with_tags" { + type = map(list(string)) + description = "Map of tag keys to lists of tag values that allow account closure. Account closure is opt-in only - if not set or empty, no accounts can be closed. When set, only accounts with matching tags can be closed. Special case: {\"__meshstack_allow_close_all__\" = [\"true\"]} allows closing all accounts without restrictions. Example: { \"Environment\" = [\"dev\", \"staging\"], \"Team\" = [\"platform\"] }" + default = {} } variable "allow_federated_role" { diff --git a/variables.tf b/variables.tf index 18a801d..b5749fe 100644 --- a/variables.tf +++ b/variables.tf @@ -39,11 +39,10 @@ variable "landing_zone_ou_arns" { default = ["arn:aws:organizations::*:ou/o-*/ou-*"] } -variable "can_close_accounts_in_resource_org_paths" { - type = list(string) - // see https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-resourceorgpaths - description = "AWS ResourceOrgPaths that are used in Landing Zones and where meshStack is allowed to close accounts." - default = [] // example: o-a1b2c3d4e5/r-f6g7h8i9j0example/ou-ghi0-awsccccc/ou-jkl0-awsddddd/ +variable "can_close_accounts_with_tags" { + type = map(list(string)) + description = "Map of tag keys to lists of tag values that allow account closure. Account closure is opt-in only - if not set or empty, no accounts can be closed. When set, only accounts with matching tags can be closed. Special case: {\"__meshstack_allow_close_all__\" = [\"true\"]} allows closing all accounts without restrictions. Example: { \"Environment\" = [\"dev\", \"staging\"], \"Team\" = [\"platform\"] }" + default = {} } # ---------------------------------------------------------------------------------------------------------------------