@fjall/components-infrastructure 37.0.0 → 39.0.0
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/dist/lib/app.d.ts +52 -12
- package/dist/lib/app.js +60 -24
- package/dist/lib/config/aws/configRecorder.js +3 -0
- package/dist/lib/config/aws/disasterRecovery.d.ts +1 -2
- package/dist/lib/config/aws/disasterRecovery.js +167 -45
- package/dist/lib/patterns/aws/buildkite/buildkite.js +7 -2
- package/dist/lib/patterns/aws/cdn.d.ts +19 -11
- package/dist/lib/patterns/aws/cdn.js +53 -25
- package/dist/lib/patterns/aws/clickhouseDatabase.js +8 -4
- package/dist/lib/patterns/aws/computeEcs.js +17 -1
- package/dist/lib/patterns/aws/computeEcsTypes.d.ts +30 -4
- package/dist/lib/patterns/aws/computePropApplicability.js +1 -0
- package/dist/lib/patterns/aws/domain.js +3 -3
- package/dist/lib/patterns/aws/externalRecordsPattern.d.ts +9 -6
- package/dist/lib/patterns/aws/externalRecordsPattern.js +22 -46
- package/dist/lib/patterns/aws/interfaces/domain.d.ts +8 -5
- package/dist/lib/patterns/aws/interfaces/pattern.d.ts +9 -10
- package/dist/lib/patterns/aws/messaging.d.ts +10 -0
- package/dist/lib/patterns/aws/messaging.js +10 -6
- package/dist/lib/patterns/aws/payload.d.ts +5 -0
- package/dist/lib/patterns/aws/payload.js +23 -11
- package/dist/lib/patterns/aws/storage.d.ts +41 -0
- package/dist/lib/patterns/aws/storage.js +23 -8
- package/dist/lib/patterns/aws/targets/targetResolution.d.ts +4 -4
- package/dist/lib/patterns/aws/targets/targetResolution.js +4 -4
- package/dist/lib/resources/aws/backup/backupVault.d.ts +27 -2
- package/dist/lib/resources/aws/backup/backupVault.js +49 -6
- package/dist/lib/resources/aws/cdn/cloudFront.d.ts +42 -18
- package/dist/lib/resources/aws/cdn/cloudFront.js +39 -51
- package/dist/lib/resources/aws/cdn/cloudFrontLogDelivery.d.ts +90 -0
- package/dist/lib/resources/aws/cdn/cloudFrontLogDelivery.js +213 -0
- package/dist/lib/resources/aws/cdn/index.d.ts +1 -0
- package/dist/lib/resources/aws/cdn/index.js +1 -0
- package/dist/lib/resources/aws/compute/ecs.d.ts +7 -4
- package/dist/lib/resources/aws/compute/ecs.js +27 -21
- package/dist/lib/resources/aws/compute/ecsServiceFactory.js +3 -0
- package/dist/lib/resources/aws/compute/ecsTypes.d.ts +11 -0
- package/dist/lib/resources/aws/compute/ecsValidation.d.ts +8 -0
- package/dist/lib/resources/aws/compute/ecsValidation.js +8 -0
- package/dist/lib/resources/aws/compute/persistentDataVolume.d.ts +8 -0
- package/dist/lib/resources/aws/compute/persistentDataVolume.js +2 -0
- package/dist/lib/resources/aws/database/dynamodb.js +2 -1
- package/dist/lib/resources/aws/database/rdsAurora.js +6 -1
- package/dist/lib/resources/aws/database/rdsInstance.js +6 -1
- package/dist/lib/resources/aws/logging/accessLogBucket.d.ts +15 -8
- package/dist/lib/resources/aws/logging/accessLogBucket.js +16 -9
- package/dist/lib/resources/aws/logging/cloudTrail.js +3 -0
- package/dist/lib/resources/aws/messaging/eventBridgeQueueGrants.d.ts +16 -0
- package/dist/lib/resources/aws/messaging/eventBridgeQueueGrants.js +91 -0
- package/dist/lib/resources/aws/messaging/eventBridgeRule.js +36 -5
- package/dist/lib/resources/aws/messaging/eventTargets.d.ts +4 -3
- package/dist/lib/resources/aws/messaging/eventTargets.js +71 -31
- package/dist/lib/resources/aws/messaging/queuePolicyQuota.d.ts +25 -0
- package/dist/lib/resources/aws/messaging/queuePolicyQuota.js +190 -0
- package/dist/lib/resources/aws/messaging/schedule.d.ts +5 -5
- package/dist/lib/resources/aws/messaging/sqs.d.ts +36 -1
- package/dist/lib/resources/aws/messaging/sqs.js +40 -2
- package/dist/lib/resources/aws/messaging/subscription.d.ts +4 -3
- package/dist/lib/resources/aws/networking/vpc.js +12 -6
- package/dist/lib/resources/aws/secrets/stackEncryptionKey.d.ts +26 -1
- package/dist/lib/resources/aws/secrets/stackEncryptionKey.js +56 -1
- package/dist/lib/resources/aws/storage/s3.d.ts +21 -1
- package/dist/lib/resources/aws/storage/s3.js +89 -3
- package/dist/lib/utils/backupTierMapping.d.ts +23 -2
- package/dist/lib/utils/backupTierMapping.js +25 -0
- package/dist/lib/utils/cdkContext.d.ts +14 -0
- package/dist/lib/utils/cdkContext.js +38 -1
- package/dist/lib/utils/exportNaming.d.ts +30 -27
- package/dist/lib/utils/exportNaming.js +32 -28
- package/dist/lib/utils/standardTagsAspect.d.ts +8 -18
- package/dist/lib/utils/standardTagsAspect.js +8 -34
- package/package.json +4 -4
|
@@ -58,6 +58,11 @@ export declare class Payload extends Construct implements IPayload {
|
|
|
58
58
|
private buildBaseDatabaseProps;
|
|
59
59
|
private createAuroraDatabase;
|
|
60
60
|
private createInstanceDatabase;
|
|
61
|
+
/**
|
|
62
|
+
* Pinned to `OPENNEXT_BUCKET_KEYS` in both directions: a key the vocabulary
|
|
63
|
+
* gains fails here until a builder exists, and a builder for a key it does
|
|
64
|
+
* not carry is an excess property. Built in the vocabulary's order.
|
|
65
|
+
*/
|
|
61
66
|
private createStorageBuckets;
|
|
62
67
|
private createAssetsBucket;
|
|
63
68
|
private createCacheBucket;
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { Construct } from "constructs";
|
|
25
25
|
import { CfnOutput, Stack } from "aws-cdk-lib";
|
|
26
|
+
import { OPENNEXT_BUCKET_KEYS, patternBucketConstructId } from "@fjall/util/patterns";
|
|
26
27
|
import { Secret } from "../../resources/aws/secrets/index.js";
|
|
27
28
|
import { Code, Runtime, Architecture, FunctionUrlAuthType } from "aws-cdk-lib/aws-lambda";
|
|
28
29
|
import { Certificate } from "aws-cdk-lib/aws-certificatemanager";
|
|
@@ -209,10 +210,25 @@ export class Payload extends Construct {
|
|
|
209
210
|
};
|
|
210
211
|
return this.app.addDatabase(DatabaseFactory.build(this.props.name, instanceProps));
|
|
211
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Pinned to `OPENNEXT_BUCKET_KEYS` in both directions: a key the vocabulary
|
|
215
|
+
* gains fails here until a builder exists, and a builder for a key it does
|
|
216
|
+
* not carry is an excess property. Built in the vocabulary's order.
|
|
217
|
+
*/
|
|
212
218
|
createStorageBuckets() {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
219
|
+
const builders = {
|
|
220
|
+
assets: () => {
|
|
221
|
+
this._assetsBucket = this.createAssetsBucket();
|
|
222
|
+
},
|
|
223
|
+
cache: () => {
|
|
224
|
+
this._cacheBucket = this.createCacheBucket();
|
|
225
|
+
},
|
|
226
|
+
media: () => {
|
|
227
|
+
this._mediaBucket = this.createMediaBucket();
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
for (const bucket of OPENNEXT_BUCKET_KEYS)
|
|
231
|
+
builders[bucket]();
|
|
216
232
|
}
|
|
217
233
|
createAssetsBucket() {
|
|
218
234
|
const assetsConfig = this.props.storage?.assets ?? {};
|
|
@@ -230,7 +246,7 @@ export class Payload extends Construct {
|
|
|
230
246
|
}
|
|
231
247
|
}
|
|
232
248
|
};
|
|
233
|
-
return this.app.addStorage(StorageFactory.build(
|
|
249
|
+
return this.app.addStorage(StorageFactory.build(patternBucketConstructId(this.props.name, "assets"), assetsProps));
|
|
234
250
|
}
|
|
235
251
|
createCacheBucket() {
|
|
236
252
|
const cacheConfig = this.props.storage?.cache ?? {};
|
|
@@ -244,7 +260,7 @@ export class Payload extends Construct {
|
|
|
244
260
|
prune: false // ISR adds files at runtime
|
|
245
261
|
}
|
|
246
262
|
};
|
|
247
|
-
return this.app.addStorage(StorageFactory.build(
|
|
263
|
+
return this.app.addStorage(StorageFactory.build(patternBucketConstructId(this.props.name, "cache"), cacheProps));
|
|
248
264
|
}
|
|
249
265
|
createMediaBucket() {
|
|
250
266
|
const mediaConfig = this.props.storage?.media ?? {};
|
|
@@ -254,7 +270,7 @@ export class Payload extends Construct {
|
|
|
254
270
|
versioned: mediaConfig.versioned
|
|
255
271
|
})
|
|
256
272
|
};
|
|
257
|
-
return this.app.addStorage(StorageFactory.build(
|
|
273
|
+
return this.app.addStorage(StorageFactory.build(patternBucketConstructId(this.props.name, "media"), mediaProps));
|
|
258
274
|
}
|
|
259
275
|
createTagCache() {
|
|
260
276
|
const tagCacheProps = {
|
|
@@ -561,11 +577,7 @@ export class Payload extends Construct {
|
|
|
561
577
|
key: `${this.pascalName}PatternResources`,
|
|
562
578
|
value: JSON.stringify({
|
|
563
579
|
databases: [this.props.name, `${this.props.name}-tag-cache`],
|
|
564
|
-
storage:
|
|
565
|
-
`${this.props.name}-assets`,
|
|
566
|
-
`${this.props.name}-cache`,
|
|
567
|
-
`${this.props.name}-media`
|
|
568
|
-
],
|
|
580
|
+
storage: OPENNEXT_BUCKET_KEYS.map((bucket) => patternBucketConstructId(this.props.name, bucket)),
|
|
569
581
|
cdn: this.props.name
|
|
570
582
|
}),
|
|
571
583
|
description: `Related resource names for pattern ${this.props.name}`
|
|
@@ -47,7 +47,35 @@ export interface S3Props {
|
|
|
47
47
|
* that versions after all is not stamped.
|
|
48
48
|
*/
|
|
49
49
|
readonly versioningExemption?: S3VersioningExemption;
|
|
50
|
+
/**
|
|
51
|
+
* `"AES256"` is SSE-S3: AWS's own key, free, and real encryption at rest.
|
|
52
|
+
* `"KMS"` is SSE-KMS with a key of YOUR OWN, so it requires `kmsKeyArn` —
|
|
53
|
+
* it is refused without one rather than falling back.
|
|
54
|
+
*
|
|
55
|
+
* The fallback is what this refuses. Asking CloudFormation for KMS
|
|
56
|
+
* encryption with no key makes CDK mint an `AWS::KMS::Key` of its own,
|
|
57
|
+
* `DeletionPolicy: Retain`, with no alias and no tags — an orphan every
|
|
58
|
+
* Fjall teardown sweep is blind to, because a sweep finds keys by the
|
|
59
|
+
* alias tag `CustomerManagedKey` stamps and that key carries none.
|
|
60
|
+
*
|
|
61
|
+
* Nor does the bucket quietly join the stack's shared key, the way an
|
|
62
|
+
* `"SSE_KMS"` queue does. A queue holds messages for at most fourteen
|
|
63
|
+
* days; a bucket holds objects forever, and an object is readable only
|
|
64
|
+
* with the key it was written under — so moving a live bucket's key
|
|
65
|
+
* revokes read access to everything already in it, at the same deploy
|
|
66
|
+
* that rewrites the reader's `kms:Decrypt` grant onto the new key. The
|
|
67
|
+
* choice of key for a bucket is the operator's, always.
|
|
68
|
+
*/
|
|
50
69
|
readonly encryption?: "AES256" | "KMS";
|
|
70
|
+
/**
|
|
71
|
+
* The customer managed key this bucket's objects are encrypted with.
|
|
72
|
+
* Implies `encryption: "KMS"` when no encryption is stated, and is refused
|
|
73
|
+
* beside `"AES256"` rather than silently ignored.
|
|
74
|
+
*
|
|
75
|
+
* An ARN rather than an `IKey` because this is the plan vocabulary the
|
|
76
|
+
* scaffold and the codemod write; a key you hold in `infrastructure.ts`
|
|
77
|
+
* passes as `key.keyArn`.
|
|
78
|
+
*/
|
|
51
79
|
readonly kmsKeyArn?: string;
|
|
52
80
|
/**
|
|
53
81
|
* S3 Bucket Keys for KMS-encrypted buckets — defaults to true when
|
|
@@ -94,6 +122,19 @@ export interface StorageBuildProps extends S3Props {
|
|
|
94
122
|
export type StorageFactoryFn = ((app: App, scope: Construct) => Storage) & {
|
|
95
123
|
stackPlacement?: "storage" | "cdn" | "compute";
|
|
96
124
|
};
|
|
125
|
+
/**
|
|
126
|
+
* Refuse the two `encryption`/`kmsKeyArn` pairings a bucket cannot honour.
|
|
127
|
+
*
|
|
128
|
+
* Both were warnings until 38.x, and both warnings said something untrue.
|
|
129
|
+
* `"KMS"` with no key did not fall back to "the default AWS managed key": it
|
|
130
|
+
* made CDK mint a retained, alias-less, tag-less key of its own beside a
|
|
131
|
+
* bucket that may be DESTROY — the orphan class this refusal exists for. And
|
|
132
|
+
* a key beside `"AES256"` was not "ignored": CDK refuses that pair itself,
|
|
133
|
+
* one frame later, in construct vocabulary the operator did not type.
|
|
134
|
+
*
|
|
135
|
+
* A key with NO stated encryption is neither, and is deliberately allowed —
|
|
136
|
+
* CDK infers KMS from the key, which is what the caller asked for.
|
|
137
|
+
*/
|
|
97
138
|
export declare function validateStorageProps(props: S3Props): void;
|
|
98
139
|
export declare class Storage extends Construct implements IStorage, IStorageConnector {
|
|
99
140
|
readonly connectorType: "storage";
|
|
@@ -4,7 +4,6 @@ import { Source, CacheControl } from "aws-cdk-lib/aws-s3-deployment";
|
|
|
4
4
|
import { Key } from "aws-cdk-lib/aws-kms";
|
|
5
5
|
import { CfnOutput, Duration, RemovalPolicy, Stack } from "aws-cdk-lib";
|
|
6
6
|
import { BucketDeployment, S3Bucket } from "../../resources/aws/storage/index.js";
|
|
7
|
-
import { FjallLogger } from "../../utils/validationLogger.js";
|
|
8
7
|
export { isStorage } from "./interfaces/storage.js";
|
|
9
8
|
function toBucketEncryption(encryption) {
|
|
10
9
|
if (!encryption)
|
|
@@ -50,14 +49,30 @@ function resolveStorageRemovalPolicy(props) {
|
|
|
50
49
|
}
|
|
51
50
|
return props.retain ? RemovalPolicy.RETAIN : undefined;
|
|
52
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Refuse the two `encryption`/`kmsKeyArn` pairings a bucket cannot honour.
|
|
54
|
+
*
|
|
55
|
+
* Both were warnings until 38.x, and both warnings said something untrue.
|
|
56
|
+
* `"KMS"` with no key did not fall back to "the default AWS managed key": it
|
|
57
|
+
* made CDK mint a retained, alias-less, tag-less key of its own beside a
|
|
58
|
+
* bucket that may be DESTROY — the orphan class this refusal exists for. And
|
|
59
|
+
* a key beside `"AES256"` was not "ignored": CDK refuses that pair itself,
|
|
60
|
+
* one frame later, in construct vocabulary the operator did not type.
|
|
61
|
+
*
|
|
62
|
+
* A key with NO stated encryption is neither, and is deliberately allowed —
|
|
63
|
+
* CDK infers KMS from the key, which is what the caller asked for.
|
|
64
|
+
*/
|
|
53
65
|
export function validateStorageProps(props) {
|
|
54
|
-
if (props.encryption === "KMS" &&
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
if (props.encryption === "KMS" && props.kmsKeyArn === undefined) {
|
|
67
|
+
throw new Error(`encryption: "KMS" needs the ARN of the key to encrypt with. Pass ` +
|
|
68
|
+
`'kmsKeyArn', or use encryption: "AES256" for S3-managed encryption ` +
|
|
69
|
+
`at rest, which costs nothing and needs no key.`);
|
|
70
|
+
}
|
|
71
|
+
if (props.kmsKeyArn !== undefined && props.encryption === "AES256") {
|
|
72
|
+
throw new Error(`'kmsKeyArn' encrypts with a key of your own and encryption: "AES256" ` +
|
|
73
|
+
`encrypts with S3's, so the pair has no meaning. Set ` +
|
|
74
|
+
`encryption: "KMS" to use the key, or drop 'kmsKeyArn' to keep ` +
|
|
75
|
+
`"AES256".`);
|
|
61
76
|
}
|
|
62
77
|
}
|
|
63
78
|
export class Storage extends Construct {
|
|
@@ -64,9 +64,9 @@ export declare function resolveBucketTarget(appName: string, bucketName: string)
|
|
|
64
64
|
export declare function resolveCustomTarget(dnsName: string, hostedZoneId: string): IAliasRecordTarget;
|
|
65
65
|
/**
|
|
66
66
|
* Resolve a `FjallTarget` to its raw DNS-name string. The return value is a
|
|
67
|
-
* CloudFormation token for the ECS/CDN/bucket variants
|
|
68
|
-
* concrete host
|
|
69
|
-
* `externalRecordsPattern.ts` where no
|
|
70
|
-
* record emission needs a string value.
|
|
67
|
+
* CloudFormation token for the ECS/CDN/bucket variants, resolved by
|
|
68
|
+
* CloudFormation at deploy (the concrete host lands in the stack's manual
|
|
69
|
+
* record `Value` output). Used only by `externalRecordsPattern.ts` where no
|
|
70
|
+
* Route53 resource exists and manual record emission needs a string value.
|
|
71
71
|
*/
|
|
72
72
|
export declare function resolveTargetToDnsName(target: FjallTarget): string;
|
|
@@ -89,10 +89,10 @@ export function resolveCustomTarget(dnsName, hostedZoneId) {
|
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
91
|
* Resolve a `FjallTarget` to its raw DNS-name string. The return value is a
|
|
92
|
-
* CloudFormation token for the ECS/CDN/bucket variants
|
|
93
|
-
* concrete host
|
|
94
|
-
* `externalRecordsPattern.ts` where no
|
|
95
|
-
* record emission needs a string value.
|
|
92
|
+
* CloudFormation token for the ECS/CDN/bucket variants, resolved by
|
|
93
|
+
* CloudFormation at deploy (the concrete host lands in the stack's manual
|
|
94
|
+
* record `Value` output). Used only by `externalRecordsPattern.ts` where no
|
|
95
|
+
* Route53 resource exists and manual record emission needs a string value.
|
|
96
96
|
*/
|
|
97
97
|
export function resolveTargetToDnsName(target) {
|
|
98
98
|
switch (target.kind) {
|
|
@@ -2,18 +2,43 @@ import { Construct } from "constructs";
|
|
|
2
2
|
import { CfnOutput, RemovalPolicy } from "aws-cdk-lib";
|
|
3
3
|
import { BackupVault as Vault, type LockConfiguration } from "aws-cdk-lib/aws-backup";
|
|
4
4
|
import { type PolicyDocument } from "aws-cdk-lib/aws-iam";
|
|
5
|
-
import {
|
|
5
|
+
import { type IKey } from "aws-cdk-lib/aws-kms";
|
|
6
|
+
export declare function backupVaultKeyAliasName(vaultName: string): string;
|
|
6
7
|
export interface BackupVaultProps {
|
|
7
8
|
vaultName: string;
|
|
8
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The key the vault is encrypted with when it already exists outside the
|
|
11
|
+
* stack (a re-owned vault's live key). `EncryptionKeyArn` is create-only,
|
|
12
|
+
* so a re-owned vault must always name its live key or CloudFormation
|
|
13
|
+
* replaces it. The wrapper aliases the key and creates none. Omit to create
|
|
14
|
+
* a stack-managed key.
|
|
15
|
+
*/
|
|
16
|
+
encryptionKey?: IKey;
|
|
9
17
|
accessPolicy?: PolicyDocument;
|
|
10
18
|
lockConfiguration?: LockConfiguration;
|
|
11
19
|
removalPolicy?: RemovalPolicy;
|
|
20
|
+
/**
|
|
21
|
+
* Import pass: declare identity, not intent. Strips the mutable properties
|
|
22
|
+
* (`BackupVaultTags`, `AccessPolicy`) from the synthesised resource so the
|
|
23
|
+
* deploy's `--import-existing-resources` change set imports a live mirror;
|
|
24
|
+
* the converge deploy re-declares them.
|
|
25
|
+
*/
|
|
26
|
+
importIdentityOnly?: boolean;
|
|
12
27
|
}
|
|
13
28
|
export declare class BackupVault extends Construct {
|
|
14
29
|
readonly vault: Vault;
|
|
15
30
|
readonly vaultArn: CfnOutput;
|
|
16
31
|
readonly vaultName: CfnOutput;
|
|
32
|
+
/** The key the vault encrypts recovery points with. */
|
|
33
|
+
readonly encryptionKey: IKey;
|
|
34
|
+
/**
|
|
35
|
+
* Whether this stack created the key. A re-owned vault names a live key the
|
|
36
|
+
* stack does not manage, and `addToResourcePolicy` on an imported key is a
|
|
37
|
+
* no-op — so a caller that needs a statement on the key policy (a
|
|
38
|
+
* cross-account copy destination does) must check this and say so when it
|
|
39
|
+
* cannot apply one.
|
|
40
|
+
*/
|
|
41
|
+
readonly ownsEncryptionKey: boolean;
|
|
17
42
|
constructor(scope: Construct, id: string, props: BackupVaultProps);
|
|
18
43
|
static build(id: string, props: BackupVaultProps): (scope: Construct) => BackupVault;
|
|
19
44
|
}
|
|
@@ -1,31 +1,74 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
2
|
import { CfnOutput, RemovalPolicy } from "aws-cdk-lib";
|
|
3
|
-
import { BackupVault as Vault } from "aws-cdk-lib/aws-backup";
|
|
3
|
+
import { BackupVault as Vault, CfnBackupVault } from "aws-cdk-lib/aws-backup";
|
|
4
|
+
import { Alias } from "aws-cdk-lib/aws-kms";
|
|
4
5
|
import { CustomerManagedKey } from "../secrets/kms.js";
|
|
6
|
+
/**
|
|
7
|
+
* Vault properties CloudFormation applies as UPDATES, not identity. An import
|
|
8
|
+
* pass strips them so `--import-existing-resources` records a live mirror and
|
|
9
|
+
* the converge deploy that follows applies them as the diff (import records
|
|
10
|
+
* properties, it never applies them — a tag declared on the import pass is
|
|
11
|
+
* "already there" forever).
|
|
12
|
+
*/
|
|
13
|
+
const MUTABLE_VAULT_PROPERTIES = ["BackupVaultTags", "AccessPolicy"];
|
|
14
|
+
export function backupVaultKeyAliasName(vaultName) {
|
|
15
|
+
return `cmk/backupVault/${vaultName}`;
|
|
16
|
+
}
|
|
5
17
|
export class BackupVault extends Construct {
|
|
6
18
|
vault;
|
|
7
19
|
vaultArn;
|
|
8
20
|
vaultName;
|
|
21
|
+
/** The key the vault encrypts recovery points with. */
|
|
22
|
+
encryptionKey;
|
|
23
|
+
/**
|
|
24
|
+
* Whether this stack created the key. A re-owned vault names a live key the
|
|
25
|
+
* stack does not manage, and `addToResourcePolicy` on an imported key is a
|
|
26
|
+
* no-op — so a caller that needs a statement on the key policy (a
|
|
27
|
+
* cross-account copy destination does) must check this and say so when it
|
|
28
|
+
* cannot apply one.
|
|
29
|
+
*/
|
|
30
|
+
ownsEncryptionKey;
|
|
9
31
|
constructor(scope, id, props) {
|
|
10
32
|
super(scope, id);
|
|
11
33
|
// RETAIN default: a DESTROY-policy vault is silently deleted with the stack.
|
|
12
34
|
// Callers opt into DESTROY explicitly for ephemeral/dev vaults.
|
|
13
35
|
const removalPolicy = props.removalPolicy ?? RemovalPolicy.RETAIN;
|
|
14
|
-
const
|
|
15
|
-
|
|
36
|
+
const aliasName = backupVaultKeyAliasName(props.vaultName);
|
|
37
|
+
let encryptionKey;
|
|
38
|
+
if (props.encryptionKey !== undefined) {
|
|
39
|
+
encryptionKey = props.encryptionKey;
|
|
40
|
+
new Alias(this, `${props.vaultName}KeyAlias`, {
|
|
41
|
+
aliasName,
|
|
42
|
+
targetKey: encryptionKey
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
encryptionKey = new CustomerManagedKey(this, `${props.vaultName}Key`, {
|
|
16
47
|
description: `Encryption key for backup vault ${props.vaultName}`,
|
|
17
|
-
aliasName
|
|
48
|
+
aliasName,
|
|
18
49
|
// Recovery points in a retained vault outlive the stack.
|
|
19
50
|
protects: "outlives-stack",
|
|
20
51
|
removalPolicy
|
|
21
|
-
});
|
|
52
|
+
}).key;
|
|
53
|
+
}
|
|
54
|
+
this.encryptionKey = encryptionKey;
|
|
55
|
+
this.ownsEncryptionKey = props.encryptionKey === undefined;
|
|
22
56
|
this.vault = new Vault(this, `${props.vaultName}Vault`, {
|
|
23
57
|
backupVaultName: props.vaultName,
|
|
24
|
-
encryptionKey
|
|
58
|
+
encryptionKey,
|
|
25
59
|
accessPolicy: props.accessPolicy,
|
|
26
60
|
lockConfiguration: props.lockConfiguration,
|
|
27
61
|
removalPolicy
|
|
28
62
|
});
|
|
63
|
+
if (props.importIdentityOnly === true) {
|
|
64
|
+
const cfnVault = this.vault.node.defaultChild;
|
|
65
|
+
if (!(cfnVault instanceof CfnBackupVault)) {
|
|
66
|
+
throw new Error(`Backup vault "${props.vaultName}": expected an AWS::Backup::BackupVault default child to strip for import`);
|
|
67
|
+
}
|
|
68
|
+
for (const property of MUTABLE_VAULT_PROPERTIES) {
|
|
69
|
+
cfnVault.addPropertyDeletionOverride(property);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
29
72
|
this.vaultArn = new CfnOutput(this, `${props.vaultName}VaultArn`, {
|
|
30
73
|
key: `${props.vaultName}VaultArn`,
|
|
31
74
|
value: this.vault.backupVaultArn,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
2
|
import { type CloudFrontPriceClass } from "@fjall/util";
|
|
3
|
+
import { CloudFrontLogDelivery, type CloudFrontLogFormat } from "./cloudFrontLogDelivery.js";
|
|
3
4
|
import { Distribution, type ICachePolicy, type IResponseHeadersPolicy } from "aws-cdk-lib/aws-cloudfront";
|
|
4
5
|
import { type IBucket } from "aws-cdk-lib/aws-s3";
|
|
5
6
|
import { type IApplicationLoadBalancer } from "aws-cdk-lib/aws-elasticloadbalancingv2";
|
|
@@ -59,25 +60,13 @@ export interface CloudFrontDistributionProps {
|
|
|
59
60
|
certificate?: ICertificate;
|
|
60
61
|
comment?: string;
|
|
61
62
|
/**
|
|
62
|
-
* Standard (
|
|
63
|
-
*
|
|
64
|
-
* distribution
|
|
65
|
-
*
|
|
66
|
-
*
|
|
63
|
+
* Standard logging (v2): access logs delivered through CloudWatch Logs to
|
|
64
|
+
* an S3 bucket in any region, including opt-in ones. Omit for no logging.
|
|
65
|
+
* The distribution's own `Logging` block (legacy logging) is never
|
|
66
|
+
* rendered — the legacy `enableLogging`/`logBucket`/`logFilePrefix` props
|
|
67
|
+
* are refused at synth since 38.0.0.
|
|
67
68
|
*/
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Delivery bucket for standard logs; without it CDK creates one in the
|
|
71
|
-
* distribution's own stack (and region). Its `env.region` is the delivery
|
|
72
|
-
* region the opt-in refusal on `enableLogging` checks, so an imported
|
|
73
|
-
* bucket must declare it: `Bucket.fromBucketAttributes(…, { region })`, or
|
|
74
|
-
* a `Bucket` in a stack pinned to its region. `fromBucketName` and
|
|
75
|
-
* `fromBucketArn` inherit this stack's region, and a token bucket region
|
|
76
|
-
* falls back to it.
|
|
77
|
-
*/
|
|
78
|
-
logBucket?: IBucket;
|
|
79
|
-
/** Key prefix for standard logs delivered to `logBucket`. */
|
|
80
|
-
logFilePrefix?: string;
|
|
69
|
+
logging?: CloudFrontLoggingProps;
|
|
81
70
|
priceClass?: CloudFrontPriceClass;
|
|
82
71
|
/** Adds a CloudFront Function to copy the viewer Host header into X-Forwarded-Host.
|
|
83
72
|
* Required when the origin is a Lambda Function URL or ALB behind CloudFront,
|
|
@@ -121,8 +110,43 @@ export interface CloudFrontDistributionProps {
|
|
|
121
110
|
*/
|
|
122
111
|
originRequestPolicy?: "all-viewer-except-host" | "none";
|
|
123
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Standard logging (v2) for a distribution. The delivery resources are
|
|
115
|
+
* created in `deliveryScope` — which MUST be a us-east-1 stack, the only
|
|
116
|
+
* region CloudWatch Logs accepts them in — and default to the distribution's
|
|
117
|
+
* own scope, which is right only when the distribution's stack is itself in
|
|
118
|
+
* us-east-1. The `Cdn` pattern resolves the scope through
|
|
119
|
+
* `App.getUsEast1CdnLoggingStack()`; direct callers in another region pass
|
|
120
|
+
* a us-east-1 stack themselves.
|
|
121
|
+
*/
|
|
122
|
+
export interface CloudFrontLoggingProps {
|
|
123
|
+
/** Bucket the logs land in — same account as the distribution. */
|
|
124
|
+
bucket: IBucket;
|
|
125
|
+
/** Key prefix (no leading or trailing slash); defaults to `cloudfront/<id>`. */
|
|
126
|
+
prefix?: string;
|
|
127
|
+
/** CloudWatch Logs output format; defaults to `json`. */
|
|
128
|
+
format?: CloudFrontLogFormat;
|
|
129
|
+
/** Scope for the delivery resources; a us-east-1 stack. */
|
|
130
|
+
deliveryScope?: Construct;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The props CloudFront standard (legacy) logging took before 38.0.0. They
|
|
134
|
+
* are not members of `CloudFrontDistributionProps` any more, so a
|
|
135
|
+
* TypeScript caller cannot pass them; a JavaScript caller, a spread from a
|
|
136
|
+
* saved config or a generator template still can, and the legacy `Logging`
|
|
137
|
+
* block would otherwise be silently dropped. Refused at synth by name.
|
|
138
|
+
*/
|
|
139
|
+
export declare const LEGACY_CLOUDFRONT_LOGGING_PROPS: readonly ["enableLogging", "logBucket", "logFilePrefix"];
|
|
140
|
+
/**
|
|
141
|
+
* `replacement` is the seam's v2 shape, named in the refusal: the resource
|
|
142
|
+
* requires the bucket and has no `false`, the pattern defaults the bucket
|
|
143
|
+
* and accepts `false`.
|
|
144
|
+
*/
|
|
145
|
+
export declare function assertNoLegacyLoggingProps(context: string, props: object, replacement: string): void;
|
|
124
146
|
export declare class CloudFrontDistribution extends Construct {
|
|
125
147
|
readonly id: string;
|
|
148
|
+
/** The v2 log delivery, when `logging` was given. */
|
|
149
|
+
readonly logDelivery?: CloudFrontLogDelivery;
|
|
126
150
|
private distribution;
|
|
127
151
|
private readonly origins;
|
|
128
152
|
/** Shared S3 access identities, keyed by the owning stack (see
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
|
-
import { CfnOutput, Names, Stack
|
|
2
|
+
import { CfnOutput, Names, Stack } from "aws-cdk-lib";
|
|
3
3
|
import { toPascalCase } from "../../../utils/capitaliseString.js";
|
|
4
4
|
import { registerCdnAliasTarget } from "../../../utils/cdnAliasTargetRegistry.js";
|
|
5
|
-
import { cdnDomainExportName
|
|
5
|
+
import { cdnDomainExportName } from "@fjall/util";
|
|
6
|
+
import { CloudFrontLogDelivery, DEFAULT_CLOUDFRONT_LOG_FORMAT } from "./cloudFrontLogDelivery.js";
|
|
6
7
|
import { Distribution, PriceClass, ViewerProtocolPolicy, CachePolicy, OriginRequestPolicy, AllowedMethods, OriginProtocolPolicy, Function as CloudFrontFunction, FunctionCode, FunctionRuntime, FunctionEventType, OriginAccessIdentity, S3OriginAccessControl } from "aws-cdk-lib/aws-cloudfront";
|
|
7
8
|
import { S3BucketOrigin, LoadBalancerV2Origin, HttpOrigin } from "aws-cdk-lib/aws-cloudfront-origins";
|
|
8
9
|
/**
|
|
@@ -43,56 +44,38 @@ const URI_REWRITE_FRAGMENTS = {
|
|
|
43
44
|
" if (seg.indexOf('.') === -1) { request.uri = uri + '/index.html'; } } }"
|
|
44
45
|
};
|
|
45
46
|
/**
|
|
46
|
-
* CloudFront standard (legacy) logging
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* distribution crosses, whether built directly or through the `Cdn`
|
|
52
|
-
* pattern — and names both cures.
|
|
53
|
-
*
|
|
54
|
-
* Logging is on exactly when `renderLogging` emits a Logging block:
|
|
55
|
-
* `enableLogging: true`, or a `logBucket` without an explicit
|
|
56
|
-
* `enableLogging: false` (that pair is CDK's own contradiction error and is
|
|
57
|
-
* left to it). The delivery region is the bucket's `env.region` when it is
|
|
58
|
-
* concrete, else the distribution stack's — the bucket CDK would create.
|
|
59
|
-
* A token delivery region (an unpinned stack, and no region on the bucket)
|
|
60
|
-
* cannot be checked and passes. A concrete region `AWS_REGIONS_METADATA`
|
|
61
|
-
* does not list is refused too — every region launched since 2019 is opt-in,
|
|
62
|
-
* so an unlisted one cannot be assumed default-enabled — naming the metadata
|
|
63
|
-
* as the cure. The guard fires whenever the delivery region is concrete and
|
|
64
|
-
* known to the metadata.
|
|
47
|
+
* The props CloudFront standard (legacy) logging took before 38.0.0. They
|
|
48
|
+
* are not members of `CloudFrontDistributionProps` any more, so a
|
|
49
|
+
* TypeScript caller cannot pass them; a JavaScript caller, a spread from a
|
|
50
|
+
* saved config or a generator template still can, and the legacy `Logging`
|
|
51
|
+
* block would otherwise be silently dropped. Refused at synth by name.
|
|
65
52
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
if (!OPT_IN_REGION_CODES.has(region))
|
|
53
|
+
export const LEGACY_CLOUDFRONT_LOGGING_PROPS = [
|
|
54
|
+
"enableLogging",
|
|
55
|
+
"logBucket",
|
|
56
|
+
"logFilePrefix"
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* `replacement` is the seam's v2 shape, named in the refusal: the resource
|
|
60
|
+
* requires the bucket and has no `false`, the pattern defaults the bucket
|
|
61
|
+
* and accepts `false`.
|
|
62
|
+
*/
|
|
63
|
+
export function assertNoLegacyLoggingProps(context, props, replacement) {
|
|
64
|
+
const legacy = new Set(LEGACY_CLOUDFRONT_LOGGING_PROPS);
|
|
65
|
+
const present = Object.entries(props)
|
|
66
|
+
.filter(([key, value]) => legacy.has(key) && value !== undefined)
|
|
67
|
+
.map(([key]) => key);
|
|
68
|
+
if (present.length === 0)
|
|
84
69
|
return;
|
|
85
|
-
throw new Error(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
"
|
|
89
|
-
"whose region CDK knows to be default-enabled: " +
|
|
90
|
-
"Bucket.fromBucketAttributes(…, { region }), or a bucket in a stack " +
|
|
91
|
-
"pinned to that region (fromBucketName and fromBucketArn inherit this " +
|
|
92
|
-
"stack's region).");
|
|
70
|
+
throw new Error(`${context}: ${present.join(", ")} ${present.length === 1 ? "is" : "are"} ` +
|
|
71
|
+
"not supported since 38.0.0 — CloudFront standard (legacy) logging was " +
|
|
72
|
+
`replaced by CloudWatch-vended standard logging (v2). Use ${replacement} ` +
|
|
73
|
+
"instead; see docs/release-notes/38.0.0.md for the migration.");
|
|
93
74
|
}
|
|
94
75
|
export class CloudFrontDistribution extends Construct {
|
|
95
76
|
id;
|
|
77
|
+
/** The v2 log delivery, when `logging` was given. */
|
|
78
|
+
logDelivery;
|
|
96
79
|
distribution;
|
|
97
80
|
origins = new Map();
|
|
98
81
|
/** Shared S3 access identities, keyed by the owning stack (see
|
|
@@ -102,7 +85,7 @@ export class CloudFrontDistribution extends Construct {
|
|
|
102
85
|
constructor(scope, id, props) {
|
|
103
86
|
super(scope, id);
|
|
104
87
|
this.id = id;
|
|
105
|
-
|
|
88
|
+
assertNoLegacyLoggingProps(`CloudFront distribution '${id}'`, props, "logging: { bucket, prefix?, format?, deliveryScope? } (or no logging prop at all)");
|
|
106
89
|
// Sanitise id for CloudFormation output keys (must be alphanumeric)
|
|
107
90
|
const outputName = toPascalCase(id);
|
|
108
91
|
const defaultOrigin = this.createOrigin(props.defaultOrigin);
|
|
@@ -158,11 +141,16 @@ export class CloudFrontDistribution extends Construct {
|
|
|
158
141
|
domainNames: props.domainNames,
|
|
159
142
|
certificate: props.certificate,
|
|
160
143
|
comment: props.comment,
|
|
161
|
-
enableLogging: props.enableLogging,
|
|
162
|
-
logBucket: props.logBucket,
|
|
163
|
-
logFilePrefix: props.logFilePrefix,
|
|
164
144
|
priceClass: this.resolvePriceClass(props.priceClass)
|
|
165
145
|
});
|
|
146
|
+
if (props.logging !== undefined) {
|
|
147
|
+
this.logDelivery = new CloudFrontLogDelivery(props.logging.deliveryScope ?? this, `${id}LogDelivery`, {
|
|
148
|
+
distribution: this.distribution,
|
|
149
|
+
bucket: props.logging.bucket,
|
|
150
|
+
prefix: props.logging.prefix ?? `cloudfront/${id}`,
|
|
151
|
+
format: props.logging.format ?? DEFAULT_CLOUDFRONT_LOG_FORMAT
|
|
152
|
+
});
|
|
153
|
+
}
|
|
166
154
|
// The CDN-qualified export is unconditional and always unique, so an app
|
|
167
155
|
// fronting several distributions stays deployable. The unqualified
|
|
168
156
|
// app-level export is emitted once from `App.synth()`, and only when the
|