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 = {}
}
# ---------------------------------------------------------------------------------------------------------------------