hq/app/AppComponents.scala (158 lines of code) (raw):
import aws.ec2.EC2
import aws.{AWS, AwsClient}
import config.Config
import controllers._
import db.IamRemediationDb
import filters.HstsFilter
import model.{AwsAccount, DEV, PROD}
import play.api.ApplicationLoader.Context
import play.api.libs.ws.WSClient
import play.api.libs.ws.ahc.AhcWSComponents
import play.api.mvc.{AnyContent, BodyParser, ControllerComponents}
import play.api.routing.Router
import play.api.{BuiltInComponentsFromContext, Logging}
import play.filters.csrf.CSRFComponents
import router.Routes
import services.{CacheService, IamRemediationService, MetricService}
import utils.attempt.Attempt
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.jdk.CollectionConverters._
import scala.language.postfixOps
import software.amazon.awssdk.core.client.config.ClientAsyncConfiguration
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider
import software.amazon.awssdk.http.async.SdkAsyncHttpClient
import software.amazon.awssdk.http.SdkHttpConfigurationOption
import software.amazon.awssdk.services.ec2.Ec2AsyncClient
import software.amazon.awssdk.services.sns.SnsAsyncClient
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.dynamodb.DynamoDbClient
import software.amazon.awssdk.services.dynamodb.endpoints.{DynamoDbEndpointProvider, DynamoDbEndpointParams}
import software.amazon.awssdk.services.ssm.SsmClient
import software.amazon.awssdk.utils.AttributeMap
import software.amazon.awssdk.core.internal.http.loader.DefaultSdkAsyncHttpClientBuilder
class AppComponents(context: Context)
extends BuiltInComponentsFromContext(context)
with CSRFComponents
with AhcWSComponents
with AssetsComponents
with Logging {
implicit val impWsClient: WSClient = wsClient
implicit val impPlayBodyParser: BodyParser[AnyContent] =
playBodyParsers.default
implicit val impControllerComponents: ControllerComponents =
controllerComponents
implicit val impAssetsFinder: AssetsFinder = assetsFinder
override lazy val httpFilters = Seq(
csrfFilter,
new HstsFilter()
)
private val stack = configuration.get[String]("stack")
private val stage = Config.getStage(configuration)
// the aim of this is to get all the regions that are available to this account
private val availableRegions: List[Region] = {
val ec2Client = AwsClient(
Ec2AsyncClient.builder
.region(Config.region)
.build(),
AwsAccount(stack, stack, stack, stack),
Config.region
)
try {
val availableRegionsAttempt: Attempt[List[Region]] = for {
ec2RegionList <- EC2.getAvailableRegions(ec2Client)
regionList = ec2RegionList.map(ec2Region =>
Region.of(ec2Region.regionName)
)
} yield regionList
Await
.result(availableRegionsAttempt.asFuture, 30 seconds)
.getOrElse(List(Config.region, Region.of("us-east-1")))
} finally {
ec2Client.client.close()
}
}
logger.info(
s"Polling in the following regions: ${availableRegions.map(_.id).mkString(", ")}"
)
val regionsNotInSdk: Set[String] = availableRegions
.map(_.id)
.toSet -- Region.regions.asScala.map(_.id).toSet
if (regionsNotInSdk.nonEmpty) {
logger.warn(
s"Regions exist that are not in the current SDK (${regionsNotInSdk.mkString(", ")}), update your SDK!"
)
}
private val cfnClients = AWS.cfnClients(configuration, availableRegions)
private val taClients = AWS.taClients(configuration)
private val s3Clients = AWS.s3Clients(configuration, availableRegions)
private val iamClients = AWS.iamClients(configuration, availableRegions)
private val securityCredentialsProvider: AwsCredentialsProviderChain = AwsCredentialsProviderChain.of(
ProfileCredentialsProvider.create("security"),
DefaultCredentialsProvider.create()
)
/*
The casting from SdkHttpConfigurationOption[Integer] to AttributeMap.Key[Any] is required because Scala compiler comlains
Integer <: Any, but Java-defined class Key is invariant in type T.
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 3.2.10)
*/
private val MAX_10_CONNECTIONS: AttributeMap = AttributeMap.builder().put(SdkHttpConfigurationOption.MAX_CONNECTIONS.asInstanceOf[AttributeMap.Key[Any]], 10).build()
private val securitySnsClient = SnsAsyncClient.builder
.credentialsProvider(securityCredentialsProvider)
.region(Config.region)
.httpClient(new DefaultSdkAsyncHttpClientBuilder().buildWithDefaults(MAX_10_CONNECTIONS))
.build()
private val securitySsmClient = SsmClient.builder
.credentialsProvider(securityCredentialsProvider)
.region(Config.region)
.build()
private val googleAuthConfig =
Config.googleSettings(stage, stack, configuration, securitySsmClient)
private val securityDynamoDbClient = stage match {
case PROD =>
DynamoDbClient.builder()
.credentialsProvider(securityCredentialsProvider)
.region(Config.region)
.build()
case DEV =>
DynamoDbClient.builder()
.credentialsProvider(securityCredentialsProvider)
.region(Config.region)
.endpointOverride(new java.net.URI("http://localhost:8000")) //An alternative could be to configure a specific builder DynamoDbEndpointParams.builder().endpoint("http://localhost:8000").region(Config.region)
.build()
}
private val securityS3Client = S3Client
.builder
.credentialsProvider(securityCredentialsProvider)
.region(Config.region)
.build()
private val cacheService = new CacheService(
configuration,
applicationLifecycle,
environment,
cfnClients,
taClients,
s3Clients,
iamClients,
availableRegions
)
new MetricService(
configuration,
applicationLifecycle,
environment,
cacheService
)
new IamRemediationService(
cacheService,
securitySnsClient,
new IamRemediationDb(securityDynamoDbClient),
configuration,
iamClients,
applicationLifecycle,
environment,
securityS3Client
)(executionContext)
override def router: Router = new Routes(
httpErrorHandler,
new HQController(configuration, googleAuthConfig),
new AuthController(environment, configuration, googleAuthConfig),
assets
)
}