fix(ec2): close the gaps in the IPAM internet-registry operations - #2533
Merged
Merged
Conversation
A review of the 15 operations that shipped in #2511 found them accepting requests they then failed to honour. Each one is fixed here with the test that proves it. - The child request document interpolated `ServiceUri`, the handles, the RPKI version and the parent BPKI TA straight into XML, so an ordinary URI carrying `&` produced a document the RIR cannot parse, and a handle carrying a quote closed an attribute early. All five are escaped. - BatchModifyIpamRoutingPolicyRegistrations read ASNs only as JSON strings, so a document writing them as numbers registered a CIDR that authorized nobody and reported itself unknown/permissive. Numbers are accepted. - A batch entry that could not be applied was skipped after the whole document had already been recorded as published, so nine of ten entries applying still reported success. The document is validated before anything is recorded, and a bad entry fails the request. - Batch entries bypassed the validation the single-op path runs, so a `maxLength` of 200 was stored and emitted for a member the model bounds to 0..48. Both paths share one validation now, and a batch `add` for a CIDR that is already registered is an upsert, matching the operation's own docs. - ModifyIpamRoutingPolicyRegistration rebuilt the record from the request, so `MaxLength`, `Description` and `PermitMoreSpecificAnnouncements` vanished whenever the request omitted them. Modify is a partial update. - DryRun returned success before the checks it exists to surface: a dry-run create against an existing CIDR or a nonexistent IPAM, and a dry-run modify or delete of a CIDR that is not registered, all reported a success the real call then refused. Every dry run now reaches the same verdict as the real call. - The create path parked an association in `pending-enable` and documented that it cannot publish until enabled, but nothing enforced it, leaving EnableIpamInternetRegistryAssociation decorative. Registration writes require an enabled association. - ClientToken was modeled on all five mutating operations and ignored by all of them, so an SDK retrying a timed-out create got a conflict instead of a replay. Tokens are recorded per action and replay their original result. - The eight paginated reads validated MaxResults and then returned everything, never emitting nextToken, and discarded the modeled Filters. They paginate and filter. - DeleteIpamInternetRegistryAssociation deleted an association out from under its registrations; it now refuses with DependencyViolation until they are gone. Also: a conformance ordering assertion compared `Option<usize>`, so a dropped delta passed vacuously (`None < Some`), and the bounded container-CLI helper left its reader thread blocked on a pipe a wrapper script's grandchild still held, leaking a thread per timed-out call on exactly the wedged-daemon path the bound exists for. The helper now runs the CLI in its own process group and kills the group, so the read end closes and the reader exits; collection is bounded by a drain grace. fakecloud-testkit carried a second copy of those helpers with the same leak and now shares core's, and `detect_bridge_gateway`, which still called the CLI unbounded, goes through them too.
A review of the previous commit found the fixes themselves incomplete. - ClientToken replay keyed only on the token, so a token reused with different parameters discarded the new request and returned the old result with a 200. Deleting 192.0.2.0/24 and then retrying the same token for 198.51.100.0/24 reported a published delete for a CIDR that is still registered, and the caller then hit the new DependencyViolation on an association it had been told was empty. Every mutating operation now records a fingerprint of the request parameters beside the token, replays only on an exact match, and answers a divergent reuse with IdempotentParameterMismatch. - The token store grew forever. ClientToken is an idempotency token, so every SDK fills a fresh one on every call, not just on retries: a create/delete loop left one permanent entry per call in state and in every snapshot. Records now age out after 24 hours and are capped oldest-first. - A batch that removed a CIDR the same document added was rejected, because removals were checked against the state before the batch while additions are applied first. The document is now validated against the state it would itself produce. - Turning Modify into a partial update left no way to clear MaxLength, Description or PermitMoreSpecificAnnouncements: an explicit empty value was dropped as absent, and a JSON null in a delta document was too. Omitted still means unchanged; present-but-empty and null now clear. - GetIpamRoutingPolicyRegistrationDeltas paged ChronologicalOrder=reverse by offset into a reversed list, so a delta appended between two page requests shifted every later index and the second page repeated entries. Reverse now pages by delta id. - MaxResults that was not a number disabled paging and returned everything, while a non-numeric NextToken was rejected; both are rejected now. - The batch path's registration check could never fail (its write variant fell through to Ok), so the validation it claimed to share was a no-op; it is replaced by the projection above. upsert_registration no longer decides create-vs-modify by string-comparing the action name. Publishing is gated on an enabled association and removal never is, so a snapshot restored from before the gate can still be emptied and deleted. Also from the same review: the bounded container-CLI helper left stdin inherited while putting the child in its own process group, so a CLI that read the terminal took SIGTTIN and stopped, and the WNOHANG wait never saw it, which burned the whole deadline; stdin is /dev/null now. The dedup left wait_bounded and its KillScope enum with no callers, and bounded_status still took &[String] where its sibling took &[&str], allocating per container in the sweep.
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.
A review of the 15 IPAM internet-registry / routing-policy-registration operations that shipped in #2511 found them accepting requests they then failed to honour. Each defect is fixed here with the test that proves it.
Wire correctness
ServiceUricontaining&, or a handle containing", produced a document the RIR cannot parse (200 to the caller, breakage discovered later).unknown/permissive.Requests accepted but not honoured
maxLength=200for a member the model bounds to 0..48. One shared validation; a batchaddon an existing CIDR is an upsert per the op's docs.Modifyrebuilt the record, silently clearingMaxLength/Description/PermitMoreSpecificAnnouncementswhen omitted. Now a partial update.ClientTokenwas modeled on all five mutating ops and ignored: a retried timed-out create got a conflict instead of a replay.MaxResultsthen returned everything with nonextToken, and discarded modeledFilters.Preconditions
DryRunreturned success before the checks it exists to surface (existing CIDR, nonexistent IPAM, unregistered CIDR). Dry runs now reach the same verdict as the real call.pending-enablewas documented but unenforced, leavingEnableIpamInternetRegistryAssociationdecorative.DeleteIpamInternetRegistryAssociationdeleted an association out from under its registrations; nowDependencyViolationuntil they are gone.Adjacent, same class
Option<usize>, so a dropped delta passed vacuously (None < Some).fakecloud-testkitcarried a second leaky copy and now shares core's, anddetect_bridge_gateway(still unbounded) goes through them.Local gate: workspace build +
clippy -D warningsclean, fmt clean, 22 IPAM unit tests, 9 bounded-CLI tests, 9 testkit tests, 69 EC2 IPAM conformance tests green.Summary by cubic
Fixes the 15 IPAM internet-registry operations that accepted requests they didn’t honor. All operations now follow their modeled contracts: wire documents are valid, batch and single-operation behavior match, mutating operations are idempotent and respect preconditions, and reads paginate and filter.
Bug Fixes
ServiceUri, handles, RPKI version, and parent BPKI TA in child request XML;&and quotes no longer produce a document the RIR can’t parse.addon an existing CIDR acts as an upsert.Modifyas a partial update, preserving omitted members.MaxLength,Description, andPermitMoreSpecificAnnouncementswhen sent as an empty value or JSONnull, while omitted still means unchanged.ClientTokenwith a fingerprint of the request parameters, so retries replay the original result; reuse with different parameters returnsIdempotentParameterMismatch.pending-enablefor registration writes; removal is not gated, so an association restored from an older snapshot can still be emptied and deleted.DependencyViolationwhen deleting an association that still has registrations.MaxResults,Filters, andnextToken; reverse paging uses delta ids so new deltas between pages aren’t repeated, and non-numericMaxResultsis rejected instead of returning everything.Option<usize>and passed vacuously when a delta was dropped./dev/nullstdin, so a wedged daemon no longer leaks a reader thread;fakecloud-testkitshares core’s helpers anddetect_bridge_gatewaygoes through them too.Written for commit 96e7bb6. Summary will update on new commits.