diff --git a/README.md b/README.md index 9d4a41a53..ac54348c9 100644 --- a/README.md +++ b/README.md @@ -498,6 +498,7 @@ Currently available middlewares: - [`http-security-headers`](/packages/http-security-headers): Applies best practice security headers to responses. It's a simplified port of HelmetJS. - [`http-urlencode-body-parser`](/packages/http-urlencode-body-parser): Automatically parses HTTP requests with URL encoded body (typically the result of a form submit). - [`s3-key-normalizer`](/packages/s3-key-normalizer): Normalizes key names in s3 events. + - [`secrets-manager`](/packages/secrets-manager): Fetches parameters from [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html). - [`ssm`](/packages/ssm): Fetches parameters from [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-paramstore.html). - [`validator`](/packages/validator): Automatically validates incoming events and outgoing responses against custom schemas - [`warmup`](/packages/warmup): Warmup middleware that helps to reduce the [cold-start issue](https://serverless.com/blog/keep-your-lambdas-warm/) diff --git a/lerna.json b/lerna.json index 5eb804c36..51515fdce 100644 --- a/lerna.json +++ b/lerna.json @@ -3,5 +3,5 @@ "packages": [ "packages/*" ], - "version": "1.0.0-alpha.16" + "version": "1.0.0-alpha.17" } diff --git a/package-lock.json b/package-lock.json index e2d6c4a58..8779efc5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "middy-monorepo", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 94a93ba0e..375068e96 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "middy-monorepo", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "🛵 The stylish Node.js middleware engine for AWS Lambda", "engines": { "node": ">=6.10" diff --git a/packages/cache/package.json b/packages/cache/package.json index 2b9e0d17c..b1712034c 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -1,6 +1,6 @@ { "name": "@middy/cache", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "Cache middleware for the middy framework", "engines": { "node": ">=6.10" diff --git a/packages/core/package.json b/packages/core/package.json index 0cf7cbaf8..4424ebaa0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@middy/core", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "🛵 The stylish Node.js middleware engine for AWS Lambda (core package)", "engines": { "node": ">=6.10" diff --git a/packages/do-not-wait-for-empty-event-loop/package.json b/packages/do-not-wait-for-empty-event-loop/package.json index 64bbc0f29..d5ae3d413 100644 --- a/packages/do-not-wait-for-empty-event-loop/package.json +++ b/packages/do-not-wait-for-empty-event-loop/package.json @@ -1,6 +1,6 @@ { "name": "@middy/do-not-wait-for-empty-event-loop", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "Middleware for the middy framework that allows to easily disable the wait for empty event loop in a Lambda function", "engines": { "node": ">=6.10" diff --git a/packages/error-logger/package.json b/packages/error-logger/package.json index 5234c0bfc..961791d8b 100644 --- a/packages/error-logger/package.json +++ b/packages/error-logger/package.json @@ -1,6 +1,6 @@ { "name": "@middy/error-logger", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "Input and output logger middleware for the middy framework", "engines": { "node": ">=6.10" diff --git a/packages/http-content-negotiation/package.json b/packages/http-content-negotiation/package.json index 75306453b..edc2177f2 100644 --- a/packages/http-content-negotiation/package.json +++ b/packages/http-content-negotiation/package.json @@ -1,6 +1,6 @@ { "name": "@middy/http-content-negotiation", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "Http content negotiation middleware for the middy framework", "engines": { "node": ">=6.10" diff --git a/packages/http-cors/package.json b/packages/http-cors/package.json index 226fad143..9e16be1aa 100644 --- a/packages/http-cors/package.json +++ b/packages/http-cors/package.json @@ -1,6 +1,6 @@ { "name": "@middy/http-cors", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "CORS (Cross-Origin Resource Sharing) middleware for the middy framework", "engines": { "node": ">=6.10" diff --git a/packages/http-error-handler/package.json b/packages/http-error-handler/package.json index 30097e52a..4fc0d31d2 100644 --- a/packages/http-error-handler/package.json +++ b/packages/http-error-handler/package.json @@ -1,6 +1,6 @@ { "name": "@middy/http-error-handler", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "Http error handler middleware for the middy framework", "engines": { "node": ">=6.10" diff --git a/packages/http-event-normalizer/package.json b/packages/http-event-normalizer/package.json index 4f9912d42..d666b6f61 100644 --- a/packages/http-event-normalizer/package.json +++ b/packages/http-event-normalizer/package.json @@ -1,6 +1,6 @@ { "name": "@middy/http-event-normalizer", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "Http event normalizer middleware for the middy framework", "engines": { "node": ">=6.10" diff --git a/packages/http-header-normalizer/package.json b/packages/http-header-normalizer/package.json index 4ed0d7ee5..c4fa32496 100644 --- a/packages/http-header-normalizer/package.json +++ b/packages/http-header-normalizer/package.json @@ -1,6 +1,6 @@ { "name": "@middy/http-header-normalizer", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "Http header normalizer middleware for the middy framework", "engines": { "node": ">=6.10" diff --git a/packages/http-json-body-parser/package.json b/packages/http-json-body-parser/package.json index fae898521..7024765f0 100644 --- a/packages/http-json-body-parser/package.json +++ b/packages/http-json-body-parser/package.json @@ -1,6 +1,6 @@ { "name": "@middy/http-json-body-parser", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "Http JSON body parser middleware for the middy framework", "engines": { "node": ">=6.10" diff --git a/packages/http-partial-response/package.json b/packages/http-partial-response/package.json index cd39254d0..d355c236c 100644 --- a/packages/http-partial-response/package.json +++ b/packages/http-partial-response/package.json @@ -1,6 +1,6 @@ { "name": "@middy/http-partial-response", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "Http partial response middleware for the middy framework", "engines": { "node": ">=6.10" diff --git a/packages/http-security-headers/package.json b/packages/http-security-headers/package.json index f68262540..ccb7f17c6 100644 --- a/packages/http-security-headers/package.json +++ b/packages/http-security-headers/package.json @@ -1,6 +1,6 @@ { "name": "@middy/http-security-header", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "Applies best practice security headers to responses. It's a simplified port of HelmetJS", "engines": { "node": ">=6.10" diff --git a/packages/http-urlencode-body-parser/package.json b/packages/http-urlencode-body-parser/package.json index e41495ff6..2a60d6536 100644 --- a/packages/http-urlencode-body-parser/package.json +++ b/packages/http-urlencode-body-parser/package.json @@ -1,6 +1,6 @@ { "name": "@middy/http-urlencode-body-parser", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "Urlencode body parser middleware for the middy framework", "engines": { "node": ">=6.10" diff --git a/packages/input-output-logger/package.json b/packages/input-output-logger/package.json index e4ff2dcd4..35da5c262 100644 --- a/packages/input-output-logger/package.json +++ b/packages/input-output-logger/package.json @@ -1,6 +1,6 @@ { "name": "@middy/input-output-logger", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "Input and output logger middleware for the middy framework", "engines": { "node": ">=6.10" diff --git a/packages/s3-key-normalizer/package.json b/packages/s3-key-normalizer/package.json index 934bf17f0..fcd221ed2 100644 --- a/packages/s3-key-normalizer/package.json +++ b/packages/s3-key-normalizer/package.json @@ -1,6 +1,6 @@ { "name": "@middy/s3-key-normalizer", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "S3 key normalizer middleware for the middy framework", "engines": { "node": ">=6.10" diff --git a/packages/secrets-manager/README.md b/packages/secrets-manager/README.md index 6de2f316b..b059add42 100644 --- a/packages/secrets-manager/README.md +++ b/packages/secrets-manager/README.md @@ -53,6 +53,7 @@ npm install --save @middy/secrets-manager - `secrets` (object) : Map of secrets to fetch from Secrets Manager, where the key is the destination, and value is secret name in Secrets Manager. Example: `{secrets: {RDS_LOGIN: 'dev/rds_login'}}` - `awsSdkOptions` (object) (optional): Options to pass to AWS.SecretsManager class constructor. +- `throwOnFailedCall` (boolean) (optional): Defaults to `false`. Set it to `true` if you want your lambda to fail in case call to AWS Secrets Manager fails (secrets don't exist or internal error). It will only print error if secrets are already cached. NOTES: * Lambda is required to have IAM permission for `secretsmanager:GetSecretValue` action diff --git a/packages/secrets-manager/__tests__/index.js b/packages/secrets-manager/__tests__/index.js index e447dde72..562d830c6 100644 --- a/packages/secrets-manager/__tests__/index.js +++ b/packages/secrets-manager/__tests__/index.js @@ -52,6 +52,7 @@ describe('🔒 SecretsManager Middleware', () => { promise = promise.then(() => { return new Promise((resolve, reject) => { handler(event, context, (error, response) => { + if (error) return reject(error) try { cb(error, { event, context, response }) resolve() @@ -68,7 +69,7 @@ describe('🔒 SecretsManager Middleware', () => { } }) }) - promise.then(done).catch(err => done(err)) + promise.then(done).catch(done) } test(`It should set secrets to context`, (done) => { @@ -217,6 +218,92 @@ describe('🔒 SecretsManager Middleware', () => { }) }) + test(`It should fail if "throwOnFailedCall" flag provided and call failed`, (done) => { + const errorMessage = 'Internal Error / Secret doesn\'t exist' + getSecretValueMock.mockReturnValueOnce({ + promise: () => Promise.reject(new Error(errorMessage)) + }) + + const errHandler = err => { + getSecretValueMock.mockClear() + expect(err.message).toEqual(errorMessage) + done() + } + return testScenario({ + mockResponse: {}, + middlewareOptions: { + secrets: { + KEY_NAME: 'failed_call' + }, + throwOnFailedCall: true + }, + callbacks: [ + () => { + throw new Error('Not supposed to be called') + } + ], + done: errHandler + }) + }) + + test(`It should resolve if "throwOnFailedCall" flag not provided and call failed`, (done) => { + const errorMessage = 'Internal Error / Secret doesn\'t exist' + getSecretValueMock.mockReturnValueOnce({ + promise: () => Promise.reject(new Error(errorMessage)) + }) + return testScenario({ + mockResponse: {}, + middlewareOptions: { + secrets: { + KEY_NAME: 'failed_call' + } + }, + callbacks: [ + () => { + expect(getSecretValueMock).toBeCalled() + getSecretValueMock.mockClear() + } + ], + done + }) + }) + + test(`It should resolve if "throwOnFailedCall" flag provided but item already cached`, (done) => { + const errorMessage = 'Internal Error / Secret doesn\'t exist' + return testScenario({ + mockResponse: { + SecretString: JSON.stringify({ Username: 'username', Password: 'password' }) + }, + middlewareOptions: { + secrets: { + KEY_NAME: 'rds_key' + }, + throwOnFailedCall: true + }, + callbacks: [ + // invocation 1: fetched + (_, { context }) => { + hasRDSLogin(context) + expect(getSecretValueMock).toBeCalled() + + getSecretValueMock.mockClear() + + // set up next attempt to fail + getSecretValueMock.mockReturnValueOnce({ + promise: () => Promise.reject(new Error(errorMessage)) + }) + }, + // invocation 2: failed but content taken from cache + (_, { context }) => { + hasRDSLogin(context) + expect(getSecretValueMock).toBeCalled() + getSecretValueMock.mockClear() + } + ], + done + }) + }) + test(`It should only refresh once per cache expiry window`, (done) => { // with cache expiry of 50ms, test what happens when one refresh fails, and // that the middleware doesn't retry for another 50ms diff --git a/packages/secrets-manager/index.d.ts b/packages/secrets-manager/index.d.ts index 3bb0300ef..c988f1cf0 100644 --- a/packages/secrets-manager/index.d.ts +++ b/packages/secrets-manager/index.d.ts @@ -6,6 +6,7 @@ interface ISecretsManagerOptions { cacheExpiryInMillis?: number; secrets?: { [key: string]: string; }; awsSdkOptions?: Partial; + throwOnFailedCall?: boolean; } declare function secretsManager(opts?: ISecretsManagerOptions): middy.IMiddyMiddlewareObject; diff --git a/packages/secrets-manager/index.js b/packages/secrets-manager/index.js index 84e8d9e3e..6fea39706 100644 --- a/packages/secrets-manager/index.js +++ b/packages/secrets-manager/index.js @@ -4,6 +4,7 @@ module.exports = opts => { const defaults = { awsSdkOptions: {}, secrets: {}, // e.g. { RDS_SECRET: 'dev/rds_login', API_SECRET: '...' } + throwOnFailedCall: false, cache: false, cacheExpiryInMillis: undefined, secretsLoaded: false, @@ -54,6 +55,10 @@ module.exports = opts => { }) .catch(err => { console.error('failed to refresh secrets from Secrets Manager:', err.message) + // throw error if there is no secret in cache already and flag throwOnFailedCall provided + if (options.throwOnFailedCall && !options.secretsCache) { + throw err + } // if we already have a cached secrets, then reset the timestamp so we don't // keep retrying on every invocation which can cause performance problems // when there's temporary problems with Secrets Manager diff --git a/packages/secrets-manager/package.json b/packages/secrets-manager/package.json index 1b338e847..9de12db4c 100644 --- a/packages/secrets-manager/package.json +++ b/packages/secrets-manager/package.json @@ -1,6 +1,6 @@ { "name": "@middy/secrets-manager", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "Secrets Manager middleware for the middy framework", "engines": { "node": ">=6.10" diff --git a/packages/ssm/package.json b/packages/ssm/package.json index a5eae53a0..a4ca539c3 100644 --- a/packages/ssm/package.json +++ b/packages/ssm/package.json @@ -1,6 +1,6 @@ { "name": "@middy/ssm", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "SSM (EC2 Systems Manager) parameters middleware for the middy framework", "engines": { "node": ">=6.10" diff --git a/packages/validator/package.json b/packages/validator/package.json index 9c3a2af83..06b762d28 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -1,6 +1,6 @@ { "name": "@middy/validator", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "Validator middleware for the middy framework", "engines": { "node": ">=6.10" diff --git a/packages/warmup/package.json b/packages/warmup/package.json index 3a14ccfdb..21399ea85 100644 --- a/packages/warmup/package.json +++ b/packages/warmup/package.json @@ -1,6 +1,6 @@ { "name": "@middy/warmup", - "version": "1.0.0-alpha.16", + "version": "1.0.0-alpha.17", "description": "Warmup (cold start mitigation) middleware for the middy framework", "engines": { "node": ">=6.10"