@aws-blocks/bb-app-setting 0.2.0 → 0.3.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.
@@ -4,6 +4,7 @@ import type { AppSettingOptions } from './types.js';
4
4
  import type { ChildLogger } from '@aws-blocks/bb-logger';
5
5
  export { AppSettingErrors } from './errors.js';
6
6
  export type { AppSettingOptions } from './types.js';
7
+ export { SECRETS_BULK_CONSTRUCT_ID } from './secrets-bulk.js';
7
8
  /**
8
9
  * A single application configuration value backed by SSM Parameter Store.
9
10
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.aws.d.ts","sourceRoot":"","sources":["../src/index.aws.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,EAA6C,MAAM,kBAAkB,CAAC;AACpF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,KAAK,EAAE,iBAAiB,EAA6B,MAAM,YAAY,CAAC;AAG/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAqBpD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,UAAU,CAAC,CAAC,GAAG,MAAM,CAAE,SAAQ,KAAK;IAChD;;;;;;OAMG;IACH,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,MAAM,EAC7B,KAAK,EAAE,WAAW,EAClB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7D,UAAU,CAAC,CAAC,CAAC;IAKhB,QAAQ,CAAC,MAAM,gBAAW;IAC1B,OAAO,CAAC,MAAM,CAAC,CAAsB;IACrC,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAY;IAE1B,2FAA2F;IAC3F,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC;gBAEf,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAczE;;;;;;;;;;;;;OAaG;IACG,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC;IA2BvB;;;;;;;;;;;;;;OAcG;IACG,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAelC"}
1
+ {"version":3,"file":"index.aws.d.ts","sourceRoot":"","sources":["../src/index.aws.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,EAA6C,MAAM,kBAAkB,CAAC;AACpF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,KAAK,EAAE,iBAAiB,EAA6B,MAAM,YAAY,CAAC;AAG/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAqB9D;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,UAAU,CAAC,CAAC,GAAG,MAAM,CAAE,SAAQ,KAAK;IAChD;;;;;;OAMG;IACH,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,MAAM,EAC7B,KAAK,EAAE,WAAW,EAClB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7D,UAAU,CAAC,CAAC,CAAC;IAKhB,QAAQ,CAAC,MAAM,gBAAW;IAC1B,OAAO,CAAC,MAAM,CAAC,CAAsB;IACrC,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAY;IAE1B,2FAA2F;IAC3F,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC;gBAEf,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAczE;;;;;;;;;;;;;OAaG;IACG,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC;IA2BvB;;;;;;;;;;;;;;OAcG;IACG,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAelC"}
package/dist/index.aws.js CHANGED
@@ -7,6 +7,7 @@ import { BB_NAME, BB_VERSION } from './version.js';
7
7
  import { Logger } from '@aws-blocks/bb-logger';
8
8
  // Re-export public types from types module (canonical source)
9
9
  export { AppSettingErrors } from './errors.js';
10
+ export { SECRETS_BULK_CONSTRUCT_ID } from './secrets-bulk.js';
10
11
  // ── Helpers ─────────────────────────────────────────────────────────────────
11
12
  function blocksError(name, message) {
12
13
  const err = new Error(`${name}: ${message}`);
@@ -1,3 +1,4 @@
1
+ export { SECRETS_BULK_CONSTRUCT_ID } from './secrets-bulk.js';
1
2
  export declare class AppSetting {
2
3
  static fromExisting(...args: any[]): any;
3
4
  constructor(...args: any[]);
@@ -1 +1 @@
1
- {"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../src/index.browser.ts"],"names":[],"mappings":"AAIA,qBAAa,UAAU;IACtB,MAAM,CAAC,YAAY,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG;gBAG5B,GAAG,IAAI,EAAE,GAAG,EAAE;CAC1B"}
1
+ {"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../src/index.browser.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,qBAAa,UAAU;IACtB,MAAM,CAAC,YAAY,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,GAAG;gBAG5B,GAAG,IAAI,EAAE,GAAG,EAAE;CAC1B"}
@@ -1,6 +1,7 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  // Browser stub - AppSetting runs server-side only
4
+ export { SECRETS_BULK_CONSTRUCT_ID } from './secrets-bulk.js';
4
5
  export class AppSetting {
5
6
  static fromExisting(...args) {
6
7
  return new AppSetting();
@@ -1,8 +1,9 @@
1
- import { Scope } from '@aws-blocks/core/cdk';
1
+ import { BuildingBlockScope } from '@aws-blocks/core/cdk';
2
2
  import type { ScopeParent } from '@aws-blocks/core';
3
3
  import type { AppSettingOptions } from './types.js';
4
4
  export { AppSettingErrors } from './errors.js';
5
5
  export type { AppSettingOptions } from './types.js';
6
+ export { SECRETS_BULK_CONSTRUCT_ID } from './secrets-bulk.js';
6
7
  /**
7
8
  * CDK construct for AppSetting. Creates a single SSM parameter (String or
8
9
  * SecureString) and grants the shared Lambda handler read/write permissions.
@@ -14,7 +15,7 @@ export type { AppSettingOptions } from './types.js';
14
15
  * or with a customer-managed key when `kmsKeyArn` is provided (the handler is
15
16
  * then granted `kms:Decrypt`/`Encrypt` on that specific key ARN).
16
17
  */
17
- export declare class AppSetting<T = string> extends Scope {
18
+ export declare class AppSetting<T = string> extends BuildingBlockScope {
18
19
  /**
19
20
  * Reference an SSM parameter that is created and owned **outside this stack**
20
21
  * (e.g. a connection string seeded by `ensureSecrets` before deploy). The
@@ -1 +1 @@
1
- {"version":3,"file":"index.cdk.d.ts","sourceRoot":"","sources":["../src/index.cdk.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,EAAwC,MAAM,sBAAsB,CAAC;AACnF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,KAAK,EAAE,iBAAiB,EAA6B,MAAM,YAAY,CAAC;AAE/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD;;;;;;;;;;GAUG;AACH,qBAAa,UAAU,CAAC,CAAC,GAAG,MAAM,CAAE,SAAQ,KAAK;IAChD;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,MAAM,EAC7B,KAAK,EAAE,WAAW,EAClB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7D,UAAU,CAAC,CAAC,CAAC;gBAKJ,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC;CAyJzE"}
1
+ {"version":3,"file":"index.cdk.d.ts","sourceRoot":"","sources":["../src/index.cdk.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,kBAAkB,EAAwC,MAAM,sBAAsB,CAAC;AAChG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,KAAK,EAAE,iBAAiB,EAA6B,MAAM,YAAY,CAAC;AAE/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAI9D;;;;;;;;;;GAUG;AACH,qBAAa,UAAU,CAAC,CAAC,GAAG,MAAM,CAAE,SAAQ,kBAAkB;IAC7D;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,MAAM,EAC7B,KAAK,EAAE,WAAW,EAClB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7D,UAAU,CAAC,CAAC,CAAC;gBAKJ,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC;CAyJzE"}
package/dist/index.cdk.js CHANGED
@@ -4,10 +4,14 @@ import * as cdk from 'aws-cdk-lib';
4
4
  import * as ssm from 'aws-cdk-lib/aws-ssm';
5
5
  import * as iam from 'aws-cdk-lib/aws-iam';
6
6
  import * as lambda from 'aws-cdk-lib/aws-lambda';
7
+ import { LogGroup } from 'aws-cdk-lib/aws-logs';
7
8
  import * as cr from 'aws-cdk-lib/custom-resources';
8
- import { Scope, registerConfig, DEFAULT_NODE_RUNTIME } from '@aws-blocks/core/cdk';
9
+ import * as ec2 from 'aws-cdk-lib/aws-ec2';
10
+ import { BuildingBlockScope, registerConfig, DEFAULT_NODE_RUNTIME } from '@aws-blocks/core/cdk';
9
11
  import { AppSettingErrors } from './errors.js';
10
12
  export { AppSettingErrors } from './errors.js';
13
+ export { SECRETS_BULK_CONSTRUCT_ID } from './secrets-bulk.js';
14
+ import { SECRETS_BULK_CONSTRUCT_ID } from './secrets-bulk.js';
11
15
  /**
12
16
  * CDK construct for AppSetting. Creates a single SSM parameter (String or
13
17
  * SecureString) and grants the shared Lambda handler read/write permissions.
@@ -19,7 +23,7 @@ export { AppSettingErrors } from './errors.js';
19
23
  * or with a customer-managed key when `kmsKeyArn` is provided (the handler is
20
24
  * then granted `kms:Decrypt`/`Encrypt` on that specific key ARN).
21
25
  */
22
- export class AppSetting extends Scope {
26
+ export class AppSetting extends BuildingBlockScope {
23
27
  /**
24
28
  * Reference an SSM parameter that is created and owned **outside this stack**
25
29
  * (e.g. a connection string seeded by `ensureSecrets` before deploy). The
@@ -38,7 +42,7 @@ export class AppSetting extends Scope {
38
42
  return new AppSetting(scope, id, opts);
39
43
  }
40
44
  constructor(scope, id, options) {
41
- super(id, { parent: scope });
45
+ super(id, { parent: scope, vpc: { interfaceEndpoints: [ec2.InterfaceVpcEndpointAwsService.SSM] } });
42
46
  // `external` is package-internal (set only by fromExisting), not on the
43
47
  // public AppSettingOptions — read it via the internal options type.
44
48
  const external = options.external ?? false;
@@ -112,7 +116,7 @@ export class AppSetting extends Scope {
112
116
  // then fail tagging it (AddTagsToResource needs ssm:GetParameters).
113
117
  // We only need runtime read access, granted below.
114
118
  if (!external) {
115
- registerSecret(cdk.Stack.of(this), parameterName, options.kmsKeyArn);
119
+ registerSecret(cdk.Stack.of(this), parameterName, this.defaults.logRetention, options.kmsKeyArn);
116
120
  }
117
121
  // Grant the handler KMS access. External secrets are read-only (Decrypt
118
122
  // only); stack-managed secrets also need Encrypt so the app can write the
@@ -171,7 +175,7 @@ const SECRET_BULK_KEY = Symbol.for('BLOCKS_SECRET_BULK_INIT');
171
175
  * All subsequent calls just append to the parameter list (resolved lazily at
172
176
  * synth time).
173
177
  */
174
- function registerSecret(stack, parameterName, keyId) {
178
+ function registerSecret(stack, parameterName, logRetention, keyId) {
175
179
  let state = stack[SECRET_BULK_KEY];
176
180
  if (state) {
177
181
  state.params.push({ name: parameterName, keyId });
@@ -183,6 +187,12 @@ function registerSecret(stack, parameterName, keyId) {
183
187
  const secretInitFn = new lambda.Function(stack, 'BlocksSecretInitFn', {
184
188
  runtime: DEFAULT_NODE_RUNTIME,
185
189
  handler: 'index.handler',
190
+ // Own the log group so its retention follows the stack-wide default
191
+ // instead of AWS's infinite retention.
192
+ logGroup: new LogGroup(stack, 'BlocksSecretInitLogs', {
193
+ retention: logRetention,
194
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
195
+ }),
186
196
  code: lambda.Code.fromInline(`
187
197
  const { SSMClient, GetParameterCommand, PutParameterCommand, DeleteParameterCommand, AddTagsToResourceCommand } = require('@aws-sdk/client-ssm');
188
198
  const crypto = require('crypto');
@@ -280,7 +290,7 @@ function registerSecret(stack, parameterName, keyId) {
280
290
  const provider = new cr.Provider(stack, 'BlocksSecretProvider', {
281
291
  onEventHandler: secretInitFn,
282
292
  });
283
- new cdk.CustomResource(stack, 'BlocksSecretsBulk', {
293
+ new cdk.CustomResource(stack, SECRETS_BULK_CONSTRUCT_ID, {
284
294
  serviceToken: provider.serviceToken,
285
295
  properties: {
286
296
  Parameters: cdk.Lazy.any({ produce: () => state.params }),
@@ -10,12 +10,13 @@ import { test } from 'node:test';
10
10
  import assert from 'node:assert';
11
11
  import * as cdk from 'aws-cdk-lib';
12
12
  import { Template, Match } from 'aws-cdk-lib/assertions';
13
- import { Scope, DEFAULT_NODE_RUNTIME } from '@aws-blocks/core/cdk';
13
+ import { Scope, DEFAULT_NODE_RUNTIME, BlocksPresets } from '@aws-blocks/core/cdk';
14
14
  import { AppSetting } from './index.cdk.js';
15
15
  class StubBlocksStack extends cdk.Stack {
16
16
  handler;
17
17
  executionRole;
18
18
  id;
19
+ defaults = BlocksPresets.production;
19
20
  constructor(scope, id) {
20
21
  super(scope, id);
21
22
  this.id = id;
@@ -31,12 +32,21 @@ class StubBlocksStack extends cdk.Stack {
31
32
  });
32
33
  }
33
34
  }
34
- function setup() {
35
+ function setup(defaults = BlocksPresets.production, stackId = 'TestStack') {
35
36
  const app = new cdk.App();
36
- const stack = new StubBlocksStack(app, 'TestStack');
37
+ const stack = new StubBlocksStack(app, stackId);
38
+ stack.defaults = defaults;
37
39
  const parent = new Scope('app');
38
40
  return { stack, parent };
39
41
  }
42
+ test('CDK: the secret-init Lambda log group adopts defaults.logRetention', () => {
43
+ const { stack, parent } = setup(BlocksPresets.sandbox, 'SecretRetentionStack');
44
+ new AppSetting(parent, 'my-secret', { secret: true, name: '/myapp/secret-key' });
45
+ const template = Template.fromStack(stack);
46
+ // The secret-init Lambda now owns an explicit log group whose retention
47
+ // follows the stack-wide default (sandbox → one week) instead of infinite.
48
+ template.hasResourceProperties('AWS::Logs::LogGroup', { RetentionInDays: 7 });
49
+ });
40
50
  test('CDK: secret AppSetting SSM policy is scoped to specific parameter ARN (not wildcard)', () => {
41
51
  const { stack, parent } = setup();
42
52
  new AppSetting(parent, 'my-secret', { secret: true, name: '/myapp/secret-key' });
@@ -4,6 +4,7 @@ import type { AppSettingOptions } from './types.js';
4
4
  import type { ChildLogger } from '@aws-blocks/bb-logger';
5
5
  export { AppSettingErrors } from './errors.js';
6
6
  export type { AppSettingOptions } from './types.js';
7
+ export { SECRETS_BULK_CONSTRUCT_ID } from './secrets-bulk.js';
7
8
  /**
8
9
  * A single application configuration value backed by SSM Parameter Store.
9
10
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.mock.d.ts","sourceRoot":"","sources":["../src/index.mock.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAA0B,MAAM,kBAAkB,CAAC;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAMpD,OAAO,KAAK,EAAE,iBAAiB,EAA6B,MAAM,YAAY,CAAC;AAE/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAsCpD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,UAAU,CAAC,CAAC,GAAG,MAAM,CAAE,SAAQ,KAAK;IAChD;;;;;;;;;OASG;IACH,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,MAAM,EAC7B,KAAK,EAAE,WAAW,EAClB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7D,UAAU,CAAC,CAAC,CAAC;IAKhB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,MAAM,CAAC,CAAsB;IACrC,OAAO,CAAC,QAAQ,CAAU;IAE1B,2FAA2F;IAC3F,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC;gBAEf,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAyBzE;;;;;;;;;;;OAWG;IACG,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC;IAWvB;;;;;;;;;;;;;;OAcG;IACG,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAelC"}
1
+ {"version":3,"file":"index.mock.d.ts","sourceRoot":"","sources":["../src/index.mock.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAA0B,MAAM,kBAAkB,CAAC;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAMpD,OAAO,KAAK,EAAE,iBAAiB,EAA6B,MAAM,YAAY,CAAC;AAE/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAsC9D;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,UAAU,CAAC,CAAC,GAAG,MAAM,CAAE,SAAQ,KAAK;IAChD;;;;;;;;;OASG;IACH,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,MAAM,EAC7B,KAAK,EAAE,WAAW,EAClB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7D,UAAU,CAAC,CAAC,CAAC;IAKhB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,MAAM,CAAC,CAAsB;IACrC,OAAO,CAAC,QAAQ,CAAU;IAE1B,2FAA2F;IAC3F,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC;gBAEf,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAyBzE;;;;;;;;;;;OAWG;IACG,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC;IAWvB;;;;;;;;;;;;;;OAcG;IACG,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAelC"}
@@ -10,6 +10,7 @@ import { Logger } from '@aws-blocks/bb-logger';
10
10
  import { BB_NAME, BB_VERSION } from './version.js';
11
11
  // Re-export public types
12
12
  export { AppSettingErrors } from './errors.js';
13
+ export { SECRETS_BULK_CONSTRUCT_ID } from './secrets-bulk.js';
13
14
  // ── Helpers ─────────────────────────────────────────────────────────────────
14
15
  const MAX_VALUE_BYTES = 4096; // SSM standard-tier 4 KB limit
15
16
  function readSettings(scope) {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Construct id of the shared, per-stack custom resource that writes every
3
+ * `secret: true` `AppSetting`'s SecureString value at deploy time (created
4
+ * lazily by the first secret AppSetting; a direct child of the stack).
5
+ *
6
+ * Exported so sibling blocks that must order a resource *after* the secret
7
+ * values are written — e.g. `bb-auth-oidc`'s IdP-registration custom resource —
8
+ * can locate it via `Stack.of(x).node.tryFindChild(SECRETS_BULK_CONSTRUCT_ID)`
9
+ * without hard-coding the string. This is the single source of truth for the id:
10
+ * the CDK layer creates the construct with it, so a rename here moves both the
11
+ * producer and every consumer together and the cross-package coupling can't
12
+ * drift silently.
13
+ */
14
+ export declare const SECRETS_BULK_CONSTRUCT_ID = "BlocksSecretsBulk";
15
+ //# sourceMappingURL=secrets-bulk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets-bulk.d.ts","sourceRoot":"","sources":["../src/secrets-bulk.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,sBAAsB,CAAC"}
@@ -0,0 +1,16 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Construct id of the shared, per-stack custom resource that writes every
5
+ * `secret: true` `AppSetting`'s SecureString value at deploy time (created
6
+ * lazily by the first secret AppSetting; a direct child of the stack).
7
+ *
8
+ * Exported so sibling blocks that must order a resource *after* the secret
9
+ * values are written — e.g. `bb-auth-oidc`'s IdP-registration custom resource —
10
+ * can locate it via `Stack.of(x).node.tryFindChild(SECRETS_BULK_CONSTRUCT_ID)`
11
+ * without hard-coding the string. This is the single source of truth for the id:
12
+ * the CDK layer creates the construct with it, so a rename here moves both the
13
+ * producer and every consumer together and the cross-package coupling can't
14
+ * drift silently.
15
+ */
16
+ export const SECRETS_BULK_CONSTRUCT_ID = 'BlocksSecretsBulk';
package/dist/version.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export declare const BB_NAME = "AppSetting";
2
- export declare const BB_VERSION = "0.2.0";
2
+ export declare const BB_VERSION = "0.3.0";
3
3
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by scripts/generate-version.mjs — do not edit manually
2
2
  export const BB_NAME = 'AppSetting';
3
- export const BB_VERSION = '0.2.0';
3
+ export const BB_VERSION = '0.3.0';
package/package.json CHANGED
@@ -1,6 +1,13 @@
1
1
  {
2
2
  "name": "@aws-blocks/bb-app-setting",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
+ "keywords": [
5
+ "aws-blocks",
6
+ "configuration",
7
+ "ssm",
8
+ "parameter-store",
9
+ "secrets"
10
+ ],
4
11
  "repository": {
5
12
  "type": "git",
6
13
  "url": "git+https://github.com/aws-devtools-labs/aws-blocks.git",
@@ -39,8 +46,8 @@
39
46
  "test": "node --test --test-concurrency=1 dist/**/*.test.js"
40
47
  },
41
48
  "dependencies": {
42
- "@aws-blocks/core": "^0.3.0",
43
- "@aws-blocks/bb-logger": "^0.1.5",
49
+ "@aws-blocks/core": "^0.5.0",
50
+ "@aws-blocks/bb-logger": "^0.2.0",
44
51
  "@aws-sdk/client-ssm": "^3.0.0",
45
52
  "@standard-schema/spec": "^1.1.0"
46
53
  },
package/src/index.aws.ts CHANGED
@@ -18,6 +18,7 @@ import type { ChildLogger } from '@aws-blocks/bb-logger';
18
18
  // Re-export public types from types module (canonical source)
19
19
  export { AppSettingErrors } from './errors.js';
20
20
  export type { AppSettingOptions } from './types.js';
21
+ export { SECRETS_BULK_CONSTRUCT_ID } from './secrets-bulk.js';
21
22
 
22
23
  // ── Helpers ─────────────────────────────────────────────────────────────────
23
24
 
@@ -2,6 +2,8 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  // Browser stub - AppSetting runs server-side only
5
+ export { SECRETS_BULK_CONSTRUCT_ID } from './secrets-bulk.js';
6
+
5
7
  export class AppSetting {
6
8
  static fromExisting(...args: any[]): any {
7
9
  return new AppSetting();
@@ -12,13 +12,14 @@ import assert from 'node:assert';
12
12
  import * as cdk from 'aws-cdk-lib';
13
13
  import type { Construct } from 'constructs';
14
14
  import { Template, Match } from 'aws-cdk-lib/assertions';
15
- import { Scope, DEFAULT_NODE_RUNTIME } from '@aws-blocks/core/cdk';
15
+ import { Scope, DEFAULT_NODE_RUNTIME, BlocksPresets, type BlocksDefaults } from '@aws-blocks/core/cdk';
16
16
  import { AppSetting } from './index.cdk.js';
17
17
 
18
18
  class StubBlocksStack extends cdk.Stack {
19
19
  public readonly handler: cdk.aws_lambda.Function;
20
20
  public readonly executionRole: cdk.aws_iam.IRole;
21
21
  public readonly id: string;
22
+ public defaults: BlocksDefaults = BlocksPresets.production;
22
23
  constructor(scope: Construct, id: string) {
23
24
  super(scope, id);
24
25
  this.id = id;
@@ -35,13 +36,23 @@ class StubBlocksStack extends cdk.Stack {
35
36
  }
36
37
  }
37
38
 
38
- function setup(): { stack: StubBlocksStack; parent: Scope } {
39
+ function setup(defaults: BlocksDefaults = BlocksPresets.production, stackId = 'TestStack'): { stack: StubBlocksStack; parent: Scope } {
39
40
  const app = new cdk.App();
40
- const stack = new StubBlocksStack(app, 'TestStack');
41
+ const stack = new StubBlocksStack(app, stackId);
42
+ stack.defaults = defaults;
41
43
  const parent = new Scope('app');
42
44
  return { stack, parent };
43
45
  }
44
46
 
47
+ test('CDK: the secret-init Lambda log group adopts defaults.logRetention', () => {
48
+ const { stack, parent } = setup(BlocksPresets.sandbox, 'SecretRetentionStack');
49
+ new AppSetting(parent, 'my-secret', { secret: true, name: '/myapp/secret-key' });
50
+ const template = Template.fromStack(stack);
51
+ // The secret-init Lambda now owns an explicit log group whose retention
52
+ // follows the stack-wide default (sandbox → one week) instead of infinite.
53
+ template.hasResourceProperties('AWS::Logs::LogGroup', { RetentionInDays: 7 });
54
+ });
55
+
45
56
  test('CDK: secret AppSetting SSM policy is scoped to specific parameter ARN (not wildcard)', () => {
46
57
  const { stack, parent } = setup();
47
58
  new AppSetting(parent, 'my-secret', { secret: true, name: '/myapp/secret-key' });
package/src/index.cdk.ts CHANGED
@@ -5,14 +5,19 @@ import * as cdk from 'aws-cdk-lib';
5
5
  import * as ssm from 'aws-cdk-lib/aws-ssm';
6
6
  import * as iam from 'aws-cdk-lib/aws-iam';
7
7
  import * as lambda from 'aws-cdk-lib/aws-lambda';
8
+ import { LogGroup, type RetentionDays } from 'aws-cdk-lib/aws-logs';
8
9
  import * as cr from 'aws-cdk-lib/custom-resources';
9
- import { Scope, registerConfig, DEFAULT_NODE_RUNTIME } from '@aws-blocks/core/cdk';
10
+ import * as ec2 from 'aws-cdk-lib/aws-ec2';
11
+ import { BuildingBlockScope, registerConfig, DEFAULT_NODE_RUNTIME } from '@aws-blocks/core/cdk';
10
12
  import type { ScopeParent } from '@aws-blocks/core';
11
13
  import { AppSettingErrors } from './errors.js';
12
14
  import type { AppSettingOptions, InternalAppSettingOptions } from './types.js';
13
15
 
14
16
  export { AppSettingErrors } from './errors.js';
15
17
  export type { AppSettingOptions } from './types.js';
18
+ export { SECRETS_BULK_CONSTRUCT_ID } from './secrets-bulk.js';
19
+
20
+ import { SECRETS_BULK_CONSTRUCT_ID } from './secrets-bulk.js';
16
21
 
17
22
  /**
18
23
  * CDK construct for AppSetting. Creates a single SSM parameter (String or
@@ -25,7 +30,7 @@ export type { AppSettingOptions } from './types.js';
25
30
  * or with a customer-managed key when `kmsKeyArn` is provided (the handler is
26
31
  * then granted `kms:Decrypt`/`Encrypt` on that specific key ARN).
27
32
  */
28
- export class AppSetting<T = string> extends Scope {
33
+ export class AppSetting<T = string> extends BuildingBlockScope {
29
34
  /**
30
35
  * Reference an SSM parameter that is created and owned **outside this stack**
31
36
  * (e.g. a connection string seeded by `ensureSecrets` before deploy). The
@@ -49,7 +54,7 @@ export class AppSetting<T = string> extends Scope {
49
54
  }
50
55
 
51
56
  constructor(scope: ScopeParent, id: string, options: AppSettingOptions<T>) {
52
- super(id, { parent: scope });
57
+ super(id, { parent: scope, vpc: { interfaceEndpoints: [ec2.InterfaceVpcEndpointAwsService.SSM] } });
53
58
 
54
59
  // `external` is package-internal (set only by fromExisting), not on the
55
60
  // public AppSettingOptions — read it via the internal options type.
@@ -151,7 +156,7 @@ export class AppSetting<T = string> extends Scope {
151
156
  // then fail tagging it (AddTagsToResource needs ssm:GetParameters).
152
157
  // We only need runtime read access, granted below.
153
158
  if (!external) {
154
- registerSecret(cdk.Stack.of(this), parameterName, options.kmsKeyArn);
159
+ registerSecret(cdk.Stack.of(this), parameterName, this.defaults.logRetention, options.kmsKeyArn);
155
160
  }
156
161
 
157
162
  // Grant the handler KMS access. External secrets are read-only (Decrypt
@@ -223,7 +228,7 @@ interface SecretBulkState {
223
228
  * All subsequent calls just append to the parameter list (resolved lazily at
224
229
  * synth time).
225
230
  */
226
- function registerSecret(stack: cdk.Stack, parameterName: string, keyId?: string): void {
231
+ function registerSecret(stack: cdk.Stack, parameterName: string, logRetention: RetentionDays, keyId?: string): void {
227
232
  let state = (stack as any)[SECRET_BULK_KEY] as SecretBulkState | undefined;
228
233
  if (state) {
229
234
  state.params.push({ name: parameterName, keyId });
@@ -237,6 +242,12 @@ function registerSecret(stack: cdk.Stack, parameterName: string, keyId?: string)
237
242
  const secretInitFn = new lambda.Function(stack, 'BlocksSecretInitFn', {
238
243
  runtime: DEFAULT_NODE_RUNTIME,
239
244
  handler: 'index.handler',
245
+ // Own the log group so its retention follows the stack-wide default
246
+ // instead of AWS's infinite retention.
247
+ logGroup: new LogGroup(stack, 'BlocksSecretInitLogs', {
248
+ retention: logRetention,
249
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
250
+ }),
240
251
  code: lambda.Code.fromInline(`
241
252
  const { SSMClient, GetParameterCommand, PutParameterCommand, DeleteParameterCommand, AddTagsToResourceCommand } = require('@aws-sdk/client-ssm');
242
253
  const crypto = require('crypto');
@@ -340,7 +351,7 @@ function registerSecret(stack: cdk.Stack, parameterName: string, keyId?: string)
340
351
  onEventHandler: secretInitFn,
341
352
  });
342
353
 
343
- new cdk.CustomResource(stack, 'BlocksSecretsBulk', {
354
+ new cdk.CustomResource(stack, SECRETS_BULK_CONSTRUCT_ID, {
344
355
  serviceToken: provider.serviceToken,
345
356
  properties: {
346
357
  Parameters: cdk.Lazy.any({ produce: () => state!.params }),
package/src/index.mock.ts CHANGED
@@ -17,6 +17,7 @@ import { BB_NAME, BB_VERSION } from './version.js';
17
17
  // Re-export public types
18
18
  export { AppSettingErrors } from './errors.js';
19
19
  export type { AppSettingOptions } from './types.js';
20
+ export { SECRETS_BULK_CONSTRUCT_ID } from './secrets-bulk.js';
20
21
 
21
22
  // ── Helpers ─────────────────────────────────────────────────────────────────
22
23
 
@@ -0,0 +1,17 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /**
5
+ * Construct id of the shared, per-stack custom resource that writes every
6
+ * `secret: true` `AppSetting`'s SecureString value at deploy time (created
7
+ * lazily by the first secret AppSetting; a direct child of the stack).
8
+ *
9
+ * Exported so sibling blocks that must order a resource *after* the secret
10
+ * values are written — e.g. `bb-auth-oidc`'s IdP-registration custom resource —
11
+ * can locate it via `Stack.of(x).node.tryFindChild(SECRETS_BULK_CONSTRUCT_ID)`
12
+ * without hard-coding the string. This is the single source of truth for the id:
13
+ * the CDK layer creates the construct with it, so a rename here moves both the
14
+ * producer and every consumer together and the cross-package coupling can't
15
+ * drift silently.
16
+ */
17
+ export const SECRETS_BULK_CONSTRUCT_ID = 'BlocksSecretsBulk';
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by scripts/generate-version.mjs — do not edit manually
2
2
  export const BB_NAME = 'AppSetting';
3
- export const BB_VERSION = '0.2.0';
3
+ export const BB_VERSION = '0.3.0';