From b5e453fc2a4dac4440fe32c675984bb276750828 Mon Sep 17 00:00:00 2001 From: will Farrell Date: Thu, 2 Jan 2025 19:50:47 -0700 Subject: [PATCH 1/5] feat: add in lambda extension for secrets --- .../parameters-secrets-extension/README.md | 46 ++ .../__benchmarks__/index.js | 38 ++ .../__tests__/fuzz.js | 25 + .../__tests__/index.js | 543 ++++++++++++++++++ .../parameters-secrets-extension/index.d.ts | 51 ++ .../parameters-secrets-extension/index.js | 78 +++ .../index.test-d.ts | 84 +++ .../parameters-secrets-extension/package.json | 77 +++ 8 files changed, 942 insertions(+) create mode 100644 packages/parameters-secrets-extension/README.md create mode 100644 packages/parameters-secrets-extension/__benchmarks__/index.js create mode 100644 packages/parameters-secrets-extension/__tests__/fuzz.js create mode 100644 packages/parameters-secrets-extension/__tests__/index.js create mode 100644 packages/parameters-secrets-extension/index.d.ts create mode 100644 packages/parameters-secrets-extension/index.js create mode 100644 packages/parameters-secrets-extension/index.test-d.ts create mode 100644 packages/parameters-secrets-extension/package.json diff --git a/packages/parameters-secrets-extension/README.md b/packages/parameters-secrets-extension/README.md new file mode 100644 index 000000000..3dd43fa84 --- /dev/null +++ b/packages/parameters-secrets-extension/README.md @@ -0,0 +1,46 @@ +
+

SSM Parameters or Secrets Manager secrets Lambda Extension middleware

+ Middy logo +

parameters-secrets-extension (AWS System Manager Parameter) middleware for the middy framework, the stylish Node.js middleware engine for AWS Lambda

+

+ + npm version + + + npm install size + + + GitHub Actions CI status badge + +
+ + Standard Code Style + + + Known Vulnerabilities + + + CodeQL + + + Core Infrastructure Initiative (CII) Best Practices + +
+ + Chat on Gitter + + + Ask questions on StackOverflow + +

+

You can read the documentation at: https://middy.js.org/docs/middlewares/parameters-secrets-extension

+
+ +## License + +Licensed under [MIT License](LICENSE). Copyright (c) 2017-2025 [Luciano Mammino](https://github.com/lmammino), [will Farrell](https://github.com/willfarrell), and the [Middy team](https://github.com/middyjs/middy/graphs/contributors). + + + FOSSA Status + diff --git a/packages/parameters-secrets-extension/__benchmarks__/index.js b/packages/parameters-secrets-extension/__benchmarks__/index.js new file mode 100644 index 000000000..bc8f6d7d0 --- /dev/null +++ b/packages/parameters-secrets-extension/__benchmarks__/index.js @@ -0,0 +1,38 @@ +import { Bench } from 'tinybench' +import middy from '../../core/index.js' +import middleware from '../index.js' + +const bench = new Bench({ time: 1_000 }) + +const context = { + getRemainingTimeInMillis: () => 30000 +} +const setupHandler = (options = {}) => { + // TODO fetch mock + const baseHandler = () => {} + return middy(baseHandler).use( + middleware({ + ...options + }) + ) +} + +const coldHandler = setupHandler({ cacheExpiry: 0 }) +const warmHandler = setupHandler() + +const event = {} +await bench + .add('without cache', async () => { + try { + await coldHandler(event, context) + } catch (e) {} + }) + .add('with cache', async () => { + try { + await warmHandler(event, context) + } catch (e) {} + }) + + .run() + +console.table(bench.table()) diff --git a/packages/parameters-secrets-extension/__tests__/fuzz.js b/packages/parameters-secrets-extension/__tests__/fuzz.js new file mode 100644 index 000000000..d9ba2dd0e --- /dev/null +++ b/packages/parameters-secrets-extension/__tests__/fuzz.js @@ -0,0 +1,25 @@ +import { test } from 'node:test' +import fc from 'fast-check' +import middy from '../../core/index.js' +import middleware from '../index.js' + +const handler = middy((event) => event).use( + middleware({ type: 'systemsmanager' }) +) +const context = { + getRemainingTimeInMillis: () => 1000 +} + +test('fuzz `event` w/ `object`', async () => { + fc.assert( + fc.asyncProperty(fc.object(), async (event) => { + await handler(event, context) + }), + { + numRuns: 100_000, + verbose: 2, + + examples: [] + } + ) +}) diff --git a/packages/parameters-secrets-extension/__tests__/index.js b/packages/parameters-secrets-extension/__tests__/index.js new file mode 100644 index 000000000..137cf43ed --- /dev/null +++ b/packages/parameters-secrets-extension/__tests__/index.js @@ -0,0 +1,543 @@ +import { test } from 'node:test' +import { equal, deepEqual } from 'node:assert/strict' +import { setTimeout } from 'node:timers/promises' +import middy from '../../core/index.js' +import { getInternal, clearCache } from '../../util/index.js' +import parametersSecretsLambdaExtension from '../index.js' + +const mockFetchCache = {} +const mockFetch = (url, response) => { + mockFetchCache[url] = JSON.stringify(response) +} +global.fetch = (url) => { + // console.log('fetch(', url, ')', mockFetchCache[url]) + fetchCount += 1 + return Promise.resolve( + new Response(mockFetchCache[url], { + status: 200, + statusText: 'OK', + headers: new Headers({ + 'Content-Type': 'application/json; charset=UTF-8' + }) + }) + ) +} + +mockFetch( + 'http://localhost:2773/systemsmanager/parameters/get/?name=/dev/service_name/key_name', + { + Parameter: { + Value: 'key-value' + } + } +) +// mockFetch( +// 'http://localhost:2773/systemsmanager/parameters/get/?name=/dev/service_name/invalid-ssm-param-name', +// { +// InvalidParameter: { +// Name: 'invalid-ssm-param-name' +// } +// } +// ) + +mockFetch('http://localhost:2773/secretsmanager/get?secretId=api_key', { + SecretString: 'token' +}) +mockFetch('http://localhost:2773/secretsmanager/get?secretId=api_key1', { + SecretString: 'token1' +}) +mockFetch('http://localhost:2773/secretsmanager/get?secretId=api_key2', { + SecretString: 'token2' +}) +mockFetch('http://localhost:2773/secretsmanager/get?secretId=rds_login', { + SecretString: JSON.stringify({ username: 'admin', password: 'secret' }) +}) + +test.beforeEach((t) => { + fetchCount = 0 + event = {} + context = { + getRemainingTimeInMillis: () => 1000 + } +}) + +test.afterEach((t) => { + t.mock.reset() + clearCache() +}) + +let fetchCount = 0 +let event = {} +let context = {} + +// systemsmanger +test('It should set SSM param value to internal storage', async (t) => { + const middleware = async (request) => { + const values = await getInternal(true, request) + equal(values.key, 'key-value') + } + + const handler = middy(() => {}) + .use( + parametersSecretsLambdaExtension({ + type: 'systemsmanager', + cacheExpiry: 0, + fetchData: { + key: '/dev/service_name/key_name' + }, + disablePrefetch: true + }) + ) + .before(middleware) + + await handler(event, context) +}) + +test('It should set SSM param value to internal storage without prefetch', async (t) => { + const middleware = async (request) => { + const values = await getInternal(true, request) + equal(values.key, 'key-value') + } + + const handler = middy(() => {}) + .use( + parametersSecretsLambdaExtension({ + type: 'systemsmanager', + cacheExpiry: 0, + fetchData: { + key: '/dev/service_name/key_name' + }, + disablePrefetch: true + }) + ) + .before(middleware) + + await handler(event, context) +}) + +test('It should set SSM param value to context', async (t) => { + const middleware = async (request) => { + equal(request.context.key, 'key-value') + } + + const handler = middy(() => {}) + .use( + parametersSecretsLambdaExtension({ + type: 'systemsmanager', + cacheExpiry: 0, + fetchData: { + key: '/dev/service_name/key_name' + }, + setToContext: true, + disablePrefetch: true + }) + ) + .before(middleware) + + await handler(event, context) +}) + +test('It should not call localhost again if parameter is cached forever', async (t) => { + const middleware = async (request) => { + const values = await getInternal(true, request) + equal(values.key, 'key-value') + } + + const handler = middy(() => {}) + .use( + parametersSecretsLambdaExtension({ + type: 'systemsmanager', + cacheExpiry: -1, + fetchData: { + key: '/dev/service_name/key_name' + } + }) + ) + .before(middleware) + + await handler(event, context) + await handler(event, context) + + equal(fetchCount, 1) +}) + +test('It should not call aws-sdk again if parameter is cached', async (t) => { + const middleware = async (request) => { + const values = await getInternal(true, request) + equal(values.key, 'key-value') + } + + const handler = middy(() => {}) + .use( + parametersSecretsLambdaExtension({ + type: 'systemsmanager', + cacheExpiry: 1000, + fetchData: { + key: '/dev/service_name/key_name' + } + }) + ) + .before(middleware) + + await handler(event, context) + await handler(event, context) + + equal(fetchCount, 1) +}) + +test('It should call aws-sdk everytime if cache disabled', async (t) => { + const middleware = async (request) => { + const values = await getInternal(true, request) + equal(values.key, 'key-value') + } + + const handler = middy(() => {}) + .use( + parametersSecretsLambdaExtension({ + type: 'systemsmanager', + cacheExpiry: 0, + fetchData: { + key: '/dev/service_name/key_name' + }, + disablePrefetch: true + }) + ) + .before(middleware) + + await handler(event, context) + await handler(event, context) + + equal(fetchCount, 2) +}) + +test('It should call aws-sdk if cache enabled but cached param has expired', async (t) => { + const middleware = async (request) => { + const values = await getInternal(true, request) + equal(values.key, 'key-value') + } + + const handler = middy(() => {}) + .use( + parametersSecretsLambdaExtension({ + type: 'systemsmanager', + cacheExpiry: 4, + fetchData: { + key: '/dev/service_name/key_name' + }, + disablePrefetch: true + }) + ) + .before(middleware) + await handler(event, context) + await setTimeout(5) + await handler(event, context) + equal(fetchCount, 2) +}) + +/* test('It should it should recover from an error if cache enabled but cached param has expired', async (t) => { + const awsError = new Error( + 'InvalidSignatureException: Signature expired: 20231103T171116Z is now earlier than 20231103T171224Z (20231103T171724Z - 5 min.)' + ) + awsError.__type = 'InvalidSignatureException' + const mockService = mockClient(SSMClient) + .on(GetParametersCommand, { + Names: ['/dev/service_name/key_name'], + WithDecryption: true + }) + .resolvesOnce({ + Parameters: [{ Name: '/dev/service_name/key_name', Value: 'key-value' }] + }) + .rejectsOnce(awsError) + .resolves({ + Parameters: [{ Name: '/dev/service_name/key_name', Value: 'key-value' }] + }) + const sendStub = mockService.send + + const middleware = async (request) => { + const values = await getInternal(true, request) + equal(values.key, 'key-value') + } + + const handler = middy(() => {}) + .use( + parametersSecretsLambdaExtension({ + type: 'systemsmanager', + cacheExpiry: 4, + fetchData: { + key: '/dev/service_name/key_name' + }, + disablePrefetch: true + }) + ) + .before(middleware) + + await handler(event, context) + await setTimeout(5) + await handler(event, context) + await setTimeout(5) + await handler(event, context) + + equal(fetchCount, 4) +}) */ + +/* test('It should throw error if InvalidParameters returned', async (t) => { + const handler = middy(() => {}).use( + parametersSecretsLambdaExtension({ + type: 'systemsmanager', + cacheExpiry: 0, + fetchData: { + a: 'invalid-ssm-param-name', + key: '/dev/service_name/key_name' + }, + disablePrefetch: true, + setToContext: true + }) + ) + + try { + await handler(event, context) + ok(false) + } catch (e) { + equal(e.message, 'Failed to resolve internal values') + deepEqual(e.cause.data, [ + new Error('InvalidParameter invalid-ssm-param-name', { + cause: { package: '@middy/parameters-secrets-extension' } + }) + ]) + } +}) */ + +/* test('It should catch if an error is returned from fetchRequest', async (t) => { + const mockService = mockClient(SSMClient) + .on(GetParametersCommand) + .rejects('timeout') + const sendStub = mockService.send + + const handler = middy(() => {}).use( + parametersSecretsLambdaExtension({ + type: 'systemsmanager', + cacheExpiry: 0, + fetchData: { + key: '/dev/service_name/key_name' + }, + setToContext: true, + disablePrefetch: true + }) + ) + + try { + await handler(event, context) + } catch (e) { + equal(fetchCount, 1) + equal(e.message, 'Failed to resolve internal values') + deepEqual(e.cause.data, [new Error('timeout')]) + } +}) */ + +// secretsmanager + +test('It should set secret to internal storage (token)', async (t) => { + const handler = middy(() => {}) + + const middleware = async (request) => { + const values = await getInternal(true, request) + equal(values.token, 'token') + } + + handler + .use( + parametersSecretsLambdaExtension({ + type: 'secretsmanager', + cacheExpiry: 0, + fetchData: { + token: 'api_key' + }, + disablePrefetch: true + }) + ) + .before(middleware) + + await handler(event, context) +}) + +test('It should set secrets to internal storage (token)', async (t) => { + const handler = middy(() => {}) + + const middleware = async (request) => { + const values = await getInternal(true, request) + equal(values.token1, 'token1') + equal(values.token2, 'token2') + } + + handler + .use( + parametersSecretsLambdaExtension({ + type: 'secretsmanager', + cacheExpiry: 0, + fetchData: { + token1: 'api_key1', + token2: 'api_key2' + }, + disablePrefetch: true + }) + ) + .before(middleware) + + await handler(event, context) +}) + +test('It should set secrets to internal storage (json)', async (t) => { + const handler = middy(() => {}) + + const middleware = async (request) => { + const values = await getInternal( + { username: 'credentials.username', password: 'credentials.password' }, + request + ) + deepEqual(values, { username: 'admin', password: 'secret' }) + } + + handler + .use( + parametersSecretsLambdaExtension({ + type: 'secretsmanager', + cacheExpiry: 0, + fetchData: { + credentials: 'rds_login' + }, + disablePrefetch: true + }) + ) + .before(middleware) + + await handler(event, context) +}) + +test('It should set SecretsManager secret to internal storage without prefetch', async (t) => { + const handler = middy(() => {}) + + const middleware = async (request) => { + const values = await getInternal(true, request) + equal(values.token, 'token') + } + + handler + .use( + parametersSecretsLambdaExtension({ + type: 'secretsmanager', + cacheExpiry: 0, + fetchData: { + token: 'api_key' + }, + disablePrefetch: true + }) + ) + .before(middleware) + + await handler(event, context) +}) + +test('It should set SecretsManager secret to context', async (t) => { + const handler = middy(() => {}) + + const middleware = async (request) => { + equal(request.context.token, 'token') + } + + handler + .use( + parametersSecretsLambdaExtension({ + type: 'secretsmanager', + cacheExpiry: 0, + fetchData: { + token: 'api_key' + }, + setToContext: true, + disablePrefetch: true + }) + ) + .before(middleware) + + await handler(event, context) +}) + +test('It should not call aws-sdk again if parameter is cached', async (t) => { + const handler = middy(() => {}) + + const middleware = async (request) => { + const values = await getInternal(true, request) + equal(values.token, 'token') + } + + handler + .use( + parametersSecretsLambdaExtension({ + type: 'secretsmanager', + cacheExpiry: -1, + fetchData: { + token: 'api_key' + } + }) + ) + .before(middleware) + + await handler(event, context) + await handler(event, context) + + equal(fetchCount, 1) +}) + +test('It should call aws-sdk if cache enabled but cached param has expired', async (t) => { + const handler = middy(() => {}) + + const middleware = async (request) => { + const values = await getInternal(true, request) + equal(values.token, 'token') + } + + handler + .use( + parametersSecretsLambdaExtension({ + type: 'secretsmanager', + cacheExpiry: 0, + fetchData: { + token: 'api_key' + }, + disablePrefetch: true + }) + ) + .before(middleware) + + await handler(event, context) + await handler(event, context) + + equal(fetchCount, 2) +}) + +/* test('It should catch if an error is returned from fetch', async (t) => { + const mockService = mockClient(SecretsManagerClient) + .on(GetSecretValueCommand, { SecretId: 'api_key' }) + .rejects('timeout') + const sendStub = mockService.send + + const handler = middy(() => {}).use( + parametersSecretsLambdaExtension({ + type: 'secretsmanager', + cacheExpiry: 0, + fetchData: { + token: 'api_key' + }, + setToContext: true, + disablePrefetch: true + }) + ) + + try { + await handler(event, context) + } catch (e) { + equal(fetchCount, 1) + equal(e.message, 'Failed to resolve internal values') + deepEqual(e.cause.data, [new Error('timeout')]) + } +}) +*/ diff --git a/packages/parameters-secrets-extension/index.d.ts b/packages/parameters-secrets-extension/index.d.ts new file mode 100644 index 000000000..f06b9cf33 --- /dev/null +++ b/packages/parameters-secrets-extension/index.d.ts @@ -0,0 +1,51 @@ +import middy from '@middy/core' +import { Options as MiddyOptions } from '@middy/util' +import { Context as LambdaContext } from 'aws-lambda' + +export type ParamType = string & { __returnType?: T } +export declare function parametersSecretsLambdaExtensionParam( + path: string +): ParamType + +export interface parametersSecretsLambdaExtensionOptions { + type: string // systemsmanager, secretsmanager + fetchData?: { [key: string]: string | ParamType } +} + +export type Context< + TOptions extends parametersSecretsLambdaExtensionOptions | undefined +> = TOptions extends { setToContext: true } + ? TOptions extends { fetchData: infer TFetchData } + ? LambdaContext & { + [Key in keyof TFetchData]: TFetchData[Key] extends ParamType + ? T + : unknown + } + : never + : LambdaContext + +export type Internal< + TOptions extends parametersSecretsLambdaExtensionOptions | undefined +> = TOptions extends parametersSecretsLambdaExtensionOptions + ? TOptions extends { fetchData: infer TFetchData } + ? { + [Key in keyof TFetchData]: TFetchData[Key] extends ParamType + ? T + : unknown + } + : {} + : {} + +declare function parametersSecretsLambdaExtension< + TOptions extends parametersSecretsLambdaExtensionOptions +>( + options?: TOptions +): middy.MiddlewareObj< + unknown, + any, + Error, + Context, + Internal +> + +export default parametersSecretsLambdaExtension diff --git a/packages/parameters-secrets-extension/index.js b/packages/parameters-secrets-extension/index.js new file mode 100644 index 000000000..ac789a12a --- /dev/null +++ b/packages/parameters-secrets-extension/index.js @@ -0,0 +1,78 @@ +import { + canPrefetch, + processCache, + getCache, + modifyCache, + getInternal, + jsonSafeParse +} from '../util/index.js' + +const defaults = { + type: undefined, // systemsmanager, secretsmanager + fetchData: {}, + disablePrefetch: false, + cacheKey: 'lambda-extension', + cacheKeyExpiry: {}, + cacheExpiry: -1, + setToContext: false +} + +const types = { + systemsmanager: { + path: '/systemsmanager/parameters/get/?name=', + response: (res) => jsonSafeParse(res.Parameter?.Value) + }, + secretsmanager: { + path: '/secretsmanager/get?secretId=', + response: (res) => jsonSafeParse(res.SecretString) + } +} + +const parametersSecretsLambdaExtensionMiddleware = (opts = {}) => { + const options = { ...defaults, ...opts } + const port = process.env.PARAMETERS_SECRETS_EXTENSION_HTTP_PORT ?? 2773 + const url = 'http://localhost:' + port + types[options.type].path + const headers = { + 'X-Aws-Parameters-Secrets-Token': process.env.AWS_SESSION_TOKEN + } + const fetchRequest = (request, cachedValues = {}) => { + const values = {} + + for (const internalKey of Object.keys(options.fetchData)) { + if (cachedValues[internalKey]) continue + + values[internalKey] = fetch(url + options.fetchData[internalKey], { + headers + }) + .then((res) => res.json()) + .then((res) => types[options.type].response(res)) + .catch((e) => { + const value = getCache(options.cacheKey).value ?? {} + value[internalKey] = undefined + modifyCache(options.cacheKey, value) + throw e + }) + } + return values + } + + if (canPrefetch(options)) { + processCache(options, fetchRequest) + } + + const parametersSecretsLambdaExtensionMiddlewareBefore = async (request) => { + const { value } = processCache(options, fetchRequest, request) + + Object.assign(request.internal, value) + + if (options.setToContext) { + const data = await getInternal(Object.keys(options.fetchData), request) + Object.assign(request.context, data) + } + } + + return { + before: parametersSecretsLambdaExtensionMiddlewareBefore + } +} +export default parametersSecretsLambdaExtensionMiddleware diff --git a/packages/parameters-secrets-extension/index.test-d.ts b/packages/parameters-secrets-extension/index.test-d.ts new file mode 100644 index 000000000..021833ab7 --- /dev/null +++ b/packages/parameters-secrets-extension/index.test-d.ts @@ -0,0 +1,84 @@ +import middy from '@middy/core' +import { getInternal } from '@middy/util' +import { expectType, expectAssignable } from 'tsd' +import parametersSecretsLambdaExtension, { + Context, + parametersSecretsLambdaExtensionParam +} from '.' +import { Context as LambdaContext } from 'aws-lambda/handler' + +// use with default options +expectType>>( + parametersSecretsLambdaExtension() +) + +// use with all options +const options = { + type: 'secretsmanager', + disablePrefetch: true +} +expectType>>( + parametersSecretsLambdaExtension(options) +) + +expectType< + middy.MiddlewareObj< + unknown, + any, + Error, + LambdaContext, + Record<'lorem' | 'ipsum', unknown> + > +>( + parametersSecretsLambdaExtension({ + type: 'systemsmanager', + fetchData: { + lorem: '/lorem', + ipsum: '/lorem' + } + }) +) + +const handler = middy(async (event: {}, context: LambdaContext) => { + return await Promise.resolve({}) +}) + +// chain of multiple ssm middleware +handler + .use( + parametersSecretsLambdaExtension({ + type: 'systemsmanager', + fetchData: { + defaults: parametersSecretsLambdaExtensionParam('/dev/defaults') + }, + cacheKey: 'ssm-defaults' + }) + ) + .use( + parametersSecretsLambdaExtension({ + type: 'secretsmanager', + fetchData: { + accessToken: parametersSecretsLambdaExtensionParam( + '/dev/service_name/access_token' + ), // single value + dbParams: parametersSecretsLambdaExtensionParam<{ + user: string + pass: string + }>('/dev/service_name/database/') // object of values, key for each path + }, + cacheExpiry: 15 * 60 * 1000, + cacheKey: 'rds-secrets', + setToContext: true + }) + ) + // ... other middleware that fetch + .before(async (request) => { + const data = await getInternal( + ['accessToken', 'dbParams', 'defaults'], + request + ) + + expectType(data.accessToken) + expectType<{ user: string; pass: string }>(data.dbParams) + expectType(data.defaults) + }) diff --git a/packages/parameters-secrets-extension/package.json b/packages/parameters-secrets-extension/package.json new file mode 100644 index 000000000..c222021f1 --- /dev/null +++ b/packages/parameters-secrets-extension/package.json @@ -0,0 +1,77 @@ +{ + "name": "@middy/parameters-secrets-extension", + "version": "6.0.0", + "description": "SSM Parameters or Secrets Manager secrets Lambda Extension middleware for the middy framework", + "type": "module", + "engines": { + "node": ">=20" + }, + "engineStrict": true, + "publishConfig": { + "access": "public" + }, + "module": "./index.js", + "exports": { + ".": { + "import": { + "types": "./index.d.ts", + "default": "./index.js" + }, + "require": { + "default": "./index.js" + } + } + }, + "types": "index.d.ts", + "files": [ + "index.js", + "index.d.ts" + ], + "scripts": { + "test": "npm run test:unit && npm run test:fuzz", + "test:unit": "node --test __tests__/index.js", + "test:fuzz": "node --test __tests__/fuzz.js", + "test:benchmark": "node __benchmarks__/index.js" + }, + "license": "MIT", + "keywords": [ + "Lambda", + "Middleware", + "Serverless", + "Framework", + "AWS", + "AWS Lambda", + "Middy", + "SSM", + "EC2 Systems Manager", + "Parameters", + "Secrets Manager", + "Secrets", + "Lambda Extension" + ], + "author": { + "name": "Middy contributors", + "url": "https://github.com/middyjs/middy/graphs/contributors" + }, + "repository": { + "type": "git", + "url": "github:middyjs/middy", + "directory": "packages/parameters-secrets-extension" + }, + "bugs": { + "url": "https://github.com/middyjs/middy/issues" + }, + "homepage": "https://middy.js.org", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/willfarrell" + }, + "dependencies": { + "@middy/util": "6.0.0" + }, + "devDependencies": { + "@middy/core": "6.0.0", + "@types/aws-lambda": "^8.10.101" + }, + "gitHead": "7a6c0fbb8ab71d6a2171e678697de9f237568431" +} From a803cdbd7b9d588195c39525c4832b9ccd6efcea Mon Sep 17 00:00:00 2001 From: will Farrell Date: Thu, 2 Jan 2025 19:57:44 -0700 Subject: [PATCH 2/5] fix: remove unused --- packages/parameters-secrets-extension/index.test-d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/parameters-secrets-extension/index.test-d.ts b/packages/parameters-secrets-extension/index.test-d.ts index 021833ab7..78c15a5e8 100644 --- a/packages/parameters-secrets-extension/index.test-d.ts +++ b/packages/parameters-secrets-extension/index.test-d.ts @@ -1,6 +1,6 @@ import middy from '@middy/core' import { getInternal } from '@middy/util' -import { expectType, expectAssignable } from 'tsd' +import { expectType } from 'tsd' import parametersSecretsLambdaExtension, { Context, parametersSecretsLambdaExtensionParam From b6294745176edc6db4a966bcd53a9390f242a667 Mon Sep 17 00:00:00 2001 From: will Farrell Date: Thu, 2 Jan 2025 19:58:47 -0700 Subject: [PATCH 3/5] ci: lint --- .../parameters-secrets-extension/index.d.ts | 23 +++++++++---------- .../index.test-d.ts | 16 ++++++------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/parameters-secrets-extension/index.d.ts b/packages/parameters-secrets-extension/index.d.ts index f06b9cf33..50e17baf8 100644 --- a/packages/parameters-secrets-extension/index.d.ts +++ b/packages/parameters-secrets-extension/index.d.ts @@ -1,9 +1,8 @@ import middy from '@middy/core' -import { Options as MiddyOptions } from '@middy/util' import { Context as LambdaContext } from 'aws-lambda' export type ParamType = string & { __returnType?: T } -export declare function parametersSecretsLambdaExtensionParam( +export declare function parametersSecretsLambdaExtensionParam ( path: string ): ParamType @@ -17,10 +16,10 @@ export type Context< > = TOptions extends { setToContext: true } ? TOptions extends { fetchData: infer TFetchData } ? LambdaContext & { - [Key in keyof TFetchData]: TFetchData[Key] extends ParamType - ? T - : unknown - } + [Key in keyof TFetchData]: TFetchData[Key] extends ParamType + ? T + : unknown + } : never : LambdaContext @@ -38,14 +37,14 @@ export type Internal< declare function parametersSecretsLambdaExtension< TOptions extends parametersSecretsLambdaExtensionOptions ->( +> ( options?: TOptions ): middy.MiddlewareObj< - unknown, - any, - Error, - Context, - Internal +unknown, +any, +Error, +Context, +Internal > export default parametersSecretsLambdaExtension diff --git a/packages/parameters-secrets-extension/index.test-d.ts b/packages/parameters-secrets-extension/index.test-d.ts index 78c15a5e8..6c9b111d0 100644 --- a/packages/parameters-secrets-extension/index.test-d.ts +++ b/packages/parameters-secrets-extension/index.test-d.ts @@ -22,13 +22,13 @@ expectType>>( ) expectType< - middy.MiddlewareObj< - unknown, - any, - Error, - LambdaContext, - Record<'lorem' | 'ipsum', unknown> - > +middy.MiddlewareObj< +unknown, +any, +Error, +LambdaContext, +Record<'lorem' | 'ipsum', unknown> +> >( parametersSecretsLambdaExtension({ type: 'systemsmanager', @@ -79,6 +79,6 @@ handler ) expectType(data.accessToken) - expectType<{ user: string; pass: string }>(data.dbParams) + expectType<{ user: string, pass: string }>(data.dbParams) expectType(data.defaults) }) From 51ef02cfcbfecbb7c3874945b62bc9097171a030 Mon Sep 17 00:00:00 2001 From: will Farrell Date: Thu, 2 Jan 2025 20:07:25 -0700 Subject: [PATCH 4/5] fix: add in decryption and encoding --- packages/parameters-secrets-extension/index.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/parameters-secrets-extension/index.js b/packages/parameters-secrets-extension/index.js index ac789a12a..27430d970 100644 --- a/packages/parameters-secrets-extension/index.js +++ b/packages/parameters-secrets-extension/index.js @@ -23,7 +23,7 @@ const types = { response: (res) => jsonSafeParse(res.Parameter?.Value) }, secretsmanager: { - path: '/secretsmanager/get?secretId=', + path: '/secretsmanager/get?withDecryption=true&secretId=', response: (res) => jsonSafeParse(res.SecretString) } } @@ -41,9 +41,12 @@ const parametersSecretsLambdaExtensionMiddleware = (opts = {}) => { for (const internalKey of Object.keys(options.fetchData)) { if (cachedValues[internalKey]) continue - values[internalKey] = fetch(url + options.fetchData[internalKey], { - headers - }) + values[internalKey] = fetch( + url + encodeURI(options.fetchData[internalKey]), + { + headers + } + ) .then((res) => res.json()) .then((res) => types[options.type].response(res)) .catch((e) => { From f8c1f66cf5030ebde4ba530ff830e284da868764 Mon Sep 17 00:00:00 2001 From: will Farrell Date: Thu, 2 Jan 2025 20:15:39 -0700 Subject: [PATCH 5/5] fix: qs --- packages/parameters-secrets-extension/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/parameters-secrets-extension/index.js b/packages/parameters-secrets-extension/index.js index 27430d970..86e29d101 100644 --- a/packages/parameters-secrets-extension/index.js +++ b/packages/parameters-secrets-extension/index.js @@ -19,11 +19,11 @@ const defaults = { const types = { systemsmanager: { - path: '/systemsmanager/parameters/get/?name=', + path: '/systemsmanager/parameters/get/?withDecryption=true&name=', response: (res) => jsonSafeParse(res.Parameter?.Value) }, secretsmanager: { - path: '/secretsmanager/get?withDecryption=true&secretId=', + path: '/secretsmanager/get?secretId=', response: (res) => jsonSafeParse(res.SecretString) } }