Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set up stage-specific config #12

Merged
merged 1 commit into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,30 @@
# Gatehouse

## Configuration

The app configuration has three levels of precedence. Any values repeated at a higher level will override those at a lower
level.
The levels are, in order of precedence:

### 1. SSM parameters
Secret and private settings are stored as
[AWS SSM parameters](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html),
named in the format:
`/<stage>/identity/gatehouse/<param key>`
where `param key` is a slash-separated
[Hocon](https://github.com/lightbend/config/blob/main/HOCON.md) key.

Eg.
`/CODE/identity/gatehouse/play/http/secret/key`
would give us a `play.http.secret.key` value.

Secrets are stored as `SecureString` parameters.
Private settings are stored as `String` parameters.

### 2. Stage-specific settings
Settings that aren't private but vary between deployment stages are set in the stage-specific config files in
the `conf` directory. These might be values that we expose in browsers, for example.

### 3. Global settings
Finally, settings that aren't private and are the same for all deployment stages are set in the `conf/application.conf`
file.
20 changes: 0 additions & 20 deletions app/load/AppComponents.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,6 @@ import scala.util.Using

class AppComponents(context: Context) extends BuiltInComponentsFromContext(context) with HttpFiltersComponents {

private val region = Region.EU_WEST_1

private val stage = context.initialConfiguration.getOptional[String]("stage").getOrElse("DEV")

private lazy val secretKey: String = {
val request = GetParameterRequest.builder
.name(s"/$stage/identity/gatehouse/playSecret")
.withDecryption(true)
.build()
Using.resource(SsmClient.builder.region(region).build()) {
_.getParameter(request).parameter.value
}
}

override def configuration: Configuration =
if (stage == "DEV")
super.configuration
else
Configuration("play.http.secret.key" -> secretKey).withFallback(super.configuration)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you planning to to use play secret rotation ? I think it's still the recommendation

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I started looking at that. Thanks for the reminder

override def httpFilters: Seq[EssentialFilter] = super.httpFilters :+ new RequestLoggingFilter(materializer)

lazy val healthCheckController = new controllers.HealthCheckController(controllerComponents)
Expand Down
44 changes: 41 additions & 3 deletions app/load/AppLoader.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,52 @@
package load

import com.gu.conf.{
ComposedConfigurationLocation,
ConfigurationLoader,
ResourceConfigurationLocation,
SSMConfigurationLocation
}
import com.gu.{AppIdentity, AwsIdentity}
import com.typesafe.config.Config
import play.api.*
import play.api.ApplicationLoader.Context
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider

import scala.util.{Failure, Success, Try}

class AppLoader extends ApplicationLoader {

private val appName = "gatehouse"

private def buildConfig(context: Context): Try[Config] = {
val credentialsProvider = DefaultCredentialsProvider.create()
val isDev = context.environment.mode == Mode.Dev
for {
identity <-
if (isDev)
Success(AwsIdentity(app = appName, stack = "identity", stage = "DEV", region = "eu-west-1"))
else
AppIdentity.whoAmI(defaultAppName = appName, credentialsProvider)
config <- Try(ConfigurationLoader.load(identity, credentialsProvider) { case identity: AwsIdentity =>
ComposedConfigurationLocation(
List(
SSMConfigurationLocation.default(identity),
ResourceConfigurationLocation(s"${identity.stage}.conf"),
)
)
})
} yield config
}

override def load(context: Context): Application = {
LoggerConfigurator(context.environment.classLoader).foreach {
_.configure(context.environment, context.initialConfiguration, Map.empty)
LoggerConfigurator(context.environment.classLoader) foreach { _.configure(context.environment) }
buildConfig(context) match {
case Success(config) =>
val newContext =
context.copy(initialConfiguration = Configuration(config).withFallback(context.initialConfiguration))
new AppComponents(newContext).application
case Failure(exception) =>
throw exception
}
new AppComponents(context).application
}
}
1 change: 0 additions & 1 deletion app/logging/LogEntry.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ private[logging] object LogEntry {
def requestAndResponse(request: RequestHeader, response: Result, duration: Long): LogEntry = {
val fields = commonFields(request, duration) ++ Map(
"status" -> response.header.status,
"content_length" -> response.header.headers.getOrElse(CONTENT_LENGTH, 0),
"content_length" -> response.header.headers.get(CONTENT_LENGTH).map(_.toInt).getOrElse(0),
)
val message =
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ lazy val root = (project in file("."))
s"-J-Dlogs.home=/var/log/${packageName.value}",
),
libraryDependencies ++= Seq(
"software.amazon.awssdk" % "ssm" % "2.23.10",
"net.logstash.logback" % "logstash-logback-encoder" % "7.3",
("com.gu" %% "simple-configuration-ssm" % "1.6.4").cross(CrossVersion.for3Use2_13),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no Scala 3 version of the library yet

"org.scalatestplus.play" %% "scalatestplus-play" % "7.0.1" % Test,
),
)
2 changes: 1 addition & 1 deletion cdk/lib/__snapshots__/gatehouse.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -887,7 +887,7 @@ aws s3 cp 's3://",
"Ref": "DistributionBucketName",
},
"/identity/TEST/gatehouse/gatehouse.deb' '/gatehouse/gatehouse.deb'
dpkg -i /gatehouse/gatehouse.deb && echo "stage=TEST" | sudo tee "/etc/gatehouse/stage.conf" > /dev/null",
dpkg -i /gatehouse/gatehouse.deb",
],
],
},
Expand Down
2 changes: 1 addition & 1 deletion cdk/lib/gatehouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class Gatehouse extends GuStack {
userData: {
distributable: {
fileName: `${ec2App}.deb`,
executionStatement: `dpkg -i /${ec2App}/${ec2App}.deb && echo "stage=${this.stage}" | sudo tee "/etc/gatehouse/stage.conf" > /dev/null`,
executionStatement: `dpkg -i /${ec2App}/${ec2App}.deb`,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we're getting the stage from EC2 tags, we no longer need to write a file with the stage value in it.

},
},
certificateProps: {
Expand Down
1 change: 1 addition & 0 deletions conf/CODE.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include "application.conf"
1 change: 1 addition & 0 deletions conf/DEV.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include "application.conf"
1 change: 1 addition & 0 deletions conf/PROD.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include "application.conf"
5 changes: 1 addition & 4 deletions conf/application.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# https://www.playframework.com/documentation/latest/Configuration
include file("/etc/gatehouse/stage.conf")

play.application.loader=load.AppLoader

play.application.loader = load.AppLoader
play.filters.hosts.routeModifiers.whiteList = [anyhost]