@fjall/components-infrastructure 36.0.0 → 37.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.
@@ -10,6 +10,14 @@ export type KeyProtectionScope = (typeof KEY_PROTECTION_SCOPES)[number];
10
10
  * forgotten (tier-aware alert policy design §8 K4, `KMS_KEY_ORPHAN`).
11
11
  */
12
12
  export { KMS_RETENTION_TAG_KEY } from "@fjall/util/aws";
13
+ /**
14
+ * Stamped beside the retention marker, valued with the alias name as `kms
15
+ * list-aliases` reports it. The alias resource goes with the stack and
16
+ * CloudFormation tags nothing on a KMS key, so after a stack delete this tag
17
+ * is the only thing that still says which stack's key a retained key was —
18
+ * the deploy-test teardown releases retained stack keys by it.
19
+ */
20
+ export { KMS_ALIAS_TAG_KEY } from "@fjall/util/aws";
13
21
  interface CustomerManagedKeyProps {
14
22
  description?: string;
15
23
  aliasName?: string;
@@ -1,4 +1,4 @@
1
- import { KMS_RETENTION_TAG_KEY } from "@fjall/util/aws";
1
+ import { KMS_ALIAS_TAG_KEY, KMS_RETENTION_TAG_KEY } from "@fjall/util/aws";
2
2
  import { CfnOutput, Duration, RemovalPolicy, Tags } from "aws-cdk-lib";
3
3
  import { Alias, Key } from "aws-cdk-lib/aws-kms";
4
4
  import { Construct } from "constructs";
@@ -17,6 +17,14 @@ export const KEY_PROTECTION_SCOPES = [
17
17
  * forgotten (tier-aware alert policy design §8 K4, `KMS_KEY_ORPHAN`).
18
18
  */
19
19
  export { KMS_RETENTION_TAG_KEY } from "@fjall/util/aws";
20
+ /**
21
+ * Stamped beside the retention marker, valued with the alias name as `kms
22
+ * list-aliases` reports it. The alias resource goes with the stack and
23
+ * CloudFormation tags nothing on a KMS key, so after a stack delete this tag
24
+ * is the only thing that still says which stack's key a retained key was —
25
+ * the deploy-test teardown releases retained stack keys by it.
26
+ */
27
+ export { KMS_ALIAS_TAG_KEY } from "@fjall/util/aws";
20
28
  /**
21
29
  * How long a scheduled-for-deletion key stays recoverable.
22
30
  *
@@ -52,8 +60,10 @@ export class CustomerManagedKey extends Construct {
52
60
  ? PENDING_DELETION_WINDOW
53
61
  : undefined
54
62
  });
63
+ const aliasName = props.aliasName || ResourceNaming.defaultKeyAlias(id);
55
64
  if (removalPolicy === RemovalPolicy.RETAIN) {
56
65
  Tags.of(this.key).add(KMS_RETENTION_TAG_KEY, props.protects);
66
+ Tags.of(this.key).add(KMS_ALIAS_TAG_KEY, ResourceNaming.qualifiedKeyAlias(aliasName));
57
67
  }
58
68
  new CfnOutput(this, `${outputName}KeyArn`, {
59
69
  key: `${outputName}Arn`,
@@ -61,7 +71,7 @@ export class CustomerManagedKey extends Construct {
61
71
  exportName: stackScopedExportName(this, `${outputName}KeyArn`)
62
72
  });
63
73
  this.alias = new Alias(this, `${id}KeyAlias`, {
64
- aliasName: props.aliasName || ResourceNaming.defaultKeyAlias(id),
74
+ aliasName,
65
75
  targetKey: this.key
66
76
  });
67
77
  new CfnOutput(this, `${outputName}KeyAliasArn`, {
@@ -1,8 +1,9 @@
1
1
  import { Stack } from "aws-cdk-lib";
2
2
  import { PolicyStatement, ServicePrincipal } from "aws-cdk-lib/aws-iam";
3
3
  import { tierDefault } from "../../../utils/backupTierMapping.js";
4
+ import { ResourceNaming } from "../../../utils/resourceNaming.js";
4
5
  import { CustomerManagedKey } from "./kms.js";
5
- const STACK_ENCRYPTION_KEY_ID = "FjallStackEncryptionKey";
6
+ const STACK_ENCRYPTION_KEY_ID = ResourceNaming.stackEncryptionKeyConstructId();
6
7
  /**
7
8
  * AWS services that publish to an application's SNS topics on the app's
8
9
  * behalf and therefore encrypt messages through the topic's key: EventBridge
@@ -48,9 +49,10 @@ export class StackEncryptionKey extends CustomerManagedKey {
48
49
  constructor(stack) {
49
50
  super(stack, STACK_ENCRYPTION_KEY_ID, {
50
51
  description: `Encrypts the enterprise-tier resources of ${stack.stackName} (DynamoDB tables, SNS topics, Lambda environment, ECS Exec sessions, log groups)`,
51
- // KMS alias names are unique per account-region, so the stack name is
52
- // the namespace — every stack in the account carries its own key.
53
- aliasName: `cmk/${stack.stackName}/${STACK_ENCRYPTION_KEY_ID}`,
52
+ // Derived through ResourceNaming so the deploy-test teardown, which
53
+ // finds the retained key by this alias after the stack is gone, cannot
54
+ // spell it differently.
55
+ aliasName: ResourceNaming.stackEncryptionKeyAlias(stack.stackName),
54
56
  protects: "outlives-stack"
55
57
  });
56
58
  }
@@ -1,22 +1,17 @@
1
- import { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, type BackupTier, type BackupTierDefaults, tierDefault } from "@fjall/util";
2
- /** Tag key used by AWS Backup plan selection rules */
3
- export declare const BACKUP_TIER_TAG_KEY = "fjall:disasterRecovery:tier";
1
+ import { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, BACKUP_TIER_TAG_KEY, BACKUP_TIER_TAG_MAP, type BackupTier, type BackupTierDefaults, tierDefault } from "@fjall/util";
4
2
  /**
5
- * The application backup tier vocabulary and its defaults table are owned by
6
- * `@fjall/util` (`BACKUP_TIERS`, `BACKUP_TIER_DEFAULTS`, `tierDefault`) and
7
- * shared with the generator (`BACKUP_VAULT_TIERS`) and the CLI's synth
8
- * expectations, so every system derives from one declaration. Re-exported
9
- * here so construct code keeps one import path for tier concerns.
3
+ * The application backup tier vocabulary, its defaults table, the DR tag key
4
+ * and the tier → tag-value map are owned by `@fjall/util` (`BACKUP_TIERS`,
5
+ * `BACKUP_TIER_DEFAULTS`, `tierDefault`, `BACKUP_TIER_TAG_KEY`,
6
+ * `BACKUP_TIER_TAG_MAP`) and shared with the generator (`BACKUP_VAULT_TIERS`),
7
+ * the CLI's synth expectations and the webapp's posture rules (which read the
8
+ * tag back from discovered resources), so every system derives from one
9
+ * declaration. Re-exported here so construct code keeps one import path for
10
+ * tier concerns: `App.applyBackupTier()` (app-level tag), `StandardTagsAspect`
11
+ * (per-resource tag) and `BackupPlan` (selection key) all read the same pair.
10
12
  */
11
13
  export type { BackupTier, BackupTierDefaults };
12
- export { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, tierDefault };
13
- /**
14
- * Shared mapping from application backup tier names to AWS Backup plan tag values.
15
- *
16
- * Used by both App.applyBackupTier() (app-level tag) and StandardTagsAspect
17
- * (per-resource tag) to ensure consistent tier → tag translation.
18
- */
19
- export declare const BACKUP_TIER_TAG_MAP: Readonly<Record<BackupTier, string>>;
14
+ export { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, BACKUP_TIER_TAG_KEY, BACKUP_TIER_TAG_MAP, tierDefault };
20
15
  /**
21
16
  * The tier a resource actually runs under: its own override, else the
22
17
  * app-level tier `App.getApp({ backup: { tier } })` declared. The single
@@ -1,18 +1,5 @@
1
- import { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, tierDefault } from "@fjall/util";
2
- /** Tag key used by AWS Backup plan selection rules */
3
- export const BACKUP_TIER_TAG_KEY = "fjall:disasterRecovery:tier";
4
- export { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, tierDefault };
5
- /**
6
- * Shared mapping from application backup tier names to AWS Backup plan tag values.
7
- *
8
- * Used by both App.applyBackupTier() (app-level tag) and StandardTagsAspect
9
- * (per-resource tag) to ensure consistent tier → tag translation.
10
- */
11
- export const BACKUP_TIER_TAG_MAP = Object.freeze({
12
- standard: "default",
13
- resilient: "resilient",
14
- enterprise: "enterprise"
15
- });
1
+ import { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, BACKUP_TIER_TAG_KEY, BACKUP_TIER_TAG_MAP, tierDefault } from "@fjall/util";
2
+ export { BACKUP_TIERS, BACKUP_TIER_DEFAULTS, BACKUP_TIER_TAG_KEY, BACKUP_TIER_TAG_MAP, tierDefault };
16
3
  /**
17
4
  * The tier a resource actually runs under: its own override, else the
18
5
  * app-level tier `App.getApp({ backup: { tier } })` declared. The single
@@ -77,6 +77,28 @@ export declare class ResourceNaming {
77
77
  * reports.
78
78
  */
79
79
  static defaultKeyAlias(constructId: string): string;
80
+ /**
81
+ * Construct id of the one `StackEncryptionKey` an enterprise-tier stack
82
+ * mints (lib/resources/aws/secrets/stackEncryptionKey.ts). It is also the
83
+ * alias suffix, so the two are spelled once.
84
+ */
85
+ static stackEncryptionKeyConstructId(): string;
86
+ /**
87
+ * Alias the stack encryption key is created under. KMS alias names are
88
+ * unique per account-region, so the stack name is the namespace. Unprefixed
89
+ * like the other alias helpers; the deploy tests that find the retained key
90
+ * after its stack is gone derive the reported form through
91
+ * [[qualifiedKeyAlias]], the same way the construct's alias tag does.
92
+ */
93
+ static stackEncryptionKeyAlias(stackName: string): string;
94
+ /**
95
+ * An alias name as KMS reports it. The CDK `Alias` prepends `alias/` to a
96
+ * resolved name that lacks it, and to a token only when the token's
97
+ * leading literal fragment lacks it — every Fjall alias starts with the
98
+ * literal `cmk/`, so the two rules agree. An already-qualified name is
99
+ * returned as it is.
100
+ */
101
+ static qualifiedKeyAlias(aliasName: string): string;
80
102
  /**
81
103
  * S3 bucket holding a VPC's flow logs. `id` is the Network construct id —
82
104
  * the network name for an additional VPC, `${stackPrefix}Vpc` for the
@@ -1,6 +1,8 @@
1
1
  import { toKebab } from "./capitaliseString.js";
2
2
  /** ELBv2's hard limit on an Application Load Balancer's name. */
3
3
  const LOAD_BALANCER_NAME_MAX_LENGTH = 32;
4
+ /** What `kms list-aliases` prepends to every alias name. */
5
+ const KMS_ALIAS_PREFIX = "alias/";
4
6
  /**
5
7
  * Centralised resource naming for infrastructure.
6
8
  * Ensures consistent, predictable names across constructs and tests.
@@ -127,6 +129,36 @@ export class ResourceNaming {
127
129
  static defaultKeyAlias(constructId) {
128
130
  return `cmk/${constructId}`;
129
131
  }
132
+ /**
133
+ * Construct id of the one `StackEncryptionKey` an enterprise-tier stack
134
+ * mints (lib/resources/aws/secrets/stackEncryptionKey.ts). It is also the
135
+ * alias suffix, so the two are spelled once.
136
+ */
137
+ static stackEncryptionKeyConstructId() {
138
+ return "FjallStackEncryptionKey";
139
+ }
140
+ /**
141
+ * Alias the stack encryption key is created under. KMS alias names are
142
+ * unique per account-region, so the stack name is the namespace. Unprefixed
143
+ * like the other alias helpers; the deploy tests that find the retained key
144
+ * after its stack is gone derive the reported form through
145
+ * [[qualifiedKeyAlias]], the same way the construct's alias tag does.
146
+ */
147
+ static stackEncryptionKeyAlias(stackName) {
148
+ return `cmk/${stackName}/${ResourceNaming.stackEncryptionKeyConstructId()}`;
149
+ }
150
+ /**
151
+ * An alias name as KMS reports it. The CDK `Alias` prepends `alias/` to a
152
+ * resolved name that lacks it, and to a token only when the token's
153
+ * leading literal fragment lacks it — every Fjall alias starts with the
154
+ * literal `cmk/`, so the two rules agree. An already-qualified name is
155
+ * returned as it is.
156
+ */
157
+ static qualifiedKeyAlias(aliasName) {
158
+ return aliasName.startsWith(KMS_ALIAS_PREFIX)
159
+ ? aliasName
160
+ : `${KMS_ALIAS_PREFIX}${aliasName}`;
161
+ }
130
162
  /**
131
163
  * S3 bucket holding a VPC's flow logs. `id` is the Network construct id —
132
164
  * the network name for an additional VPC, `${stackPrefix}Vpc` for the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/components-infrastructure",
3
- "version": "36.0.0",
3
+ "version": "37.0.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fjall-tech/fjall.git",
@@ -80,8 +80,8 @@
80
80
  },
81
81
  "dependencies": {
82
82
  "@aws-sdk/client-organizations": "^3.1098.0",
83
- "@fjall/generator": "^36.0.0",
84
- "@fjall/util": "^36.0.0",
83
+ "@fjall/generator": "^37.0.0",
84
+ "@fjall/util": "^37.0.0",
85
85
  "constructs": "^10.7.2",
86
86
  "zod": "^4.4.3"
87
87
  },