Skip to content

Commit

Permalink
Various tweaks, getting rid of AWS Secret Manager support, adding Lambda
Browse files Browse the repository at this point in the history
Regarding AWS Secrets Manager, see it's secret-rotation lifecycle:

https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html

The `createSecret`, `setSecret`, `testSecret`, `finishSecret` lifecycle
that Secrets Manager uses to update a secret is an unhelpful complication
for the use case of rotating the Play application secret, where there's no
meaningful, useful & easy test for a secret. The Secrets Manager API also
only exposes the `CreatedDate` of a secret, not when it was finalised,
which doesn't marry well with `play-secret-rotation's need for a
'publication time' of a secret, from which a usage-delay will determine
when that secret becomes active.
  • Loading branch information
rtyley committed Apr 13, 2018
1 parent 8834da2 commit 01e7fa8
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 64 deletions.
25 changes: 23 additions & 2 deletions aws-parameterstore/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
Using AWS Parameter Store for Play Secret Rotation
=======

##### IAM Policy
##### IAM Policies

###### Play app server IAM Policy

Your Play app servers will need an IAM policy like this in order
to read the encrypted Secret parameter:
to read the secret 'state':

```
- Effect: Allow
Expand All @@ -17,3 +19,22 @@ to read the encrypted Secret parameter:
Action: kms:Decrypt
Resource: 'arn:aws:kms:eu-west-1:111222333444:key/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
```

###### Secret-Updating Lambda IAM Policy

Your Play app servers will need an IAM policy like this in order
to read the encrypted Secret parameter:

```
- 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'
```


2 changes: 2 additions & 0 deletions aws-parameterstore/lambda/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// replace the conventional main artifact with an uber-jar
addArtifact(artifact in (Compile, packageBin), assembly)
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.gu.play.secretrotation.aws

import com.amazonaws.auth.profile.ProfileCredentialsProvider
import com.amazonaws.auth.{AWSCredentialsProvider, InstanceProfileCredentialsProvider}
import com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagementClientBuilder
import com.amazonaws.services.simplesystemsmanagement.model.{DescribeParametersRequest, ParameterStringFilter, PutParameterRequest}
import com.gu.play.secretrotation.SecretGenerator.generateSecret

object ParameterStoreLambda extends App {

def lambdaHandler(): Unit = {
new Updater(
credentials = new InstanceProfileCredentialsProvider(false),
parameterName = System.getenv("PARAMETER_NAME")
).updateSecret()
}

/*
This is only invoked (as a 'main' method) in development, *not* when running as an AWS Lambda.
run com.gu.play.secretrotation.aws.ParameterStoreLambda
*/
args.toList match {
case profileName :: parameterName =>
val devUpdater = new Updater(
credentials = new ProfileCredentialsProvider(profileName),
parameterName = args(1)
)
devUpdater.updateSecret()
case _ =>
Console.err.println("ERROR - expected exactly 2 arguments: yourProfile yourParameterName")
System.exit(1)
}

class Updater(credentials: AWSCredentialsProvider, parameterName: String) {
val ssmClient =
AWSSimpleSystemsManagementClientBuilder.standard().withCredentials(credentials).build()

def updateSecret() = {
val describeSecretParameterRequest = new DescribeParametersRequest()
.withParameterFilters(new ParameterStringFilter().withKey("Name").withValues(parameterName))

val metadata = ssmClient.describeParameters(describeSecretParameterRequest).getParameters.get(0)

val kmsKeyId = metadata.getKeyId

val putParameterResult =
ssmClient.putParameter(new PutParameterRequest()
.withName(parameterName)
.withOverwrite(true)
.withKeyId(kmsKeyId)
.withValue(generateSecret)
)
println(s"Updated secret, put parameter version ${putParameterResult.getVersion}")
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import play.api.http.SecretConfiguration
import scala.collection.JavaConverters._
import scala.concurrent.duration._

object AwsParameterStore {
object ParameterStore {
val InitialVersion = 1

class SecretSupplier(
Expand Down

This file was deleted.

28 changes: 17 additions & 11 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,23 @@ lazy val core =
)
)

val awsSdkVersion = "1.11.310"
val awsSdkVersion = "1.11.313"

lazy val `aws-parameterstore` = project.settings(baseSettings: _*).dependsOn(core).settings(
libraryDependencies ++= Seq(
"com.amazonaws" % "aws-java-sdk-ssm" % awsSdkVersion
)
val awsSsm = "com.amazonaws" % "aws-java-sdk-ssm" % awsSdkVersion

lazy val `aws-parameterstore` = project.in(file("aws-parameterstore/state-supplier")).settings(baseSettings: _*).dependsOn(core).settings(
libraryDependencies += awsSsm
)

lazy val `aws-secretsmanager` = project.settings(baseSettings: _*).dependsOn(core).settings(
libraryDependencies ++= Seq(
"com.amazonaws" % "aws-java-sdk-secretsmanager" % awsSdkVersion
)
lazy val `aws-parameterstore-lambda` = project.in(file("aws-parameterstore/lambda"))
.settings(baseSettings: _*).dependsOn(`secret-generator`).settings(
libraryDependencies += awsSsm
)

lazy val `secret-generator` = project.settings(baseSettings: _*)

lazy val `play-secret-rotation-root` = (project in file(".")).aggregate(core, `aws-parameterstore`, `aws-secretsmanager`).
lazy val `play-secret-rotation-root` = (project in file("."))
.aggregate(core, `aws-parameterstore`, `aws-parameterstore-lambda`).
settings(baseSettings: _*).settings(
publishArtifact := false,
publish := {},
Expand All @@ -54,4 +55,9 @@ lazy val `play-secret-rotation-root` = (project in file(".")).aggregate(core, `a
)
)


assemblyMergeStrategy in assembly := {
{
case PathList("META-INF", xs @ _*) => MergeStrategy.discard
case x => MergeStrategy.first
}
}
2 changes: 2 additions & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.8")
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.3")

addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6")
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.gu.play.secretrotation

import java.security.SecureRandom

object SecretGenerator {

/**
* Copied from play.sbt.ApplicationSecretGenerator#generateSecret ...we could
* have just introduced that as a dependency, but might as well reduce the
* size of the AWS Lambda jar...
* @return 64-character play secret
*/
def generateSecret = {
val random = new SecureRandom()

(1 to 64).map { _ =>
(random.nextInt(75) + 48).toChar
}.mkString.replaceAll("\\\\+", "/")
}
}

0 comments on commit 01e7fa8

Please sign in to comment.