Skip to content

feat(auth): apply deletion_protection to the Cognito user pool - #599

Merged
JWThewes merged 2 commits into
aws-samples:mainfrom
thenom:feat/cognito-deletion-protection
Oct 26, 2026
Merged

JWThewes merged 2 commits into
aws-samples:mainfrom
thenom:feat/cognito-deletion-protection

Conversation

@thenom

@thenom thenom commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Issue #, if available: None. Follow-up to #461, which added deletion protection to the data stores but not the Cognito user pool

Description of changes:

Problem

The root module already has a deletion_protection variable, and other stateful resources use it, but it isn't passed to the auth module. The Cognito user pool is therefore never protected. Every workspace is keyed by a user's Cognito sub, so an accidental pool deletion or replacement can't be recovered.

Solution

  • Add a deletion_protection variable to modules/auth, defaulting to false.
  • Pass the root variable into the module.
  • Set deletion_protection = "ACTIVE" / "INACTIVE" on aws_cognito_user_pool.main.
  • Teardown: scripts/destroy.sh now includes the user pool in its protection-preparation targets, still filtered by state membership for partial teardowns. scripts/inspect-terraform-plan.mjs --deletion-protection-only now accepts an in-place Cognito deletion_protection change from ACTIVE to INACTIVE with nothing else changing. Creation, deletion, replacement, enabling and unrelated updates are still rejected.

Testing

  • New release-tools.test.mjs cases:
    • the inspector accepts a Cognito ACTIVE to INACTIVE update and an already-INACTIVE no-op
    • the inspector rejects unrelated field changes, enabling, boolean values, replacement, deletion and creation
    • destroy.sh targets the pool when it's in state, and clears protection before the destroy
    • the existing destroy test confirms the pool isn't targeted when it's absent (partial teardown)
  • Production refusal is covered by the existing destroy tests.
  • terraform-hardening.test.mjs now requires the teardown target list to include the protected user pool.
  • node --test on terraform-hardening.test.mjs and release-tools.test.mjs: 64 tests pass. All three new tests fail without the script changes. The other scripts/test files pass (39 tests).
  • terraform fmt -check, terraform validate -no-tests and every terraform test suite from the Terraform workflow pass on Terraform 1.15.0.

Impact

  • The root deletion_protection default is true, so deployments that keep the default will see an in-place update turning on user pool deletion protection. Deployments that set it to false see no change.
  • scripts/destroy.sh and installer-managed teardown clear the pool's protection before destroying it, as they already do for Neptune and DynamoDB. A bare terraform destroy needs deletion_protection=false applied first.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

The root deletion_protection variable was not passed to the auth module,
so the user pool was never protected. Every workspace is keyed by a
user's Cognito sub, so a deleted pool cannot be recovered. Thread the
existing variable through; the default stays false.
Comment thread terraform/main.tf
module "auth" {
source = "./modules/auth"

deletion_protection = var.deletion_protection

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Include Cognito in the disable-before-destroy workflow

The root deletion_protection default is true, so this wiring makes Cognito ACTIVE on ordinary/default deployments. However, scripts/destroy.sh prepares only its explicit Neptune/DynamoDB PROTECTION_TARGETS; it omits module.auth.aws_cognito_user_pool.main. The subsequent destroy with -var=deletion_protection=false does not first update the pool, and the locked AWS provider calls DeleteUserPool while protection remains ACTIVE. Both scripts/destroy.sh dev and installer-managed teardown can therefore fail partway through.

Please add the pool to the preparation targets and protected-resource coverage, preserving the existing state-membership filter for partial teardowns. Also extend scripts/inspect-terraform-plan.mjs: adding the target alone still fails because its preparation allowlist accepts only DynamoDB/Neptune boolean true→false. Narrowly allow an in-place Cognito ACTIVE→INACTIVE update with no other field changes; keep unrelated updates, creation, deletion and replacement rejected.

Regression acceptance: default-protected non-production teardown, already-INACTIVE/no-op, pool absent after partial teardown, production refusal, and unsafe preparation-plan rejection. Please also correct the PR description's no-change-at-default statement to reflect the root default.

Verified with actual-root mock plans, the unmodified destroy helper plus a recording Terraform fixture (baseline succeeds; protected state fails), and the actual inspector rejecting a clean Cognito disable plan. No cloud deletion was performed. AWS documents that active protection rejects deletion: https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_DeleteUserPool.html.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, you're right, and the description was wrong about the default; it has been corrected. Fixed in 92f264c:

  • destroy.sh adds module.auth.aws_cognito_user_pool.main to PROTECTION_TARGETS, still filtered by state membership.
  • inspect-terraform-plan.mjs accepts an in-place Cognito deletion_protection change from ACTIVE to INACTIVE only. Unrelated field changes, enabling, boolean values, replacement, deletion and creation stay rejected.
  • terraform-hardening.test.mjs now requires the protected pool in the target list.
  • New release-tools.test.mjs cases cover the inspector accept/reject matrix, the already-INACTIVE no-op, the pool being targeted (and cleared before destroy) when it is in state, and not being targeted when absent. Production refusal stays covered by the existing destroy tests. The three new tests fail without the script changes.

The root deletion_protection variable defaults to true, so wiring it to
the user pool makes the pool ACTIVE on default deployments. destroy.sh
only cleared protection on Neptune and DynamoDB, so the destroy then
failed on DeleteUserPool.

Add the pool to the teardown preparation targets (still filtered by state
membership for partial teardowns), and let the preparation plan inspector
accept an in-place Cognito deletion_protection ACTIVE -> INACTIVE update
with no other change. Creation, deletion, replacement and unrelated
updates stay rejected.
@thenom
thenom marked this pull request as ready for review October 23, 2026 10:47

@JWThewes JWThewes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for contributing

@JWThewes
JWThewes merged commit 165e66f into aws-samples:main Oct 26, 2026
@jeromevdl jeromevdl added area/infra Terraform, installer and deployment area/ci GitHub Actions, hooks, lint, release tooling, E2E harness size/S < 200 changed lines (lockfiles excluded) labels Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ci GitHub Actions, hooks, lint, release tooling, E2E harness area/infra Terraform, installer and deployment size/S < 200 changed lines (lockfiles excluded)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants