Skip to content

Commit

Permalink
feat: support resolving secrets name via function (#879)
Browse files Browse the repository at this point in the history
  • Loading branch information
solaris007 authored Dec 7, 2023
1 parent 6671e85 commit 841e7a0
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 3 deletions.
31 changes: 28 additions & 3 deletions packages/helix-shared-secrets/src/secrets-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,30 @@ const cache = {
data: null,
};

/**
* Allow dynamic setting of the secrets path via user-supplied function or default to the
* defaultSecretsPath. If the user-supplied function throws an error, log it and use the
* defaultSecretsPath. The user-supplied function may be async or sync, which is both
* handled by using await.
*
* @param {SecretsOptions} opts - the options
* @param {UniversalContext} ctx - the context
* @return {Promise<string>} the secrets path
*/
async function evaluatePathFromName(opts, ctx) {
let secretsPath = `/helix-deploy/${ctx.func.package}/${ctx.func.name}`;

try {
const userPath = typeof opts.name === 'function' ? await opts.name.call(ctx, opts) : opts.name;
secretsPath = userPath || secretsPath;
} catch (e) {
const { log = console } = ctx;
log.error(`error in user-supplied 'name' function: ${e.message}`);
}

return secretsPath;
}

/**
* reset the cache - for testing only
*/
Expand Down Expand Up @@ -59,21 +83,22 @@ export async function loadSecrets(ctx, opts) {
const {
expiration = CACHE_EXPIRATION,
checkDelay = CHECK_DELAY,
name = `/helix-deploy/${ctx.func.package}/${ctx.func.name}`,
} = opts;

const secretsPath = await evaluatePathFromName(opts, ctx);

const sm = new SecretsManager(process.env);
const now = Date.now();
let lastChanged = 0;

if (!cache.checked) {
cache.checked = now;
} else if (now > cache.checked + checkDelay) {
lastChanged = await sm.getLastChangedDate(name);
lastChanged = await sm.getLastChangedDate(secretsPath);
cache.checked = Date.now();
}
if (!cache.data || now > cache.loaded + expiration || lastChanged > cache.loaded) {
const params = await sm.loadSecrets(name);
const params = await sm.loadSecrets(secretsPath);
const nower = Date.now();
// eslint-disable-next-line no-console
console.info(`loaded ${Object.entries(params).length} package parameter in ${nower - now}ms`);
Expand Down
72 changes: 72 additions & 0 deletions packages/helix-shared-secrets/test/secrets-wrapper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,78 @@ describe('Secrets Wrapper Unit Tests', () => {
assert.strictEqual(resp.status, 200);
});

it('fetches secrets with custom name function', async () => {
nock('https://secretsmanager.us-east-1.amazonaws.com/')
.post('/')
.reply((uri, body) => {
assert.strictEqual(body, '{"SecretId":"/dynamic-path"}');
return [200, {
SecretString: JSON.stringify({ SOME_SECRET: 'pssst' }),
}, {
'content-type': 'application/json',
}];
});

const nameFunction = () => '/dynamic-path';

const main = wrap((req, ctx) => {
assert.deepStrictEqual(ctx.env, { SOME_SECRET: 'pssst' });
return new Response(200);
}).with(secrets, { name: nameFunction });
const resp = await main(new Request('http://localhost'), DEFAULT_CONTEXT());
assert.strictEqual(resp.status, 200);
});

it('fetches secrets with default path if custom function returns empty', async () => {
nock('https://secretsmanager.us-east-1.amazonaws.com/')
.post('/')
.reply((uri, body) => {
assert.strictEqual(body, '{"SecretId":"/helix-deploy/helix3/helix-admin"}');
return [200, {
SecretString: JSON.stringify({ SOME_SECRET: 'pssst' }),
}, {
'content-type': 'application/json',
}];
});

const nameFunction = (ctx, opts) => {
assert.deepStrictEqual(ctx, DEFAULT_CONTEXT());
assert.deepStrictEqual(opts, { name: nameFunction });
return '';
};

const main = wrap((req, ctx) => {
assert.deepStrictEqual(ctx.env, { SOME_SECRET: 'pssst' });
return new Response(200);
}).with(secrets, { name: nameFunction });
const resp = await main(new Request('http://localhost'), DEFAULT_CONTEXT());
assert.strictEqual(resp.status, 200);
});

it('fetches secrets with default path if custom function throws error', async () => {
nock('https://secretsmanager.us-east-1.amazonaws.com/')
.post('/')
.reply((uri, body) => {
assert.strictEqual(body, '{"SecretId":"/helix-deploy/helix3/helix-admin"}');
return [200, {
SecretString: JSON.stringify({ SOME_SECRET: 'pssst' }),
}, {
'content-type': 'application/json',
}];
});

const nameFunction = () => {
throw new Error('boom');
};

const main = wrap((req, ctx) => {
assert.deepStrictEqual(ctx.env, { SOME_SECRET: 'pssst' });
return new Response(200);
}).with(secrets, { name: nameFunction });
const resp = await main(new Request('http://localhost'), DEFAULT_CONTEXT());
assert.strictEqual(resp.status, 200);
});

it('caches secrets', async () => {
nock('https://secretsmanager.us-east-1.amazonaws.com/')
.post('/')
Expand Down

0 comments on commit 841e7a0

Please sign in to comment.