canopycms-cdk 0.0.67 → 0.0.68-int.101
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/constructs/asset-support.d.ts +103 -81
- package/dist/constructs/asset-support.js +193 -196
- package/dist/constructs/cms-distribution.d.ts +7 -7
- package/dist/constructs/cms-distribution.js +19 -67
- package/dist/constructs/cms-service.d.ts +55 -8
- package/dist/constructs/cms-service.js +67 -15
- package/dist/constructs/editor-routing.d.ts +131 -0
- package/dist/constructs/editor-routing.js +313 -0
- package/dist/index.d.ts +1 -0
- package/lambda/asset-transform/dist/handler.js +1336 -650
- package/package.json +4 -4
- package/worker/dist/index.js +1739 -800
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ AWS CDK constructs and EC2 worker for deploying CanopyCMS.
|
|
|
14
14
|
- Lambda Function URL (for CloudFront origin)
|
|
15
15
|
- EC2 Worker (t4g.nano spot in ASG, public subnet, EFS mount)
|
|
16
16
|
- Security groups and IAM roles (least-privilege)
|
|
17
|
+
- `attachTo(distribution)`: adds the editor's `/edit`, `/edit/*` and `/api/canopycms/*` behaviors (and, optionally, the CMS build's `assetPrefix` and the preview route) to a CloudFront distribution you already own
|
|
17
18
|
|
|
18
19
|
**`CanopyCmsDistribution`** — CloudFront + DNS (optional):
|
|
19
20
|
|
|
@@ -21,8 +22,9 @@ AWS CDK constructs and EC2 worker for deploying CanopyCMS.
|
|
|
21
22
|
- CloudFront distribution with Lambda Function URL origin
|
|
22
23
|
- Route53 A/AAAA alias records
|
|
23
24
|
- Cache policies: no-cache for API/editor, long-cache for static assets
|
|
25
|
+
- Response headers: framing protection (`frame-ancestors 'self'`), `nosniff`, HSTS, `X-Robots-Tag: noindex`
|
|
24
26
|
|
|
25
|
-
Use `CanopyCmsDistribution`
|
|
27
|
+
Use `CanopyCmsDistribution` when the CMS gets its own domain. To serve the editor from an existing distribution, call `cmsService.attachTo(distribution)` instead of wiring the `functionUrl` by hand; see [Serving the editor from a distribution you already own](../../docs/deploying-to-aws.md#serving-the-editor-from-a-distribution-you-already-own).
|
|
26
28
|
|
|
27
29
|
### EC2 Worker
|
|
28
30
|
|
|
@@ -128,50 +128,91 @@ export interface AssetSupportProps {
|
|
|
128
128
|
*/
|
|
129
129
|
readonly autoDeleteObjects?: boolean;
|
|
130
130
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
131
|
+
* Deny every `s3:PutObject` to `assets/*`, `asset-originals/*` and `asset-meta/*` that carries
|
|
132
|
+
* no `If-None-Match`, so no principal can replace an object under those content-addressed
|
|
133
|
+
* prefixes. Standalone mode only: with a BYO `bucket` the prop is refused, and the same statement
|
|
134
|
+
* belongs in that bucket's own policy (see docs/deploying-to-aws.md).
|
|
135
135
|
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
136
|
+
* Opt-in because the Deny binds every writer: the CMS Lambda, which runs the `canopycms` your app
|
|
137
|
+
* installs, and with `lazyPublicTransforms` the transform Lambda, which bundles the `canopycms`
|
|
138
|
+
* this package was built with and calls S3 through the Lambda runtime's AWS SDK. Upgrade and
|
|
139
|
+
* deploy both to releases whose stores write create-only, then enable this. It also denies every
|
|
140
|
+
* unconditional write, such as a plain `aws s3 cp`, and every multipart upload (`UploadPart`
|
|
141
|
+
* carries no `If-None-Match`). Replication is authorized as `s3:ReplicateObject`, so it is not
|
|
142
|
+
* denied; a replica bucket takes the same statement.
|
|
141
143
|
*
|
|
142
|
-
*
|
|
143
|
-
* `
|
|
144
|
+
* Create-only holds only as far as `s3:DeleteObject` does: a delete then a create replaces an
|
|
145
|
+
* object, so keep Delete on `assets/*` and `asset-originals/*` to a break-glass role. That is
|
|
146
|
+
* also how a bad derivative is replaced: delete it, then rerun `materialize-assets`. On a
|
|
147
|
+
* versioned bucket the delete writes a delete marker and the bad version stays restorable; a
|
|
148
|
+
* replica keeps the bad version until the rematerialized object replicates.
|
|
144
149
|
*
|
|
145
|
-
* @default
|
|
150
|
+
* @default false
|
|
146
151
|
*/
|
|
147
|
-
readonly
|
|
152
|
+
readonly enforceCreateOnlyWrites?: boolean;
|
|
148
153
|
/**
|
|
149
|
-
*
|
|
150
|
-
*
|
|
154
|
+
* A replica of the bucket's `assets/` prefix. Both public behaviors fail over
|
|
155
|
+
* to it on 500, 502, 503 or 504 from the primary, never on a miss: the
|
|
156
|
+
* replica only holds what the primary has. With `lazyPublicTransforms`,
|
|
157
|
+
* `/assets/t/*`'s one fallback slot is the transform Lambda, so only
|
|
158
|
+
* `/assets/*` gets the replica.
|
|
159
|
+
*
|
|
160
|
+
* It is read through its own OAC, whose grant CDK writes only on a bucket it
|
|
161
|
+
* owns: an imported replica's policy must allow this distribution itself.
|
|
162
|
+
* Import one in another region with `Bucket.fromBucketAttributes({ bucketName,
|
|
163
|
+
* region })` so its regional domain name is right. Replication must cover
|
|
164
|
+
* `assets/`; `materialize-assets` writes only the primary.
|
|
165
|
+
*
|
|
166
|
+
* @default - no failover
|
|
151
167
|
*/
|
|
152
|
-
readonly
|
|
168
|
+
readonly replicaBucket?: s3.IBucket;
|
|
153
169
|
/**
|
|
154
|
-
*
|
|
170
|
+
* Compute a missing `/assets/t/*` derivative on request with a transform
|
|
171
|
+
* Lambda, instead of serving only what `canopycms materialize-assets` wrote.
|
|
172
|
+
* Off, the public path computes nothing and an unmaterialized URL is a miss,
|
|
173
|
+
* which is also what a live preview on another origin than the editor shows
|
|
174
|
+
* for a draft's new crop.
|
|
175
|
+
*
|
|
176
|
+
* On, anyone can mint any allowlisted transform of a public asset - bounded
|
|
177
|
+
* per asset except crop, at ~10^16 rects - capped by reserved concurrency and
|
|
178
|
+
* an `assets/t/` expiry that applies only to objects the Lambda writes, which
|
|
179
|
+
* carry the tag `canopy-transform=lazy`: what `materialize-assets` writes is
|
|
180
|
+
* never expired. On a BYO `bucket` this construct cannot write that rule, so
|
|
181
|
+
* lazy mode there requires an explicit `transformOutputRetention`.
|
|
155
182
|
*
|
|
156
|
-
*
|
|
157
|
-
* not pre-warmed capacity, so it costs nothing when idle. It bounds the
|
|
158
|
-
* blast radius of the anonymous `/assets/t/*` path; see the default
|
|
159
|
-
* constant's doc comment. Raise it for an unusually image-heavy site;
|
|
160
|
-
* setting it to 0 would disable transforms entirely.
|
|
183
|
+
* @default false
|
|
161
184
|
*/
|
|
162
|
-
readonly
|
|
185
|
+
readonly lazyPublicTransforms?: boolean;
|
|
163
186
|
/**
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
187
|
+
* Require the transform Lambda's code asset to carry the `.deployable`
|
|
188
|
+
* marker a full `build:lambda` writes, proving it has its native sharp
|
|
189
|
+
* binary. Checked only with `lazyPublicTransforms`. Set `false` ONLY in this
|
|
190
|
+
* package's own tests, which synth against the `--skip-native` fixture.
|
|
167
191
|
*
|
|
168
|
-
*
|
|
169
|
-
|
|
170
|
-
|
|
192
|
+
* @default true
|
|
193
|
+
*/
|
|
194
|
+
readonly requireDeployableBundle?: boolean;
|
|
195
|
+
/** Lazy mode only. Retention for the transform Lambda's log group (default 90 days). */
|
|
196
|
+
readonly transformLogRetention?: logs.RetentionDays;
|
|
197
|
+
/**
|
|
198
|
+
* Lazy mode only. Concurrency cap on the transform Lambda (default 10), a
|
|
199
|
+
* reservation that costs nothing idle. 0 disables transforms.
|
|
200
|
+
*/
|
|
201
|
+
readonly transformReservedConcurrency?: number;
|
|
202
|
+
/**
|
|
203
|
+
* Lazy mode only. How long the Lambda's `assets/t/` outputs are kept
|
|
204
|
+
* (default 180 days), written as a lifecycle rule on a bucket this construct
|
|
205
|
+
* creates. On a BYO `bucket` nothing is written and the prop is required:
|
|
206
|
+
* passing it states that your bucket expires them itself, filtering on the
|
|
207
|
+
* tag `canopy-transform=lazy` so what `materialize-assets` writes survives,
|
|
208
|
+
* and on a versioned bucket also expiring noncurrent versions. Remove that
|
|
209
|
+
* rule before leaving lazy mode: a key the Lambda wrote first stays tagged
|
|
210
|
+
* after a build references it. A cross-account bucket's policy must also
|
|
211
|
+
* grant the transform role `s3:PutObjectTagging` on `assets/t/*`.
|
|
171
212
|
*/
|
|
172
213
|
readonly transformOutputRetention?: Duration;
|
|
173
214
|
/**
|
|
174
|
-
* Execution role for the transform Lambda (default: CDK creates one).
|
|
215
|
+
* Lazy mode only. Execution role for the transform Lambda (default: CDK creates one).
|
|
175
216
|
*
|
|
176
217
|
* Set this when the role's ARN has to be computable WITHOUT a reference to
|
|
177
218
|
* this construct - the motivating case is an asset bucket in a different AWS
|
|
@@ -192,11 +233,10 @@ export interface AssetSupportProps {
|
|
|
192
233
|
*/
|
|
193
234
|
readonly transformRole?: iam.Role;
|
|
194
235
|
/**
|
|
195
|
-
* Name for the transform Lambda's CloudWatch log group (default:
|
|
236
|
+
* Lazy mode only. Name for the transform Lambda's CloudWatch log group (default:
|
|
196
237
|
* `/canopycms/<stackName>/transform`). Deliberately NOT
|
|
197
|
-
* `/aws/lambda/<function-name>` - see
|
|
198
|
-
*
|
|
199
|
-
* the function has ever been deployed without one. Override to follow an
|
|
238
|
+
* `/aws/lambda/<function-name>` - see the log group's comment in
|
|
239
|
+
* `buildTransformLambda` for why. Override to follow an
|
|
200
240
|
* org naming convention, or when instantiating this construct twice in one
|
|
201
241
|
* stack (the default name would collide).
|
|
202
242
|
*/
|
|
@@ -249,10 +289,8 @@ export declare const ASSET_BEHAVIOR_SPREAD_MISTAKE_KEYS: readonly ["assets", "as
|
|
|
249
289
|
* ```ts
|
|
250
290
|
* const behaviors = assetSupport.assetBehaviors()
|
|
251
291
|
*
|
|
252
|
-
* // CloudFront
|
|
253
|
-
* //
|
|
254
|
-
* // '/assets/*' - otherwise the broader S3-only pattern swallows transform
|
|
255
|
-
* // requests and they 403 with no Lambda fallback.
|
|
292
|
+
* // CloudFront stops at the first matching pattern, so '/assets/t/*' MUST
|
|
293
|
+
* // come before '/assets/*', or lazy mode's misses never reach the Lambda.
|
|
256
294
|
* new cloudfront.Distribution(this, 'Dist', {
|
|
257
295
|
* defaultBehavior: ...,
|
|
258
296
|
* additionalBehaviors: {
|
|
@@ -264,18 +302,13 @@ export declare const ASSET_BEHAVIOR_SPREAD_MISTAKE_KEYS: readonly ["assets", "as
|
|
|
264
302
|
*/
|
|
265
303
|
export interface AssetCloudFrontBehaviors {
|
|
266
304
|
/**
|
|
267
|
-
* `/assets
|
|
268
|
-
* pipeline wrote, plus already-computed transform outputs under
|
|
269
|
-
* `assets/t/...`, which also live under this prefix). S3 origin only -
|
|
270
|
-
* nothing here is ever computed on demand.
|
|
305
|
+
* `/assets/*`: the S3 read origin (an origin group with `replicaBucket`).
|
|
271
306
|
*/
|
|
272
307
|
readonly assets: cloudfront.BehaviorOptions;
|
|
273
308
|
/**
|
|
274
|
-
* `/assets/t
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
* configuring both is defense-in-depth). CloudFront caches the failover
|
|
278
|
-
* response.
|
|
309
|
+
* `/assets/t/*`: the same read origin as `assets`, or with
|
|
310
|
+
* `lazyPublicTransforms` an origin group falling over to the transform
|
|
311
|
+
* Lambda on 403 or 404.
|
|
279
312
|
*/
|
|
280
313
|
readonly assetsTransform: cloudfront.BehaviorOptions;
|
|
281
314
|
}
|
|
@@ -312,15 +345,8 @@ export interface AssetUploadBehaviorRouteOptions extends AssetUploadBehaviorOpti
|
|
|
312
345
|
* ```
|
|
313
346
|
*
|
|
314
347
|
* Separate from `AssetSupport.uploadBehavior()` because the upload route needs
|
|
315
|
-
* the bucket and nothing else
|
|
316
|
-
*
|
|
317
|
-
* Those grants never reach the BUCKET policy: `Grant.addToPrincipalOrResource`
|
|
318
|
-
* stops after the identity statement for a same-account grantee, and
|
|
319
|
-
* `addToResourcePolicy` on a bucket CDK does not own is a no-op - an owned
|
|
320
|
-
* bucket, one imported by name, and one imported by ARN in another account all
|
|
321
|
-
* emit zero `AWS::S3::BucketPolicy`, so a cross-account adopter writes the
|
|
322
|
-
* resource half on the bucket's own side. Prefer the method when you already
|
|
323
|
-
* have an `AssetSupport`; both funnel into the same builder and cannot drift.
|
|
348
|
+
* the bucket and nothing else. Prefer the method when you already have an
|
|
349
|
+
* `AssetSupport`; both funnel into the same builder and cannot drift.
|
|
324
350
|
*
|
|
325
351
|
* No bucket CORS rule is written and none is needed: the edge supplies
|
|
326
352
|
* `Access-Control-Allow-Origin`, and S3's ACCEPTANCE of a presigned POST is
|
|
@@ -336,12 +362,12 @@ export declare function assetUploadBehavior(scope: Construct, options: AssetUplo
|
|
|
336
362
|
* Wires:
|
|
337
363
|
*
|
|
338
364
|
* - The bucket's asset-prefix lifecycle rule + CORS (standalone mode only).
|
|
339
|
-
* - The
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
* OAC-locked Function URL
|
|
365
|
+
* - The public read path: `/assets/*` and `/assets/t/*` served from S3 (plus
|
|
366
|
+
* `replicaBucket` on 5xx). It computes nothing; derivatives come from
|
|
367
|
+
* `canopycms materialize-assets`.
|
|
368
|
+
* - With `lazyPublicTransforms`, the transform Lambda
|
|
369
|
+
* (`../../lambda/asset-transform/handler.ts`, built by `build:lambda`), its
|
|
370
|
+
* log group and its OAC-locked Function URL, behind `/assets/t/*`.
|
|
345
371
|
* - `attachTo(distribution)`, which attaches the two CloudFront behaviors a
|
|
346
372
|
* consuming distribution needs in the only safe order (see its doc comment
|
|
347
373
|
* for why the order is the whole point); `assetBehaviors()` is the escape
|
|
@@ -356,11 +382,12 @@ export declare function assetUploadBehavior(scope: Construct, options: AssetUplo
|
|
|
356
382
|
export declare class AssetSupport extends Construct {
|
|
357
383
|
/** The bucket in use (either created here, or the BYO `props.bucket`). */
|
|
358
384
|
readonly bucket: s3.IBucket;
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
385
|
+
/** The lazy transform Lambda; undefined unless `lazyPublicTransforms`. */
|
|
386
|
+
readonly transformFunction: lambda.Function | undefined;
|
|
387
|
+
/** The transform Lambda's CloudWatch log group; undefined unless `lazyPublicTransforms`. */
|
|
388
|
+
readonly transformLogGroup: logs.LogGroup | undefined;
|
|
389
|
+
/** The transform Lambda's Function URL; undefined unless `lazyPublicTransforms`. */
|
|
390
|
+
readonly transformFunctionUrl: lambda.FunctionUrl | undefined;
|
|
364
391
|
/** Effective advisory upload-size cap (see `AssetSupportProps.maxUploadBytes`). */
|
|
365
392
|
readonly maxUploadBytes: number;
|
|
366
393
|
private readonly behaviors;
|
|
@@ -388,6 +415,7 @@ export declare class AssetSupport extends Construct {
|
|
|
388
415
|
*/
|
|
389
416
|
private uploadOrigin?;
|
|
390
417
|
constructor(scope: Construct, id: string, props: AssetSupportProps);
|
|
418
|
+
private buildTransformLambda;
|
|
391
419
|
private buildBehaviors;
|
|
392
420
|
/**
|
|
393
421
|
* The two CloudFront behavior configs this system needs.
|
|
@@ -424,9 +452,8 @@ export declare class AssetSupport extends Construct {
|
|
|
424
452
|
* site's cookies and any cached basic-auth credential are live hazards
|
|
425
453
|
* there, stripped by policy and function rather than never sent at all.
|
|
426
454
|
* - NOT one shared distribution serving asset reads AND writes for every
|
|
427
|
-
* environment. That means one `AssetSupport`,
|
|
428
|
-
*
|
|
429
|
-
* every environment's asset pipeline at once. Only the upload route moves;
|
|
455
|
+
* environment. That means one `AssetSupport`, so a `canopycms-cdk` bump
|
|
456
|
+
* would move every environment's asset pipeline at once. Only the upload route moves;
|
|
430
457
|
* it depends on the bucket and nothing else, the same property that lets
|
|
431
458
|
* `assetUploadBehavior` build it from a bucket alone.
|
|
432
459
|
*/
|
|
@@ -435,17 +462,12 @@ export declare class AssetSupport extends Construct {
|
|
|
435
462
|
* Attach both asset behaviors to a concrete CloudFront distribution, in the
|
|
436
463
|
* only safe order.
|
|
437
464
|
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
* keep working. `'/assets/*'` also sorts BEFORE `'/assets/t/*'`
|
|
445
|
-
* lexicographically (`*` = 0x2A, `t` = 0x74), so alphabetizing the keys
|
|
446
|
-
* reproduces exactly this failure with no synth or deploy error to catch it -
|
|
447
|
-
* and `assetBehaviors()` attaches no path pattern, so nothing there stops a
|
|
448
|
-
* caller getting it wrong.
|
|
465
|
+
* CloudFront stops at the first matching pattern, and `/assets/*` also
|
|
466
|
+
* matches every `/assets/t/*` request. With `lazyPublicTransforms`, attaching
|
|
467
|
+
* it first means no miss ever reaches the transform Lambda, with no synth or
|
|
468
|
+
* deploy error; `'/assets/*'` also sorts first alphabetically. The two
|
|
469
|
+
* behaviors are identical otherwise, and the order is kept so that switching
|
|
470
|
+
* modes changes nothing else.
|
|
449
471
|
*
|
|
450
472
|
* `overrides` is merged into BOTH behaviors. The motivating case is a
|
|
451
473
|
* distribution running a viewer-request function on every behavior - tier
|