@fjall/components-infrastructure 33.1.0 → 35.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.
Files changed (50) hide show
  1. package/dist/lib/app.js +2 -0
  2. package/dist/lib/config/aws/deployBoundarySingleton.d.ts +6 -0
  3. package/dist/lib/config/aws/deployBoundarySingleton.js +10 -2
  4. package/dist/lib/config/aws/identityCenter.js +2 -0
  5. package/dist/lib/config/aws/scpPreset.js +11 -1
  6. package/dist/lib/patterns/aws/bastionFactory.js +2 -1
  7. package/dist/lib/patterns/aws/cdn.d.ts +4 -1
  8. package/dist/lib/patterns/aws/clickhouseDatabase.js +2 -4
  9. package/dist/lib/patterns/aws/compute.d.ts +2 -2
  10. package/dist/lib/patterns/aws/computeEc2.d.ts +5 -18
  11. package/dist/lib/patterns/aws/computeEc2.js +9 -3
  12. package/dist/lib/patterns/aws/computeEcs.d.ts +4 -4
  13. package/dist/lib/patterns/aws/computeEcs.js +5 -9
  14. package/dist/lib/patterns/aws/computePropApplicability.js +1 -1
  15. package/dist/lib/patterns/aws/fivetranProxy.d.ts +8 -3
  16. package/dist/lib/patterns/aws/fivetranProxy.js +9 -6
  17. package/dist/lib/patterns/aws/interfaces/compute.d.ts +8 -0
  18. package/dist/lib/resources/aws/cdn/cloudFront.d.ts +16 -0
  19. package/dist/lib/resources/aws/cdn/cloudFront.js +52 -2
  20. package/dist/lib/resources/aws/compute/ec2.d.ts +33 -6
  21. package/dist/lib/resources/aws/compute/ec2.js +63 -14
  22. package/dist/lib/resources/aws/compute/ec2SshAccess.d.ts +80 -0
  23. package/dist/lib/resources/aws/compute/ec2SshAccess.js +166 -0
  24. package/dist/lib/resources/aws/compute/ecs.d.ts +11 -2
  25. package/dist/lib/resources/aws/compute/ecs.js +29 -6
  26. package/dist/lib/resources/aws/compute/lambda.d.ts +5 -0
  27. package/dist/lib/resources/aws/compute/lambda.js +9 -3
  28. package/dist/lib/resources/aws/database/dynamodb.js +5 -6
  29. package/dist/lib/resources/aws/iam/identityCenter/user.js +2 -0
  30. package/dist/lib/resources/aws/logging/accessLogBucket.d.ts +5 -3
  31. package/dist/lib/resources/aws/logging/accessLogBucket.js +6 -3
  32. package/dist/lib/resources/aws/messaging/sns.d.ts +17 -7
  33. package/dist/lib/resources/aws/messaging/sns.js +4 -4
  34. package/dist/lib/resources/aws/networking/vpcPeeringAccepterRole.js +3 -2
  35. package/dist/lib/resources/aws/networking/vpcPeeringConnection.js +2 -1
  36. package/dist/lib/resources/aws/secrets/stackEncryptionKey.d.ts +17 -4
  37. package/dist/lib/resources/aws/secrets/stackEncryptionKey.js +20 -4
  38. package/dist/lib/resources/aws/storage/bucketDeployment.d.ts +5 -0
  39. package/dist/lib/resources/aws/storage/bucketDeployment.js +7 -0
  40. package/dist/lib/resources/aws/storage/s3.d.ts +2 -1
  41. package/dist/lib/resources/aws/utilities/awsCustomResource.d.ts +9 -0
  42. package/dist/lib/resources/aws/utilities/awsCustomResource.js +16 -0
  43. package/dist/lib/resources/aws/utilities/customResource.js +2 -0
  44. package/dist/lib/resources/aws/utilities/customResourceProvider.d.ts +6 -0
  45. package/dist/lib/resources/aws/utilities/customResourceProvider.js +8 -0
  46. package/dist/lib/utils/deployTimeWriterMarkerAspect.d.ts +23 -0
  47. package/dist/lib/utils/deployTimeWriterMarkerAspect.js +35 -0
  48. package/dist/lib/utils/resourceTypeTags.d.ts +41 -0
  49. package/dist/lib/utils/resourceTypeTags.js +54 -0
  50. package/package.json +5 -4
@@ -1,7 +1,6 @@
1
1
  import { Construct } from "constructs";
2
2
  import { CfnOutput } from "aws-cdk-lib";
3
3
  import { Topic } from "aws-cdk-lib/aws-sns";
4
- import { tierDefault } from "../../../utils/backupTierMapping.js";
5
4
  import { StackEncryptionKey } from "../secrets/stackEncryptionKey.js";
6
5
  import { toRemovalPolicy } from "./utils.js";
7
6
  export class SNSTopic extends Construct {
@@ -11,10 +10,11 @@ export class SNSTopic extends Construct {
11
10
  super(scope, id);
12
11
  this.id = id;
13
12
  const isFifo = props.fifo === true;
13
+ const encryptionOverride = props.encryption === undefined
14
+ ? undefined
15
+ : props.encryption === "SSE_KMS";
14
16
  const masterKey = props.masterKey ??
15
- (tierDefault(props.appBackupTier, "customerManagedKeys")
16
- ? StackEncryptionKey.of(this).forSnsServicePublishers()
17
- : undefined);
17
+ StackEncryptionKey.forTier(this, props.appBackupTier, encryptionOverride)?.forSnsServicePublishers();
18
18
  const topicName = props.topicName
19
19
  ? isFifo
20
20
  ? `${props.topicName}.fifo`
@@ -1,7 +1,8 @@
1
1
  import { Construct } from "constructs";
2
- import { ArnFormat, Stack, Tags } from "aws-cdk-lib";
2
+ import { ArnFormat, Stack } from "aws-cdk-lib";
3
3
  import { AccountPrincipal, CompositePrincipal, PolicyDocument, PolicyStatement, Role } from "aws-cdk-lib/aws-iam";
4
4
  import { applyCostAllocationTags } from "../../../utils/costAllocationTags.js";
5
+ import { applyResourceTypeTag, FJALL_RESOURCE_TYPES } from "../../../utils/resourceTypeTags.js";
5
6
  export class VpcPeeringAccepterRole extends Construct {
6
7
  role;
7
8
  roleArn;
@@ -53,7 +54,7 @@ export class VpcPeeringAccepterRole extends Construct {
53
54
  }
54
55
  });
55
56
  this.roleArn = this.role.roleArn;
56
- Tags.of(this).add("fjall:resource:type", "vpc-peering-accepter");
57
+ applyResourceTypeTag(this, FJALL_RESOURCE_TYPES.vpcPeeringAccepter);
57
58
  applyCostAllocationTags(this, {
58
59
  service: "vpcPeeringAccepter",
59
60
  domain: props.costAllocationDomain ?? props.localVpc.node.id,
@@ -4,6 +4,7 @@ import { CfnRoute, CfnVPCPeeringConnection, SubnetType } from "aws-cdk-lib/aws-e
4
4
  import { PolicyStatement } from "aws-cdk-lib/aws-iam";
5
5
  import { AwsCustomResourcePolicy, PhysicalResourceId } from "aws-cdk-lib/custom-resources";
6
6
  import { applyCostAllocationTags } from "../../../utils/costAllocationTags.js";
7
+ import { applyResourceTypeTag, FJALL_RESOURCE_TYPES } from "../../../utils/resourceTypeTags.js";
7
8
  import { AwsCustomResource } from "../utilities/awsCustomResource.js";
8
9
  import { CrossAccountReturnRoutes } from "./crossAccountReturnRoutes.js";
9
10
  export class VpcPeeringConnection extends Construct {
@@ -92,7 +93,7 @@ export class VpcPeeringConnection extends Construct {
92
93
  enableDns: enableDnsResolution
93
94
  });
94
95
  }
95
- Tags.of(this).add("fjall:resource:type", "vpc-peering");
96
+ applyResourceTypeTag(this, FJALL_RESOURCE_TYPES.vpcPeering);
96
97
  Tags.of(this).add("fjall:peering:peer-vpc", props.peerVpcId);
97
98
  if (props.peerAccountId) {
98
99
  Tags.of(this).add("fjall:peering:peer-account", props.peerAccountId);
@@ -1,5 +1,6 @@
1
1
  import type { IKey } from "aws-cdk-lib/aws-kms";
2
2
  import type { Construct } from "constructs";
3
+ import { type BackupTier } from "../../../utils/backupTierMapping.js";
3
4
  import { CustomerManagedKey } from "./kms.js";
4
5
  /**
5
6
  * The one customer-managed KMS key an enterprise-tier stack encrypts its
@@ -16,10 +17,12 @@ import { CustomerManagedKey } from "./kms.js";
16
17
  * costs $1–3/month; a destroyed key would render the surviving table
17
18
  * permanently unreadable after its 30-day pending window.
18
19
  *
19
- * Grant-based consumers (DynamoDB, Lambda `environmentEncryption`, ECS Exec)
20
- * read `key` directly — the SDK/CDK grants they need ride the resource's own
21
- * IAM path. Service-side encryptors need a resource-policy admission first:
22
- * CloudWatch Logs through [[forLogGroups]], SNS service publishers through
20
+ * Constructs reach it through [[forTier]] — the one place the tier's
21
+ * `customerManagedKeys` column is read. Grant-based consumers (DynamoDB,
22
+ * Lambda `environmentEncryption`, ECS Exec) then read `key` directly — the
23
+ * SDK/CDK grants they need ride the resource's own IAM path. Service-side
24
+ * encryptors need a resource-policy admission first: CloudWatch Logs through
25
+ * [[forLogGroups]], SNS service publishers through
23
26
  * [[forSnsServicePublishers]]. Each admission is added once per stack.
24
27
  */
25
28
  export declare class StackEncryptionKey extends CustomerManagedKey {
@@ -28,6 +31,16 @@ export declare class StackEncryptionKey extends CustomerManagedKey {
28
31
  private constructor();
29
32
  /** The stack's shared key, created on first use. */
30
33
  static of(scope: Construct): StackEncryptionKey;
34
+ /**
35
+ * The stack's shared key when the tier's `customerManagedKeys` column says
36
+ * so, else `undefined` — the one place the column is read. A resource with
37
+ * its own encryption vocabulary (DynamoDB `encryption`, SNS `encryption`)
38
+ * passes the explicit choice as `override`; `undefined` lets the tier
39
+ * decide. The key is only created when the answer is yes, so an opt-out at
40
+ * enterprise mints nothing and an opt-in below enterprise mints the shared
41
+ * key rather than a per-resource one.
42
+ */
43
+ static forTier(scope: Construct, tier: BackupTier | undefined, override?: boolean): StackEncryptionKey | undefined;
31
44
  /**
32
45
  * The key, with CloudWatch Logs admitted to it. The CDK `LogGroup` sets
33
46
  * `KmsKeyId` but adds no key-policy statement, and without one the
@@ -1,5 +1,6 @@
1
1
  import { Stack } from "aws-cdk-lib";
2
2
  import { PolicyStatement, ServicePrincipal } from "aws-cdk-lib/aws-iam";
3
+ import { tierDefault } from "../../../utils/backupTierMapping.js";
3
4
  import { CustomerManagedKey } from "./kms.js";
4
5
  const STACK_ENCRYPTION_KEY_ID = "FjallStackEncryptionKey";
5
6
  /**
@@ -33,10 +34,12 @@ const SNS_SERVICE_PUBLISHERS = [
33
34
  * costs $1–3/month; a destroyed key would render the surviving table
34
35
  * permanently unreadable after its 30-day pending window.
35
36
  *
36
- * Grant-based consumers (DynamoDB, Lambda `environmentEncryption`, ECS Exec)
37
- * read `key` directly — the SDK/CDK grants they need ride the resource's own
38
- * IAM path. Service-side encryptors need a resource-policy admission first:
39
- * CloudWatch Logs through [[forLogGroups]], SNS service publishers through
37
+ * Constructs reach it through [[forTier]] — the one place the tier's
38
+ * `customerManagedKeys` column is read. Grant-based consumers (DynamoDB,
39
+ * Lambda `environmentEncryption`, ECS Exec) then read `key` directly — the
40
+ * SDK/CDK grants they need ride the resource's own IAM path. Service-side
41
+ * encryptors need a resource-policy admission first: CloudWatch Logs through
42
+ * [[forLogGroups]], SNS service publishers through
40
43
  * [[forSnsServicePublishers]]. Each admission is added once per stack.
41
44
  */
42
45
  export class StackEncryptionKey extends CustomerManagedKey {
@@ -59,6 +62,19 @@ export class StackEncryptionKey extends CustomerManagedKey {
59
62
  return existing;
60
63
  return new StackEncryptionKey(stack);
61
64
  }
65
+ /**
66
+ * The stack's shared key when the tier's `customerManagedKeys` column says
67
+ * so, else `undefined` — the one place the column is read. A resource with
68
+ * its own encryption vocabulary (DynamoDB `encryption`, SNS `encryption`)
69
+ * passes the explicit choice as `override`; `undefined` lets the tier
70
+ * decide. The key is only created when the answer is yes, so an opt-out at
71
+ * enterprise mints nothing and an opt-in below enterprise mints the shared
72
+ * key rather than a per-resource one.
73
+ */
74
+ static forTier(scope, tier, override) {
75
+ const onStackKey = override ?? tierDefault(tier, "customerManagedKeys");
76
+ return onStackKey ? StackEncryptionKey.of(scope) : undefined;
77
+ }
62
78
  /**
63
79
  * The key, with CloudWatch Logs admitted to it. The CDK `LogGroup` sets
64
80
  * `KmsKeyId` but adds no key-policy statement, and without one the
@@ -8,6 +8,11 @@ import { type Construct } from "constructs";
8
8
  * service-auto-created. Per the Wrapper Routing Discipline rule (generator
9
9
  * standards) and the infrastructure wrapper-routing pattern (§ A3), `lib/{config,
10
10
  * patterns}/` MUST instantiate this class rather than the raw CDK construct.
11
+ *
12
+ * The handler is a deploy-time writer: aws-cdk-lib hoists its function and
13
+ * execution role to the stack scope under an id that embeds a per-variant
14
+ * uuid (memory, VPC, ephemeral storage), so the role is marked through the
15
+ * public `handlerRole` seam rather than by construct id.
11
16
  */
12
17
  export declare class BucketDeployment extends CdkBucketDeployment {
13
18
  constructor(scope: Construct, id: string, props: BucketDeploymentProps);
@@ -1,4 +1,5 @@
1
1
  import { BucketDeployment as CdkBucketDeployment } from "aws-cdk-lib/aws-s3-deployment";
2
+ import { markDeployTimeWriter } from "../../../utils/resourceTypeTags.js";
2
3
  import { stackSingletonLogGroup } from "../logging/singletonLogGroup.js";
3
4
  /**
4
5
  * Routing-point wrapper for `aws-cdk-lib/aws-s3-deployment.BucketDeployment`.
@@ -8,6 +9,11 @@ import { stackSingletonLogGroup } from "../logging/singletonLogGroup.js";
8
9
  * service-auto-created. Per the Wrapper Routing Discipline rule (generator
9
10
  * standards) and the infrastructure wrapper-routing pattern (§ A3), `lib/{config,
10
11
  * patterns}/` MUST instantiate this class rather than the raw CDK construct.
12
+ *
13
+ * The handler is a deploy-time writer: aws-cdk-lib hoists its function and
14
+ * execution role to the stack scope under an id that embeds a per-variant
15
+ * uuid (memory, VPC, ephemeral storage), so the role is marked through the
16
+ * public `handlerRole` seam rather than by construct id.
11
17
  */
12
18
  export class BucketDeployment extends CdkBucketDeployment {
13
19
  constructor(scope, id, props) {
@@ -19,5 +25,6 @@ export class BucketDeployment extends CdkBucketDeployment {
19
25
  // site instantiates first.
20
26
  logGroup: props.logGroup ?? stackSingletonLogGroup(scope, "BucketDeployment")
21
27
  });
28
+ markDeployTimeWriter(this.handlerRole);
22
29
  }
23
30
  }
@@ -68,7 +68,8 @@ export interface S3BucketProps extends BucketProps {
68
68
  * `S3_VERSIONING_DISABLED` finding `mitigated` rather than `open` on
69
69
  * buckets whose contents are rebuilt, not kept. Constructs pass it for the
70
70
  * buckets they own (Payload build output and ISR cache, ClickHouse cold
71
- * tier); an author may pass it for a disposable bucket of their own.
71
+ * tier, the stack access-log bucket); an author may pass it for a
72
+ * disposable bucket of their own.
72
73
  */
73
74
  versioningExemption?: S3VersioningExemption;
74
75
  publicReadAccess?: boolean;
@@ -1,5 +1,14 @@
1
1
  import * as customResource from "aws-cdk-lib/custom-resources";
2
2
  import { type Construct } from "constructs";
3
+ /**
4
+ * The stack-scope construct id under which aws-cdk-lib hoists the one
5
+ * `AwsCustomResource` provider every site in a stack shares: the singleton's
6
+ * `lambdaPurpose` ("AWS") followed by its uuid with the hyphens stripped —
7
+ * `SingletonFunction.constructName`'s derivation, from the public
8
+ * `PROVIDER_FUNCTION_UUID`. Test fixtures address the hoisted function and
9
+ * its execution role by this prefix.
10
+ */
11
+ export declare const AWS_CUSTOM_RESOURCE_PROVIDER_ID: string;
3
12
  export declare class AwsCustomResource extends customResource.AwsCustomResource {
4
13
  readonly response: string;
5
14
  constructor(scope: Construct, id: string, props: customResource.AwsCustomResourceProps);
@@ -1,5 +1,16 @@
1
+ import { Stack } from "aws-cdk-lib";
1
2
  import * as customResource from "aws-cdk-lib/custom-resources";
3
+ import { markDeployTimeWriter } from "../../../utils/resourceTypeTags.js";
2
4
  import { stackSingletonLogGroup } from "../logging/singletonLogGroup.js";
5
+ /**
6
+ * The stack-scope construct id under which aws-cdk-lib hoists the one
7
+ * `AwsCustomResource` provider every site in a stack shares: the singleton's
8
+ * `lambdaPurpose` ("AWS") followed by its uuid with the hyphens stripped —
9
+ * `SingletonFunction.constructName`'s derivation, from the public
10
+ * `PROVIDER_FUNCTION_UUID`. Test fixtures address the hoisted function and
11
+ * its execution role by this prefix.
12
+ */
13
+ export const AWS_CUSTOM_RESOURCE_PROVIDER_ID = `AWS${customResource.AwsCustomResource.PROVIDER_FUNCTION_UUID.replace(/[^a-zA-Z0-9]/g, "")}`;
3
14
  export class AwsCustomResource extends customResource.AwsCustomResource {
4
15
  response;
5
16
  constructor(scope, id, props) {
@@ -14,5 +25,10 @@ export class AwsCustomResource extends customResource.AwsCustomResource {
14
25
  resources: customResource.AwsCustomResourcePolicy.ANY_RESOURCE
15
26
  })
16
27
  });
28
+ // aws-cdk-lib hoists the shared provider — and, unless the site passed
29
+ // `role`, its execution role — to the stack scope, out of reach of any
30
+ // tag on this construct. `findChild` throws if the hoist ever moves, so a
31
+ // CDK upgrade that would silently un-mark the role fails at synth.
32
+ markDeployTimeWriter(Stack.of(this).node.findChild(AWS_CUSTOM_RESOURCE_PROVIDER_ID));
17
33
  }
18
34
  }
@@ -5,6 +5,7 @@ import { Construct } from "constructs";
5
5
  import { SingletonFunction, LambdaFunction } from "../compute/lambda.js";
6
6
  import { DEFAULT_CUSTOM_RESOURCE_TIMEOUT_SECONDS } from "../compute/ecsConstants.js";
7
7
  import { DEFAULT_FRAMEWORK_LOG_RETENTION, LogGroup } from "../logging/logGroup.js";
8
+ import { markDeployTimeWriter } from "../../../utils/resourceTypeTags.js";
8
9
  export class CustomResource extends Construct {
9
10
  response;
10
11
  resource;
@@ -71,5 +72,6 @@ export class CustomResource extends Construct {
71
72
  })
72
73
  });
73
74
  this.response = this.resource.getAtt("Response").toString();
75
+ markDeployTimeWriter(this);
74
76
  }
75
77
  }
@@ -1,5 +1,11 @@
1
1
  import { Provider, type ProviderProps } from "aws-cdk-lib/custom-resources";
2
2
  import { type Construct } from "constructs";
3
+ /**
4
+ * Routing-point wrapper for the custom-resource provider framework. The
5
+ * framework's onEvent/isComplete functions and their roles are deploy-time
6
+ * writers; the caller's own handler (`props.onEventHandler`) lives outside
7
+ * this scope and is the caller's to mark.
8
+ */
3
9
  export declare class CustomResourceProvider extends Provider {
4
10
  constructor(scope: Construct, id: string, props: ProviderProps);
5
11
  }
@@ -1,6 +1,14 @@
1
1
  import { Provider } from "aws-cdk-lib/custom-resources";
2
+ import { markDeployTimeWriter } from "../../../utils/resourceTypeTags.js";
3
+ /**
4
+ * Routing-point wrapper for the custom-resource provider framework. The
5
+ * framework's onEvent/isComplete functions and their roles are deploy-time
6
+ * writers; the caller's own handler (`props.onEventHandler`) lives outside
7
+ * this scope and is the caller's to mark.
8
+ */
2
9
  export class CustomResourceProvider extends Provider {
3
10
  constructor(scope, id, props) {
4
11
  super(scope, id, props);
12
+ markDeployTimeWriter(this);
5
13
  }
6
14
  }
@@ -0,0 +1,23 @@
1
+ import { type IAspect } from "aws-cdk-lib";
2
+ import type { IConstruct } from "constructs";
3
+ /**
4
+ * Marks the execution roles aws-cdk-lib's lightweight custom-resource
5
+ * provider framework (`CustomResourceProviderBase`) renders as raw
6
+ * `AWS::IAM::Role` CfnResources: `Custom::VpcRestrictDefaultSG` under the
7
+ * `@aws-cdk/aws-ec2:restrictDefaultSecurityGroup` flag every scaffolded
8
+ * `cdk.json` sets, `Custom::CrossAccountZoneDelegation`, and whichever the
9
+ * next CDK release adds. Those roles have no tag manager, so `Tags.of()` —
10
+ * the wrappers' mechanism — never reaches them; this aspect writes the marker
11
+ * as a property override instead. Registered once per `App`, so every stack
12
+ * gets it without a per-construct site to forget.
13
+ *
14
+ * Recognised structurally, not by construct id: a `Role` CfnResource whose
15
+ * parent exposes the framework's `serviceToken` + `roleArn` pair. Prototype
16
+ * probing rather than `instanceof`, for the duplicate-aws-cdk-lib reason
17
+ * `logGroupHygiene` records. The full `Provider` framework exposes no
18
+ * `roleArn` and builds L2 roles, so it is not matched here — its wrappers tag
19
+ * it directly.
20
+ */
21
+ export declare class DeployTimeWriterMarkerAspect implements IAspect {
22
+ visit(node: IConstruct): void;
23
+ }
@@ -0,0 +1,35 @@
1
+ import { CfnResource } from "aws-cdk-lib";
2
+ import { markDeployTimeWriterCfn } from "./resourceTypeTags.js";
3
+ /**
4
+ * Marks the execution roles aws-cdk-lib's lightweight custom-resource
5
+ * provider framework (`CustomResourceProviderBase`) renders as raw
6
+ * `AWS::IAM::Role` CfnResources: `Custom::VpcRestrictDefaultSG` under the
7
+ * `@aws-cdk/aws-ec2:restrictDefaultSecurityGroup` flag every scaffolded
8
+ * `cdk.json` sets, `Custom::CrossAccountZoneDelegation`, and whichever the
9
+ * next CDK release adds. Those roles have no tag manager, so `Tags.of()` —
10
+ * the wrappers' mechanism — never reaches them; this aspect writes the marker
11
+ * as a property override instead. Registered once per `App`, so every stack
12
+ * gets it without a per-construct site to forget.
13
+ *
14
+ * Recognised structurally, not by construct id: a `Role` CfnResource whose
15
+ * parent exposes the framework's `serviceToken` + `roleArn` pair. Prototype
16
+ * probing rather than `instanceof`, for the duplicate-aws-cdk-lib reason
17
+ * `logGroupHygiene` records. The full `Provider` framework exposes no
18
+ * `roleArn` and builds L2 roles, so it is not matched here — its wrappers tag
19
+ * it directly.
20
+ */
21
+ export class DeployTimeWriterMarkerAspect {
22
+ visit(node) {
23
+ if (!CfnResource.isCfnResource(node) ||
24
+ node.cfnResourceType !== "AWS::IAM::Role") {
25
+ return;
26
+ }
27
+ const parent = node.node.scope;
28
+ if (parent === undefined || !isLightweightProvider(parent))
29
+ return;
30
+ markDeployTimeWriterCfn(node);
31
+ }
32
+ }
33
+ function isLightweightProvider(construct) {
34
+ return "serviceToken" in construct && "roleArn" in construct;
35
+ }
@@ -0,0 +1,41 @@
1
+ import { FJALL_RESOURCE_TYPE_TAG_KEY, FJALL_RESOURCE_TYPES, type FjallResourceType } from "@fjall/util/aws";
2
+ import { type CfnResource } from "aws-cdk-lib";
3
+ import type { IConstruct } from "constructs";
4
+ export { FJALL_RESOURCE_TYPE_TAG_KEY, FJALL_RESOURCE_TYPES, type FjallResourceType };
5
+ /**
6
+ * Stamp the `fjall:resource:type` marker on a construct subtree. The marker
7
+ * answers "what IS this resource" for resources whose physical name is
8
+ * CloudFormation-generated, so a consumer never has to pattern-match a name.
9
+ */
10
+ export declare function applyResourceTypeTag(scope: IConstruct, resourceType: FjallResourceType): void;
11
+ /**
12
+ * Mark a deploy-time-only writer subtree — the execution role CloudFormation
13
+ * assumes while driving a custom resource, plus the handler, the provider
14
+ * framework function and their log groups.
15
+ *
16
+ * Every writer producer in the library routes through one of two mechanisms,
17
+ * nothing stamping the key by hand:
18
+ *
19
+ * - this helper, called by each wrapper that constructs a writer: the shared
20
+ * `CustomResource` utility and the `CustomResourceProvider` wrapper (their
21
+ * own subtree), the `SingletonFunction`, `AwsCustomResource` and
22
+ * `BucketDeployment` wrappers (the function and role aws-cdk-lib hoists to
23
+ * the stack scope, out of reach of any tag on the wrapper itself), the
24
+ * identity-store user provider, and the Identity Centre ListInstances role;
25
+ * - `DeployTimeWriterMarkerAspect` (registered by `App`), for the execution
26
+ * roles aws-cdk-lib's lightweight provider framework renders as raw
27
+ * CfnResources — `Tags.of()` cannot reach those, so the marker goes on as a
28
+ * property override via `markDeployTimeWriterCfn`.
29
+ *
30
+ * The producer set is enumerated from source by
31
+ * `__tests__/deployTimeWriterCensus.test.ts`: a new construction of a CDK
32
+ * writer class fails that test until it names the mechanism that marks it.
33
+ */
34
+ export declare function markDeployTimeWriter(scope: IConstruct): void;
35
+ /**
36
+ * The raw-CfnResource twin of `markDeployTimeWriter`, for a role the
37
+ * lightweight `CustomResourceProviderBase` framework renders without a tag
38
+ * manager: the marker is written straight into the resource's `Tags`
39
+ * property. Called only by `DeployTimeWriterMarkerAspect`.
40
+ */
41
+ export declare function markDeployTimeWriterCfn(role: CfnResource): void;
@@ -0,0 +1,54 @@
1
+ import { FJALL_RESOURCE_TYPE_TAG_KEY, FJALL_RESOURCE_TYPES } from "@fjall/util/aws";
2
+ import { Tags } from "aws-cdk-lib";
3
+ // Canonical home: @fjall/util (`util/src/aws/infraTags.ts`) — shared with the
4
+ // Quick-Create template's hand-written twin and the webapp posture exclusion.
5
+ // Re-exported here so construct-package consumers keep one import path.
6
+ export { FJALL_RESOURCE_TYPE_TAG_KEY, FJALL_RESOURCE_TYPES };
7
+ /**
8
+ * Stamp the `fjall:resource:type` marker on a construct subtree. The marker
9
+ * answers "what IS this resource" for resources whose physical name is
10
+ * CloudFormation-generated, so a consumer never has to pattern-match a name.
11
+ */
12
+ export function applyResourceTypeTag(scope, resourceType) {
13
+ Tags.of(scope).add(FJALL_RESOURCE_TYPE_TAG_KEY, resourceType);
14
+ }
15
+ /**
16
+ * Mark a deploy-time-only writer subtree — the execution role CloudFormation
17
+ * assumes while driving a custom resource, plus the handler, the provider
18
+ * framework function and their log groups.
19
+ *
20
+ * Every writer producer in the library routes through one of two mechanisms,
21
+ * nothing stamping the key by hand:
22
+ *
23
+ * - this helper, called by each wrapper that constructs a writer: the shared
24
+ * `CustomResource` utility and the `CustomResourceProvider` wrapper (their
25
+ * own subtree), the `SingletonFunction`, `AwsCustomResource` and
26
+ * `BucketDeployment` wrappers (the function and role aws-cdk-lib hoists to
27
+ * the stack scope, out of reach of any tag on the wrapper itself), the
28
+ * identity-store user provider, and the Identity Centre ListInstances role;
29
+ * - `DeployTimeWriterMarkerAspect` (registered by `App`), for the execution
30
+ * roles aws-cdk-lib's lightweight provider framework renders as raw
31
+ * CfnResources — `Tags.of()` cannot reach those, so the marker goes on as a
32
+ * property override via `markDeployTimeWriterCfn`.
33
+ *
34
+ * The producer set is enumerated from source by
35
+ * `__tests__/deployTimeWriterCensus.test.ts`: a new construction of a CDK
36
+ * writer class fails that test until it names the mechanism that marks it.
37
+ */
38
+ export function markDeployTimeWriter(scope) {
39
+ applyResourceTypeTag(scope, FJALL_RESOURCE_TYPES.deployTimeWriter);
40
+ }
41
+ /**
42
+ * The raw-CfnResource twin of `markDeployTimeWriter`, for a role the
43
+ * lightweight `CustomResourceProviderBase` framework renders without a tag
44
+ * manager: the marker is written straight into the resource's `Tags`
45
+ * property. Called only by `DeployTimeWriterMarkerAspect`.
46
+ */
47
+ export function markDeployTimeWriterCfn(role) {
48
+ role.addPropertyOverride("Tags", [
49
+ {
50
+ Key: FJALL_RESOURCE_TYPE_TAG_KEY,
51
+ Value: FJALL_RESOURCE_TYPES.deployTimeWriter
52
+ }
53
+ ]);
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/components-infrastructure",
3
- "version": "33.1.0",
3
+ "version": "35.0.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fjall-tech/fjall.git",
@@ -39,7 +39,8 @@
39
39
  "clean": "rm -rf ./dist",
40
40
  "clean:node": "rm -rf ./node_modules",
41
41
  "build:cert-gen-lambda": "node lib/lambda-assets/cert-generator/src/build.mjs",
42
- "build": "npm run clean && npm run build:cert-gen-lambda && tsc && cp -r lib/layers dist/lib/layers && mkdir -p dist/lib/lambda-assets/cert-generator/asset && cp lib/lambda-assets/cert-generator/asset/index.js dist/lib/lambda-assets/cert-generator/asset/index.js && cp lib/lambda-assets/cert-generator/asset/package.json dist/lib/lambda-assets/cert-generator/asset/package.json && mkdir -p dist/lib/lambda-assets/identity-store-user/asset && cp lib/lambda-assets/identity-store-user/asset/index.js dist/lib/lambda-assets/identity-store-user/asset/index.js && cp lib/lambda-assets/identity-store-user/asset/package.json dist/lib/lambda-assets/identity-store-user/asset/package.json && mkdir -p dist/lib/lambda-assets/static-site-forms/asset && cp lib/lambda-assets/static-site-forms/asset/index.js dist/lib/lambda-assets/static-site-forms/asset/index.js && cp lib/lambda-assets/static-site-forms/asset/package.json dist/lib/lambda-assets/static-site-forms/asset/package.json && cp lib/resources/aws/compute/lifecycleHookLambda.source.cjs dist/lib/resources/aws/compute/lifecycleHookLambda.source.cjs && cp lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs dist/lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs && cp lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs dist/lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs && cp lib/patterns/aws/devSubstrate.waker.source.cjs dist/lib/patterns/aws/devSubstrate.waker.source.cjs && node ../../scripts/stamp-build.mjs",
42
+ "build": "node ../../scripts/run-build.mjs --purpose=build -- npm run build:steps",
43
+ "build:steps": "npm run clean && npm run build:cert-gen-lambda && tsc && cp -r lib/layers dist/lib/layers && mkdir -p dist/lib/lambda-assets/cert-generator/asset && cp lib/lambda-assets/cert-generator/asset/index.js dist/lib/lambda-assets/cert-generator/asset/index.js && cp lib/lambda-assets/cert-generator/asset/package.json dist/lib/lambda-assets/cert-generator/asset/package.json && mkdir -p dist/lib/lambda-assets/identity-store-user/asset && cp lib/lambda-assets/identity-store-user/asset/index.js dist/lib/lambda-assets/identity-store-user/asset/index.js && cp lib/lambda-assets/identity-store-user/asset/package.json dist/lib/lambda-assets/identity-store-user/asset/package.json && mkdir -p dist/lib/lambda-assets/static-site-forms/asset && cp lib/lambda-assets/static-site-forms/asset/index.js dist/lib/lambda-assets/static-site-forms/asset/index.js && cp lib/lambda-assets/static-site-forms/asset/package.json dist/lib/lambda-assets/static-site-forms/asset/package.json && cp lib/resources/aws/compute/lifecycleHookLambda.source.cjs dist/lib/resources/aws/compute/lifecycleHookLambda.source.cjs && cp lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs dist/lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs && cp lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs dist/lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs && cp lib/patterns/aws/devSubstrate.waker.source.cjs dist/lib/patterns/aws/devSubstrate.waker.source.cjs && node ../../scripts/stamp-build.mjs",
43
44
  "prepack": "node ../../scripts/check-dist-freshness.mjs",
44
45
  "prepublishOnly": "node ../../scripts/audit-package-tarball.mjs",
45
46
  "watch": "tsc -w",
@@ -79,8 +80,8 @@
79
80
  },
80
81
  "dependencies": {
81
82
  "@aws-sdk/client-organizations": "^3.1098.0",
82
- "@fjall/generator": "^33.1.0",
83
- "@fjall/util": "^33.1.0",
83
+ "@fjall/generator": "^35.0.0",
84
+ "@fjall/util": "^35.0.0",
84
85
  "constructs": "^10.7.2",
85
86
  "zod": "^4.4.3"
86
87
  },