Akka Version :
Akka功能:
语言:Scala
我正在使用Akka 2.4.7的Http Server功能在不同的端口上提供多个 HTTPS 服务连接。 在这个阶段,要求actor系统的这个组件在一个JVM中托管几个HTTPS服务 - 它是一个连接和集成其他服务的后端。
我想使用Typesafe's ssl-config库来配置每个HTTPS服务器。我该怎么做(我的尝试不成功)?
对于每个服务,我在application.conf中定义了ssl-config配置块。 nexted配置的一个例子是:
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"}
]
}
}
}
}
我使用 application.conf 中定义的 my-service 的HOCON路径获取配置的这一部分,并将其与参考默认配置合并以创建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()
}
现在有一个SSLConfigSettings我现在可以创建一个 AkkaSSLConfig 对象,而 Akka 2.4.7 可以用来创建一个HttpsConnectionContext函数原型:
//#HTTPS的上下文创建 // ConnectionContext def https( sslContext:SSLContext, sslConfig:选项[AkkaSSLConfig] =无, enabledCipherSuites:选项[immutable.Seq [String]] =无, enabledProtocols:选项[immutable.Seq [String]] =无, clientAuth:选项[TLSClientAuth] =无, sslParameters:选项[SSLParameters] =无)= 新的HttpsConnectionContext(sslContext,sslConfig,enabledCipherSuites,enabledProtocols,clientAuth,sslParameters) //#HTTPS的上下文创建
因此,我可以使用以下之类的代码启动HTTPS服务器(注意:请求handeler在其他地方定义,提供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()
服务器无异常或错误启动,并在定义的端口 8991 上绑定到 127.0.0.1 。
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]
我使用浏览器或curl访问服务器,结果不佳。它要求我知道错误的客户端证书,因为我已经在ssl-conf中明确配置它们不需要它们并且JDK8中的ssl-conf设置为默认情况下不需要它。
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
使用带有_s_client_选项的 openssl 的进一步调查显示,没有发生SSL握手且没有返回证书,尽管知道密钥库是好的并且在其他地方工作。
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
---
Akka登录调试模式显示没有异常,并且已建立TCP连接,TLS actor开始然后立即停止。
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
在运行时调试显示正在拾取密钥库:
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)"
如果我手动创建HttpsConnectionContext并且不使用ssl-conf或AkkaSSLConfig,那么有效是什么 - 但这不是目标。如何使用AkkaSSLconf对象和Typesafe ssl-config库配置和创建HTTPS配置连接?
如果我特意要求提供TLS上下文的 TLS 实例,请执行以下操作:
val sslCtx = SSLContext.getInstance("TLS")
我得到一个异常,即sslContext没有初始化。但是对于init
SSLContext,我需要创建密钥库,信任库,这一切都很好,但感觉我忽略了已经拥有所有这些东西的ssl-conf库的所有优点定义
我发现您可以使用以下方法创建HTTPS连接上下文:
Http().createServerHttpsContext(akkaSSLConfig)
您可以使用AkkaSSLConfig创建HTTPS服务器上下文,这是我追求的好东西。问题在于测试HTTPS服务器不起作用,它只挂起1分钟,但例外情况为:
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)
我在GitHub here上的Akka回购中查看了createServerHttpsContext的源代码,发现:
// 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)
}
为什么HTTPS服务器不能与createServerHttpsContext(..)
一起使用?特别是考虑到手动你基本上设置了一个TLS SSLContext,KeyManagerFactory (带有密钥存储区),一个SecureRandom实例,然后关闭。
答案 0 :(得分:4)
正如其他评论中所述,有一个git hub问题,表明尚未支持“自动”使用该配置。但是,这个问题现在已经结束;尚未完成moved。我查看了未来版本的发行说明,但我没有看到任何与此相关的内容。现在非常注重安全性,我很惊讶SSL / TSL的设置不是开箱即用的。
我正在使用v2.4.4(当前版本是2.4.16)并且与提问者类似,我发现了一个很难的方法,虽然akk-http文档告诉你使用配置,实际上从调试中你可以看到读取配置,实际使用它的实现,没有完成。我在日志中收到了这条消息:
akka.actor.ActorSystemImpl(OtisRestActorSystem)] 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)
我尝试使用他们的ssl配置“手动构建服务器HttpsConnectionContext”,但我无法让它工作。
还有其他消息,当我最初进行故障排除时,显示它在配置的密钥存储区中读取(它不使用类路径来查找它,因此它最初无法找到它)。所以我不确定哪些部件正在工作,哪些部件缺失。所以我最终完全放弃了akka-http ssl配置并自己设置,因为我的用例非常简单。我只想启用服务器端SSL / TSL。
在我的配置中,我有:
ssl {
keyStoreFileName = "myKeyFile.p12"
keyStorePassword = "myPassword"
}
为了阅读我的设置,我有:
class Settings(config: Config) extends Extension {
object Ssl {
var KeyStoreFileName = config.getString("ssl.keyStoreFileName")
var KeyStorePassword = config.getString("ssl.keyStorePassword")
}
}
对于“App”,我有:
object RestWebServiceApp extends App with RouteConcatenation {
import akka.event.{Logging, LoggingAdapter}
import akka.http.scaladsl.{ ConnectionContext, HttpsConnectionContext, Http }
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model.MediaTypes._
import akka.stream.{ActorMaterializer, ActorMaterializerSettings}
import java.io.InputStream
import java.security.{ SecureRandom, KeyStore }
import javax.net.ssl.{ SSLContext, TrustManagerFactory, KeyManagerFactory }
import JsonSupport._
implicit val system = ActorSystem("OtisRestActorSystem")
implicit val materializer: ActorMaterializer = ActorMaterializer(ActorMaterializerSettings(system))
implicit val ec = system.dispatcher
... //setting up all the routes, etc.
val settings = Settings(system)
val fileName = settings.Ssl.KeyStoreFileName
val keyFile: InputStream = getClass.getClassLoader.getResourceAsStream(fileName)
require(keyFile != null, s"Failed to load key file: ${settings.Ssl.KeyStoreFileName}")
val extension = if(fileName.lastIndexOf('.')>0) fileName.substring(fileName.lastIndexOf('.')+1) else ""
val keyStore: KeyStore = extension.toLowerCase match {
case "jks" => KeyStore.getInstance("jks") //Java Key Store; Java default and only works with Java; tested
case "jcek" => KeyStore.getInstance("JCEKS") //Java Cryptography Extension KeyStore; Java 1.4+; not tested
case "pfx" | "p12" => KeyStore.getInstance("PKCS12") // PKCS #12, Common and supported by many languages/frameworks; tested
case _ => throw new IllegalArgumentException(s"Key has an unknown type extension $extension. Support types are jks, jcek, pfx, p12.")
}
val password: Array[Char] = (if(settings.Ssl.KeyStorePassword==null) "" else settings.Ssl.KeyStorePassword).toCharArray
keyStore.load(keyFile, password)
//TODO: looks like the "SunX509", "TLS", are defined in the keystore, should we pull them out rather than hard coding?
val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(keyStore, password)
val tmf: TrustManagerFactory = TrustManagerFactory.getInstance("SunX509")
tmf.init(keyStore)
val sslContext: SSLContext = SSLContext.getInstance("TLS")
sslContext.init(keyManagerFactory.getKeyManagers, tmf.getTrustManagers, new SecureRandom)
val https: HttpsConnectionContext = ConnectionContext.https(sslContext)
Http().setDefaultServerHttpContext(https)
Http().bindAndHandle(routes, "localhost", 433, connectionContext = https)
}
答案 1 :(得分:1)
尝试回答这个问题:因为它还没有实现:)有一个open issue at github。
答案 2 :(得分:0)
当您的HTTPS服务器挂起1分钟,然后产生“由于上游故障而中止tcp连接:在最后1分钟没有传递任何元素。”错误,可能是你的随机数生成器没有足够的熵来在合理的时间内产生足够的随机数。
在我的情况下,我在初始化new SecureRandom
时使用SecureRandom.getInstanceString
代替SSLContext
来“修复”此问题。这对我来说似乎是随机/安全的,但当然你应该自己决定。