Skip to content

Latest commit

 

History

History
140 lines (108 loc) · 6.44 KB

File metadata and controls

140 lines (108 loc) · 6.44 KB

Using AWS Parameter Store for Play Secret Rotation

play-secret-rotation artifacts

play-secret-rotation artifacts

There are three parts to this:

Create the AWS Parameter to hold the Secret

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:

image

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.

Play server

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.

Dependencies

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:

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",
)
Updating ApplicationComponents with the rotating secret

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'

Secret-Updating Lambda

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.