Merge pull request #20252 from ktoso/wip-sni-ktoso

htp #20214 allow workaround for disabling SNI
This commit is contained in:
Konrad Malawski 2016-04-11 11:52:12 +02:00
commit 5fd5a36689
12 changed files with 350 additions and 43 deletions

View file

@ -0,0 +1,49 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.javadsl;
import akka.actor.AbstractActor;
import akka.actor.ActorSystem;
import akka.http.javadsl.*;
import akka.http.javadsl.model.HttpRequest;
import akka.http.javadsl.model.HttpResponse;
import akka.japi.Pair;
import akka.japi.pf.ReceiveBuilder;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.javadsl.Flow;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import com.typesafe.sslconfig.akka.AkkaSSLConfig;
import scala.concurrent.ExecutionContextExecutor;
import scala.util.Try;
import java.util.concurrent.CompletionStage;
import static akka.http.javadsl.ConnectHttp.toHost;
import static akka.pattern.PatternsCS.pipe;
@SuppressWarnings("unused")
public class HttpsExamplesDocTest {
// compile only test
public void testConstructRequest() {
String unsafeHost = "example.com";
//#disable-sni-connection
final ActorSystem system = ActorSystem.create();
final ActorMaterializer mat = ActorMaterializer.create(system);
final Http http = Http.get(system);
// WARNING: disabling SNI is a very bad idea, please don't unless you have a very good reason to.
final AkkaSSLConfig defaultSSLConfig = AkkaSSLConfig.get(system);
final AkkaSSLConfig badSslConfig = defaultSSLConfig
.convertSettings(s -> s.withLoose(s.loose().withDisableSNI(true)));
final HttpsConnectionContext badCtx = http.createClientHttpsContext(badSslConfig);
http.outgoingConnection(ConnectHttp.toHostHttps(unsafeHost).withCustomHttpsContext(badCtx));
//#disable-sni-connection
}
}

View file

@ -45,8 +45,33 @@ to rely on the configured default client-side ``HttpsContext``.
If no custom ``HttpsContext`` is defined the default context uses Java's default TLS settings. Customizing the
``HttpsContext`` can make the Https client less secure. Understand what you are doing!
Detailed configuration and workarounds
--------------------------------------
Akka HTTP relies on `Typesafe SSL-Config`_ which is a library maintained by Lightbend that makes configuring
things related to SSL/TLS much simpler than using the raw SSL APIs provided by the JDK. Please refer to its
documentation to learn more about it.
All configuration options available to this library may be set under the ``akka.ssl-context`` configuration for Akka HTTP applications.
.. note::
When encountering problems connecting to HTTPS hosts we highly encourage to reading up on the excellent ssl-config
configuration. Especially the quick start sections about `adding certificates to the trust store`_ should prove
very useful, for example to easily trust a self-signed certificate that applications might use in development mode.
.. warning::
While it is possible to disable certain checks using the so called "loose" settings in SSL Config, we **strongly recommend**
to instead attempt to solve these issues by properly configuring TLSfor example by adding trusted keys to the keystore.
If however certain checks really need to be disabled because of misconfigured (or legacy) servers that your
application has to speak to, instead of disabling the checks globally (i.e. in ``application.conf``) we suggest
configuring the loose settings for *specific connections* that are known to need them disabled (and trusted for some other reason).
The pattern of doing so is documented in the folowing sub-sections.
.. _adding certificates to the trust store: http://typesafehub.github.io/ssl-config/WSQuickStart.html#connecting-to-a-remote-server-over-https
Hostname verification
---------------------
^^^^^^^^^^^^^^^^^^^^^
Hostname verification proves that the Akka HTTP client is actually communicating with the server it intended to
communicate with. Without this check a man-in-the-middle attack is possible. In the attack scenario, an alternative
@ -57,9 +82,43 @@ The default ``HttpsContext`` enables hostname verification. Akka HTTP relies on
to implement this and security options for SSL/TLS. Hostname verification is provided by the JDK
and used by Akka HTTP since Java 7, and on Java 6 the verification is implemented by ssl-config manually.
.. note::
We highly recommend updating your Java runtime to the latest available release,
preferably JDK 8, as it includes this and many more security features related to TLS.
For further recommended reading we would like to highlight the `fixing hostname verification blog post`_ by blog post by Will Sargent.
.. _Typesafe SSL-Config: https://github.com/typesafehub/ssl-config
.. _Typesafe SSL-Config: http://typesafehub.github.io/ssl-config
.. _fixing hostname verification blog post: https://tersesystems.com/2014/03/23/fixing-hostname-verification/
.. _akka.http.javadsl.Http: @github@/akka-http-core/src/main/scala/akka/http/javadsl/Http.scala
Server Name Indication (SNI)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SNI is an TLS extension which aims to guard against man-in-the-middle attacks. It does so by having the client send the
name of the virtual domain it is expecting to talk to as part of the TLS handshake.
It is specified as part of `RFC 6066`_.
Disabling TLS security features, at your own risk
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. warning::
It is highly discouraged to disable any of the security features of TLS, however do acknowlage that workarounds may sometimes be needed.
Before disabling any of the features one should consider if they may be solvable *within* the TLS world,
for example by `trusting a certificate`_, or `configuring the trusted cipher suites`_.
There's also a very important section in the ssl-config docs titled `LooseSSL - Please read this before turning anything off!`_.
If disabling features is indeed desired, we recommend doing so for *specific connections*,
instead of globally configuring it via ``application.conf``.
The following shows an example of disabling SNI for a given connection:
.. includecode:: ../../code/docs/http/scaladsl/HttpsExamplesSpec.scala
:include: disable-sni-connection
The ``badSslConfig`` is a copy of the default ``AkkaSSLConfig`` with with the slightly changed configuration to disable SNI.
This value can be cached and used for connections which should indeed not use this feature.
.. _RFC 6066: https://tools.ietf.org/html/rfc6066#page-6
.. _LooseSSL - Please read this before turning anything off!: http://typesafehub.github.io/ssl-config/LooseSSL.html#please-read-this-before-turning-anything-off
.. _trusting a certificate: http://typesafehub.github.io/ssl-config/WSQuickStart.html
.. _configuring the trusted cipher suites: http://typesafehub.github.io/ssl-config/CipherSuites.html

View file

@ -0,0 +1,30 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl
import akka.actor.{ ActorLogging, ActorSystem }
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.util.ByteString
import com.typesafe.sslconfig.akka.AkkaSSLConfig
import org.scalatest.{ Matchers, WordSpec }
class HttpsExamplesSpec extends WordSpec with Matchers {
"disable SNI for connection" in {
pending // compile-time only test
val unsafeHost = "example.com"
//#disable-sni-connection
implicit val system = ActorSystem()
implicit val mat = ActorMaterializer()
// WARNING: disabling SNI is a very bad idea, please don't unless you have a very good reason to.
val badSslConfig = AkkaSSLConfig().mapSettings(s => s.withLoose(s.loose.withDisableSNI(true)))
val badCtx = Http().createClientHttpsContext(badSslConfig)
Http().outgoingConnectionHttps(unsafeHost, connectionContext = badCtx)
//#disable-sni-connection
}
}

View file

@ -45,8 +45,33 @@ to rely on the configured default client-side ``HttpsContext``.
If no custom ``HttpsContext`` is defined the default context uses Java's default TLS settings. Customizing the
``HttpsContext`` can make the Https client less secure. Understand what you are doing!
Detailed configuration and workarounds
--------------------------------------
Akka HTTP relies on `Typesafe SSL-Config`_ which is a library maintained by Lightbend that makes configuring
things related to SSL/TLS much simpler than using the raw SSL APIs provided by the JDK. Please refer to its
documentation to learn more about it.
All configuration options available to this library may be set under the ``akka.ssl-context`` configuration for Akka HTTP applications.
.. note::
When encountering problems connecting to HTTPS hosts we highly encourage to reading up on the excellent ssl-config
configuration. Especially the quick start sections about `adding certificates to the trust store`_ should prove
very useful, for example to easily trust a self-signed certificate that applications might use in development mode.
.. warning::
While it is possible to disable certain checks using the so called "loose" settings in SSL Config, we **strongly recommend**
to instead attempt to solve these issues by properly configuring TLSfor example by adding trusted keys to the keystore.
If however certain checks really need to be disabled because of misconfigured (or legacy) servers that your
application has to speak to, instead of disabling the checks globally (i.e. in ``application.conf``) we suggest
configuring the loose settings for *specific connections* that are known to need them disabled (and trusted for some other reason).
The pattern of doing so is documented in the folowing sub-sections.
.. _adding certificates to the trust store: http://typesafehub.github.io/ssl-config/WSQuickStart.html#connecting-to-a-remote-server-over-https
Hostname verification
---------------------
^^^^^^^^^^^^^^^^^^^^^
Hostname verification proves that the Akka HTTP client is actually communicating with the server it intended to
communicate with. Without this check a man-in-the-middle attack is possible. In the attack scenario, an alternative
@ -57,9 +82,40 @@ The default ``HttpsContext`` enables hostname verification. Akka HTTP relies on
to implement this and security options for SSL/TLS. Hostname verification is provided by the JDK
and used by Akka HTTP since Java 7, and on Java 6 the verification is implemented by ssl-config manually.
.. note::
We highly recommend updating your Java runtime to the latest available release,
preferably JDK 8, as it includes this and many more security features related to TLS.
For further recommended reading we would like to highlight the `fixing hostname verification blog post`_ by blog post by Will Sargent.
.. _Typesafe SSL-Config: https://github.com/typesafehub/ssl-config
.. _Typesafe SSL-Config: http://typesafehub.github.io/ssl-config
.. _fixing hostname verification blog post: https://tersesystems.com/2014/03/23/fixing-hostname-verification/
.. _akka.http.scaladsl.Http: @github@/akka-http-core/src/main/scala/akka/http/scaladsl/Http.scala
Server Name Indication (SNI)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SNI is an TLS extension which aims to guard against man-in-the-middle attacks. It does so by having the client send the
name of the virtual domain it is expecting to talk to as part of the TLS handshake.
It is specified as part of `RFC 6066`_.
Disabling TLS security features, at your own risk
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. warning::
It is highly discouraged to disable any of the security features of TLS, however do acknowlage that workarounds may sometimes be needed.
Before disabling any of the features one should consider if they may be solvable *within* the TLS world,
for example by `trusting a certificate`_, or `configuring the trusted cipher suites`_ etc.
If disabling features is indeed desired, we recommend doing so for *specific connections*,
instead of globally configuring it via ``application.conf``.
The following shows an example of disabling SNI for a given connection:
.. includecode:: ../../code/docs/http/scaladsl/HttpsExamplesSpec.scala
:include: disable-sni-connection
The ``badSslConfig`` is a copy of the default ``AkkaSSLConfig`` with with the slightly changed configuration to disable SNI.
This value can be cached and used for connections which should indeed not use this feature.
.. _RFC 6066: https://tools.ietf.org/html/rfc6066#page-6
.. _trusting a certificate: http://typesafehub.github.io/ssl-config/WSQuickStart.html
.. _configuring the trusted cipher suites: http://typesafehub.github.io/ssl-config/CipherSuites.html

View file

@ -12,6 +12,7 @@ import scala.compat.java8.OptionConverters
object ConnectionContext {
//#https-context-creation
// ConnectionContext
/** Used to serve HTTPS traffic. */
def https(sslContext: SSLContext): HttpsConnectionContext =
scaladsl.ConnectionContext.https(sslContext)

View file

@ -12,6 +12,7 @@ import akka.http.javadsl.model.ws._
import akka.http.javadsl.settings.{ ConnectionPoolSettings, ClientConnectionSettings, ServerSettings }
import akka.{ NotUsed, stream }
import akka.stream.TLSProtocol._
import com.typesafe.sslconfig.akka.AkkaSSLConfig
import scala.concurrent.Future
import scala.util.Try
import akka.stream.scaladsl.Keep
@ -657,6 +658,12 @@ class Http(system: ExtendedActorSystem) extends akka.actor.Extension {
def setDefaultClientHttpsContext(context: HttpsConnectionContext): Unit =
delegate.setDefaultClientHttpsContext(context.asInstanceOf[akka.http.scaladsl.HttpsConnectionContext])
def createClientHttpsContext(sslConfig: AkkaSSLConfig): HttpsConnectionContext =
delegate.createClientHttpsContext(sslConfig)
def createDefaultClientHttpsContext(): HttpsConnectionContext =
delegate.createDefaultClientHttpsContext()
private def adaptTupleFlow[T, Mat](scalaFlow: stream.scaladsl.Flow[(scaladsl.model.HttpRequest, T), (Try[scaladsl.model.HttpResponse], T), Mat]): Flow[Pair[HttpRequest, T], Pair[Try[HttpResponse], T], Mat] = {
implicit val _ = JavaMapping.identity[T]
JavaMapping.toJava(scalaFlow)(JavaMapping.flowMapping[Pair[HttpRequest, T], (scaladsl.model.HttpRequest, T), Pair[Try[HttpResponse], T], (Try[scaladsl.model.HttpResponse], T), Mat])

View file

@ -20,6 +20,7 @@ trait ConnectionContext extends akka.http.javadsl.ConnectionContext {
object ConnectionContext {
//#https-context-creation
// ConnectionContext
def https(sslContext: SSLContext,
enabledCipherSuites: Option[immutable.Seq[String]] = None,
enabledProtocols: Option[immutable.Seq[String]] = None,

View file

@ -42,6 +42,7 @@ class HttpExt(private val config: Config)(implicit val system: ActorSystem) exte
import Http._
override val sslConfig = AkkaSSLConfig(system)
validateAndWarnAboutLooseSettings()
private[this] val defaultConnectionPoolSettings = ConnectionPoolSettings(system)
@ -739,7 +740,31 @@ trait DefaultSSLContextCreation {
protected def system: ActorSystem
protected def sslConfig: AkkaSSLConfig
protected def createDefaultClientHttpsContext(): HttpsConnectionContext = {
// --- log warnings ---
private[this] def log = system.log
def validateAndWarnAboutLooseSettings() = {
val WarningAboutGlobalLoose = "This is very dangerous and may expose you to man-in-the-middle attacks. " +
"If you are forced to interact with a server that is behaving such that you must disable this setting, " +
"please disable it for a given connection instead, by configuring a specific HttpsConnectionContext " +
"for use only for the trusted target that hostname verification would have blocked."
if (sslConfig.config.loose.disableHostnameVerification)
log.warning("Detected that Hostname Verification is disabled globally (via ssl-config's akka.ssl-config.loose.disableHostnameVerification) for the Http extension! " +
WarningAboutGlobalLoose)
if (sslConfig.config.loose.disableSNI) {
log.warning("Detected that Server Name Indication (SNI) is disabled globally (via ssl-config's akka.ssl-config.loose.disableSNI) for the Http extension! " +
WarningAboutGlobalLoose)
}
}
// --- end of log warnings ---
def createDefaultClientHttpsContext(): HttpsConnectionContext =
createClientHttpsContext(sslConfig)
def createClientHttpsContext(sslConfig: AkkaSSLConfig): HttpsConnectionContext = {
val config = sslConfig.config
val log = Logging(system, getClass)
@ -776,8 +801,11 @@ trait DefaultSSLContextCreation {
case SslClientAuth.Need Some(TLSClientAuth.Need)
case SslClientAuth.None Some(TLSClientAuth.None)
}
// hostname!
defaultParams.setEndpointIdentificationAlgorithm("https")
if (!sslConfig.config.loose.disableHostnameVerification) {
defaultParams.setEndpointIdentificationAlgorithm("https")
}
new HttpsConnectionContext(sslContext, Some(cipherSuites.toList), Some(defaultProtocols.toList), clientAuth, Some(defaultParams))
}

View file

@ -0,0 +1,72 @@
/**
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.http.scaladsl
import akka.actor.ActorSystem
import akka.event.Logging
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import akka.testkit.{ EventFilter, TestKit, TestProbe }
import com.typesafe.config.{ Config, ConfigFactory }
import org.scalatest.{ Matchers, WordSpec }
import scala.concurrent.Await
import scala.concurrent.duration._
class SslConfigWarningsSpec extends WordSpec with Matchers {
val testConf: Config = ConfigFactory.parseString("""
akka.loglevel = INFO
akka.stdout-loglevel = INFO
akka.log-dead-letters = OFF
akka.http.server.request-timeout = infinite
akka.ssl-config {
loose {
disableSNI = true
disableHostnameVerification = true
}
}
""")
"ssl-config loose options should cause warnings to be logged" should {
"warn if SNI is disabled globally" in {
implicit val system = ActorSystem(getClass.getSimpleName, testConf)
implicit val materializer = ActorMaterializer()
val p = TestProbe()
system.eventStream.subscribe(p.ref, classOf[Logging.LogEvent])
EventFilter.warning(start = "Detected that Server Name Indication (SNI) is disabled globally ", occurrences = 1) intercept {
Http()(system)
}
// the very big warning shall be logged only once per actor system (extension)
EventFilter.warning(start = "Detected that Server Name Indication (SNI) is disabled globally ", occurrences = 0) intercept {
Http()(system)
}
TestKit.shutdownActorSystem(system)
}
"warn if hostname verification is disabled globally" in {
implicit val system = ActorSystem(getClass.getSimpleName, testConf)
implicit val materializer = ActorMaterializer()
val p = TestProbe()
system.eventStream.subscribe(p.ref, classOf[Logging.LogEvent])
val msgStart = "Detected that Hostname Verification is disabled globally "
EventFilter.warning(start = msgStart, occurrences = 1) intercept { Http()(system) }
// the very big warning shall be logged only once per actor system (extension)
EventFilter.warning(start = msgStart, occurrences = 0) intercept { Http()(system) }
TestKit.shutdownActorSystem(system)
}
}
}

View file

@ -157,30 +157,6 @@ private[akka] class TLSActor(settings: ActorMaterializerSettings,
e
}
// since setting a custom HostnameVerified (in JDK8, update 60 still) disables SNI
// see here: https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SNIExamples
// resolves: https://github.com/akka/akka/issues/19287
private def applySNI(hostname: String, params: SSLParameters): Unit = {
val serverName = new SNIHostName(hostname)
params.setServerNames(Collections.singletonList(serverName))
}
private def cloneSSLParameters(parameters: SSLParameters): SSLParameters = {
val clone = new SSLParameters()
clone.setCipherSuites(parameters.getCipherSuites)
clone.setProtocols(parameters.getProtocols)
clone.setWantClientAuth(parameters.getWantClientAuth)
clone.setNeedClientAuth(parameters.getNeedClientAuth)
clone.setEndpointIdentificationAlgorithm(parameters.getEndpointIdentificationAlgorithm)
clone.setAlgorithmConstraints(parameters.getAlgorithmConstraints)
clone.setServerNames(parameters.getServerNames)
clone.setSNIMatchers(parameters.getSNIMatchers)
clone.setUseCipherSuitesOrder(parameters.getUseCipherSuitesOrder)
clone
}
var currentSession = engine.getSession
applySessionParameters(firstSession)
@ -193,12 +169,9 @@ private[akka] class TLSActor(settings: ActorMaterializerSettings,
case Some(TLSClientAuth.Need) engine.setNeedClientAuth(true)
case _ // do nothing
}
params.sslParameters foreach { p
//first copy the mutable SLLParameters before modifying to prevent race condition
val parameters = cloneSSLParameters(p)
hostInfo foreach { case (host, _) applySNI(host, parameters) }
engine.setSSLParameters(parameters)
}
// configure Server Name Indication unless ssl-config disabled it (in which case we already logged many warnings)
applySNI(params)
engine.beginHandshake()
lastHandshakeStatus = engine.getHandshakeStatus
@ -494,4 +467,31 @@ private[akka] class TLSActor(settings: ActorMaterializerSettings,
if (tracing) log.debug(s"STOP Outbound Closed: ${engine.isOutboundDone} Inbound closed: ${engine.isInboundDone}")
context.stop(self)
}
// Additional ssl-config related setup
// since setting a custom HostnameVerified (in JDK8, update 60 still) disables SNI
// see here: https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SNIExamples
// resolves: https://github.com/akka/akka/issues/19287
private def applySNI(params: NegotiateNewSession): Unit = for {
sslParams params.sslParameters
(hostname, _) hostInfo
if !sslConfig.config.loose.disableSNI
} yield {
// first copy the *mutable* SLLParameters before modifying to prevent race condition in `setServerNames`
val clone = new SSLParameters()
clone.setCipherSuites(sslParams.getCipherSuites)
clone.setProtocols(sslParams.getProtocols)
clone.setWantClientAuth(sslParams.getWantClientAuth)
clone.setNeedClientAuth(sslParams.getNeedClientAuth)
clone.setEndpointIdentificationAlgorithm(sslParams.getEndpointIdentificationAlgorithm)
clone.setAlgorithmConstraints(sslParams.getAlgorithmConstraints)
clone.setSNIMatchers(sslParams.getSNIMatchers)
clone.setUseCipherSuitesOrder(sslParams.getUseCipherSuitesOrder)
// apply the changes
clone.setServerNames(Collections.singletonList(new SNIHostName(hostname)))
engine.setSSLParameters(clone)
}
}

View file

@ -53,7 +53,7 @@ object Dependencies {
val reactiveStreams = "org.reactivestreams" % "reactive-streams" % "1.0.0" // CC0
// ssl-config
val sslConfigAkka = "com.typesafe" %% "ssl-config-akka" % "0.1.3" // ApacheV2
val sslConfigAkka = "com.typesafe" %% "ssl-config-akka" % "0.2.1" // ApacheV2
// For akka-http spray-json support
val sprayJson = "io.spray" %% "spray-json" % "1.3.2" // ApacheV2

View file

@ -716,6 +716,10 @@ object MiMa extends AutoPlugin {
// #19390 Add flow monitor
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.stream.scaladsl.FlowOpsMat.monitor"),
// #20214
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.DefaultSSLContextCreation.createClientHttpsContext"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.DefaultSSLContextCreation.validateAndWarnAboutLooseSettings"),
// #20080, #20081 remove race condition on HTTP client
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.http.scaladsl.Http#HostConnectionPool.gatewayFuture"),
ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.http.scaladsl.Http#HostConnectionPool.copy"),