From 7bfa51381a1eaf1151373110649d1c85973dda91 Mon Sep 17 00:00:00 2001 From: Mohammad Alhussan Date: Tue, 19 Aug 2025 18:20:54 +0200 Subject: [PATCH 1/2] feat: allow restricting account closure based on tags Restricting on ResourceOrgPaths deemed useful until it was no longer possible to use OUs other than the root. We therefore switch to using ResourceTag instead. closes https://github.com/meshcloud/terraform-aws-meshplatform/issues/22 --- README.md | 39 ++++++++++++++- main.tf | 22 ++++---- .../README.md | 2 +- .../data.tf | 50 +++++++++++++------ .../variables.tf | 9 ++-- variables.tf | 9 ++-- 6 files changed, 94 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index cda8e85..4acc56b 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,43 @@ 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) +can_close_accounts_with_tags = {} + +# Allow closing accounts with specific tags +can_close_accounts_with_tags = { + "Environment" = ["dev", "staging"] + "Team" = ["platform"] +} + +# Allow closing all accounts +can_close_accounts_with_tags = { + "__meshstack_allow_close_all__" = ["true"] +} +``` + +**How it works:** + +- **Empty**: No accounts can be closed +- **Tag-based**: Only accounts that have at least one of the specified tags can be closed. For example, with `"Environment" = ["dev", "staging"]`, an account can be closed if it has the tag `Environment=dev` OR `Environment=staging` +- **Multiple tag keys**: Acts as OR logic - an account can be closed if it matches ANY of the tag criteria +- **Special key**: `__meshstack_allow_close_all__` with value `["true"]` allows closing all accounts without any tag restrictions + +**Examples:** +- Account with `Environment=dev` → Can be closed (matches first example) +- Account with `Team=platform` → Can be closed (matches first example) +- Account with `Environment=prod` → Cannot be closed (doesn't match any values) +- Account with no tags → Cannot be closed (unless using special key) + ## How to Use This Module ### Using AWS Portal @@ -197,7 +234,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 = {} } # --------------------------------------------------------------------------------------------------------------------- From 35e8a949c45ad45711472c2738f79dce8db82809 Mon Sep 17 00:00:00 2001 From: Mohammad Alhussan Date: Wed, 20 Aug 2025 08:44:30 +0200 Subject: [PATCH 2/2] chore: update changelog --- CHANGELOG.md | 14 +++++++++++++- README.md | 24 +++++++----------------- 2 files changed, 20 insertions(+), 18 deletions(-) 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 4acc56b..66996f7 100644 --- a/README.md +++ b/README.md @@ -91,34 +91,24 @@ You can configure which AWS accounts can be closed by meshStack using the `can_c ### Example Configuration ```hcl -# No account closure (default) +# 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"] - "Team" = ["platform"] + "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 +# Allow closing all accounts without restrictions can_close_accounts_with_tags = { "__meshstack_allow_close_all__" = ["true"] } ``` -**How it works:** - -- **Empty**: No accounts can be closed -- **Tag-based**: Only accounts that have at least one of the specified tags can be closed. For example, with `"Environment" = ["dev", "staging"]`, an account can be closed if it has the tag `Environment=dev` OR `Environment=staging` -- **Multiple tag keys**: Acts as OR logic - an account can be closed if it matches ANY of the tag criteria -- **Special key**: `__meshstack_allow_close_all__` with value `["true"]` allows closing all accounts without any tag restrictions - -**Examples:** -- Account with `Environment=dev` → Can be closed (matches first example) -- Account with `Team=platform` → Can be closed (matches first example) -- Account with `Environment=prod` → Cannot be closed (doesn't match any values) -- Account with no tags → Cannot be closed (unless using special key) - ## How to Use This Module ### Using AWS Portal