Skip to content

Commit

Permalink
Merge branch 'feature/jsonRpcHttpsSupport' into phase/daedalus
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicolas Tallar committed Sep 8, 2017
2 parents 1fa3f01 + 303b052 commit 7cb0545
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 52 deletions.
23 changes: 19 additions & 4 deletions src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -141,16 +141,31 @@ mantis {
}

rpc {
# Whether to enable JSON-RPC HTTP endpoint
# JSON-RPC mode
# Available modes are: http, https
# Choosing https requires creating a certificate and setting up 'certificate-keystore-path' and
# 'certificate-password-file'
# See: https://github.com/input-output-hk/mantis/wiki/Creating-self-signed-certificate-for-using-JSON-RPC-with-HTTPS
mode = "http"

# Whether to enable JSON-RPC endpoint
enabled = true

# Listening address of JSON-RPC HTTP endpoint
# Listening address of JSON-RPC HTTP/HTTPS endpoint
interface = "127.0.0.1"

# Listening port of JSON-RPC HTTP endpoint
# Listening port of JSON-RPC HTTP/HTTPS endpoint
port = 8546

# Enabled JSON-RPC APIs over the HTTP endpoint
# Path to the JKS keystore storing the certificate (used only for https)
# null value indicates no certificate is being used
certificate-keystore-path = null

# File with the password used for accessing the certificate (used only for https)
# null value indicates no certificate is being used
certificate-password-file = null

# Enabled JSON-RPC APIs over the JSON-RPC endpoint
# Available choices are: eth, web3, net, personal
apis = "eth,web3,net"

Expand Down
6 changes: 5 additions & 1 deletion src/main/scala/io/iohk/ethereum/App.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ object App {

peerDiscoveryManager // unlazy

if (jsonRpcHttpServerConfig.enabled) jsonRpcHttpServer.run()
maybeJsonRpcServer match {
case Right(jsonRpcServer) if jsonRpcServerConfig.enabled => jsonRpcServer.run()
case Left(error) if jsonRpcServerConfig.enabled => log.error(error)
case _=> //Nothing
}
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.iohk.ethereum.jsonrpc.server

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import io.iohk.ethereum.jsonrpc._
import io.iohk.ethereum.jsonrpc.server.JsonRpcServer.JsonRpcServerConfig
import io.iohk.ethereum.utils.Logger

import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}

class JsonRpcHttpServer(val jsonRpcController: JsonRpcController, config: JsonRpcServerConfig)
(implicit val actorSystem: ActorSystem)
extends JsonRpcServer with Logger {

def run(): Unit = {
implicit val materializer = ActorMaterializer()

val bindingResultF = Http(actorSystem).bindAndHandle(route, config.interface, config.port)

bindingResultF onComplete {
case Success(serverBinding) => log.info(s"JSON RPC HTTP server listening on ${serverBinding.localAddress}")
case Failure(ex) => log.error("Cannot start JSON HTTP RPC server", ex)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package io.iohk.ethereum.jsonrpc.server

import java.io.{File, FileInputStream}
import java.security.{KeyStore, SecureRandom}
import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManagerFactory}

import akka.actor.ActorSystem
import akka.http.scaladsl.{ConnectionContext, Http}
import akka.stream.ActorMaterializer
import io.iohk.ethereum.jsonrpc.JsonRpcController
import io.iohk.ethereum.jsonrpc.server.JsonRpcHttpsServer.HttpsSetupResult
import io.iohk.ethereum.jsonrpc.server.JsonRpcServer.JsonRpcServerConfig
import io.iohk.ethereum.utils.Logger

import scala.concurrent.ExecutionContext.Implicits.global
import scala.io.Source
import scala.util.{Failure, Success, Try}

class JsonRpcHttpsServer(val jsonRpcController: JsonRpcController, config: JsonRpcServerConfig,
secureRandom: SecureRandom)(implicit val actorSystem: ActorSystem)
extends JsonRpcServer with Logger {

def run(): Unit = {
implicit val materializer = ActorMaterializer()

val maybeSslContext = validateCertificateFiles(config.certificateKeyStorePath, config.certificatePasswordFile).flatMap{
case (certificatePath, passwordFile) =>
val passwordReader = Source.fromFile(passwordFile)
try {
val password = passwordReader.getLines().mkString
obtainSSLContext(certificatePath, password)
} finally {
passwordReader.close()
}
}

val maybeHttpsContext = maybeSslContext.map(sslContext => ConnectionContext.https(sslContext))

maybeHttpsContext match {
case Right(httpsContext) =>
Http().setDefaultServerHttpContext(httpsContext)
val bindingResultF = Http().bindAndHandle(route, config.interface, config.port, connectionContext = httpsContext)

bindingResultF onComplete {
case Success(serverBinding) => log.info(s"JSON RPC HTTPS server listening on ${serverBinding.localAddress}")
case Failure(ex) => log.error("Cannot start JSON HTTPS RPC server", ex)
}
case Left(error) => log.error(s"Cannot start JSON HTTPS RPC server due to: $error")
}
}

/**
* Constructs the SSL context given a certificate
*
* @param certificateKeyStorePath, path to the keystore where the certificate is stored
* @param password for accessing the keystore with the certificate
* @return the SSL context with the obtained certificate or an error if any happened
*/
private def obtainSSLContext(certificateKeyStorePath: String, password: String): HttpsSetupResult[SSLContext] = {
val passwordCharArray: Array[Char] = password.toCharArray

val ks: KeyStore = KeyStore.getInstance("JKS")
val keyStoreInitResult: HttpsSetupResult[Unit] = Option(new FileInputStream(certificateKeyStorePath))
.toRight("Certificate keystore creation failed")
.flatMap { keyStore =>
Try(ks.load(keyStore, passwordCharArray)).toEither.left.map( _.getMessage) }

keyStoreInitResult.map { _ =>
val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(ks, passwordCharArray)

val tmf: TrustManagerFactory = TrustManagerFactory.getInstance("SunX509")
tmf.init(ks)

val sslContext: SSLContext = SSLContext.getInstance("TLS")
sslContext.init(keyManagerFactory.getKeyManagers, tmf.getTrustManagers, secureRandom)
sslContext
}

}

/**
* Validates that the keystore certificate file and password file were configured and that the files exists
*
* @param maybeCertificatePath, with the path to the keystore certificate if it was configured
* @param maybePasswordFile, with the path to the password file if it was configured
* @return the certificate path and password file or the error detected
*/
private def validateCertificateFiles(maybeCertificatePath: Option[String],
maybePasswordFile: Option[String]): HttpsSetupResult[(String, String)] =
(maybeCertificatePath, maybePasswordFile) match {
case (Some(certificatePath), Some(passwordFile)) =>
val certificateFileMissing = !new File(certificatePath).exists()
val passwordFileMissing = !new File(passwordFile).exists()
if(certificateFileMissing && passwordFileMissing)
Left("Certificate keystore path and password file configured but files are missing")
else if(certificateFileMissing)
Left("Certificate keystore path configured but file is missing")
else if(passwordFileMissing)
Left("Certificate password file configured but file is missing")
else
Right(certificatePath -> passwordFile)
case (None, None) => Left("Certificate keystore path and password file configuration required")
case (None, _) => Left("Certificate keystore path configuration required")
case (_, None) => Left("Certificate password file configuration required")
}

}

object JsonRpcHttpsServer {
type HttpsSetupResult[T] = Either[String, T]
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
package io.iohk.ethereum.jsonrpc.http
package io.iohk.ethereum.jsonrpc.server

import java.security.SecureRandom

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.{MalformedRequestContentRejection, RejectionHandler, Route}
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import akka.http.scaladsl.server.{MalformedRequestContentRejection, RejectionHandler, Route}
import ch.megard.akka.http.cors.scaladsl.CorsDirectives._
import ch.megard.akka.http.cors.scaladsl.settings.CorsSettings
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
import io.iohk.ethereum.jsonrpc.http.JsonRpcHttpServer.JsonRpcHttpServerConfig
import io.iohk.ethereum.jsonrpc.{JsonRpcController, JsonRpcErrors, JsonRpcRequest, JsonRpcResponse}
import io.iohk.ethereum.utils.Logger
import org.json4s.JsonAST.JInt
import org.json4s.{DefaultFormats, native}

import scala.util.{Failure, Success}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import ch.megard.akka.http.cors.scaladsl.CorsDirectives._

class JsonRpcHttpServer(jsonRpcController: JsonRpcController, config: JsonRpcHttpServerConfig)
(implicit val actorSystem: ActorSystem)
extends Json4sSupport with Logger {
trait JsonRpcServer extends Json4sSupport {
val jsonRpcController: JsonRpcController

implicit val serialization = native.Serialization

Expand All @@ -46,16 +43,10 @@ class JsonRpcHttpServer(jsonRpcController: JsonRpcController, config: JsonRpcHtt
}
}

def run(): Unit = {
implicit val materializer = ActorMaterializer()

val bindingResultF = Http(actorSystem).bindAndHandle(route, config.interface, config.port)

bindingResultF onComplete {
case Success(serverBinding) => log.info(s"JSON RPC server listening on ${serverBinding.localAddress}")
case Failure(ex) => log.error("Cannot start JSON RPC server", ex)
}
}
/**
* Try to start JSON RPC server
*/
def run(): Unit

private def handleRequest(request: JsonRpcRequest) = {
complete(jsonRpcController.handleRequest(request))
Expand All @@ -64,15 +55,24 @@ class JsonRpcHttpServer(jsonRpcController: JsonRpcController, config: JsonRpcHtt
private def handleBatchRequest(requests: Seq[JsonRpcRequest]) = {
complete(Future.sequence(requests.map(request => jsonRpcController.handleRequest(request))))
}

}

object JsonRpcHttpServer {
object JsonRpcServer extends Logger {

def apply(jsonRpcController: JsonRpcController, config: JsonRpcServerConfig, secureRandom: SecureRandom)
(implicit actorSystem: ActorSystem): Either[String, JsonRpcServer] = config.mode match {
case "http" => Right(new JsonRpcHttpServer(jsonRpcController, config)(actorSystem))
case "https" => Right(new JsonRpcHttpsServer(jsonRpcController, config, secureRandom)(actorSystem))
case _ => Left(s"Cannot start JSON RPC server: Invalid mode ${config.mode} selected")
}

trait JsonRpcHttpServerConfig {
trait JsonRpcServerConfig {
val mode: String
val enabled: Boolean
val interface: String
val port: Int
val certificateKeyStorePath: Option[String]
val certificatePasswordFile: Option[String]
}

}
10 changes: 5 additions & 5 deletions src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import io.iohk.ethereum.db.components.{SharedLevelDBDataSources, Storages}
import io.iohk.ethereum.db.storage.AppStateStorage
import io.iohk.ethereum.db.storage.pruning.PruningMode
import io.iohk.ethereum.domain.{Blockchain, BlockchainImpl}
import io.iohk.ethereum.jsonrpc.server.JsonRpcServer.JsonRpcServerConfig
import io.iohk.ethereum.jsonrpc.NetService.NetServiceConfig
import io.iohk.ethereum.ledger.{Ledger, LedgerImpl}
import io.iohk.ethereum.network.{PeerManagerActor, ServerActor}
import io.iohk.ethereum.jsonrpc._
import io.iohk.ethereum.jsonrpc.http.JsonRpcHttpServer
import io.iohk.ethereum.jsonrpc.http.JsonRpcHttpServer.JsonRpcHttpServerConfig
import io.iohk.ethereum.jsonrpc.server.JsonRpcServer
import io.iohk.ethereum.keystore.{KeyStore, KeyStoreImpl}
import io.iohk.ethereum.mining.BlockGenerator
import io.iohk.ethereum.network.PeerManagerActor.PeerConfiguration
Expand Down Expand Up @@ -326,11 +326,11 @@ trait JSONRpcControllerBuilder {

trait JSONRpcHttpServerBuilder {

self: ActorSystemBuilder with BlockChainBuilder with JSONRpcControllerBuilder =>
self: ActorSystemBuilder with BlockChainBuilder with JSONRpcControllerBuilder with SecureRandomBuilder =>

lazy val jsonRpcHttpServerConfig: JsonRpcHttpServerConfig = Config.Network.Rpc
lazy val jsonRpcServerConfig: JsonRpcServerConfig = Config.Network.Rpc

lazy val jsonRpcHttpServer = new JsonRpcHttpServer(jsonRpcController, jsonRpcHttpServerConfig)
lazy val maybeJsonRpcServer = JsonRpcServer(jsonRpcController, jsonRpcServerConfig, secureRandom)
}

trait OmmersPoolBuilder {
Expand Down
9 changes: 7 additions & 2 deletions src/main/scala/io/iohk/ethereum/utils/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import io.iohk.ethereum.db.dataSource.LevelDbConfig
import io.iohk.ethereum.db.storage.pruning.{ArchivePruning, BasicPruning, PruningMode}
import io.iohk.ethereum.domain.{Address, UInt256}
import io.iohk.ethereum.jsonrpc.JsonRpcController.JsonRpcConfig
import io.iohk.ethereum.jsonrpc.http.JsonRpcHttpServer.JsonRpcHttpServerConfig
import io.iohk.ethereum.jsonrpc.server.JsonRpcServer.JsonRpcServerConfig
import io.iohk.ethereum.network.PeerManagerActor.{FastSyncHostConfiguration, PeerConfiguration}
import io.iohk.ethereum.network.rlpx.RLPxConnectionHandler.RLPxConfiguration
import io.iohk.ethereum.utils.NumericUtils._
Expand Down Expand Up @@ -76,9 +76,11 @@ object Config {
override val updateNodesInterval: FiniteDuration = peerConfig.getDuration("update-nodes-interval").toMillis.millis
}

object Rpc extends JsonRpcHttpServerConfig with JsonRpcConfig {
object Rpc extends JsonRpcServerConfig with JsonRpcConfig {
private val rpcConfig = networkConfig.getConfig("rpc")

val mode = rpcConfig.getString("mode")

val enabled = rpcConfig.getBoolean("enabled")
val interface = rpcConfig.getString("interface")
val port = rpcConfig.getInt("port")
Expand All @@ -90,6 +92,9 @@ object Config {
providedApis
}

val certificateKeyStorePath: Option[String] = Try(rpcConfig.getString("certificate-keystore-path")).toOption
val certificatePasswordFile: Option[String] = Try(rpcConfig.getString("certificate-password-file")).toOption

}

}
Expand Down
Loading

0 comments on commit 7cb0545

Please sign in to comment.