Skip to content

Commit

Permalink
Merge pull request #3134 from rossabaker/issue-2499-jetty
Browse files Browse the repository at this point in the history
Support SSLParameters in jetty-server
  • Loading branch information
aeons committed Jan 31, 2020
2 parents 35df259 + 7a94bd1 commit d7f70be
Showing 1 changed file with 112 additions and 56 deletions.
168 changes: 112 additions & 56 deletions jetty/src/main/scala/org/http4s/server/jetty/JettyBuilder.scala
Expand Up @@ -3,9 +3,10 @@ package server
package jetty

import cats.effect._
import cats.implicits._
import java.net.InetSocketAddress
import java.util
import javax.net.ssl.SSLContext
import javax.net.ssl.{SSLContext, SSLParameters}
import javax.servlet.{DispatcherType, Filter}
import javax.servlet.http.HttpServlet
import org.eclipse.jetty.server.{ServerConnector, Server => JServer}
Expand All @@ -15,6 +16,7 @@ import org.eclipse.jetty.util.component.{AbstractLifeCycle, LifeCycle}
import org.eclipse.jetty.util.ssl.SslContextFactory
import org.eclipse.jetty.util.thread.{QueuedThreadPool, ThreadPool}
import org.http4s.server.SSLKeyStoreSupport.StoreInfo
import org.http4s.server.jetty.JettyBuilder._
import org.http4s.servlet.{AsyncHttp4sServlet, ServletContainer, ServletIo}
import org.http4s.syntax.all._
import org.log4s.getLogger
Expand All @@ -28,7 +30,7 @@ sealed class JettyBuilder[F[_]] private (
private val asyncTimeout: Duration,
shutdownTimeout: Duration,
private val servletIo: ServletIo[F],
sslBits: Option[SSLConfig],
sslConfig: SslConfig,
mounts: Vector[Mount[F]],
private val serviceErrorHandler: ServiceErrorHandler[F],
banner: immutable.Seq[String]
Expand All @@ -46,7 +48,7 @@ sealed class JettyBuilder[F[_]] private (
asyncTimeout: Duration = asyncTimeout,
shutdownTimeout: Duration = shutdownTimeout,
servletIo: ServletIo[F] = servletIo,
sslBits: Option[SSLConfig] = sslBits,
sslConfig: SslConfig = sslConfig,
mounts: Vector[Mount[F]] = mounts,
serviceErrorHandler: ServiceErrorHandler[F] = serviceErrorHandler,
banner: immutable.Seq[String] = banner
Expand All @@ -58,7 +60,7 @@ sealed class JettyBuilder[F[_]] private (
asyncTimeout,
shutdownTimeout,
servletIo,
sslBits,
sslConfig,
mounts,
serviceErrorHandler,
banner)
Expand All @@ -70,13 +72,30 @@ sealed class JettyBuilder[F[_]] private (
trustStore: Option[StoreInfo] = None,
clientAuth: SSLClientAuthMode = SSLClientAuthMode.NotRequested
): Self =
copy(
sslBits = Some(KeyStoreBits(keyStore, keyManagerPassword, protocol, trustStore, clientAuth)))
copy(sslConfig =
new KeyStoreBits(keyStore, keyManagerPassword, protocol, trustStore, clientAuth))

@deprecated(
"Use `withSslContext` (note lowercase). To request client certificates, use `withSslContextAndParameters, calling either `.setWantClientAuth(true)` or `setNeedClientAuth(true)` on the `SSLParameters`.",
"0.21.0-RC3")
def withSSLContext(
sslContext: SSLContext,
clientAuth: SSLClientAuthMode = SSLClientAuthMode.NotRequested): Self =
copy(sslBits = Some(SSLContextBits(sslContext, clientAuth)))
copy(sslConfig = new ContextWithClientAuth(sslContext, clientAuth))

/** Configures the server with TLS, using the provided `SSLContext` and its
* default `SSLParameters` */
def withSslContext(sslContext: SSLContext): Self =
copy(sslConfig = new ContextOnly(sslContext))

/** Configures the server with TLS, using the provided `SSLContext` and its
* default `SSLParameters` */
def withSslContextAndParameters(sslContext: SSLContext, sslParameters: SSLParameters): Self =
copy(sslConfig = new ContextWithParameters(sslContext, sslParameters))

/** Disables SSL. */
def withoutSsl: Self =
copy(sslConfig = NoSsl)

override def bindSocketAddress(socketAddress: InetSocketAddress): Self =
copy(socketAddress = socketAddress)
Expand Down Expand Up @@ -142,56 +161,13 @@ sealed class JettyBuilder[F[_]] private (
def withBanner(banner: immutable.Seq[String]): Self =
copy(banner = banner)

private def getConnector(jetty: JServer): ServerConnector = {
def httpsConnector(sslContextFactory: SslContextFactory) =
new ServerConnector(
jetty,
sslContextFactory
)

sslBits match {
case Some(KeyStoreBits(keyStore, keyManagerPassword, protocol, trustStore, clientAuth)) =>
// SSL Context Factory
val sslContextFactory = new SslContextFactory.Server()
sslContextFactory.setKeyStorePath(keyStore.path)
sslContextFactory.setKeyStorePassword(keyStore.password)
sslContextFactory.setKeyManagerPassword(keyManagerPassword)
sslContextFactory.setProtocol(protocol)
updateClientAuth(sslContextFactory, clientAuth)

trustStore.foreach { trustManagerBits =>
sslContextFactory.setTrustStorePath(trustManagerBits.path)
sslContextFactory.setTrustStorePassword(trustManagerBits.password)
}

httpsConnector(sslContextFactory)

case Some(SSLContextBits(sslContext, clientAuth)) =>
val sslContextFactory = new SslContextFactory.Server()
sslContextFactory.setSslContext(sslContext)
updateClientAuth(sslContextFactory, clientAuth)

httpsConnector(sslContextFactory)

private def getConnector(jetty: JServer): ServerConnector =
sslConfig.makeSslContextFactory match {
case Some(sslContextFactory) =>
new ServerConnector(jetty, sslContextFactory)
case None =>
new ServerConnector(jetty)
}
}

private def updateClientAuth(
sslContextFactory: SslContextFactory.Server,
clientAuthMode: SSLClientAuthMode): Unit =
clientAuthMode match {
case SSLClientAuthMode.NotRequested =>
sslContextFactory.setWantClientAuth(false)
sslContextFactory.setNeedClientAuth(false)

case SSLClientAuthMode.Requested =>
sslContextFactory.setWantClientAuth(true)

case SSLClientAuthMode.Required =>
sslContextFactory.setNeedClientAuth(true)
}

def resource: Resource[F, Server[F]] =
Resource(F.delay {
Expand Down Expand Up @@ -231,7 +207,7 @@ sealed class JettyBuilder[F[_]] private (
new InetSocketAddress(host, port)
}

lazy val isSecure: Boolean = sslBits.isDefined
lazy val isSecure: Boolean = sslConfig.isSecure
}

banner.foreach(logger.info(_))
Expand Down Expand Up @@ -261,11 +237,91 @@ object JettyBuilder {
asyncTimeout = defaults.ResponseTimeout,
shutdownTimeout = defaults.ShutdownTimeout,
servletIo = ServletContainer.DefaultServletIo,
sslBits = None,
sslConfig = NoSsl,
mounts = Vector.empty,
serviceErrorHandler = DefaultServiceErrorHandler,
banner = defaults.Banner
)

private sealed trait SslConfig {
def makeSslContextFactory: Option[SslContextFactory.Server]
def isSecure: Boolean
}

private class KeyStoreBits(
keyStore: StoreInfo,
keyManagerPassword: String,
protocol: String,
trustStore: Option[StoreInfo],
clientAuth: SSLClientAuthMode
) extends SslConfig {
def makeSslContextFactory: Option[SslContextFactory.Server] = {
val sslContextFactory = new SslContextFactory.Server()
sslContextFactory.setKeyStorePath(keyStore.path)
sslContextFactory.setKeyStorePassword(keyStore.password)
sslContextFactory.setKeyManagerPassword(keyManagerPassword)
sslContextFactory.setProtocol(protocol)
updateClientAuth(sslContextFactory, clientAuth)

trustStore.foreach { trustManagerBits =>
sslContextFactory.setTrustStorePath(trustManagerBits.path)
sslContextFactory.setTrustStorePassword(trustManagerBits.password)
}
sslContextFactory.some
}
def isSecure = true
}

private class ContextWithClientAuth(sslContext: SSLContext, clientAuth: SSLClientAuthMode)
extends SslConfig {
def makeSslContextFactory: Option[SslContextFactory.Server] = {
val sslContextFactory = new SslContextFactory.Server()
sslContextFactory.setSslContext(sslContext)
updateClientAuth(sslContextFactory, clientAuth)
sslContextFactory.some
}
def isSecure = true
}

private class ContextOnly(sslContext: SSLContext) extends SslConfig {
def makeSslContextFactory: Option[SslContextFactory.Server] = {
val sslContextFactory = new SslContextFactory.Server()
sslContextFactory.setSslContext(sslContext)
sslContextFactory.some
}
def isSecure = true
}

private class ContextWithParameters(sslContext: SSLContext, sslParameters: SSLParameters)
extends SslConfig {
def makeSslContextFactory: Option[SslContextFactory.Server] = {
val sslContextFactory = new SslContextFactory.Server()
sslContextFactory.setSslContext(sslContext)
sslContextFactory.customize(sslParameters)
sslContextFactory.some
}
def isSecure = true
}

private object NoSsl extends SslConfig {
def makeSslContextFactory: Option[SslContextFactory.Server] = None
def isSecure = false
}

private def updateClientAuth(
sslContextFactory: SslContextFactory.Server,
clientAuthMode: SSLClientAuthMode): Unit =
clientAuthMode match {
case SSLClientAuthMode.NotRequested =>
sslContextFactory.setWantClientAuth(false)
sslContextFactory.setNeedClientAuth(false)

case SSLClientAuthMode.Requested =>
sslContextFactory.setWantClientAuth(true)

case SSLClientAuthMode.Required =>
sslContextFactory.setNeedClientAuth(true)
}
}

private final case class Mount[F[_]](f: (ServletContextHandler, Int, JettyBuilder[F]) => Unit)

0 comments on commit d7f70be

Please sign in to comment.