Permalink
Browse files

! can, http: Add SSLSessionInfo header to requests on server and resp…

…onses on client
  • Loading branch information...
mpilquist committed Sep 9, 2013
1 parent 80982d4 commit e48690062296816d8238b58f3cb91872e42cd6a2
@@ -283,6 +283,11 @@ spray.can {
# in chunks regardless of whether chunking is actually used on the wire.
# Set to infinite to disable auto chunking.
incoming-auto-chunking-threshold-size = infinite
+
+ # Enables/disables inclusion of an SSL-Session-Info header in parsed
+ # messages over SSL transports (i.e., HttpRequest on server side and
+ # HttpResponse on client side).
+ ssl-session-info-header = off
}
# Fully qualified config path which holds the dispatcher configuration
@@ -20,6 +20,7 @@ package client
import scala.concurrent.duration.Duration
import akka.actor.{ SupervisorStrategy, ReceiveTimeout, ActorRef }
import akka.io.{ Tcp, IO }
+import spray.can.parsing.SSLSessionInfoSupport
import spray.http.{ SetRequestTimeout, Confirmed, HttpRequestPart }
import spray.io._
@@ -81,10 +82,11 @@ private[can] object HttpClientConnection {
import settings._
ClientFrontend(requestTimeout) >>
ResponseChunkAggregation(responseChunkAggregationLimit) ? (responseChunkAggregationLimit > 0) >>
+ SSLSessionInfoSupport ? parserSettings.sslSessionInfoHeader >>
ResponseParsing(parserSettings) >>
RequestRendering(settings) >>
ConnectionTimeouts(idleTimeout) ? (reapingCycle.isFinite && idleTimeout.isFinite) >>
- SslTlsSupport(false) >>
+ SslTlsSupport(parserSettings.sslSessionInfoHeader) >>
TickGenerator(reapingCycle) ? (idleTimeout.isFinite || requestTimeout.isFinite)
}
@@ -33,6 +33,7 @@ case class ParserSettings(
autoChunkingThreshold: Long,
uriParsingMode: Uri.ParsingMode,
illegalHeaderWarnings: Boolean,
+ sslSessionInfoHeader: Boolean,
headerValueCacheLimits: Map[String, Int]) {
require(maxUriLength > 0, "max-uri-length must be > 0")
@@ -67,6 +68,7 @@ object ParserSettings extends SettingsCompanion[ParserSettings]("spray.can.parsi
c getPossiblyInfiniteLongBytes "incoming-auto-chunking-threshold-size",
Uri.ParsingMode(c getString "uri-parsing-mode"),
c getBoolean "illegal-header-warnings",
+ c getBoolean "ssl-session-info-header",
cacheConfig.entrySet.asScala.map(kvp kvp.getKey -> cacheConfig.getInt(kvp.getKey))(collection.breakOut))
}
}
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2011-2013 spray.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package spray.can
+package parsing
+
+import spray.can.server.RequestParsing
+import spray.io._
+import spray.http.HttpMessageStart
+import spray.http.HttpHeaders.`SSL-Session-Info`
+import spray.util.SSLSessionInfo
+
+/** Pipeline stage that adds the `SSL-Session-Info` header to incoming HTTP messages. */
+object SSLSessionInfoSupport extends PipelineStage {
+
+ def apply(context: PipelineContext, commandPL: CPL, eventPL: EPL): Pipelines =
+ new Pipelines {
+ var sslSessionInfo: Option[`SSL-Session-Info`] = None
+
+ def addSessionInfoHeader(message: HttpMessageStart): HttpMessageStart =
+ sslSessionInfo.fold(message) { info message.mapHeaders { hdrs info :: hdrs } }
+
+ val commandPipeline: CPL = commandPL
+
+ val eventPipeline: EPL = {
+ case SslTlsSupport.SSLSessionEstablished(info)
+ sslSessionInfo = Some(`SSL-Session-Info`(info))
+
+ case Http.MessageEvent(msg: HttpMessageStart) if sslSessionInfo.isDefined
+ eventPL(Http.MessageEvent(addSessionInfoHeader(msg)))
+
+ case RequestParsing.HttpMessageStartEvent(part, closeAfterResponseCompletion) if sslSessionInfo.isDefined
+ eventPL(RequestParsing.HttpMessageStartEvent(addSessionInfoHeader(part), closeAfterResponseCompletion))
+
+ case ev eventPL(ev)
+ }
+ }
+}
@@ -23,6 +23,7 @@ import akka.io.Tcp
import spray.can.server.StatsSupport.StatsHolder
import spray.io._
import spray.can.Http
+import spray.can.parsing.SSLSessionInfoSupport
import spray.http.{ SetTimeoutTimeout, SetRequestTimeout }
private[can] class HttpServerConnection(tcpConnection: ActorRef,
@@ -91,104 +92,115 @@ private[can] object HttpServerConnection {
* | MessageHandlerDispatch.DispatchCommand,
* | generates HttpResponsePartRenderingContext
* |------------------------------------------------------------------------------------------
- * /\ |
- * | HttpMessagePart | HttpResponsePartRenderingContext
- * | IOServer.Closed | IOServer.Tell
- * | IOServer.SentOk |
- * | TickGenerator.Tick |
- * | \/
+ * /\ |
+ * | HttpMessagePart | HttpResponsePartRenderingContext
+ * | IOServer.Closed | IOServer.Tell
+ * | IOServer.SentOK |
+ * | TickGenerator.Tick |
+ * | \/
* |------------------------------------------------------------------------------------------
* | RequestChunkAggregation: listens to HttpMessagePart events, generates HttpRequest events
* |------------------------------------------------------------------------------------------
- * /\ |
- * | HttpMessagePart | HttpResponsePartRenderingContext
- * | IOServer.Closed | IOServer.Tell
- * | IOServer.SentOk |
- * | TickGenerator.Tick |
- * | \/
+ * /\ |
+ * | HttpMessagePart | HttpResponsePartRenderingContext
+ * | IOServer.Closed | IOServer.Tell
+ * | IOServer.SentOK |
+ * | TickGenerator.Tick |
+ * | \/
* |------------------------------------------------------------------------------------------
* | PipeliningLimiter: throttles incoming requests according to the PipeliningLimit, listens
* | to HttpResponsePartRenderingContext commands and HttpRequestPart events,
* | generates StopReading and ResumeReading commands
* |------------------------------------------------------------------------------------------
- * /\ |
- * | HttpMessagePart | HttpResponsePartRenderingContext
- * | IOServer.Closed | IOServer.Tell
- * | IOServer.SentOk | IOServer.StopReading
- * | TickGenerator.Tick | IOServer.ResumeReading
- * | \/
+ * /\ |
+ * | HttpMessagePart | HttpResponsePartRenderingContext
+ * | IOServer.Closed | IOServer.Tell
+ * | IOServer.SentOK | IOServer.StopReading
+ * | TickGenerator.Tick | IOServer.ResumeReading
+ * | \/
* |------------------------------------------------------------------------------------------
* | StatsSupport: listens to most commands and events to collect statistics
* |------------------------------------------------------------------------------------------
- * /\ |
- * | HttpMessagePart | HttpResponsePartRenderingContext
- * | IOServer.Closed | IOServer.Tell
- * | IOServer.SentOk | IOServer.StopReading
- * | TickGenerator.Tick | IOServer.ResumeReading
- * | \/
+ * /\ |
+ * | HttpMessagePart | HttpResponsePartRenderingContext
+ * | IOServer.Closed | IOServer.Tell
+ * | IOServer.SentOK | IOServer.StopReading
+ * | TickGenerator.Tick | IOServer.ResumeReading
+ * | \/
* |------------------------------------------------------------------------------------------
* | RemoteAddressHeaderSupport: add `Remote-Address` headers to incoming requests
* |------------------------------------------------------------------------------------------
- * /\ |
- * | HttpMessagePart | HttpResponsePartRenderingContext
- * | IOServer.Closed | IOServer.Tell
- * | IOServer.SentOk | IOServer.StopReading
- * | TickGenerator.Tick | IOServer.ResumeReading
- * | \/
+ * /\ |
+ * | HttpMessagePart | HttpResponsePartRenderingContext
+ * | IOServer.Closed | IOServer.Tell
+ * | IOServer.SentOK | IOServer.StopReading
+ * | TickGenerator.Tick | IOServer.ResumeReading
+ * | \/
+ * |------------------------------------------------------------------------------------------
+ * | SSLSessionInfoSupport: add `SSL-Session-Info` header to incoming requests
+ * |------------------------------------------------------------------------------------------
+ * /\ |
+ * | HttpMessagePart | HttpResponsePartRenderingContext
+ * | IOServer.Closed | IOServer.Tell
+ * | IOServer.SentOK | IOServer.StopReading
+ * | TickGenerator.Tick | IOServer.ResumeReading
+ * | SslTlsSupport.SSLSessionEstablished |
+ * | \/
* |------------------------------------------------------------------------------------------
* | RequestParsing: converts Received events to HttpMessagePart,
* | generates HttpResponsePartRenderingContext (in case of errors)
* |------------------------------------------------------------------------------------------
- * /\ |
- * | IOServer.Closed | HttpResponsePartRenderingContext
- * | IOServer.SentOk | IOServer.Tell
- * | IOServer.Received | IOServer.StopReading
- * | TickGenerator.Tick | IOServer.ResumeReading
- * | \/
+ * /\ |
+ * | IOServer.Closed | HttpResponsePartRenderingContext
+ * | IOServer.SentOK | IOServer.Tell
+ * | IOServer.Received | IOServer.StopReading
+ * | TickGenerator.Tick | IOServer.ResumeReading
+ * | SslTlsSupport.SSLSessionEstablished |
+ * | \/
* |------------------------------------------------------------------------------------------
* | ResponseRendering: converts HttpResponsePartRenderingContext
* | to Send and Close commands
* |------------------------------------------------------------------------------------------
- * /\ |
- * | IOServer.Closed | IOServer.Send
- * | IOServer.SentOk | IOServer.Close
- * | IOServer.Received | IOServer.Tell
- * | TickGenerator.Tick | IOServer.StopReading
- * | | IOServer.ResumeReading
- * | \/
+ * /\ |
+ * | IOServer.Closed | IOServer.Send
+ * | IOServer.SentOK | IOServer.Close
+ * | IOServer.Received | IOServer.Tell
+ * | TickGenerator.Tick | IOServer.StopReading
+ * | SslTlsSupport.SSLSessionEstablished | IOServer.ResumeReading
+ * | \/
* |------------------------------------------------------------------------------------------
* | ConnectionTimeouts: listens to Received events and Send commands and
* | TickGenerator.Tick, generates Close commands
* |------------------------------------------------------------------------------------------
- * /\ |
- * | IOServer.Closed | IOServer.Send
- * | IOServer.SentOk | IOServer.Close
- * | IOServer.Received | IOServer.Tell
- * | TickGenerator.Tick | IOServer.StopReading
- * | | IOServer.ResumeReading
- * | \/
+ * /\ |
+ * | IOServer.Closed | IOServer.Send
+ * | IOServer.SentOK | IOServer.Close
+ * | IOServer.Received | IOServer.Tell
+ * | TickGenerator.Tick | IOServer.StopReading
+ * | SslTlsSupport.SSLSessionEstablished | IOServer.ResumeReading
+ * | \/
* |------------------------------------------------------------------------------------------
* | SslTlsSupport: listens to event Send and Close commands and Received events,
* | provides transparent encryption/decryption in both directions
* |------------------------------------------------------------------------------------------
- * /\ |
- * | IOServer.Closed | IOServer.Send
- * | IOServer.SentOk | IOServer.Close
- * | IOServer.Received | IOServer.Tell
- * | TickGenerator.Tick | IOServer.StopReading
- * | | IOServer.ResumeReading
- * | \/
+ * /\ |
+ * | IOServer.Closed | IOServer.Send
+ * | IOServer.SentOK | IOServer.Close
+ * | IOServer.Received | IOServer.Tell
+ * | TickGenerator.Tick | IOServer.StopReading
+ * | | IOServer.ResumeReading
+ * | \/
* |------------------------------------------------------------------------------------------
* | TickGenerator: listens to Closed events,
* | dispatches TickGenerator.Tick events to the head of the event PL
* |------------------------------------------------------------------------------------------
- * /\ |
- * | IOServer.Closed | IOServer.Send
- * | IOServer.SentOk | IOServer.Close
- * | IOServer.Received | IOServer.Tell
- * | TickGenerator.Tick | IOServer.StopReading
- * | | IOServer.ResumeReading
- * | \/
+ * /\ |
+ * | IOServer.Closed | IOServer.Send
+ * | IOServer.SentOK | IOServer.Close
+ * | IOServer.Received | IOServer.Tell
+ * | TickGenerator.Tick | IOServer.StopReading
+ * | | IOServer.ResumeReading
+ * | \/
*/
def pipelineStage(settings: ServerSettings, statsHolder: Option[StatsHolder]) = {
import settings._
@@ -197,10 +209,11 @@ private[can] object HttpServerConnection {
PipeliningLimiter(pipeliningLimit) ? (pipeliningLimit > 0) >>
StatsSupport(statsHolder.get) ? statsSupport >>
RemoteAddressHeaderSupport ? remoteAddressHeader >>
+ SSLSessionInfoSupport ? parserSettings.sslSessionInfoHeader >>
RequestParsing(settings) >>
ResponseRendering(settings) >>
ConnectionTimeouts(idleTimeout) ? (reapingCycle.isFinite && idleTimeout.isFinite) >>
- SslTlsSupport(false) ? sslEncryption >>
+ SslTlsSupport(parserSettings.sslSessionInfoHeader) ? sslEncryption >>
TickGenerator(reapingCycle) ? (reapingCycle.isFinite && (idleTimeout.isFinite || requestTimeout.isFinite)) >>
BackPressureHandling(backpressureSettings.get.noAckRate, backpressureSettings.get.readingLowWatermark) ? backpressureSettings.isDefined
}
@@ -19,6 +19,7 @@ package spray.http
import scala.annotation.{ implicitNotFound, tailrec }
import java.net.InetSocketAddress
+import spray.util.SSLSessionInfo
abstract class HttpHeader extends ToStringRenderable {
def name: String
@@ -378,6 +379,18 @@ object HttpHeaders {
protected def companion = `X-Forwarded-For`
}
+ /**
+ * Provides information about the SSL session the message was received over.
+ *
+ * For non-certificate based cipher suites (e.g., Kerberos), `localCertificates` and `peerCertificates` are both empty lists.
+ */
+ object `SSL-Session-Info` extends ModeledCompanion
+ case class `SSL-Session-Info`(info: SSLSessionInfo) extends ModeledHeader {
+ def renderValue[R <: Rendering](r: R): r.type = r ~~ "peer = " ~~ info.peerPrincipal.map { _.toString }.getOrElse("none")
+ protected def companion = `SSL-Session-Info`
+ override def toString = s"$name($info)"
+ }
+
case class RawHeader(name: String, value: String) extends HttpHeader {
val lowercaseName = name.toLowerCase
def render[R <: Rendering](r: R): r.type = r ~~ name ~~ ':' ~~ ' ' ~~ value
@@ -64,6 +64,8 @@ object HttpResponsePart {
sealed trait HttpMessageStart extends HttpMessagePart {
def message: HttpMessage
+
+ def mapHeaders(f: List[HttpHeader] List[HttpHeader]): HttpMessageStart
}
object HttpMessageStart {
@@ -295,10 +297,16 @@ object MessageChunk {
case class ChunkedRequestStart(request: HttpRequest) extends HttpMessageStart with HttpRequestPart {
def message = request
+
+ def mapHeaders(f: List[HttpHeader] List[HttpHeader]): ChunkedRequestStart =
+ ChunkedRequestStart(request mapHeaders f)
}
case class ChunkedResponseStart(response: HttpResponse) extends HttpMessageStart with HttpResponsePart {
def message = response
+
+ def mapHeaders(f: List[HttpHeader] List[HttpHeader]): ChunkedResponseStart =
+ ChunkedResponseStart(response mapHeaders f)
}
object ChunkedMessageEnd extends ChunkedMessageEnd("", Nil)

0 comments on commit e486900

Please sign in to comment.