Skip to content

fix(amplify-category-auth): clean up OIDC provider on auth stack teardown#14952

Open
ahmedhamouda78 wants to merge 4 commits into
aws-amplify:devfrom
ahmedhamouda78:fix/openid-provider-teardown-leak
Open

fix(amplify-category-auth): clean up OIDC provider on auth stack teardown#14952
ahmedhamouda78 wants to merge 4 commits into
aws-amplify:devfrom
ahmedhamouda78:fix/openid-provider-teardown-leak

Conversation

@ahmedhamouda78

@ahmedhamouda78 ahmedhamouda78 commented Jul 1, 2026

Copy link
Copy Markdown
Member

Description of changes

The auth category's OpenId custom resource Lambda (openIdLambda.js) only handled Create/Update events. On stack deletion CloudFormation sends a Delete event, which fell through to a no-op — so the account-global IAM OIDC provider (accounts.google.com) was never removed. Orphaned providers accumulate in the account on every teardown of an auth resource configured with Google/OpenID federation.

This PR:

  • Adds a Delete handler to openIdLambda.js. Because the provider is account-global and keyed by URL (the create path reuses it and appends client IDs), the handler removes only the client IDs this resource registered and deletes the provider only once no client IDs remain — preserving providers shared by other Amplify apps in the same account. All IAM calls tolerate an already-removed provider/client ID, so the handler is idempotent.
  • Grants the OpenId Lambda role iam:RemoveClientIDFromOpenIDConnectProvider and iam:DeleteOpenIDConnectProvider (scoped to the accounts.google.com provider ARN) in auth-cognito-stack-builder.ts.
  • Adds unit tests for create, sole-owner delete, shared-provider delete, and idempotent delete.

Issue #, if available

N/A

Description of how you validated changes

  • New Jest unit test openIdLambda.test.js passes (6/6), driving the real handler through: create, update-appends, sole-owner delete, shared-provider delete, concurrent-deletion race, and idempotent delete.
  • Confirmed the IAM policy change does not alter existing stack-builder snapshots.

Checklist

  • PR description included
  • Tests are changed or added
  • New AWS SDK calls or CloudFormation actions have been added to relevant test and service IAM policies

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

…down

The OpenId custom resource Lambda only handled Create/Update requests, so on
stack deletion the account-global IAM OIDC provider (accounts.google.com) was
never removed. Orphaned providers accumulate in the account on every teardown.

Add a Delete handler that removes the client IDs this resource registered and
deletes the provider only once no client IDs remain, so providers shared by
other Amplify apps in the same account are preserved. All IAM calls tolerate
an already-removed provider/client ID, keeping the handler idempotent.

Grant the OpenId Lambda role iam:RemoveClientIDFromOpenIDConnectProvider and
iam:DeleteOpenIDConnectProvider (scoped to the accounts.google.com provider).

Adds unit tests covering create, sole-owner delete, shared-provider delete,
and idempotent delete.
@ahmedhamouda78 ahmedhamouda78 requested a review from a team as a code owner July 1, 2026 17:45
…a deps

cfn-response and @aws-sdk/client-iam are provided by the Lambda runtime and
are not installed in the package, so jest.mock could not resolve them and the
suite failed to run in CI. Mark both mocks as virtual.
Comment thread packages/amplify-category-auth/resources/auth-custom-resource/openIdLambda.js Outdated
- Re-read the provider after removing our client IDs and delete it if a
  concurrently-deleting stack emptied it, closing a race that could leave a
  provider with zero client IDs orphaned in the account.
- Harden provider lookup to match on the URL host / :oidc-provider/<host>
  ARN suffix instead of brittle string splitting.
- Clarify in the stack builder that account-level iam:ListOpenIDConnectProviders
  is granted in the adjacent statement (required by the lookup).
- Tests: guard invoke() when response.send is never called, rename the URL
  constant to avoid shadowing the global, and add Update-appends and
  concurrent-deletion-race cases.
@ahmedhamouda78

Copy link
Copy Markdown
Member Author

Thanks for the thorough review @soberm — all six points addressed in b16780b:

  • [major] Concurrent-deletion race: After removing our client IDs I now re-read the provider and delete it if the list is empty, so two stacks racing on a shared provider can't leave it orphaned with zero client IDs. The existing NoSuchEntityException catch handles the case where the other deleter wins the final delete. Added a deletes the provider when a concurrent stack empties it during our Delete test that simulates the interleaving.
  • [minor] Fragile URL matching: findProviderArn now derives the host via new URL(url).host and matches the provider ARN suffix. One correction to the suggested snippet: real OIDC provider ARNs use :oidc-provider/<host> (colon), e.g. arn:aws:iam::<account>:oidc-provider/accounts.google.com, so I matched on :oidc-provider/${providerHost} rather than /oidc-provider/... — the latter never matches.
  • [minor] ListOpenIDConnectProviders: It's granted in the adjacent statement (account-level, Resource: *) since it can't be scoped to a single provider ARN. Added a comment there noting it's required by findProviderArn.
  • [minor] invoke() guard: Now throws handler never called response.send instead of returning undefined.
  • [nit] URL shadowing: Renamed the test constant to GOOGLE_OIDC_URL.
  • [nit] Update coverage: Added a test that creates, then updates with an additional client ID and asserts it's appended.

All six unit tests pass locally under CI-like conditions (runtime-only deps mocked virtually).

soberm
soberm previously approved these changes Jul 2, 2026
sarayev
sarayev previously approved these changes Jul 3, 2026

@sarayev sarayev 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.

Review — Approve with comments ✅

Solid fix for a real latent leak: the OpenId custom-resource Lambda never handled Delete, so the accounts.google.com OIDC provider (+ IAM resources) leaked on stack teardown. The Delete handler (removes only this stack's client IDs, deletes the provider only when empty), scoped IAM perms, idempotency (NoSuchEntityException tolerance), shared-provider safety, and the new URL(url).host refactor are all good. 6-scenario test file is a nice addition.

Should-fix (cheap, none break the happy path)

  1. Inaccurate comment — auth-cognito-stack-builder.ts:~1073. Says ListOpenIDConnectProviders must use Resource '*', but the code scopes to arn:aws:iam::<acct>:oidc-provider/* (via selector: '*'). Misleading — reword to describe the wildcard provider ARN.
  2. Asymmetric race — openIdLambda.js:~93 (sole-owner branch). Shared-provider branch re-reads before deleting; sole-owner deletes from the stale initial read. A concurrent Create could be destroyed. Mirror the re-read (Get → confirm empty → delete).
  3. Missing url null-guard. clientIdList got a guard this PR; url didn't → new URL(undefined) throws TypeError. Add event.ResourceProperties.url ?? '' (or validate early).

Nits

  1. PR body says 4 tests; file has 6 — update the count.
  2. openIdLambda.test.js:5 mutates module-level mockState (guideline: reset a let in beforeEach).
  3. Shared-provider test injects state directly; prefer a 2nd Create to set up shared state (also exercises add-client-ID path).
  4. Pre-existing gap (not this PR): no snapshot coverage of the OpenId IAM policy.

Please run the full split e2e + all PR checks and get them green before merge.

- Mirror the pre-delete re-read in the sole-owner branch so a provider that a
  concurrent stack added client IDs to (or re-created) is not destroyed; only
  our client IDs are dropped in that case.
- Guard findProviderArn against a missing url (no more new URL(undefined) throw).
- Reword the stack-builder IAM comment: ListOpenIDConnectProviders is scoped to
  the wildcard provider ARN (oidc-provider/*), not Resource '*'.
- Tests: reset mockState via reassignment in beforeEach, and set up the
  shared-provider case with a real second Create (exercises add-client-ID).
- Apply prettier formatting (fixes the lint/prettier-check CI failure).
@ahmedhamouda78 ahmedhamouda78 dismissed stale reviews from sarayev and soberm via d153b19 July 3, 2026 15:53
@ahmedhamouda78

ahmedhamouda78 commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

Thanks @sarayev — addressed in d153b19:

Should-fix

  1. Inaccurate IAM comment: Reworded — ListOpenIDConnectProviders is granted against the wildcard provider ARN (oidc-provider/*) in this account, not Resource '*'.
  2. Asymmetric race (sole-owner branch): The sole-owner branch now re-reads immediately before deleting (Get → confirm nothing but our client IDs remain → delete). If a concurrent Create added client IDs (or re-created the provider) after our first read, we fall back to removing only our client IDs instead of deleting. Both branches are now symmetric.
  3. url null-guard: findProviderArn returns early when url is falsy, so new URL(undefined) can no longer throw.

Nits
4. Updated the PR description (6 tests, not 4).
5. mockState is now reset via reassignment (let + beforeEach).
6. The shared-provider test now sets up shared state with a real second Create (which finds the existing provider and adds its client ID), exercising the add-client-ID path instead of injecting state.
7. Snapshot coverage of the OpenId IAM policy — agreed it's a pre-existing gap; leaving it out of this PR's scope.

All 6 unit tests pass, and prettier/lint is now clean (the earlier _lint failure was a prettier formatting miss on the test file, fixed here).

@sarayev sarayev 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 addressing the feedback — approving, this looks good to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants