Repository navigation
fix(sns): apply an SNS subscription attribute edit in place instead of replacing it (#4853) - #4854
Merged
Merged
Conversation
…f replacing it (#4853) SNSSubscriptionProvider.update unsubscribed and subscribed again on every update: a new subscription ARN, messages lost in the gap, and an email or http(s) endpoint back to pending confirmation. It now reads the subscription and sets only the attributes that differ with SetSubscriptionAttributes, resetting a removed RawMessageDelivery / FilterPolicy / FilterPolicyScope / RedrivePolicy to its default. A create-only difference, an unresettable removal, a FilterPolicy changing with its scope, and an unreadable record keep the delete-first replacement and its #1778/#1967 abort.
…unconfirmed one (#4853) Review round 1 on #4854: a FilterPolicyScope is not reset on a subscription left with no filter policy (it filters nothing, and SNS may refuse it, failing every later deploy); a subscription AWS reports PendingConfirmation takes the replacement. The thrown-delete tests reach the replacement through an explicit create-only readback. The integ adds a scope-only in-place phase and drops policy and scope together.
…ion-check the replacement arm (#4853) Security review on #4854: - S1: the policy+scope guard keys on the PLAN, so a new FilterPolicy meeting a scope dropped from the template replaces rather than evaluating the new policy against the old scope between two calls. - N2: create() takes a CreateContext and masks the Subscribe error text; replace() hands it the update's masker. - N3: replace() hands its delete the record's expectedRegion.
…tion-in-place-update
Owner
Author
|
Merged — thanks. Planning against the live readback rather than previousProperties was the right call for the revert callers, and the security round (plan-keyed policy+scope guard, masked replacement errors, region forwarded) closed every gap it raised; all five SNS fixtures passed and the pre-fix arm went red at Phase 2. |
This was referenced Oct 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Editing a mutable attribute of an
AWS::SNS::Subscription(RawMessageDelivery,FilterPolicy,FilterPolicyScope,RedrivePolicy,DeliveryPolicy,ReplayPolicy,SubscriptionRoleArn) madecdkd deployunsubscribe and subscribe again. The subscription got a new ARN, messages published in the gap were not delivered, and an email / http(s) endpoint went back toPendingConfirmation. CloudFormation applies these withSetSubscriptionAttributes, in place.SNSSubscriptionProvider.updatenow:GetSubscriptionAttributes) and plans against what AWS holds, neverpreviousProperties. Three of the fourupdate()callers (deploy, rollback revert,--revert-failed) pass the state record as the previous side, which can equal the desired bag while AWS drifted, and acdkd drift --reverthands the record as both bags;SetSubscriptionAttributescall each. Policies compare parsed; an attribute AWS omits counts as its default, so an email subscription is never sentRawMessageDelivery(AWS refuses it on that protocol);RawMessageDelivery->false,FilterPolicy/RedrivePolicy->"",FilterPolicyScope->MessageAttributes(after the policy). A scope is NOT reset on a subscription left with no policy: it filters nothing there, and SNS may refuse to set one without a policy, which would fail every later deploy;TopicArn/Protocol/Endpoint) differing from AWS. The engine routes such a template change to its own replacement, so this arm is a rollback revert or a record naming another subscription;PendingConfirmation: true(an unconfirmed email / http(s) endpoint), which loses nothing by being re-created;DeliveryPolicy,ReplayPolicyorSubscriptionRoleArn, which have no documented reset;FilterPolicyand aFilterPolicyScopechange in the same plan, whatever the template declares (a scope dropped from the template resets too; security review S1).Subscribeapplies them together, while two calls would evaluate the new policy against the old scope in between, and a failed second call would leave a pair neither side asked for. Neither order closes that gap, since a scope set first is refused against the old policy's shape;PendingConfirmationplaceholder id, a subscription AWS no longer has (NotFound), and a readback with no attributes;SetSubscriptionAttributesfailure. The AWS text is masked throughcontext.maskSecrets(wrapMaskedAwsErrorthroughwrapMaskedAwsCallError,causekept). The provider now declaresUpdateContext, so it leaves theOMITS_UPDATE_CONTEXTworklist (scripts/check-provider-update-context.ts);create()now takes aCreateContextand masks theSubscribeerror text: AWS can quote a rejectedFilterPolicyholding a resolved secret back, andreplace()hands it the update's masker, neverreplayingState.replace()also hands its delete the record'sexpectedRegion, so aNotFoundfrom another region is refused byassertRegionMatchrather than read as already gone. Every update error path is masked now, and earlier text in this PR body said so too soon: before this round, the replacement arm'sSubscribeerror was not masked.The delete / replace safety logic (#1778, #1967) is not modified; it is now reached only by the arms above.
Tests
tests/unit/provisioning/sns-subscription-in-place-update-4853.test.ts(16 cases), red before the change:RawMessageDelivery,FilterPolicyandSubscriptionRoleArn;resets a dropped FilterPolicyScope while a policy stayswas flipped to that. The scope-alone reset and the empty-policy exemption are pinned too;Subscribeerror is masked (N2), and aNotFoundwith the record in another region refuses the replacement (N3).sns-subscription-provider.test.ts,sns-subscription-provider-roundtrip.test.ts,replace-path-delete-skip-outcome.test.ts,sns-replace-abort-rollback-caller.test.ts,sns-subscription-thrown-delete.test.ts. The fix(provisioning): six more delete-result discarders outside the deploy engine — provider delegation plus four REPLACE paths that orphan or duplicate the old resource #1778 / fix(sns): a THROWN delete during a subscription replacement still creates, leaving two live subscriptions #1967 abort cases now reach the replacement through an explicit create-only difference in the readback (the reachable path), and the thrown-delete discriminator asserts the single readback. The round-trip cases assert the in-place calls.previousProperties;FilterPolicy+ scope guard off;wasReplaced: true;Arnattribute dropped;PendingConfirmationcheck off;create()'s mask off, orreplace()not passing the masker (N2);replace()not forwardingexpectedRegion(N3);Integration
Arm in the existing
tests/integration/sns-subscription-update/fixture. No open PR holds it;noecho-named-producer(#4850) is untouched.RawMessageDelivery: falseandFilterPolicy {kind:[a]}.CDKD_TEST_UPDATE=raw-delivery,filter-b):RawMessageDeliverytrue andFilterPolicy {kind:[b]}reach AWS on the SAME subscription. The ARN, statephysicalId,attributes.Arnand theFn::GetAttOutput are unchanged, and the topic holds exactly one subscription. A binary without this change is red here: the ARN changes.+body-scope):FilterPolicyScopeMessageBodyalone, applied in place, same ARN.+drop-filter):FilterPolicyand its scope removed from the template. The policy is cleared on the live subscription and the scope is left, same ARN.could not read its attributes, with SNS'sInvalid parameteras an independent sentinel), and no Unsubscribe or Subscribe is issued. The fix(sns): a THROWN delete during a subscription replacement still creates, leaving two live subscriptions #1967 thrown-delete abort sits on the replacement path, which an attribute edit no longer takes; the unit tests pin it.Not run against real AWS.
src/provisioning/providers/**is in theinteg-destroymarker scope, so a clean/run-integis needed before merge. Suggested:sns-subscription-update, plussns-subscription-filter,sns-inline-subscription,sns-pending-subscription,sns-sqs-event.Closes #4853