Maybe there's a nicer way, but I managed to get this to work by writing a custom webpack loader. This answer is specifically for my case where I needed to export details of multiple cognito pools to a single file for use in a custom lambda authorizer, but the pattern should work for any scenario, and isn't necessarily tied to SSM either (you could generate the file using any method as the loader is just plain Javascript). It brought my Lambda execution times down from ~40ms (using SSM) down to ~2ms.
Template file & usage
First I created an example template .json file, with the structure matching the data I have stored in SSM. This could be anywhere, but I put it in generated/cognitoConfig.json
. This is useful for documentation and code assist at point-of-use.
{
"pools": [
{
"_note_": "This is just an example. This file gets totally replaced with the real pool config by cognitoConfigLoader on deploy",
"clientId":"example-client",
"name":"example",
"poolArn":"arn:aws:cognito-idp:eu-west-2:account-id:userpool/example-pool",
"poolEndpoint":"cognito-idp.eu-west-2.amazonaws.com/example-pool",
"poolId":"example-pool",
"poolKeysUrl":"https://cognito-idp.eu-west-2.amazonaws.com/example-pool/.well-known/jwks.json",
"region":"eu-west-2"
}
]
}
This can then be imported and used within the lambda code (ES6). For example:
import * as cognitoData from '../../generated/cognitoConfig.json';
function getPoolConfig(name) {
const poolConfig = cognitoData.pools.filter(pool => pool.name === name)[0]
}
Webpack loader
I configured a custom webpack loader that runs against this template file:
const path = require('path');
module.exports = {
//...other webpack config
module: {
// These execute from bottom to top
rules: [
// ...other rules (e.g. babel)
// Retrieve cognito pool information from SSM and store
{
test: /cognitoConfig\.json$/,
include: path.resolve(__dirname, "generated"),
loader: path.resolve(__dirname, "webpack-loaders/cognitoConfigLoader.js"),
},
]
}
}
I then wrote a webpack loader that searches for all matching SSM parameters, and writes the contents to the JSON file. The serverless webpack plugin provides access to the underlying serverless object, so the current AWS credentials can be accessed.
For bonus points, I also got it to download the signing keys, but I didn't include that here as I don't want to clutter the answer:
const slsw = require("serverless-webpack");
const { SSMClient, GetParametersByPathCommand } = require("@aws-sdk/client-ssm")
const { fromIni } = require("@aws-sdk/credential-provider-ini")
module.exports = function () {
const callback = this.async()
buildPoolsJson().then(
poolsJson => callback(undefined, poolsJson),
error => callback(error)
)
}
async function buildPoolsJson() {
const poolsParameters= await loadPoolsParameters()
return JSON.stringify({
pools: poolsParameters
})
}
async function loadPoolsParameters() {
const awsProvider = slsw.lib.serverless.service.provider
const ssmClient = new SSMClient({
credentials: fromIni({ profile: awsProvider.profile }),
region: awsProvider.region,
})
const poolParamsResponse = await ssmClient.send(new GetParametersByPathCommand({
Path: "/terraform/cognito-pools",
}))
return poolParamsResponse.Parameters.map(parameter => {
return JSON.parse(parameter.Value);
})
}