fix(#856): the internal dispatch paths — every identifier in every CloudFormation stack - #1293
Merged
Merged
Conversation
Every earlier tier of #856 migrated a generator. Counting generators answers the wrong question: a plugin whose minters are all migrated still publishes random identifiers if it is called with nothing to derive from. Substrate has four internal dispatch sites — a RequestContext built in-process rather than parsed off the wire — and the CloudFormation deployer is one of them, so every identifier of every resource in every CloudFormation stack was undirected randomness while the draw-site count read two. dispatch now seeds a mint from its request id, and WithDeployerMint derives that request id from the request that asked for the deployment. The two fix different failures: the first makes a replayed internal event reproduce, the second makes a replayed CreateStack — which re-runs the whole deployment — reproduce too. Reverting either alone gives 7 and 6 differences respectively against the new replay test, each reported as a successful 200. Falling out of the same seam: a StackDriftDetectionId is no longer substrate's internal req-… string, deploySSMAssociation is no longer the deployer's last randomHex caller, and an API Gateway proxy integration carries a derived mint into the Lambda invocation it dispatches. The Lambda ESM poller's two dispatches stay mintless, with an in-code note, because a wall-clock ticker records nothing for a replay to derive from (#1292). Refs #856.
6 tasks done
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
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.
Tier 8 of #856, and the one tier the draw-site counter could not have found.
Every earlier tier migrated a generator: a function that called
crypto/randand nowdraws from
IDMintinstead. That work is finished — two non-testrand.Readsites remain,one of which is
IDMint's own fallback. But counting draw sites answers the wrong question.A plugin whose minters are all migrated still publishes random identifiers if it is
called with nothing to derive from, and substrate has four places that call a plugin
with a
RequestContextit built itself rather than parsed off the wire.The CloudFormation deployer is the expensive one. Deploying a stack turns each resource into
an internal EC2, IAM or S3 request, and those requests were built with a fresh
generateRequestID()and no mint — so every identifier of every resource in everyCloudFormation stack was undirected randomness, while
StackDeployeritself contains nodraw site at all. A replayed
DescribeStackResourcesreported four differentPhysicalResourceIdvalues in a 200, andCreateStack'sstate_hash_afternevermatched, because that hash covers all of them.
What changed
Two changes, fixing different failures:
dispatchsetsIDs: NewIDMint(requestID)on the context it builds. That makes a replayedinternal event reproduce, because a replay dispatches with the recorded request id.
WithDeployerMintderives the internal request id itself from the mint of the request thatasked for the deployment (
"req-cfn-" + m.Hex(12)). That makes a replayed outerCreateStackreproduce — replaying it re-runs the whole deployment, so the internal idshave to come out the same way twice. The sequence is reproducible because
DeployWithOptionsalready sorts by type priority then logical id, never by map iteration,and a deployment is single-goroutine.
Both halves were measured by reverting each alone against the new replay test:
7 differences without the mint (all four
PhysicalResourceIdvalues plusCreateStack'sstate_hash_after,critical), 6 differences without the derived request id. In bothcases the replay reported every event as a success — a 200 describing four resources that
were not the recorded ones, which is the silent-wrong-answer class rather than a visible
failure.
Three smaller pieces fall out of the same seam:
StartStackDriftDetectionanswered aStackDriftDetectionIdthat was substrate's internalreq-…string verbatim — a shape no AWS reference describes, in an element a consumer handsstraight back to
DescribeStackDriftDetectionStatus. It is nowIDMint.HexUUID.deploySSMAssociationwas the deployer's lastrandomHexcaller. It mints from thedeployer's mint directly, because there is no request to dispatch: SSM's plugin models Run
Command and Parameter Store, not State Manager.
carries a derived mint into it. The two
requestIdvalues inside the proxy event payloadare deliberately untouched: they are handed to a function whose code never runs, so no API
observation depends on them.
A Lambda event-source-mapping poll is deliberately left on the fallback, with an in-code note
pointing at #1292: its dispatches come from a wall-clock ticker and are recorded nowhere, so
there is no recorded id to derive from and a seed there would be no more reproducible than
the fallback it replaced.
Client.Deployseeds a mint from a fresh request id rather than passing nil, so an in-processdeploy derives its resource ids from one seed. The seed is random because an in-process deploy
is not a recorded AWS request — there is no outer event to re-run — and each dispatched request
records the derived id it used, so a replay of those events still reuses it.
Tests
TestReplay_ACloudFormationStackReplaysWithTheIdentifiersItCreatedrecords a four-resourcestack over three services, replays the stream with
ValidateState, and asserts zeroDifferences,TotalEvents == SuccessEventsandStateValid— the assertion this issue hasnever been able to make about a stack. Plus
TestIDs_ADriftDetectionIDIsDerivedAndUUIDShaped(shape, the published bound, and the statusread that takes the id back) and
TestIDs_AProxyIntegrationInvokeIDDerivesFromTheRequestThatReachedIt, which covers theseedless branch through a new
APIGatewayInternalRequestIDForTestwrapper — that branch isunreachable through the integration, since a request off the wire always carries a mint.
make lint0 issues,make testgreen, the five make checks pass with the wire-bookkeepingratchet still at 330, patch coverage 29/29 against
git diff -U0 main.Provenance
API_DetectStackDriftpublishesStackDriftDetectionIdas a String with a maximum lengthof 36 and no pattern. The UUID shape is observed from the page's own sample response,
2f2b2d60-df86-11e7-bea1-500c2example.IDMint.HexUUIDrenders exactly 36 characters, so thepublished bound is met at its limit. Reading AWS's example is the provenance #671 permits;
inventing a bound from a sibling operation is not.
Everything else here is a change of where bytes come from, not of what they look like: no
identifier's rendering changes except the drift-detection id, which was not an AWS shape to
begin with.
Compatibility
StackDriftDetectionIdno longer looks likereq-…. A test asserting on that literalprefix — substrate's internal request-id format, in an AWS response element — will need
updating; one asserting the published 36-character bound will not.
before it holds physical resource ids drawn from
crypto/rand, and replaying it now derivesdifferent ones. That is the same one-way migration every earlier tier of Identifiers minted from crypto/rand make a replay diverge from its recording, and three replay checks are weakened for it #856 carried, and it
is the direction that makes replay assertable at all: re-record the stream.
Refs #856.