@middy/secrets-manager
Version:
Secrets Manager middleware for the middy framework
179 lines (161 loc) • 4.58 kB
JavaScript
// Copyright 2017 - 2026 will Farrell, Luciano Mammino, and Middy contributors.
// SPDX-License-Identifier: MIT
import {
DescribeSecretCommand,
GetSecretValueCommand,
SecretsManagerClient,
} from "@aws-sdk/client-secrets-manager";
import {
canPrefetch,
catchInvalidSignatureException,
createClient,
createPrefetchClient,
getCache,
getInternal,
jsonSafeParse,
modifyCache,
processCache,
validateOptions,
} from "@middy/util";
const name = "secrets-manager";
const pkg = `@middy/${name}`;
const defaults = {
AwsClient: SecretsManagerClient,
awsClientOptions: {},
awsClientAssumeRole: undefined,
awsClientCapture: undefined,
fetchData: {},
fetchRotationDate: false, // true: apply to all or {key: true} for individual
disablePrefetch: false,
cacheKey: pkg,
cacheKeyExpiry: {},
cacheExpiry: -1, // ignored when fetchRotationRules is true/object
setToContext: false,
};
const optionSchema = {
type: "object",
properties: {
AwsClient: { instanceof: "Function" },
awsClientOptions: { type: "object" },
awsClientAssumeRole: { type: "string" },
awsClientCapture: { instanceof: "Function" },
fetchData: {
type: "object",
additionalProperties: { type: "string" },
},
fetchRotationDate: {
oneOf: [
{ type: "boolean" },
{ type: "object", additionalProperties: { type: "boolean" } },
],
},
disablePrefetch: { type: "boolean" },
cacheKey: { type: "string" },
cacheKeyExpiry: {
type: "object",
additionalProperties: { type: "number", minimum: -1 },
},
cacheExpiry: { type: "number", minimum: -1 },
setToContext: { type: "boolean" },
},
additionalProperties: false,
};
export const secretsManagerValidateOptions = (options) =>
validateOptions(pkg, optionSchema, options);
const secretsManagerMiddleware = (opts = {}) => {
const options = {
...defaults,
...opts,
cacheKeyExpiry: { ...defaults.cacheKeyExpiry, ...opts.cacheKeyExpiry },
};
const fetchDataKeys = Object.keys(options.fetchData);
const fetchRequest = (request, cachedValues = {}) => {
const values = {};
for (const internalKey of fetchDataKeys) {
if (cachedValues[internalKey]) continue;
const fetchRotation =
options.fetchRotationDate === true ||
options.fetchRotationDate?.[internalKey];
const rotationPromise = fetchRotation
? client
.send(
new DescribeSecretCommand({
SecretId: options.fetchData[internalKey],
}),
)
.catch((e) =>
catchInvalidSignatureException(
e,
client,
new DescribeSecretCommand({
SecretId: options.fetchData[internalKey],
}),
),
)
.then((resp) => {
if (options.cacheExpiry < 0) {
if (resp.NextRotationDate) {
options.cacheKeyExpiry[internalKey] =
resp.NextRotationDate * 1000;
}
} else {
const lastChanged =
Math.max(
resp.LastRotationDate ?? 0,
resp.LastChangedDate ?? 0,
) *
1000 +
options.cacheExpiry;
options.cacheKeyExpiry[internalKey] = resp.NextRotationDate
? Math.min(lastChanged, resp.NextRotationDate * 1000)
: lastChanged;
}
})
: undefined;
const fetchSecret = () => {
const command = new GetSecretValueCommand({
SecretId: options.fetchData[internalKey],
});
return client
.send(command)
.catch((e) => catchInvalidSignatureException(e, client, command))
.then((resp) => jsonSafeParse(resp.SecretString));
};
values[internalKey] = (
rotationPromise ? rotationPromise.then(fetchSecret) : fetchSecret()
).catch((e) => {
const value = getCache(options.cacheKey).value ?? {};
value[internalKey] = undefined;
modifyCache(options.cacheKey, value);
throw e;
});
}
return values;
};
let client;
let clientInit;
if (canPrefetch(options)) {
client = createPrefetchClient(options);
processCache(options, fetchRequest);
}
const secretsManagerMiddlewareBefore = async (request) => {
if (!client) {
clientInit ??= createClient(options, request);
client = await clientInit;
}
const { value } = processCache(options, fetchRequest, request);
Object.assign(request.internal, value);
if (options.setToContext) {
const data = await getInternal(fetchDataKeys, request);
Object.assign(request.context, data);
}
};
return {
before: secretsManagerMiddlewareBefore,
};
};
export default secretsManagerMiddleware;
// used for TS type inference (see index.d.ts)
export function secretsManagerParam(name) {
return name;
}