Skip to content

[SSM.7] AFT management account fails "SSM documents should have the block public sharing setting enabled" #632

Description

@yousefdebaz-fivexlio

Describe the outcome you'd like

AFT should enable the SSM block public sharing setting in the AFT management account, in every Control Tower governed region, gated behind a variable so it can be opted out of.

The setting is account-level but evaluated per region, so a single resource is not enough. Using the AWS provider v6 per-resource region argument keeps it to one resource with no extra provider blocks:

resource "aws_ssm_service_setting" "block_public_sharing" {
  for_each      = var.create_ssm_block_public_sharing ? toset(var.ssm_block_public_sharing_regions) : toset([])
  region        = each.value
  setting_id    = "/ssm/documents/console/public-sharing-permission"
  setting_value = "Disable"
}

with the region list unioned against var.ct_home_region in locals, so the home region is covered whether or not it is listed:

ssm_block_public_sharing_regions = distinct(concat([var.ct_home_region], var.ssm_block_public_sharing_regions))

Is your feature request related to a problem you are currently experiencing? If so, please describe.

Security Hub raises [SSM.7] "SSM documents should have the block public sharing setting enabled" against our AFT management account on every deployment. It is severity Critical and part of AWS Foundational Security Best Practices v1.0.0. The underlying setting defaults to allowing public sharing, and per the control documentation it "can differ for each AWS Region" — so the finding is raised independently in every governed region, not once per account. For a landing zone governing N regions, that is N Critical findings against a single shared-services account.

We remediated every other account in the organization by adding this setting to our account baseline, which AFT applies through the account and global customizations pipelines. The AFT management account is the one account that path cannot reach: it is not a vended account, so customizations never target it. Its resources come from this module, applied from the Control Tower management account.

That leaves three options for a customer:

  1. Fix it by hand, or with an out-of-band script. Drifts, is invisible to the Terraform state that owns the rest of the account, has to be repeated per region, and has to be re-verified after every AFT upgrade and every time a region is added to the landing zone.
  2. Add our own aws_ssm_service_setting to the root configuration that calls this module. This requires hand-rolling a second aws provider that assumes AWSControlTowerExecution into the AFT management account, duplicating the aft_management provider this module already defines internally. It works, but every AFT customer who cares about SSM.7 has to write and maintain the same boilerplate.
  3. Have AFT own the setting, the same way AFT already owns other security settings for its own account.

Option 3 is the only one that does not push recurring work onto every AFT customer. Because the finding is Critical and sits in a shared-services account, in a regulated environment it has to be either remediated or covered by a documented and periodically renewed exception, per region. Remediation is one resource.

Additional context

Precedent. AFT has accepted this class of change before, each time gated behind a variable rather than changing behavior unconditionally:

Issue Driver Outcome
#396 CloudWatch log groups not encrypted with a CMK Shipped in 1.15.0 as cloudwatch_log_group_enable_cmk_encryption
#359 [DynamoDB.1] capacity scaling Shipped in 1.15.0
#299 / #398 OSS state lock table not encrypted with a CMK Open, same driver

sns_topic_enable_cmk_encryption follows the same opt-in pattern. The request here is identical in shape and smaller in blast radius than any of the above.

Suggested placement. modules/aft-account-request-framework. Its default aws provider is already aws.aft_management, so the setting lands in the right account with no new provider wiring, and the module already owns the AFT management account's core infrastructure.

Sourcing the governed region list — the one open design question. Control Tower governed regions do not appear to be discoverable from Terraform: the AWS provider exposes aws_controltower_landing_zone only as a resource, and the aws_controltower_controls data source returns enabled controls rather than regions. Since AFT does not manage the landing zone, it cannot read the manifest's governedRegions. My implementation therefore takes the list as an explicit variable, defaulting to [] (home region only), which is backward compatible but does require the customer to declare it.

You have options here that I do not, and any of them would be better than an explicit list:

  • AFT already enumerates regions server-side for the delete-default-VPC feature (get_aws_regions in aft_common/feature_options.py, via ec2:DescribeRegions). Something similar could source the landing zone's governed regions from controltower:GetLandingZone in the CT management account.
  • Alternatively, defaulting to data.aws_regions (all enabled regions) removes the need for input entirely, at the cost of managing the setting in regions Control Tower does not govern.

I went with the explicit variable specifically to avoid AFT reaching outside Control Tower's scope, but I would defer to whatever you prefer.

Reference implementation. Running on a fork: . Seven files, no deletions of existing behavior: the resource, the region local, two root variables, the module variables, the module's provider constraint, and the terraform-docs input table. terraform fmt -recursive -check is clean and terraform validate passes against 1.21.1 with no new warnings. Region merge and for_each expansion verified across four cases, including the case where the caller omits the home region (it is still covered) and an invalid region (rejected by variable validation).

One note on the provider constraint: modules/aft-account-request-framework currently declares aws >= 4.9.0, but the per-resource region argument requires v6. I bumped it to >= 6.0.0, < 7.0.0 to match the range the root module already pins, so effective version selection is unchanged.

I am aware that CONTRIBUTING.md says contributions are not being accepted, so this is not a request to merge a PR. The branch is there purely as a reference implementation if it saves you time. Happy for it to be reimplemented however you prefer.

Compatibility notes.

  • No new IAM permissions. ssm:GetServiceSetting and ssm:UpdateServiceSetting are already covered by the AWSControlTowerExecution role the aft_management provider assumes.
  • No effect on vended accounts or on any existing AFT resource.
  • Per the AWS documentation, enabling the setting does not affect SSM documents that are already shared publicly, so it cannot break an existing workflow that depends on one.
  • for_each keys are region names, so adding or removing a governed region does not churn the other instances.
  • Destroying the resource (setting the variable to false after it exists) resets the setting to the AWS default. Worth calling out in the variable description if that concerns you.

Version. Reproduced on AFT 1.21.1. Applies to any version, since the setting has never been managed by AFT.

i let opus 5 write this :)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions