There are three parts to this:
- Create the parameter in AWS Parameter Store to hold the secret
- Update your Play server to read the rotating secrets from AWS Parameter Store
- Install an AWS Lambda to update the secret on a regular basis
As an example we'll use an AWS Parameter called /Example/PlayAppSecret
- create your own
AWS Parameter to hold the Application Secret, using a type of SecureString
and whichever
KMS key you want to use:
Every time you update this Parameter, your Play app servers will fetch the new secret state
as soon as their short-lived caches expire. After the usageDelay
has passed, they will
start to sign cookies using the new secret, but will continue to accept cookies signed
with the old secret until overlapDuration
has passed.
The Play Server is only responsible for reading the updates of the Application Secret - it doesn't update the secret itself. The state of the Application Secret (the old & new secrets, and when to begin switching over between the two) is fetched from AWS Parameter Store and cached with a short-lifetime, to ensure that soon after the AWS Parameter containing the secret is updated, all app servers are ready to begin using it.
You'll need to add two library dependencies for com.gu.play-secret-rotation
- one dependency specific
to your Play version, and another specific to your AWS SDK version:
- Play ...
- AWS SDK (v1 or v2) ...
So, for example:
libraryDependencies ++= Seq(
"com.gu.play-secret-rotation" %% "play-v28" % "0.x",
"com.gu.play-secret-rotation" %% "aws-parameterstore-sdk-v2" % "0.x",
)
In your ApplicationComponents
, mix-in RotatingSecretComponents
and provide the secretStateSupplier
required by that trait:
import com.gu.play.secretrotation._
val secretStateSupplier: SnapshotProvider = {
import com.gu.play.secretrotation.aws.parameterstore
new parameterstore.SecretSupplier(
TransitionTiming(usageDelay = ofMinutes(3), overlapDuration = ofHours(2)),
"/Example/PlayAppSecret",
parameterstore.AwsSdkV1(AWSSimpleSystemsManagementClientBuilder.defaultClient())
)
}
Note that you'll probably have to define credentials/region on the AWSSimpleSystemsManagementClient
.
Your Play app servers will need an IAM policy like this in order to read the secret 'state':
- Effect: Allow
Action: ssm:GetParameters
Resource: 'arn:aws:ssm:eu-west-1:111222333444:parameter/Example/PlayAppSecret'
- Effect: Allow
Action: kms:Decrypt
Resource: 'arn:aws:kms:eu-west-1:111222333444:key/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
You don't have to do this step straight away! You've already got a fair bit of benefit from being able to update your Application Secret without downtime, and you should check that your Play servers are operating well by testing with a manual update of the Parameter value before you continue.
Once you're happy that manual updates are working, you can start automatic scheduled updates with an AWS Lambda: download the latest published jar for the AWS Lambda.
Set the Lambda Function code Handler
to this value:
com.gu.play.secretrotation.aws.parameterstore.Lambda::lambdaHandler
Set the Lambda Environment variable PARAMETER_NAME
to the name of the
parameter that contains the secret (in this example 'Example/PlayAppSecret').
Set the Lambda Execution role to have a policy like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ssm:DescribeParameters",
"Resource": "arn:aws:ssm:eu-west-1:111222333444:*"
},
{
"Effect": "Allow",
"Action": "ssm:PutParameter",
"Resource": "arn:aws:ssm:eu-west-1:111222333444:parameter/Example/PlayAppSecret"
},
{
"Effect": "Allow",
"Action": "kms:Encrypt",
"Resource": "arn:aws:kms:eu-west-1:111222333444:key/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
},
{
"Effect": "Allow",
"Action": ["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"],
"Resource": "arn:aws:logs:*:*:*"
}
]
}
Finally, use an AWS CloudWatch Scheduled Event to trigger the Lambda to run at regular intervals.
The Lambda should not run more often than the overlapDuration
defined in the secretStateSupplier
in your Play Server - every 6 hours with a 2 hour overlap will probably work well.