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

aws secrets provider #153

Merged
merged 7 commits into from
Feb 16, 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
7 changes: 7 additions & 0 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* limitations under the License.
*/


import sbt._

object Dependencies {
Expand Down Expand Up @@ -66,6 +67,7 @@ object Dependencies {
val http4sBlazeBackend = "0.23.15"
val playJson = "3.0.1"

val awssdk = "2.23.15"
}


Expand Down Expand Up @@ -137,6 +139,7 @@ object Dependencies {
val playOrg = "org.playframework"
val sbtOrg = "com.github.sbt"
val logbackOrg = "ch.qos.logback"
val awsSdkOrg = "software.amazon.awssdk"

// zio
lazy val zioCore = zioOrg %% "zio" % Versions.zio
Expand All @@ -162,6 +165,9 @@ object Dependencies {
// Fa-db
lazy val faDbDoobie = faDbOrg %% "doobie" % Versions.fadb

// aws
lazy val awsSecretsManagerSdk = awsSdkOrg % "secretsmanager" % Versions.awssdk

// testing
lazy val zioTest = zioOrg %% "zio-test" % Versions.zio % Test
lazy val zioTestSbt = zioOrg %% "zio-test-sbt" % Versions.zio % Test
Expand All @@ -183,6 +189,7 @@ object Dependencies {
tapirSwagger,
tapirPlayJson,
playJson,
awsSecretsManagerSdk,
zioTest,
zioTestSbt,
zioTestJunit,
Expand Down
4 changes: 4 additions & 0 deletions server/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
# maximum number of connections that HikariCP will keep in the pool, including both idle and in-use connections
maxPoolSize=10
}
aws {
region = "af-south-1"
dbPasswordSecretName = "serviceUserSecretKey"
}
ssl {
enabled=false
keyStorePassword=password
Expand Down
4 changes: 3 additions & 1 deletion server/src/main/scala/za/co/absa/atum/server/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
package za.co.absa.atum.server

import za.co.absa.atum.server.api.controller._
import za.co.absa.atum.server.api.database.runs.functions.{CreatePartitioningIfNotExists, WriteCheckpoint}
import za.co.absa.atum.server.api.database.{PostgresDatabaseProvider, TransactorProvider}
import za.co.absa.atum.server.api.database.runs.functions.{CreatePartitioningIfNotExists, WriteCheckpoint}
import za.co.absa.atum.server.api.http.Server
import za.co.absa.atum.server.api.repository.{CheckpointRepositoryImpl, PartitioningRepositoryImpl}
import za.co.absa.atum.server.api.service.{CheckpointServiceImpl, PartitioningServiceImpl}
import za.co.absa.atum.server.aws.AwsSecretsProviderImpl
import zio._
import zio.config.typesafe.TypesafeConfigProvider
import zio.logging.backend.SLF4J
Expand All @@ -43,6 +44,7 @@ object Main extends ZIOAppDefault with Server {
WriteCheckpoint.layer,
PostgresDatabaseProvider.layer,
TransactorProvider.layer,
AwsSecretsProviderImpl.layer,
zio.Scope.default
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,39 @@ package za.co.absa.atum.server.api.database

import com.zaxxer.hikari.HikariConfig
import doobie.hikari.HikariTransactor
import za.co.absa.atum.server.config.PostgresConfig
import za.co.absa.atum.server.aws.AwsSecretsProvider
import za.co.absa.atum.server.config.{AwsConfig, PostgresConfig}
import zio.Runtime.defaultBlockingExecutor
import zio._
import zio.interop.catz._

object TransactorProvider {

val layer: ZLayer[Any with Scope, Throwable, HikariTransactor[Task]] = ZLayer {
val layer: ZLayer[Any with Scope with AwsSecretsProvider, Throwable, HikariTransactor[Task]] = ZLayer {
for {
postgresConfig <- ZIO.config[PostgresConfig](PostgresConfig.config)
awsConfig <- ZIO.config[AwsConfig](AwsConfig.config)

awsSecretsProvider <- ZIO.service[AwsSecretsProvider]
password <- awsSecretsProvider.getSecretValue(awsConfig.dbPasswordSecretName)
// fallback to password property's value from postgres section of reference.conf; useful for local testing
.orElse {
ZIO.logError("Credentials were not retrieved from AWS, falling back to config value.")
.as(postgresConfig.password)
}

hikariConfig = {
val config = new HikariConfig()
config.setDriverClassName(postgresConfig.dataSourceClass)
config.setJdbcUrl(
s"jdbc:postgresql://${postgresConfig.serverName}:${postgresConfig.portNumber}/${postgresConfig.databaseName}"
)
config.setUsername(postgresConfig.user)
config.setPassword(postgresConfig.password)
config.setPassword(password)
config.setMaximumPoolSize(postgresConfig.maxPoolSize)
config
}

xa <- HikariTransactor
.fromHikariConfig[Task](hikariConfig, defaultBlockingExecutor.asExecutionContext)
.toScopedZIO
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2021 ABSA Group Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package za.co.absa.atum.server.aws

import zio.Task
import zio.macros.accessible

@accessible
trait AwsSecretsProvider {
def getSecretValue(secretName: String): Task[String]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2021 ABSA Group Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package za.co.absa.atum.server.aws

import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest
import za.co.absa.atum.server.config.AwsConfig
import zio._

class AwsSecretsProviderImpl(secretsManagerClient: SecretsManagerClient) extends AwsSecretsProvider {
override def getSecretValue(secretName: String): Task[String] = {
ZIO.attempt {
val getSecretValueRequest = GetSecretValueRequest.builder().secretId(secretName).build()
secretsManagerClient.getSecretValue(getSecretValueRequest).secretString()
}
}
}

object AwsSecretsProviderImpl {
val layer: ZLayer[Any, Config.Error, AwsSecretsProviderImpl] = ZLayer {
for {
awsConfig <- ZIO.config[AwsConfig](AwsConfig.config)
} yield new AwsSecretsProviderImpl(SecretsManagerClient.builder().region(Region.of(awsConfig.region)).build())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2021 ABSA Group Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package za.co.absa.atum.server.config

import zio.Config
import zio.config.magnolia.deriveConfig

case class AwsConfig(
region: String,
dbPasswordSecretName: String
)

object AwsConfig {
val config: Config[AwsConfig] = deriveConfig[AwsConfig].nested("aws")
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package za.co.absa.atum.server.api
package za.co.absa.atum.server

import zio.config.typesafe.TypesafeConfigProvider
import zio.test.ZIOSpec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ package za.co.absa.atum.server.api.database.runs.functions

import org.junit.runner.RunWith
import za.co.absa.atum.model.dto.{PartitionDTO, PartitioningSubmitDTO}
import za.co.absa.atum.server.ConfigProviderSpec
import za.co.absa.atum.server.api.database.PostgresDatabaseProvider
import za.co.absa.atum.server.api.{ConfigProviderSpec, TestTransactorProvider}
import za.co.absa.atum.server.api.TestTransactorProvider
import zio.test._
import zio.test.junit.ZTestJUnitRunner
import zio._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ package za.co.absa.atum.server.api.database.runs.functions
import org.junit.runner.RunWith
import za.co.absa.atum.model.dto.MeasureResultDTO.{ResultValueType, TypedValue}
import za.co.absa.atum.model.dto._
import za.co.absa.atum.server.ConfigProviderSpec
import za.co.absa.atum.server.api.database.PostgresDatabaseProvider
import za.co.absa.atum.server.api.{ConfigProviderSpec, TestTransactorProvider}
import za.co.absa.atum.server.api.TestTransactorProvider
import za.co.absa.fadb.exceptions.DataNotFoundException
import za.co.absa.fadb.status.FunctionStatus
import zio.test._
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2021 ABSA Group Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package za.co.absa.atum.server.aws

import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.{mock, when}
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient
import software.amazon.awssdk.services.secretsmanager.model.{GetSecretValueRequest, GetSecretValueResponse}
import za.co.absa.atum.server.ConfigProviderSpec
import za.co.absa.atum.server.config.AwsConfig
import zio.test._
import zio.test.junit.ZTestJUnitRunner
import zio.{Scope, ZIO, ZLayer}

@RunWith(classOf[ZTestJUnitRunner])
class AwsSecretsProviderSpec extends ConfigProviderSpec {

private val secretsManagerClientMock = mock(classOf[SecretsManagerClient])

private val dummySecretValue = "expectedValue"
private val mockedResponse = GetSecretValueResponse.builder().secretString(dummySecretValue).build()

when(secretsManagerClientMock.getSecretValue(any[GetSecretValueRequest]())).thenReturn(mockedResponse)

private val testAwsSecretsProviderLayer = ZLayer.succeed(new AwsSecretsProviderImpl(secretsManagerClientMock))

override def spec: Spec[TestEnvironment with Scope, Any] = {

suite("AwsSecretsProviderSuite")(
test("GetSecretValue returns expected secret's value"){
for {
awsConfig <- ZIO.config[AwsConfig](AwsConfig.config)
awsSecretValue <- AwsSecretsProvider.getSecretValue(awsConfig.dbPasswordSecretName)
} yield assertTrue(dummySecretValue == awsSecretValue)
}
)

}.provide(
testAwsSecretsProviderLayer
)

}
Loading