Akka Version:
- Akka 2.4.7
Akka Features:
HTTPS Server Support
Typesafe's ssl-config
Language: Scala
I am using the Http Server feature of Akka 2.4.7 to provide several HTTPS service connections on different ports. At this stage the requirement is for this component of the actor system to host several of the HTTPS services in the one JVM - it is a backend that connects and integrates other services.
Question:
I want to use the Typesafe's ssl-config library to configure each HTTPS server. How do I do this (I am unsuccessful in my attempts)?
What I have tried:
For each service I have defined ssl-config configuration blocks in application.conf. An example of a nexted configuration is :
my-service {
ssl-config = {
debug {
all = true
}
sslParameters {
clientAuth : "none"
}
ssl = {
keyManager = {
stores = [
{path: tmp/certs/autumn/devhost.jks, password: "not-real-password", type: "JKS"}
]
}
}
}
}
I grab this portion of the config from using the HOCON path for my-service defined in application.conf and merge it with the reference default configuration to create a SSLConfigSettings.
def parseSslConfig(config: Config): SSLConfigSettings = {
val cfg = config.withFallback(ConfigFactory.defaultReference().getConfig("ssl-config"))
val parser = new SSLConfigParser(EnrichedConfig(cfg), getClass.getClassLoader)
parser.parse()
}
Now having an SSLConfigSettings I now can create an AkkaSSLConfig object which in turn, in Akka 2.4.7, can be used to create a HttpsConnectionContext using the function prototype:
//#https-context-creation // ConnectionContext def https( sslContext: SSLContext, sslConfig: Option[AkkaSSLConfig] = None, enabledCipherSuites: Option[immutable.Seq[String]] = None, enabledProtocols: Option[immutable.Seq[String]] = None, clientAuth: Option[TLSClientAuth] = None, sslParameters: Option[SSLParameters] = None) = new HttpsConnectionContext(sslContext, sslConfig, enabledCipherSuites, enabledProtocols, clientAuth, sslParameters) //#https-context-creation
So simply I can fire up a HTTPS server with code like the following (note: the request handeler is defined elsewhere providing the Future[HttpResponse]
)
val akkaSSLConfig: AkkaSSLConfig = AkkaSSLConfig().withSettings(sslConfigSettings)
val serverConnectionContext = ConnectionContext.https(SSLContext.getDefault, Some(akkaSSLConfig))
val httpServer = httpServerSystem.bind(interface = "127.0.0.1",
port = 8991,
connectionContext = serverConnectionContext)
val bindingFuture: Future[Http.ServerBinding] = httpServer.to(Sink.foreach {
connection =>
system.log.info(s"Accepted HTTP connection " +
s"[Local: address=${connection.localAddress.getAddress.getHostAddress}, port=${connection.localAddress.getPort};" +
s" Remote: address=${connection.remoteAddress.getAddress.getHostAddress} port=${connection.remoteAddress.getPort}]" + connection.remoteAddress)
connection.handleWithAsyncHandler(httpRequest => requestHandler(httpRequest, connection.localAddress, connection.remoteAddress))
}).run()
The server starts up without exception or error and binds to 127.0.0.1 on the defined port 8991.
2016-06-11 14:07:51,403 DEBUG [autumn-backend-akka.actor.default-dispatcher-7] TcpListener - Successfully bound to /127.0.0.1:8991
2016-06-11 14:07:51,404 DEBUG [autumn-backend-akka.actor.default-dispatcher-7] TcpListener - started (akka.io.TcpListener@3d1d819f)
2016-06-11 14:07:51,404 DEBUG [autumn-backend-akka.actor.default-dispatcher-7] TcpListener - now watched by Actor[akka://autumn-backend/system/IO-TCP/selectors/$a#-745039521]
2016-06-11 14:07:51,407 DEBUG [autumn-backend-akka.actor.default-dispatcher-5] TcpListener - now watched by Actor[akka://autumn-backend/user/StreamSupervisor-0/$$a#-672917867]
I access the server using a browser or curl and the result is not good. It's asking for a client certificate which I know is wrong, as I have explicitly configured in ssl-conf that they are not needed and ssl-conf in JDK8 sets to this to not needed by default.
curl -v https://localhost:8991
* Rebuilt URL to: https://localhost:8991/
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8991 (#0)
* SSL peer handshake failed, the server most likely requires a client certificate to connect
* Closing connection 0
curl: (35) SSL peer handshake failed, the server most likely requires a client certificate to connect
Further investigation with openssl with _s_client_ option shows that there is no SSL handshake occurring and there are no certificates being returned, despite know that the keystore is good and works elsewhere.
openssl s_client -showcerts -connect localhost:8991
CONNECTED(00000003)
140735299473488:error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure:s23_clnt.c:769:
---
no peer certificate available
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 317 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
---
The Akka log in debug mode shows not exceptions and that a TCP connection has been made, a TLS actor starts and then stop immediately.
2016-06-11 14:09:26,378 DEBUG [autumn-backend-akka.actor.default-dispatcher-6] TcpListener - New connection accepted
2016-06-11 14:09:26,378 DEBUG [autumn-backend-akka.actor.default-dispatcher-9] SelectionHandler - now supervising Actor[akka://autumn-backend/system/IO-TCP/selectors/$a/9#1252313265]
2016-06-11 14:09:26,378 DEBUG [autumn-backend-akka.actor.default-dispatcher-5] TcpIncomingConnection - started (akka.io.TcpIncomingConnection@6f12f120)
2016-06-11 14:09:26,378 DEBUG [autumn-backend-akka.actor.default-dispatcher-5] TcpIncomingConnection - now watched by Actor[akka://autumn-backend/system/IO-TCP/selectors/$a#-745039521]
2016-06-11 14:09:26,381 INFO [autumn-backend-akka.actor.default-dispatcher-7] ActorSystemImpl - Accepted HTTP connection [Local: address=127.0.0.1, port=8991; Remote: address=127.0.0.1 port=58726]/127.0.0.1:58726
2016-06-11 14:09:26,384 DEBUG [autumn-backend-akka.actor.default-dispatcher-9] StreamSupervisor - now supervising Actor[akka://autumn-backend/user/StreamSupervisor-0/flow-9-0-unknown-operation#149184815]
2016-06-11 14:09:26,385 DEBUG [autumn-backend-akka.actor.default-dispatcher-7] TcpIncomingConnection - now watched by Actor[akka://autumn-backend/user/StreamSupervisor-0/$$j#-1999211380]
2016-06-11 14:09:26,385 DEBUG [autumn-backend-akka.actor.default-dispatcher-9] ActorGraphInterpreter - started (akka.stream.impl.fusing.ActorGraphInterpreter@57451dc8)
2016-06-11 14:09:26,385 DEBUG [autumn-backend-akka.actor.default-dispatcher-5] StreamSupervisor - now supervising Actor[akka://autumn-backend/user/StreamSupervisor-0/flow-9-1-unknown-operation#1511230856]
sslConfig.config.loose.disableSNI = false
2016-06-11 14:09:26,387 DEBUG [autumn-backend-akka.actor.default-dispatcher-5] TLSActor - started (akka.stream.impl.io.TLSActor@50f220e8)
2016-06-11 14:09:26,389 DEBUG [autumn-backend-akka.actor.default-dispatcher-5] TLSActor - stopped
Debugging at runtime shows that the keystore is being picked up:
akkaSSLConfig = {com.typesafe.sslconfig.akka.AkkaSSLConfig@7851}
system = {akka.actor.ActorSystemImpl@7850} "akka://autumn-backend"
config = {com.typesafe.sslconfig.ssl.SSLConfigSettings@7849} "SSLConfig(None,SSLDebugConfig(false,false,false,None,false,false,false,false,None,false,false,false,false,false),false,Vector(RSA keySize < 2048, DSA keySize < 2048, EC keySize < 224),Vector(MD2, MD4, MD5),None,Some(Vector(TLSv1.2, TLSv1.1, TLSv1)),class com.typesafe.sslconfig.ssl.DefaultHostnameVerifier,KeyManagerConfig(SunX509,List(KeyStoreConfig(None,Some(config/certs/autumn/devhost.jks),Some(A8C7B78Ymb),JKS))),SSLLooseConfig(false,None,None,false,false,false,false),TLSv1.2,None,None,SSLParametersConfig(Default,Vector()),TrustManagerConfig(PKIX,List()))"
default = false
protocol = "TLSv1.2"
checkRevocation = {scala.None$@7905} "None"
revocationLists = {scala.None$@7905} "None"
enabledCipherSuites = {scala.None$@7905} "None"
enabledProtocols = {scala.Some@7906} "Some(Vector(TLSv1.2, TLSv1.1, TLSv1))"
disabledSignatureAlgorithms = {scala.collection.immutable.Vector@7907} "Vector" size = 3
disabledKeyAlgorithms = {scala.collection.immutable.Vector@7911} "Vector" size = 3
sslParametersConfig = {com.typesafe.sslconfig.ssl.SSLParametersConfig@7917} "SSLParametersConfig(Default,Vector())"
keyManagerConfig = {com.typesafe.sslconfig.ssl.KeyManagerConfig@7918} "KeyManagerConfig(SunX509,List(KeyStoreConfig(None,Some(config/certs/autumn/devhost.jks),Some(A8C7B78Ymb),JKS)))"
algorithm = "SunX509"
keyStoreConfigs = {scala.collection.immutable.$colon$colon@7942} "::" size = 1
0 = {com.typesafe.sslconfig.ssl.KeyStoreConfig@9390} "KeyStoreConfig(None,Some(config/certs/autumn/devhost.jks),Some(not-real-password),JKS)"
What works is if I create a HttpsConnectionContext manually and don't use ssl-conf or AkkaSSLConfig - but that's not the goal. How do I configure and create a HTTPS config connection using an AkkaSSLconf object and the Typesafe ssl-config library?
UPDATE 1:
If I specifically ask for a TLS instance of the TLS context like this:
val sslCtx = SSLContext.getInstance("TLS")
I get an exception that the sslContext is not initialised. But to init
the SSLContext, I need to create the keystore, truststore, which is all well and fine, but it feels like I am ignoring all of the goodness of the ssl-conf library that has all of this stuff already defined.
Update 2:
I found that you can create the HTTPS Connection Context with the following method:
Http().createServerHttpsContext(akkaSSLConfig)
You can create the HTTPS Server context using the AkkaSSLConfig which is the good stuff I am after. Problem is in testing the HTTPS Server doesn't work, it just hangs for 1 minute with the exception:
2016-06-12 11:14:53,222 DEBUG [autumn-backend-akka.actor.default-dispatcher-12] RepointableActorRef - Aborting tcp connection because of upstream failure: No elements passed in the last 1 minute.
akka.stream.impl.Timers$IdleTimeoutBidi$$anon$7.onTimer(Timers.scala:160)
akka.stream.stage.TimerGraphStageLogic.akka$stream$stage$TimerGraphStageLogic$$onInternalTimer(GraphStage.scala:1125)
akka.stream.stage.TimerGraphStageLogic$$anonfun$akka$stream$stage$TimerGraphStageLogic$$getTimerAsyncCallback$1.apply(GraphStage.scala:1114)
akka.stream.stage.TimerGraphStageLogic$$anonfun$akka$stream$stage$TimerGraphStageLogic$$getTimerAsyncCallback$1.apply(GraphStage.scala:1114)
akka.stream.impl.fusing.GraphInterpreter.runAsyncInput(GraphInterpreter.scala:572)
akka.stream.impl.fusing.GraphInterpreterShell.receive(ActorGraphInterpreter.scala:420)
akka.stream.impl.fusing.ActorGraphInterpreter.akka$stream$impl$fusing$ActorGraphInterpreter$$processEvent(ActorGraphInterpreter.scala:604)
akka.stream.impl.fusing.ActorGraphInterpreter$$anonfun$receive$1.applyOrElse(ActorGraphInterpreter.scala:619)
akka.actor.Actor$class.aroundReceive(Actor.scala:484)
I looked at the source for createServerHttpsContext on the Akka repo on GitHub here and found:
// currently the same configuration as client by default, however we should tune this for server-side apropriately (!)
def createServerHttpsContext(sslConfig: AkkaSSLConfig): HttpsConnectionContext = {
log.warning("Automatic server-side configuration is not supported yet, will attempt to use client-side settings. " +
"Instead it is recommended to construct the Servers HttpsConnectionContext manually (via SSLContext).")
createClientHttpsContext(sslConfig)
}
Why doesn't the HTTPS server work with the createServerHttpsContext(..)
? Especially given that manually you basically set a TLS SSLContext, KeyManagerFactory (with key stores), an instance of SecureRandom and off you go.