diff --git a/build.sbt b/build.sbt index bc0ac1d..f632d82 100644 --- a/build.sbt +++ b/build.sbt @@ -24,6 +24,8 @@ libraryDependencies ++= Seq( "com.sksamuel.elastic4s" %% "elastic4s-http-streams" % elastic4sVersion, ) +libraryDependencies += "com.pauldijou" %% "jwt-core" % "1.0.0" + libraryDependencies += "org.parboiled" %% "parboiled" % "2.1.4" libraryDependencies += "io.spray" %% "spray-json" % "1.3.3" libraryDependencies += "org.scalactic" %% "scalactic" % "3.0.4" diff --git a/src/main/scala/de/upb/cs/swt/delphi/instancemanagement/InstanceRegistry.scala b/src/main/scala/de/upb/cs/swt/delphi/instancemanagement/InstanceRegistry.scala index 3aa6b28..2623bab 100644 --- a/src/main/scala/de/upb/cs/swt/delphi/instancemanagement/InstanceRegistry.scala +++ b/src/main/scala/de/upb/cs/swt/delphi/instancemanagement/InstanceRegistry.scala @@ -20,9 +20,11 @@ import java.net.InetAddress import akka.http.scaladsl.Http import akka.http.scaladsl.model._ +import akka.http.scaladsl.model.headers.RawHeader import akka.http.scaladsl.unmarshalling.Unmarshal import akka.util.ByteString import de.upb.cs.swt.delphi.instancemanagement.InstanceEnums.{ComponentType, InstanceState} +import de.upb.cs.swt.delphi.webapi.authorization.AuthProvider import de.upb.cs.swt.delphi.webapi.{AppLogging, Configuration, _} import spray.json._ @@ -92,7 +94,10 @@ object InstanceRegistry extends InstanceJsonSupport with AppLogging { method = HttpMethods.POST, configuration.instanceRegistryUri + ReportOperationType.toOperationUriString(operationType, id)) - Await.result(Http(system).singleRequest(request) map { response => + val useGenericNameForToken = operationType == ReportOperationType.Start //Must use generic name for startup, no id known at that point + + Await.result(Http(system).singleRequest(request.withHeaders(RawHeader("Authorization", + s"Bearer ${AuthProvider.generateJwt(useGenericName = useGenericNameForToken)}"))) map { response => if (response.status == StatusCodes.OK) { log.info(s"Successfully reported ${operationType.toString} to Instance Registry.") Success() @@ -146,7 +151,7 @@ object InstanceRegistry extends InstanceJsonSupport with AppLogging { configuration.instanceRegistryUri + s"/matchingInstance?Id=${configuration.assignedID.getOrElse(-1)}&ComponentType=ElasticSearch") - Await.result(Http(system).singleRequest(request) map { response => + Await.result(Http(system).singleRequest(request.withHeaders(RawHeader("Authorization",s"Bearer ${AuthProvider.generateJwt()}"))) map { response => response.status match { case StatusCodes.OK => try { @@ -189,7 +194,7 @@ object InstanceRegistry extends InstanceJsonSupport with AppLogging { configuration.instanceRegistryUri + s"/matchingResult?CallerId=${configuration.assignedID.getOrElse(-1)}&MatchedInstanceId=$idToPost&MatchingSuccessful=$isElasticSearchReachable") - Await.result(Http(system).singleRequest(request) map { response => + Await.result(Http(system).singleRequest(request.withHeaders(RawHeader("Authorization",s"Bearer ${AuthProvider.generateJwt()}"))) map { response => if (response.status == StatusCodes.OK) { log.info(s"Successfully posted matching result to Instance Registry.") Success() @@ -216,7 +221,7 @@ object InstanceRegistry extends InstanceJsonSupport with AppLogging { val request = HttpRequest(method = HttpMethods.POST, configuration.instanceRegistryUri + s"/deregister?Id=$id") - Await.result(Http(system).singleRequest(request) map { response => + Await.result(Http(system).singleRequest(request.withHeaders(RawHeader("Authorization",s"Bearer ${AuthProvider.generateJwt()}"))) map { response => if (response.status == StatusCodes.OK) { log.info("Successfully deregistered from Instance Registry.") Success() @@ -237,7 +242,8 @@ object InstanceRegistry extends InstanceJsonSupport with AppLogging { def postInstance(instance: Instance, uri: String)(): Future[HttpResponse] = { try { val request = HttpRequest(method = HttpMethods.POST, uri = uri, entity = instance.toJson(instanceFormat).toString()) - Http(system).singleRequest(request) + //Use generic name for startup, no id present at this point + Http(system).singleRequest(request.withHeaders(RawHeader("Authorization",s"Bearer ${AuthProvider.generateJwt(useGenericName = true)}"))) } catch { case dx: DeserializationException => log.warning(s"Failed to deregister to Instance Registry, exception: $dx") diff --git a/src/main/scala/de/upb/cs/swt/delphi/webapi/Configuration.scala b/src/main/scala/de/upb/cs/swt/delphi/webapi/Configuration.scala index 84de7ed..9573eae 100644 --- a/src/main/scala/de/upb/cs/swt/delphi/webapi/Configuration.scala +++ b/src/main/scala/de/upb/cs/swt/delphi/webapi/Configuration.scala @@ -88,6 +88,8 @@ class Configuration( //Server and Elasticsearch configuration } lazy val instanceId: Option[Long] = InstanceRegistry.handleInstanceStart(configuration = this) + val jwtSecretKey: String = sys.env.getOrElse("DELPHI_JWT_SECRET","changeme") + } diff --git a/src/main/scala/de/upb/cs/swt/delphi/webapi/Server.scala b/src/main/scala/de/upb/cs/swt/delphi/webapi/Server.scala index 824200d..2f2684b 100644 --- a/src/main/scala/de/upb/cs/swt/delphi/webapi/Server.scala +++ b/src/main/scala/de/upb/cs/swt/delphi/webapi/Server.scala @@ -31,17 +31,13 @@ object Server extends HttpApp with JsonSupport with AppLogging { def main(args: Array[String]): Unit = { - sys.addShutdownHook({ - log.warning("Received shutdown signal.") - InstanceRegistry.handleInstanceStop(configuration) - }) StartupCheck.check(configuration) Server.startServer(configuration.bindHost, configuration.bindPort, system) - val terminationFuture = system.terminate() + InstanceRegistry.handleInstanceStop(configuration) - terminationFuture.onComplete { + system.terminate().onComplete{ sys.exit(0) } } diff --git a/src/main/scala/de/upb/cs/swt/delphi/webapi/authorization/AuthProvider.scala b/src/main/scala/de/upb/cs/swt/delphi/webapi/authorization/AuthProvider.scala new file mode 100644 index 0000000..f03ad12 --- /dev/null +++ b/src/main/scala/de/upb/cs/swt/delphi/webapi/authorization/AuthProvider.scala @@ -0,0 +1,35 @@ +// Copyright (C) 2018 The Delphi Team. +// See the LICENCE file distributed with this work for additional +// information regarding copyright ownership. +// +// 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 de.upb.cs.swt.delphi.webapi.authorization + +import de.upb.cs.swt.delphi.webapi +import pdi.jwt.{Jwt, JwtAlgorithm, JwtClaim} + +object AuthProvider { + + def generateJwt(validFor: Long = 1, useGenericName: Boolean = false): String = { + val claim = JwtClaim() + .issuedNow + .expiresIn(validFor * 60) + .startsNow + . + ("user_id", if (useGenericName) webapi.configuration.instanceName else s"${webapi.configuration.assignedID.get}") + . + ("user_type", "Component") + + + Jwt.encode(claim, webapi.configuration.jwtSecretKey, JwtAlgorithm.HS256) + } + +}