feat(#1277): the CloudFront origin access control family, and a tagged create - #1280
Merged
Merged
Conversation
…d create An origin access control is what lets a distribution serve a private S3 bucket, and substrate routed none of it: /2020-05-31/origin-access-control was not a path the plugin knew, so the create reached the unknown-route refusal and a consumer keeping its bucket private could not run against the emulator at all. That is the first of the ten missing operations #1274's adopter found, and the one it named as blocking. The four operations land in emulator/cloudfront_oac.go, along with the one thing this service had not modelled: a version. DeleteOriginAccessControl takes an If-Match, so the record stores an ETag and the delete separates the three ways it can fail — an absent control is NoSuchOriginAccessControl/404, a missing version is InvalidIfMatchVersion/400 and a stale one is PreconditionFailed/412, because telling a caller that sent no version that its version was stale is a wrong answer. The four Required: Yes config members are validated, three against their published enums and case-sensitively: accepting "S3" would pass a request through substrate that AWS refuses. An account using no controls answers no Items element at all, which is what the page states and what a decoder cannot tell from an empty one, so the test asserts on the raw XML. CreateDistributionWithTags is the same path and verb as CreateDistribution with ?WithTags — a bare query key, so the routing tests for the key's presence; testing for a value would have sent a tagged create to the untagged handler and dropped the tags while answering 201. Its body is decoded strictly, unlike CreateDistribution's, for #883's reason. generateCloudFrontID converts to the request's IDMint rather than gaining a fourth crypto/rand caller, which crosses CloudFront off #856: a recorded stream that creates a control, creates a distribution, invalidates inside it and deletes the control with the ETag the create handed out replays with zero differences. 28 draw sites remain. OriginAccessControlInUse/409 and OriginAccessControlAlreadyExists/409 are deliberately absent and documented as such — the first needs a distribution's origins, which substrate records nowhere (#1271). Refs #1274.
The code's published description is "The If-Match version is missing or not valid", which is two cases, and the second was answering PreconditionFailed — telling a caller with a typo that its version was stale. Decidable only because the ETag rendering is substrate's own: a value outside the shape it mints was never handed out, so it cannot be a stale one.
Codecov Report❌ Patch coverage is
📢 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.
An origin access control is what lets a CloudFront distribution serve a private S3 bucket: CloudFront signs the origin request with SigV4 and the bucket policy trusts the distribution rather than the world. Substrate routed none of it —
/2020-05-31/origin-access-controlwas not a pathparseCloudFrontOperationknew, so the create reachedHandleRequest's unknown-route refusal and a consumer keeping its bucket private could not run against the emulator at all. That is the first of the ten missing operations #1274's adopter found by replacing ~1,100 lines of hand-written AWS fakes, and the one it named as blocking.Closes #1277. Refs #1274.
What lands
The four OAC operations, in a new
emulator/cloudfront_oac.go:CreateOriginAccessControl— 201 with theETagandLocationheaders. Neither is in the API Reference's Response Syntax block, which shows the XML body alone; both are top-level output members in the CLI and the SDKs, and theETagis the value a caller has to hold to delete the control later. Emitting them as headers is how a REST/XML output member that is not in the body reaches a caller.Required: Yesconfig members are validated, three of them against their published enums (s3|mediastore|mediapackagev2|lambda,never|always|no-override,sigv4), case-sensitively: acceptingS3would pass a request through substrate that AWS refuses, which is the direction a consumer pays for with a failed live deploy.GetOriginAccessControl— the same document and the same version, through one renderer so the two cannot drift.ListOriginAccessControls— the account's controls whole. An account using none answers noItemselement at all, which is what the page states and what a decoder cannot tell from an empty one, so the test asserts on the raw XML.Marker/MaxItems/NextMarkerare not rendered, for the reasonListDistributionsomits them.DeleteOriginAccessControl— 204, with the version contract separated into the codes the reference publishes for it: absent control →NoSuchOriginAccessControl/404, missing or malformedIf-Match→InvalidIfMatchVersion/400 (the code's own description is "missing or not valid", two cases in one sentence), well-formed but stale →PreconditionFailed/412.CreateDistributionWithTags— the same path and verb asCreateDistributionwith?WithTags. The key is published bare, so a bare query key arrives as the sentinel"1"and the routing tests for the key's presence; testing for a value would have sent a tagged create to the untagged handler and dropped the tags while answering 201. Its body is decoded strictly, unlikeCreateDistribution's: a caller that asked for tags must not be handed an untagged distribution and a success, which is #883's argument applied to the create path. Both halves happen in one write, so the distribution is never observable untagged.CloudFront crosses off #856.
generateCloudFrontIDwas one of the remainingcrypto/randdraw sites, and adding a fourth caller to it would have widened a list #856 is actively shortening, so it converts here instead: it takes the request'sIDMint, loses its error return, and the alphabet it maps onto is a named constant its three callers share. The mapping is byte-for-byte the one it performed before, so an ID recorded by an earlier substrate is still the shape this one mints. 29 → 28 draw sites.Two published behaviours deliberately absent
Both are recorded in the code and in
docs/services.mdrather than left to be discovered:OriginAccessControlInUse/409 needs to know which distributions reference the control, and substrate records noOrigins— the same gap "A configuration is not a distribution" describes, and whyGetDistributionConfiganswers two of five required members. CloudFront: UpdateDistribution should reject an incomplete DistributionConfig (IllegalUpdate / missing-field errors) #1271 is where a distribution starts recording its configuration and the check becomes answerable; the deferral is noted on both issues and in both directions in the docs.OriginAccessControlAlreadyExists/409 is published for a control "with the specified parameters" without publishing which parameters, and an OAC carries noCallerReferenceto key a duplicate on the way a distribution does. A repeated create mints a second control; a consumer converging by name should list first.UpdateOriginAccessControlis not implemented — #1274 lists four OAC operations, and a create-if-absent converge loop does not reach the fifth.One reading is substrate's rather than AWS's, and is worth a reviewer's attention: the
ETagshape. AWS publishes only that the value identifies the current version, so substrate mints the sameE-prefixed form it mints IDs in. That is also what makes "malformed" decidable — a value outside that shape was never handed out here, so it cannot be a stale one — which is a statement about substrate's own minting, not a claim about what CloudFront accepts. Either way the request is refused; only the code a caller reads differs.Verification
make lint— 0 issues.make test(race,-count=1) — all green.make docs-reference-check docs-versions version-check discarded-unmarshal-check wire-bookkeeping-check— the ratchet still reports 330 wire-visible bookkeeping fields: the new state type carries none of the five names, since the record is read back under the account that created it and no ARN path addresses one.grep -c 'rand\.Read('over non-testemulator/— 28, down one, with no new draw site.xml.Marshal/json.Marshalover a struct of strings), which cannot be reached and must not discard their error.TestReplay_ACloudFrontCreateReplaysWithTheIdentifiersItMintedis load-bearing, not decorative: pointed at a nil mint it reports five major differences and a critical state-hash mismatch, including the recordedCreateInvalidationansweringNoSuchDistributionand the recorded delete answeringNoSuchOriginAccessControl.