From 183fa2c1a11e3313dc1d5b5851d4f432d7c9ff72 Mon Sep 17 00:00:00 2001 From: Ezequiel Surijon Date: Wed, 4 Jul 2018 13:08:26 -0300 Subject: [PATCH] Add support for AWS Elasticsearch Service by signing request properly. Credentials are gathered from auth configuration, AWS access key maps to username and AWS secret maps to password configs AWS Region is infered from host name --- app/elastic/AwsSigner.scala | 84 +++++++++++++++++++++++++++++ app/elastic/HTTPElasticClient.scala | 3 +- build.sbt | 1 + 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 app/elastic/AwsSigner.scala diff --git a/app/elastic/AwsSigner.scala b/app/elastic/AwsSigner.scala new file mode 100644 index 00000000..2f8cc8f8 --- /dev/null +++ b/app/elastic/AwsSigner.scala @@ -0,0 +1,84 @@ +package elastic + +import java.net.URI + +import com.amazonaws.DefaultRequest +import com.amazonaws.auth.AWS4Signer +import com.amazonaws.auth.AWSCredentials +import com.amazonaws.auth.BasicAWSCredentials +import com.amazonaws.http.HttpMethodName +import scala.collection.JavaConverters._ +import java.io.StringReader +import java.io.ByteArrayInputStream +import java.io.InputStream + +object AwsSigner { + + def sing(method: String, url: String, headers: Seq[(String, String)], body: Option[String], secret: String, key: String) : Seq[(String, String)] = { + + val uri = URI.create(url) + + if(uri.getHost.endsWith(".es.amazonaws.com")) { + + val credentials: AWSCredentials = new BasicAWSCredentials(secret, key) + + + val host = if(uri.getPort() == -1) { + s"""${uri.getScheme}://${uri.getHost()}""" + } else { + s"""${uri.getScheme}://${uri.getHost()}:${uri.getPort()}""" + } + + val pattern = """^https:\/\/.+\.(.+)\.es\.amazonaws\.com$""".r + + val region = host match { + case pattern(region) => region + } + + val endpoint = uri.getPath(); + val queryStringOpt = Option(uri.getQuery()); + + val params = queryStringOpt + .map( queryString => { + queryString + .split("&") + .map( text => { + val keyVal = text.split("=", 2) + keyVal(0) -> Seq(keyVal(1)).asJava + }) + .toMap + }) + .getOrElse(Map.empty) + .asJava + + val content: InputStream = new ByteArrayInputStream(body.getOrElse("").getBytes) + + val request = new DefaultRequest[Unit]("es") + request.setHttpMethod(HttpMethodName.fromValue(method)) + request.setEndpoint(URI.create(host)) + request.setResourcePath(endpoint) + request.setHeaders(headers.toMap[String, String].asJava) + request.setParameters(params) + request.setContent(content) + + val signer = new AWS4Signer() + signer.setRegionName(region); + signer.setServiceName("es"); + signer.sign(request, credentials); + + val hdrs = request.getHeaders(); + + Seq( + "Host" -> hdrs.get("Host"), + "Authorization" -> hdrs.get("Authorization"), + "X-Amz-Date" -> hdrs.get("X-Amz-Date") + ) + + } else { + Seq.empty + } + + } + + +} \ No newline at end of file diff --git a/app/elastic/HTTPElasticClient.scala b/app/elastic/HTTPElasticClient.scala index 80a05c21..fa6e422c 100644 --- a/app/elastic/HTTPElasticClient.scala +++ b/app/elastic/HTTPElasticClient.scala @@ -324,7 +324,8 @@ class HTTPElasticClient @Inject()(client: WSClient) extends ElasticClient { val request = authentication.foldLeft(client.url(url).withMethod(method).withHttpHeaders(headers: _*)) { case (request, auth) => - request.withAuth(auth.username, auth.password, WSAuthScheme.BASIC) + val awsHeaders = AwsSigner.sing(method, url, headers, body, auth.username, auth.password) + request.addHttpHeaders(awsHeaders: _*) } body.fold(request)(request.withBody((_))).execute.map { response => diff --git a/build.sbt b/build.sbt index 59edd1c3..7b46a163 100644 --- a/build.sbt +++ b/build.sbt @@ -12,6 +12,7 @@ libraryDependencies ++= Seq( "com.typesafe.play" %% "play-slick" % "3.0.3", "com.typesafe.play" %% "play-slick-evolutions" % "3.0.3", "org.xerial" % "sqlite-jdbc" % "3.23.1", + "com.amazonaws" % "aws-java-sdk-core" % "1.11.359", "org.specs2" %% "specs2-junit" % "3.9.2" % "test", "org.specs2" %% "specs2-core" % "3.9.2" % "test", "org.specs2" %% "specs2-mock" % "3.9.2" % "test"