Skip to content

Commit

Permalink
feat: add in support for manual rotation
Browse files Browse the repository at this point in the history
  • Loading branch information
willfarrell committed Aug 7, 2023
1 parent c3db9d1 commit 07a824c
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 12 deletions.
91 changes: 88 additions & 3 deletions packages/secrets-manager/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,11 +245,14 @@ test.serial(
)

test.serial(
'It should call aws-sdk if cache enabled but cached param has expired using rotation',
'It should call aws-sdk if cache enabled but cached param has expired using LastRotationDate',
async (t) => {
const mockService = mockClient(SecretsManagerClient)
.on(DescribeSecretCommand, { SecretId: 'api_key' })
.resolves({ NextRotationDate: Date.now() + 50 })
.resolves({
LastRotationDate: Date.now() / 1000 - 50,
LastChangedDate: Date.now() / 1000 - 50
})
.on(GetSecretValueCommand, { SecretId: 'api_key' })
.resolves({ SecretString: 'token' })
const sendStub = mockService.send
Expand All @@ -264,7 +267,89 @@ test.serial(
.use(
secretsManager({
AwsClient: SecretsManagerClient,
cacheExpiry: 0,
cacheExpiry: 100,
fetchData: {
token: 'api_key'
},
fetchRotationDate: true,
disablePrefetch: true
})
)
.before(middleware)

await handler(event, context) // fetch x 2
await handler(event, context)
await setTimeout(100)
await handler(event, context) // fetch x 2

t.is(sendStub.callCount, 2 * 2)
}
)

test.serial(
'It should call aws-sdk if cache enabled but cached param has expired using LastRotationDate, fallback to NextRotationDate',
async (t) => {
const mockService = mockClient(SecretsManagerClient)
.on(DescribeSecretCommand, { SecretId: 'api_key' })
.resolves({
LastRotationDate: Date.now() / 1000 - 25,
LastChangedDate: Date.now() / 1000 - 25,
NextRotationDate: Date.now() / 1000 + 50
})
.on(GetSecretValueCommand, { SecretId: 'api_key' })
.resolves({ SecretString: 'token' })
const sendStub = mockService.send
const handler = middy(() => {})

const middleware = async (request) => {
const values = await getInternal(true, request)
t.is(values.token, 'token')
}

handler
.use(
secretsManager({
AwsClient: SecretsManagerClient,
cacheExpiry: 100,
fetchData: {
token: 'api_key'
},
fetchRotationDate: true,
disablePrefetch: true
})
)
.before(middleware)

await handler(event, context) // fetch x 2
await handler(event, context)
await setTimeout(100)
await handler(event, context) // fetch x 2

t.is(sendStub.callCount, 2 * 2)
}
)

test.serial(
'It should call aws-sdk if cache enabled but cached param has expired using NextRotationDate',
async (t) => {
const mockService = mockClient(SecretsManagerClient)
.on(DescribeSecretCommand, { SecretId: 'api_key' })
.resolves({ NextRotationDate: Date.now() / 1000 + 50 })
.on(GetSecretValueCommand, { SecretId: 'api_key' })
.resolves({ SecretString: 'token' })
const sendStub = mockService.send
const handler = middy(() => {})

const middleware = async (request) => {
const values = await getInternal(true, request)
t.is(values.token, 'token')
}

handler
.use(
secretsManager({
AwsClient: SecretsManagerClient,
cacheExpiry: -1,
fetchData: {
token: 'api_key'
},
Expand Down
17 changes: 9 additions & 8 deletions packages/secrets-manager/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,17 @@ const defaults = {
fetchRotationDate: false, // true: apply to all or {key: true} for individual
disablePrefetch: false,
cacheKey: 'secrets-manager',
cacheKeyExpiry: {},
cacheExpiry: -1, // ignored when fetchRotationRules is true/object
setToContext: false
}

const future = Date.now() * 2
const secretsManagerMiddleware = (opts = {}) => {
const options = { ...defaults, ...opts }

const fetch = (request, cachedValues = {}) => {
const values = {}

if (options.fetchRotationRules) {
options.cacheExpiry = 0
}

for (const internalKey of Object.keys(options.fetchData)) {
if (cachedValues[internalKey]) continue

Expand All @@ -54,9 +50,14 @@ const secretsManagerMiddleware = (opts = {}) => {
})
)
.then((resp) => {
if (resp.NextRotationDate) {
options.cacheExpiry = Math.min(
options.cacheExpiry || future,
if (options.cacheExpiry < 0) {
options.cacheKeyExpiry[internalKey] =
resp.NextRotationDate * 1000
} else {
options.cacheKeyExpiry[internalKey] = Math.min(
Math.max(resp.LastRotationDate, resp.LastChangedDate) *
1000 +
options.cacheExpiry,
resp.NextRotationDate * 1000
)
}
Expand Down
2 changes: 1 addition & 1 deletion website/docs/middlewares/secrets-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ npm install --save-dev @aws-sdk/client-secrets-manager
- `awsClientAssumeRole` (string) (optional): Internal key where secrets are stored. See [@middy/sts](/docs/middlewares/sts) on to set this.
- `awsClientCapture` (function) (optional): Enable XRay by passing `captureAWSv3Client` from `aws-xray-sdk` in.
- `fetchData` (object) (required): Mapping of internal key name to API request parameter `SecretId`.
- `fetchRotationDate` (boolean|object) (default `false`): Boolean to apply to all or mapping of internal key name to boolean indicating what secrets should fetch NextRotationDate before the secret. `cacheExpiry` is ignored when this is not `false`.
- `fetchRotationDate` (boolean|object) (default `false`): Boolean to apply to all or mapping of internal key name to boolean. This indicates what secrets should fetch and cached based on `NextRotationDate`/`LastRotationDate`/`LastChangedDate`. `cacheExpiry` of `-1` will use `NextRotationDate`, while any other value will be added to the `LastRotationDate` or `LastChangedDate`, whichever is more recent. If secrets have different rotation schedules, use multiple instances of this middleware.
- `disablePrefetch` (boolean) (default `false`): On cold start requests will trigger early if they can. Setting `awsClientAssumeRole` disables prefetch.
- `cacheKey` (string) (default `secrets-manager`): Cache key for the fetched data responses. Must be unique across all middleware.
- `cacheExpiry` (number) (default `-1`): How long fetch data responses should be cached for. `-1`: cache forever, `0`: never cache, `n`: cache for n ms.
Expand Down

0 comments on commit 07a824c

Please sign in to comment.