Skip to content

Commit

Permalink
Merge pull request #462 from guardian/db-rt/remove-play-v28-support
Browse files Browse the repository at this point in the history
Drop v28 support
  • Loading branch information
rtyley authored May 24, 2024
2 parents 357a2c7 + cfade76 commit c4b240a
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 119 deletions.
21 changes: 8 additions & 13 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@ import sbtversionpolicy.withsbtrelease.ReleaseVersion.fromAggregatedAssessedComp

lazy val baseSettings = Seq(
scalaVersion := "2.13.14",
crossScalaVersions := Seq(scalaVersion.value, "3.3.3"),
organization := "com.gu.play-secret-rotation",
licenses := Seq("Apache-2.0" -> url("https://www.apache.org/licenses/LICENSE-2.0")),
scalacOptions ++= Seq("-deprecation", "-unchecked", "-release:11"),
Test / testOptions +=
Tests.Argument(TestFrameworks.ScalaTest,"-u", s"test-results/scala-${scalaVersion.value}")
)

lazy val crossCompileScala3 = crossScalaVersions := Seq(scalaVersion.value, "3.3.3")

lazy val core =
project.settings(crossCompileScala3, baseSettings).settings(
project.settings(baseSettings).settings(
libraryDependencies ++= Seq(
"com.github.blemale" %% "scaffeine" % "5.2.1",
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.5",
Expand All @@ -23,7 +22,7 @@ lazy val core =
)

lazy val `aws-parameterstore-secret-supplier-base` =
project.in(file("aws-parameterstore/secret-supplier")).settings(crossCompileScala3, baseSettings).dependsOn(core)
project.in(file("aws-parameterstore/secret-supplier")).settings(baseSettings).dependsOn(core)

val awsSdkForVersion = Map(
1 -> "com.amazonaws" % "aws-java-sdk-ssm" % "1.12.716",
Expand All @@ -32,26 +31,25 @@ val awsSdkForVersion = Map(

def awsParameterStoreWithSdkVersion(version: Int)=
Project(s"aws-parameterstore-sdk-v$version", file(s"aws-parameterstore/secret-supplier/aws-sdk-v$version"))
.settings(crossCompileScala3, baseSettings)
.settings(baseSettings)
.dependsOn(`aws-parameterstore-secret-supplier-base`)
.settings(libraryDependencies += awsSdkForVersion(version))

lazy val `aws-parameterstore-sdk-v1` = awsParameterStoreWithSdkVersion(1)
lazy val `aws-parameterstore-sdk-v2` = awsParameterStoreWithSdkVersion(2)

lazy val `aws-parameterstore-lambda` = project.in(file("aws-parameterstore/lambda"))
.settings(crossCompileScala3, baseSettings).dependsOn(`secret-generator`).settings(
.settings(baseSettings).dependsOn(`secret-generator`).settings(
libraryDependencies ++= Seq(
"com.amazonaws" % "aws-lambda-java-core" % "1.2.3",
"com.amazonaws" % "aws-lambda-java-events" % "3.11.5",
awsSdkForVersion(1)
)
)

lazy val `secret-generator` = project.settings(crossCompileScala3, baseSettings)
lazy val `secret-generator` = project.settings(baseSettings)

val exactPlayVersions = Map(
"28" -> "com.typesafe.play" %% "play" % "2.8.21",
"29" -> "com.typesafe.play" %% "play" % "2.9.2",
"30" -> "org.playframework" %% "play" % "3.0.2"
)
Expand All @@ -63,16 +61,13 @@ def playVersion(majorMinorVersion: String)= {
.settings(libraryDependencies += exactPlayVersions(majorMinorVersion))
}


lazy val `play-v28` = playVersion("28")
lazy val `play-v29` = playVersion("29").settings(crossCompileScala3)
lazy val `play-v30` = playVersion("30").settings(crossCompileScala3)
lazy val `play-v29` = playVersion("29")
lazy val `play-v30` = playVersion("30")


lazy val `play-secret-rotation-root` = (project in file("."))
.aggregate(
core,
`play-v28`,
`play-v29`,
`play-v30`,
`aws-parameterstore-secret-supplier-base`,
Expand Down
104 changes: 0 additions & 104 deletions play/play-v28/RotatingSecretComponents.scala

This file was deleted.

1 change: 0 additions & 1 deletion play/play-v29/RotatingSecretComponents.scala

This file was deleted.

104 changes: 104 additions & 0 deletions play/play-v29/RotatingSecretComponents.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.gu.play.secretrotation

import java.time.Clock
import java.time.Clock.systemUTC

import play.api.http._
import play.api.mvc._
import play.api.mvc.request.{DefaultRequestFactory, RequestFactory}
import play.api.{BuiltInComponentsFromContext, Configuration}
import play.api.libs.crypto.{CSRFTokenSigner, CSRFTokenSignerProvider, DefaultCSRFTokenSigner, DefaultCookieSigner}
import java.security.MessageDigest
import com.github.blemale.scaffeine.{Scaffeine, LoadingCache}
import scala.concurrent.duration._


trait RotatingSecretComponents extends BuiltInComponentsFromContext {

val secretStateSupplier: SnapshotProvider

override def configuration: Configuration = {
val nonRotatingSecretOnlyUsedToSatisfyConfigChecks = secretStateSupplier.snapshot().secrets.active

Configuration("play.http.secret.key" -> nonRotatingSecretOnlyUsedToSatisfyConfigChecks).withFallback(super.configuration)
}

override lazy val requestFactory: RequestFactory =
RotatingSecretComponents.requestFactoryFor(secretStateSupplier, httpConfiguration)

override lazy val csrfTokenSigner: CSRFTokenSigner =
new RotatingSecretComponents.RotatingKeyCSRFTokenSigner(secretStateSupplier, systemUTC())
}

object RotatingSecretComponents {
def requestFactoryFor(snapshotProvider: SnapshotProvider, hc: HttpConfiguration): RequestFactory =
new DefaultRequestFactory(
new DefaultCookieHeaderEncoding(hc.cookies),
new RotatingKeySessionCookieBaker(hc.session, snapshotProvider),
new RotatingKeyFlashCookieBaker(hc.flash, snapshotProvider)
)


trait RotatingSecretCookieCodec extends CookieDataCodec {
val snapshotProvider: SnapshotProvider
val jwtConfiguration: JWTConfiguration

implicit val c: Clock = systemUTC()

private def jwtCodecFor(secret: String) = DefaultJWTCookieDataCodec(SecretConfiguration(secret), jwtConfiguration)

override def encode(data: Map[String, String]): String =
jwtCodecFor(snapshotProvider.snapshot().secrets.active).encode(data)

override def decode(data: String): Map[String, String] = {
snapshotProvider.snapshot().decode[Map[String, String]](jwtCodecFor(_).decode(data), _.nonEmpty).getOrElse(Map.empty)
}
}

class RotatingKeySessionCookieBaker(
val config: SessionConfiguration,
val snapshotProvider: SnapshotProvider) extends SessionCookieBaker with RotatingSecretCookieCodec {
override val jwtConfiguration: JWTConfiguration = config.jwt
}

class RotatingKeyFlashCookieBaker(
val config: FlashConfiguration,
val snapshotProvider: SnapshotProvider) extends FlashCookieBaker with RotatingSecretCookieCodec {
override val jwtConfiguration: JWTConfiguration = config.jwt
}

class RotatingKeyCSRFTokenSigner(
snapshotProvider: SnapshotProvider,
clock: Clock) extends CSRFTokenSigner {

private val signerCache: LoadingCache[String, DefaultCSRFTokenSigner] = Scaffeine()
.expireAfterAccess(1.day) // stop the cache growing indefinitely
.build(secret => new DefaultCSRFTokenSigner(new DefaultCookieSigner(SecretConfiguration(secret)), clock))

private def signerForActiveSecret() = signerCache.get(snapshotProvider.snapshot().secrets.active)

override def signToken(token: String): String = signerForActiveSecret().signToken(token)
override def generateToken: String = signerForActiveSecret().generateToken
override def generateSignedToken: String = signerForActiveSecret().generateSignedToken
override def constantTimeEquals(a: String, b: String): Boolean = signerForActiveSecret().constantTimeEquals(a, b)

/**
* This method verifies tokens which may have been signed with a previous secret that we still consider valid
* for now. It tries all applicable secrets to see if any of them can verify the token.
*/
override def extractSignedToken(token: String): Option[String] =
snapshotProvider.snapshot().decodeOpt(secret => signerCache.get(secret).extractSignedToken(token))

/**
* It's important that this method doesn't just delegate to an underlying `DefaultCSRFTokenSigner`, because this
* method uses the `extractSignedToken()` method, and we need to use the tolerant version of that method that's
* only available in _this_ class.
*/
override def compareSignedTokens(tokenA: String, tokenB: String): Boolean = {
for {
rawA <- extractSignedToken(tokenA)
rawB <- extractSignedToken(tokenB)
} yield MessageDigest.isEqual(rawA.getBytes("utf-8"), rawB.getBytes("utf-8"))
}.getOrElse(false)
}
}
2 changes: 1 addition & 1 deletion play/play-v30/RotatingSecretComponents.scala

0 comments on commit c4b240a

Please sign in to comment.