2
0
mirror of https://github.com/VinylDNS/vinyldns synced 2025-08-22 02:02:14 +00:00

refactor: allow assume role with optional externalId

This commit is contained in:
Pedro Kiefer 2022-05-04 16:22:15 -03:00
parent b3312b7e90
commit 4ada2885aa
No known key found for this signature in database
GPG Key ID: B50C634A326C64F7
5 changed files with 134 additions and 20 deletions

View File

@ -30,7 +30,7 @@ import vinyldns.core.domain.{Fqdn, record}
import vinyldns.core.domain.record.{RecordSet, RecordType}
class Route53IntegrationSpec
extends AnyWordSpec
extends AnyWordSpec
with BeforeAndAfterAll
with BeforeAndAfterEach
with Matchers {
@ -52,6 +52,8 @@ class Route53IntegrationSpec
"test",
Option("access"),
Option("secret"),
None,
None,
sys.env.getOrElse("R53_SERVICE_ENDPOINT", "http://localhost:19003"),
"us-east-1"
)

View File

@ -16,13 +16,9 @@
package vinyldns.route53.backend
import java.util.UUID
import cats.data.OptionT
import cats.effect.IO
import com.amazonaws.auth.{
AWSStaticCredentialsProvider,
BasicAWSCredentials,
DefaultAWSCredentialsProviderChain
}
import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration
import com.amazonaws.handlers.AsyncHandler
import com.amazonaws.services.route53.{AmazonRoute53Async, AmazonRoute53AsyncClientBuilder}
@ -278,21 +274,23 @@ object Route53Backend {
r53ClientBuilder.withEndpointConfiguration(
new EndpointConfiguration(config.serviceEndpoint, config.signingRegion)
)
// If either of accessKey or secretKey are empty in conf file; then use AWSCredentialsProviderChain to figure out
// credentials.
// https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html
val credProvider = config.accessKey
.zip(config.secretKey)
.map {
case (key, secret) =>
new AWSStaticCredentialsProvider(
new BasicAWSCredentials(key, secret)
)
}
.headOption
.getOrElse {
new DefaultAWSCredentialsProviderChain()
val r53CredBuilder = Route53Credentials.builder
for {
accessKey <- config.accessKey
secretKey <- config.secretKey
} r53CredBuilder.basicCredentials(accessKey, secretKey)
for (role <- config.roleArn) {
config.externalId match {
case Some(externalId) =>
r53CredBuilder.withRole(role, UUID.randomUUID().toString, externalId)
case None => r53CredBuilder.withRole(role, UUID.randomUUID().toString)
}
}
val credProvider = r53CredBuilder.build().provider
r53ClientBuilder.withCredentials(credProvider).build()
}

View File

@ -0,0 +1,111 @@
/*
* Copyright 2018 Comcast Cable Communications Management, LLC
*
* 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 vinyldns.route53.backend
import com.amazonaws.auth._
import org.slf4j.LoggerFactory
import com.amazonaws.services.securitytoken.{
AWSSecurityTokenService,
AWSSecurityTokenServiceClientBuilder
}
private[backend] sealed trait Route53Credentials extends Serializable {
def provider: AWSCredentialsProvider
}
private[backend] final case object DefaultCredentials extends Route53Credentials {
def provider: AWSCredentialsProvider = new DefaultAWSCredentialsProviderChain
}
private[backend] final case class BasicCredentials(accessKeyId: String, secretKey: String)
extends Route53Credentials {
private final val logger = LoggerFactory.getLogger(classOf[Route53Backend])
def provider: AWSCredentialsProvider =
try {
new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKeyId, secretKey))
} catch {
case e: IllegalArgumentException =>
logger.error(
"Error when using accessKey/secret: {}. Using DefaultProviderChain.",
e.getMessage()
)
new DefaultAWSCredentialsProviderChain
}
}
private[backend] final case class STSCredentials(
roleArn: String,
sessionName: String,
externalId: Option[String] = None,
longLivedCreds: Route53Credentials = DefaultCredentials
) extends Route53Credentials {
def provider: AWSCredentialsProvider = {
lazy val stsClient: AWSSecurityTokenService =
AWSSecurityTokenServiceClientBuilder
.standard()
.withCredentials(longLivedCreds.provider)
.build()
val builder = new STSAssumeRoleSessionCredentialsProvider.Builder(roleArn, sessionName)
.withStsClient(stsClient)
externalId match {
case Some(externalId) =>
builder
.withExternalId(externalId)
.build()
case None =>
builder.build()
}
}
}
object Route53Credentials {
class Builder {
private var basicCreds: Option[BasicCredentials] = None
private var stsCreds: Option[STSCredentials] = None
def basicCredentials(accessKeyId: String, secretKey: String): Builder = {
basicCreds = Option(BasicCredentials(accessKeyId, secretKey))
this
}
def withRole(roleArn: String, sessionName: String): Builder = {
stsCreds = Option(STSCredentials(roleArn, sessionName))
this
}
def withRole(roleArn: String, sessionName: String, externalId: String): Builder = {
stsCreds = Option(
STSCredentials(
roleArn,
sessionName,
Option(externalId)
)
)
this
}
def build(): Route53Credentials =
stsCreds.map(_.copy(longLivedCreds = longLivedCreds)).getOrElse(longLivedCreds)
private def longLivedCreds: Route53Credentials = basicCreds.getOrElse(DefaultCredentials)
}
def builder: Builder = new Builder
}

View File

@ -27,6 +27,8 @@ final case class Route53BackendConfig(
id: String,
accessKey: Option[String],
secretKey: Option[String],
roleArn: Option[String],
externalId: Option[String],
serviceEndpoint: String,
signingRegion: String
)

View File

@ -94,6 +94,7 @@ object Dependencies {
lazy val r53Dependencies = Seq(
"com.amazonaws" % "aws-java-sdk-core" % awsV withSources(),
"com.amazonaws" % "aws-java-sdk-sts" % awsV withSources(),
"com.amazonaws" % "aws-java-sdk-route53" % awsV withSources()
)